Mysql索引使用案例分析
1 Index Design
1.1 设计过程
- 考察只用排序 还是 先检索在排序(排序会限制索引使用)。
- 考察哪些列选择性更好,哪些列在where中最多。
- 选择性好的放左面还是用的多的放左面?一般是用的多的放左面。原因:
- 用的多
- 可以把前面用的多的条件使用in跳过(例如性别)(in组合不能太多否则就不再评估了,受eq_range_index_dive_limit控制)
- 范围查询尽量放在最右面
2 Explain
1000万行数据的测试表
sysbench oltp_common --mysql-socket=/home/mingjie.gmj/databases/data/mydata5470/tmp/mysql.sock --mysql-user=root --mysql-db=idxdb --db-driver=mysql --tables=4 --table-size=10000000 --threads=128 prepare
alter table sbtest1 add (t1 int default 0);
update sbtest1 set t1=FLOOR(RAND() * 1000000);
alter table sbtest1 add (t2 int default 0);
update sbtest1 set t2=FLOOR(RAND() * 1000000);
alter table sbtest1 add (qad varchar(32) default '');
update sbtest1 set qad=MD5(RAND());
alter table sbtest1 add index (k,t1,t2,qad);
alter table sbtest1 add index (k,t2,qad);
CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
`t1` int(11) DEFAULT '0',
`t2` int(11) DEFAULT '0',
`qad` varchar(32) DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`),
KEY `k` (`k`,`t1`,`t2`,`qad`),
KEY `k_2` (`k`,`t2`,`qad`)
) ENGINE=InnoDB AUTO_INCREMENT=10000001 DEFAULT CHARSET=utf8mb4
CREATE TABLE `sbtest2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_2` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=10000001 DEFAULT CHARSET=utf8mb4
CREATE TABLE `sbtest3` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_3` (`k`),
KEY `c` (`c`)
) ENGINE=InnoDB AUTO_INCREMENT=10000001 DEFAULT CHARSET=utf8mb4
alter table sbtest4 add column (t1 int not null default 0);
alter table sbtest4 add index (k,t1);
update sbtest4 set t1=FLOOR(RAND() * 1000000);
alter table sbtest4 add column (qad varchar(32) not null default '');
alter table sbtest4 add index (k,t1,qad);
update sbtest4 set qad=MD5(RAND());
CREATE TABLE `sbtest4` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
`t1` int(11) NOT NULL DEFAULT '0',
`qad` varchar(32) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_4` (`k`),
KEY `k` (`k`,`t1`),
KEY `k_2` (`k`,`t1`,`qad`)
) ENGINE=InnoDB AUTO_INCREMENT=10000001 DEFAULT CHARSET=utf8mb4
2.1 type优先级
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
null
mysql能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。例如:在索引列中选取最小值,可以单独查找索引来完成,不需要在执行时访问表
mysql> explain select max(k) from sbtest1\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: NULL
partitions: NULL
type: NULL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: NULL
filtered: NULL
Extra: Select tables optimized away
const, system
mysql能对查询的某部分进行优化并将其转化成一个常量(可以看show warnings 的结果)。用于 primary key 或 unique key 的所有列与常数比较时,所以表最多有一个匹配行,读取1次,速度比较快。
mysql> explain select * from (select * from sbtest1 where id=10000) sb\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest1
partitions: NULL
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: const
rows: 1
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.01 sec)
mysql> show warnings\G
*************************** 1. row ***************************
Level: Note
Code: 1003
Message: /* select#1 */ select '10000' AS `id`,'5032908' AS `k`,'16518908930-89069434581-00330047360-80153545765-52786063008-17719135532-97186968876-24800122573-76373447456-17506809060' AS `c`,'42734141361-53867137877-50446368300-00647311019-92369579010' AS `pad`,'755900' AS `t1`,'103761' AS `t2`,'b13976c059dafb164fbd0543fbc5754d' AS `qad` from `idxdb`.`sbtest1` where 1
eq_ref
(用到唯一索引的连接,性能很好)
primary key 或 unique key 索引的所有部分被连接使用 ,最多只会返回一条符合条件的记录。这可能是在 const 之外最好的联接类型了,简单的 select 查询不会出现这种 type。
explain select * from sbtest1 a left join sbtest2 b on a.id=b.id\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: a
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 9565215
filtered: 100.00
Extra: NULL
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: b
partitions: NULL
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: idxdb.a.id
rows: 1
filtered: 100.00
Extra: NULL
ref
相比 eq_ref
,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要和某个值相比较,可能会找到多个符合条件的行。
简单查询出多行
mysql> explain select * from sbtest1 where k=4983851\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest1
partitions: NULL
type: ref
possible_keys: k_1,k
key: k_1
key_len: 4
ref: const
rows: 94
filtered: 100.00
Extra: NULL
一行能连多行
explain select * from sbtest1 a left join sbtest2 b on a.id=b.k\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: a
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 9565215
filtered: 100.00
Extra: NULL
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: b
partitions: NULL
type: ref
possible_keys: k_2
key: k_2
key_len: 4
ref: idxdb.a.id
rows: 5
filtered: 100.00
Extra: NULL
index_merge
表示使用了索引合并的优化方法,耗费大量CPU资源
mysql> explain select * from sbtest3 where k=100000 or c='asd'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest3
partitions: NULL
type: index_merge
possible_keys: k_3,c
key: k_3,c
key_len: 4,480
ref: NULL
rows: 2
filtered: 100.00
Extra: Using union(k_3,c); Using where
range
范围扫描通常出现在 in(), between ,> ,<, >= 等操作中。使用一个索引来检索给定范围的行。
mysql> explain select * from sbtest1 where id>5000000\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest1
partitions: NULL
type: range
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: NULL
rows: 4782607
filtered: 100.00
Extra: Using where
index
和ALL一样,不同就是mysql只需扫描索引树,这通常比ALL快一些。
mysql> explain select count(*) from sbtest1\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest1
partitions: NULL
type: index
possible_keys: NULL
key: k_1
key_len: 4
ref: NULL
rows: 9565215
filtered: 100.00
Extra: Using index
2.2 key_len计算
key_len计算规则如下:
- 字符串
- char(n):n字节长度
- varchar(n):2字节存储字符串长度,如果是utf-8,则长度 3n + 2,uft8mb4,则长度为4n+2
- 数值类型 :
- tinyint:1字节
- smallint:2字节
- int:4字节
- bigint:8字节
- 时间类型
- date:3字节
- timestamp:4字节
- datetime:8字节
- 如果字段允许为 NULL,需要1字节记录是否为 NULL
索引最大长度是768字节,当字符串过长时,mysql会做一个类似左前缀索引的处理,将前半部分的字符提取出来做索引。
测试1(重要)
CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
`t1` int(11) DEFAULT '0',
`t2` int(11) DEFAULT '0',
`qad` varchar(32) DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`),
KEY `k` (`k`,`t1`,`t2`,`qad`)
)
explain select * from sbtest1 force index(k)
where k=4983851 and t1=654542 and t2=670862 and qad='b6616ae504d039e04c54882ddcfc28ff';
+------+---------+-------------------------+------+----------+-------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------------------------+------+----------+-------+
| k | 145 | const,const,const,const | 1 | 100.00 | NULL |
+------+---------+-------------------------+------+----------+-------+
intNotNull 4(not null)
intNotNull int 9 = 4 + 4 + 1(非空标示)
intNotNull int int 14 = 9 + 4 + 1 (非空标示)
intNotNull int int vchar(32) 145 = 14 + 32*4(mb4要*4) + 2(变长+2定长不加)+ 1(非空标示)
测试2(重要)
CREATE TABLE `sbtest4` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
`t1` int(11) NOT NULL DEFAULT '0',
`qad` varchar(32) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_4` (`k`),
KEY `k` (`k`,`t1`),
KEY `k_2` (`k`,`t1`,`qad`)
) ENGINE=InnoDB AUTO_INCREMENT=10000001 DEFAULT CHARSET=utf8mb4
explain select * from sbtest4 force index(k_2)
where k=4983851 and t1=654542 and qad='b6616ae504d039e04c54882ddcfc28ff';
+------+---------+-------------------+------+----------+-------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------------------+------+----------+-------+
| k_2 | 138 | const,const,const | 1 | 100.00 | NULL |
+------+---------+-------------------+------+----------+-------+
138 = 4 + 4 + 130(mb4需要乘4然后加上2变长:32*4+2)
2.3 extra含义
Using index
:这发生在对表的请求列都是同一索引的部分的时候,返回的列数据只使用了索引中的信息,而没有再去访问表中的行记录。是性能高的表现。
Using where
:mysql服务器将在存储引擎检索行后再进行过滤。就是先读取整行数据,再按 where 条件进行检查,符合就留下,不符合就丢弃。
Using temporary
:mysql需要创建一张临时表来处理查询。出现这种情况一般是要进行优化的,首先是想到用索引来优化。
Using filesort
:mysql 会对结果使用一个外部索引排序,而不是按索引次序从表里读取行。此时mysql会根据联接类型浏览所有符合条件的记录,并保存排序关键字和行指针,然后排序关键字并按顺序检索行信息。这种情况下一般也是要考虑使用索引来优化的。
https://dev.mysql.com/doc/refman/8.0/en/explain-output.html
3 I do and I understand
3.1 IN相关测试
一些结论
- in列索引可以正常通过使用(后面的也可以用到)
- in列后面的所有列无法用索引排序!
组合索引k:k, t1, t2, qad
KEY `k` (`k`,`t1`,`t2`,`qad`)
–in k–t1–:走两列索引range
mysql> explain select * from sbtest1 force index(k) where k in (4983851,4983852) and t1=654542\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest1
partitions: NULL
type: range
possible_keys: k
key: k
key_len: 9
ref: NULL
rows: 2
filtered: 100.00
Extra: Using index condition
- key_len = 9 = 4 + 5 ✅
- Using index condition优先查index数据不够回表
–k--in t1–t2–:走三列索引正常通过range列
mysql> explain select * from sbtest1 force index(k) where k=4983851 and t1 in(654542,654543) and t2=670862\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest1
partitions: NULL
type: range
possible_keys: k
key: k
key_len: 14
ref: NULL
rows: 2
filtered: 100.00
Extra: Using index condition
- ken_len = 14 = 4 + 5 + 5 (t1和t2没有非空约束)三个字段都走了
–k--t1–in t2–:走三列索引正常通过range列
mysql> explain select * from sbtest1 force index(k) where k=4983851 and t1 = 654542 and t2 in (670862, 670863)\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest1
partitions: NULL
type: range
possible_keys: k
key: k
key_len: 14
ref: NULL
rows: 2
filtered: 100.00
Extra: Using index condition
- ken_len = 14 = 4 + 5 + 5 (t1和t2没有非空约束)三个字段都走了
–in k–t1–in t2–:多in正常走三列索引
mysql> explain select * from sbtest1 force index(k) where k in (4983851,4983851) and t1 = 654542 and t2 in (670862, 670863)\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest1
partitions: NULL
type: range
possible_keys: k
key: k
key_len: 14
ref: NULL
rows: 2
filtered: 100.00
Extra: Using index condition
- ken_len = 14 = 4 + 5 + 5 (t1和t2没有非空约束)三个字段都走了
–k--t1–t2–order qad–:正常走3列索引无需排序
explain select * from sbtest1 force index(k) where k=4983851 and t1 = 654542 and t2 =670862 order by qad\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest1
partitions: NULL
type: ref
possible_keys: k
key: k
key_len: 14
ref: const,const,const
rows: 1
filtered: 100.00
Extra: Using index condition
key_len14三个字段都走了
mysql> explain select * from sbtest1 force index(k) where k=4983851 and t1 = 654542 order by t2,qad\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest1
partitions: NULL
type: ref
possible_keys: k
key: k
key_len: 9
ref: const,const
rows: 1
filtered: 100.00
Extra: Using index condition
key_len9两个字段都走了,排序两列
–k--in t1–t2–order qad–:走三列的索引,但是无法排序(优化方法)
mysql> explain select * from sbtest1 force index(k) where k=4983851 and t1 in(654542,654543) and t2=670862 order by qad\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest1
partitions: NULL
type: range
possible_keys: k
key: k
key_len: 14
ref: NULL
rows: 2
filtered: 100.00
Extra: Using index condition; Using filesort
把in列从索引中踢掉
alter table sbtest1 add index (k,t2,qad);
再进行测试,完美解决
mysql> explain select * from sbtest1 force index(k_2) where k=4983851 and t1 in(654542,654543) and t2=670862 order by qad\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest1
partitions: NULL
type: ref
possible_keys: k_2
key: k_2
key_len: 9
ref: const,const
rows: 1
filtered: 20.00
Extra: Using index condition; Using where
- 走索引拿数据没有的回表
- 走索引没有覆盖全条件,where再对结果集用t1条件过滤
3.2 范围相关测试
一些结论
- 多个范围查询只能用到一个,把其他的转换成in或等值放在索引最前面(选择性低的索引放左面泛用性更高,因为选择性低的条件经常用,或者可以用in或等值拼到前面,曲线救国也能走组合索引)!
- 范围查询那一列可以正常走索引,但是后面的列无论点查还是排序都不能用索引了!
组合索引k:k, t1, t2, qad
KEY `k` (`k`,`t1`,`t2`,`qad`)
–k--t1>–t2–:走两列索引被范围查询截断
mysql> explain select * from sbtest1 force index(k) where k=4983851 and t1>654542 and t2=670862\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest1
partitions: NULL
type: range
possible_keys: k
key: k
key_len: 9
ref: NULL
rows: 31
filtered: 10.00
Extra: Using index condition
- key_len: 9= 4 + 5 = k1 + t1
–k--t1–t2>–:走三列索引
mysql> explain select * from sbtest1 force index(k) where k=4983851 and t1=654542 and t2>670862\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest1
partitions: NULL
type: range
possible_keys: k
key: k
key_len: 14
ref: NULL
rows: 1
filtered: 100.00
Extra: Using index condition
- key_len: 14 = 4 + 5 + 5 = k + t1 + t2
–k--t1–t2>–orderby t2–:范围查询那个列可以排序
mysql> explain select * from sbtest1 force index(k) where k=4983851 and t1=654542 and t2>670862 order by t2\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest1
partitions: NULL
type: range
possible_keys: k
key: k
key_len: 14
ref: NULL
rows: 1
filtered: 100.00
Extra: Using index condition
–k--t1–t2>–orderby t2,pad–:范围查询和后接的pad都能索引排序
CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
`t1` int(11) NOT NULL,
`t2` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `k` (`k`,`t1`,`t2`,`pad`)
)
mysql> explain select * from sbtest1 force index(k) where k=4983851 and t1=654542 and t2>670862 order by t2,pad\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest1
partitions: NULL
type: range
possible_keys: k
key: k
key_len: 12
ref: NULL
rows: 1
filtered: 100.00
Extra: Using index condition
–k--t1–t2>–orderby qad–:范围查询后面的列不能排序
mysql> explain select * from sbtest1 force index(k) where k=4983851 and t1=654542 and t2>670862 order by qad\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest1
partitions: NULL
type: range
possible_keys: k
key: k
key_len: 14
ref: NULL
rows: 1
filtered: 100.00
Extra: Using index condition; Using filesort
–k--t1–t2>–orderby t2,qad–:带范围条件且顺序正确的排序能走索引
mysql> explain select * from sbtest1 force index(k) where k=4983851 and t1=654542 order by t2,qad\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: sbtest1
partitions: NULL
type: ref
possible_keys: k
key: k
key_len: 9
ref: const,const
rows: 1
filtered: 100.00
Extra: Using index condition
–in k–join t1–t2–:只能走k
-- s1的(`k`,`t1`,`t2`,`qad`)只能走k
explain
select * from sbtest2 s2
straight_join sbtest1 s1 force index(k)
on s2.id=s1.t1
where s2.k=5012066 and s1.k in (5002170,5002170) and s1.t2=738425;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: s2
partitions: NULL
type: ref
possible_keys: PRIMARY,k_2
key: k_2
key_len: 4
ref: const
rows: 111
filtered: 100.00
Extra: NULL
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: s1
partitions: NULL
type: range
possible_keys: k
key: k
key_len: 4
ref: NULL
rows: 106
filtered: 0.05
Extra: Using index condition; Using where; Using join buffer (Block Nested Loop)
–k--join t1–in t2–:走k和t1
-- s1的(`k`,`t1`,`t2`,`qad`)能走k和t1
explain
select * from sbtest2 s2
straight_join sbtest1 s1 force index(k)
on s2.id=s1.t1
where s2.k=5012066 and s1.k = 5002170 and s1.t2 in (738425,738425);
+------+---------+-------------------+------+----------+-----------------------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------------------+------+----------+-----------------------+
| k_2 | 4 | const | 111 | 100.00 | NULL |
| k | 9 | const,idxdb.s2.id | 1 | 20.00 | Using index condition |
+------+---------+-------------------+------+----------+-----------------------+
in后面是子查询:不走索引
可以改写为inner join或逗号连接加where
mysql> explain select * from sbtest1 where id in ( select max(id) from sbtest1 where k=4983851 group by t1 )\G
*************************** 1. row ***************************
id: 1
select_type: PRIMARY
table: sbtest1
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 9565215
filtered: 100.00
Extra: Using where
*************************** 2. row ***************************
id: 2
select_type: SUBQUERY
table: sbtest1
partitions: NULL
type: ref
possible_keys: k_1,k,k_2
key: k
key_len: 4
ref: const
rows: 94
filtered: 100.00
Extra: Using where; Using index
3.3 group by相关测试
- 先查出来在聚合,列少尽量走覆盖
- key_len不会计算覆盖列的长度
- where最左原则,聚合列能覆盖就覆盖,应该是这类SQL的最优解
-- sbtest1:
-- KEY `k` (`k`,`t1`,`t2`,`qad`),
-- KEY `t1` (`t1`,`t2`,`k`)
-- KEY `t1_2` (`t1`,`k`,`t2`)
-- (`k`,`t1`,`t2`,`qad`)走`k`和`t1`覆盖`t2`
explain select max(t2) from sbtest1 force index(k)
where k=4983851 and t1=654542 group by t2;
-+------+---------+-------------+------+----------+--------------------------+
| key | key_len | ref | rows | filtered | Extra |
-+------+---------+-------------+------+----------+--------------------------+
| k | 9 | const,const | 1 | 100.00 | Using where; Using index |
-+------+---------+-------------+------+----------+--------------------------+ref
-- (`t1`,`t2`,`k`)走`t1`覆盖`t2`
explain select max(t2) from sbtest1 force index(t1)
where k=4983851 and t1=654542 group by t2;
+------+---------+-------+------+----------+--------------------------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------+------+----------+--------------------------+
| t1 | 5 | const | 18 | 0.28 | Using where; Using index |
+------+---------+-------+------+----------+--------------------------+ref
-- (`t1`,`k`,`t2`)走`t1`和`k`覆盖`t2`
explain select max(t2) from sbtest1 force index(t1_2)
where k=4983851 and t1=654542 group by t2;
+------+---------+-------------+------+----------+--------------------------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------------+------+----------+--------------------------+
| t1_2 | 9 | const,const | 1 | 100.00 | Using where; Using index |
+------+---------+-------------+------+----------+--------------------------+ref
3.4 多表连接后排序测试
(leftjoin在数据量相同的情况下会优化为join)
被驱动表:连接键在索引中,与其他条件连续就能用,连接键可以看做点查条件
结论:
-
多列索引里面哪一列都能用于连接键,前后中间都可以,前提条件是要连着用,中间断了就不行了,也是最左原则,缺一列都不能用;用哪列连的看ref
-
对于被驱动的表来说:多列索引里面哪一列都能用于连接键,前后中间都可以,前提条件是要连着用,中间断了就不行了,也是最左原则,缺一列都不能用
-- s2: KEY `k_2` (`k`)
-- s1:
-- KEY `k` (`k`,`t1`,`t2`,`qad`),
-- KEY `t1` (`t1`,`t2`,`k`)
-- KEY `t1_2` (`t1`,`k`,`t2`)
-- s1的(`k`,`t1`,`t2`,`qad`)走`k`和`t1`
explain
select * from sbtest2 s2
straight_join sbtest1 s1 force index(k)
on s2.id=s1.t1
where s2.k=5012066 and s1.k=5002170;
+------+---------+-------------------+------+
| key | key_len | ref | rows |
+------+---------+-------------------+------+
| k_2 | 4 | const | 111 |
| k | 9 | const,idxdb.s2.id | 1 |
+------+---------+-------------------+------+
-- s1的(`k`,`t1`,`t2`,`qad`)走`k`和`t1`和`t2`
explain
select * from sbtest2 s2
straight_join sbtest1 s1 force index(k)
on s2.id=s1.t1
where s2.k=5012066 and s1.k=5002170 and s1.t2=738425;
+------+---------+-------------------------+------+
| key | key_len | ref | rows |
+------+---------+-------------------------+------+
| k_2 | 4 | const | 111 |
| k | 14 | const,idxdb.s2.id,const | 1 |
+------+---------+-------------------------+------+
-- s1的(`t1`,`t2`,`k`)只能走`t1`
explain
select * from sbtest2 s2
straight_join sbtest1 s1 force index(t1)
on s2.id=s1.t1
where s2.k=5012066 and s1.k=5002170;
+------+---------+-------------+------+
| key | key_len | ref | rows |
+------+---------+-------------+------+
| k_2 | 4 | const | 111 |
| t1 | 5 | idxdb.s2.id | 9 |
+------+---------+-------------+------+
-- s1的(`t1`,`t2`,`k`)能走`t1`,`t2`,`k`
explain
select * from sbtest2 s2
straight_join sbtest1 s1 force index(t1)
on s2.id=s1.t1
where s2.k=5012066 and s1.k=5002170 and s1.t2=738425;
+------+---------+-------------------------+------+
| key | key_len | ref | rows |
+------+---------+-------------------------+------+
| k_2 | 4 | const | 111 |
| t1 | 14 | idxdb.s2.id,const,const | 1 |
+------+---------+-------------------------+------+
-- s1的(`t1`,`k`,`t2`)可以用`t1`,`k`
explain
select * from sbtest2 s2
straight_join sbtest1 s1 force index(t1_2)
on s2.id=s1.t1
where s2.k=5012066 and s1.k=5002170;
+------+---------+-------------------+------+
| key | key_len | ref | rows |
+------+---------+-------------------+------+
| k_2 | 4 | const | 111 |
| t1_2 | 9 | idxdb.s2.id,const | 1 |
+------+---------+-------------------+------+
-- s1的(`t1`,`k`,`t2`)可以用`t1`,`k`,`t2`
explain
select * from sbtest2 s2
straight_join sbtest1 s1 force index(t1_2)
on s2.id=s1.t1
where s2.k=5012066 and s1.k=5002170 and s1.t2=738425;
+------+---------+-------------------------+------+
| key | key_len | ref | rows |
+------+---------+-------------------------+------+
| k_2 | 4 | const | 111 |
| t1_2 | 14 | idxdb.s2.id,const,const | 1 |
+------+---------+-------------------------+------+
驱动表:驱动表按一般规则
小结
- **用驱动表排序,按一般规则判断order by是不是走索引,然后把顺序的结果集拿到,再去连后表!**排序列和连接列的组合索引一般是没有效果的,不要这样用
- 对于驱动表来说索引连接键没什么用,只需要关注索引覆盖where和orderby的情况
- 对于被驱动的表来说:多列索引里面哪一列都能用于连接键,前后中间都可以,前提条件是要连着用,中间断了就不行了,也是最左原则,缺一列都不能用
测试过程
Order by(后表)列
- order by用连接键不能走索引,除非有等值条件,但是有等值条件就没必要排序了,所以还是不走索引(排序被连接的表想想也是没有意义)
-- s2: KEY `k_2` (`k`)
-- s1:
-- KEY `k` (`k`,`t1`,`t2`,`qad`),
-- KEY `t1` (`t1`,`t2`,`k`)
-- KEY `t1_2` (`t1`,`k`,`t2`)
-- s1的(`k`,`t1`,`t2`,`qad`)走`k`和`t1`
-- order by 能用k排序,因为有k等值
explain
select * from sbtest2 s2
straight_join sbtest1 s1 force index(k)
on s2.id=s1.t1
where s2.k=5012066 and s1.k=5002170
order by s1.k;
+------+---------+-------------------+------+----------+-------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------------------+------+----------+-------+
| k_2 | 4 | const | 111 | 100.00 | NULL |
| k | 9 | const,idxdb.s2.id | 1 | 100.00 | NULL |
+------+---------+-------------------+------+----------+-------+
-- s1的(`k`,`t1`,`t2`,`qad`)走`k`和`t1`
-- order by 不能用t1排序
explain
select * from sbtest2 s2
straight_join sbtest1 s1 force index(k)
on s2.id=s1.t1
where s2.k=5012066 and s1.k=5002170
order by s1.t1;
+------+---------+-------------------+------+----------+---------------------------------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------------------+------+----------+---------------------------------+
| k_2 | 4 | const | 111 | 100.00 | Using temporary; Using filesort |
| k | 9 | const,idxdb.s2.id | 1 | 100.00 | NULL |
+------+---------+-------------------+------+----------+---------------------------------+
-- s1的(`k`,`t1`,`t2`,`qad`)走`k`和`t1`和`t2`
-- order by 能用t2排序因为t2有单值
explain
select * from sbtest2 s2
straight_join sbtest1 s1 force index(k)
on s2.id=s1.t1
where s2.k=5012066 and s1.k=5002170 and s1.t2=738425
order by s1.t2;
+------+---------+-------------------------+------+----------+-------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------------------------+------+----------+-------+
| k_2 | 4 | const | 111 | 100.00 | NULL |
| k | 14 | const,idxdb.s2.id,const | 1 | 100.00 | NULL |
+------+---------+-------------------------+------+----------+-------+
-- s1的(`k`,`t1`,`t2`,`qad`)走`k`和`t1`
-- order by t2不能走索引因为t2没有单值条件
explain
select * from sbtest2 s2
straight_join sbtest1 s1 force index(k)
on s2.id=s1.t1
where s2.k=5012066 and s1.k=5002170
order by s1.t2;
+------+---------+-------------------+------+----------+---------------------------------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------------------+------+----------+---------------------------------+
| k_2 | 4 | const | 111 | 100.00 | Using temporary; Using filesort |
| k | 9 | const,idxdb.s2.id | 1 | 100.00 | NULL |
+------+---------+-------------------+------+----------+---------------------------------+
-- s1的(`t1`,`t2`,`k`)只走t1不排序
explain
select * from sbtest2 s2
straight_join sbtest1 s1 force index(t1)
on s2.id=s1.t1
where s2.k=5012066 and s1.t2=738425
order by s1.t1;
+------+---------+-------------+------+----------+---------------------------------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------------+------+----------+---------------------------------+
| k_2 | 4 | const | 111 | 100.00 | Using temporary; Using filesort |
| t1 | 5 | idxdb.s2.id | 9 | 0.53 | Using index condition |
+------+---------+-------------+------+----------+---------------------------------+
-- s1的(`t1`,`t2`,`k`)走`t1`和`t2`不排序
explain
select * from sbtest2 s2
straight_join sbtest1 s1 force index(t1)
on s2.id=s1.t1
where s2.k=5012066 and s1.t2=738425
order by s1.t1;
+------+---------+-------------------+------+----------+---------------------------------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------------------+------+----------+---------------------------------+
| k_2 | 4 | const | 111 | 100.00 | Using temporary; Using filesort |
| t1 | 10 | idxdb.s2.id,const | 1 | 100.00 | NULL |
+------+---------+-------------------+------+----------+---------------------------------+
Order by(前表)列
- 排序列放索引前面,连接列放后面才能避免排序
- 对于驱动表来说,索引连接键没什么用,按一般规则关注索引覆盖where和orderby的情况
-- s2: KEY `k_2` (`k`)
-- s1:
-- KEY `k` (`k`,`t1`,`t2`,`qad`),
-- KEY `t1` (`t1`,`t2`,`k`)
-- KEY `t1_2` (`t1`,`k`,`t2`)
-- s1的(`t1`,`t2`,`k`)不走
explain
select * from sbtest1 s1 force index(t1)
straight_join sbtest2 s2
on s1.t1=s2.k
order by s1.t2;
+------+---------+-------------+---------+----------+-----------------------------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------------+---------+----------+-----------------------------+
| NULL | NULL | NULL | 9565215 | 100.00 | Using where; Using filesort |
| k_2 | 4 | idxdb.s1.t1 | 5 | 100.00 | NULL |
+------+---------+-------------+---------+----------+-----------------------------+
-- s1的(`t1`,`t2`,`k`)走t1和t2可以避免排序!
explain
select * from sbtest1 s1 force index(t1)
straight_join sbtest2 s2
on s1.t2=s2.k
order by s1.t1;
+------+---------+-------------+---------+----------+-------------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------------+---------+----------+-------------+
| t1 | 14 | NULL | 9565215 | 100.00 | Using where |
| k_2 | 4 | idxdb.s1.t2 | 5 | 100.00 | NULL |
+------+---------+-------------+---------+----------+-------------+
-- s1的KEY `t1_3` (`t1`)
explain
select * from sbtest1 s1 force index(t1_3)
straight_join sbtest2 s2
on s1.t2=s2.k
order by s1.t1;
+------+---------+-------------+---------+----------+-------------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------------+---------+----------+-------------+
| t1_3 | 5 | NULL | 9565215 | 100.00 | Using where |
| k_2 | 4 | idxdb.s1.t2 | 5 | 100.00 | NULL |
+------+---------+-------------+---------+----------+-------------+
-- s1的(`t1`,`t2`,`k`)
explain
select * from sbtest1 s1 force index(t1)
straight_join sbtest2 s2
on s1.t1=s2.k
order by s1.t1;
+------+---------+-------------+---------+----------+-------------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------------+---------+----------+-------------+
| t1 | 14 | NULL | 9565215 | 100.00 | Using where |
| k_2 | 4 | idxdb.s1.t1 | 5 | 100.00 | NULL |
+------+---------+-------------+---------+----------+-------------+
-- (`k`,`t1`,`t2`,`qad`),
-- order by s1.t1 可以索引排序:先用索引正常排序,完了在连接
-- order by s1.t2 不可以排序
-- order by s1.k 可以索引排序:按连接键直接排序!
-- 对于驱动表来说,连接键没什么用,只需要关注索引覆盖where的情况
explain
select * from sbtest1 s1 force index(k)
straight_join sbtest2 s2
on s1.t2=s2.k
where s1.k=5012066
order by s1.t1;
+------+---------+-------------+------+----------+-----------------------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------------+------+----------+-----------------------+
| k | 4 | const | 116 | 100.00 | Using index condition |
| k_2 | 4 | idxdb.s1.t2 | 5 | 100.00 | NULL |
+------+---------+-------------+------+----------+-----------------------+
3.5 order by测试
总结
-
(a,b,c)都有点查的话都能用于排序
-
(a,b,c)ab有点查,c能用于排序,最左原则:要连续匹配
-
(a,b,c)a有点查的话b能用于排序c不行,违反最左原则
-
(a,b,c)a用于排序,b不能点查,违反最左原则
几句话总结:
- 最左原则是要有连续的点查,后面能接排序,能接点查;
- 排序放到左面是不能当作点查的,左一是排序就违背最左原则了
- 排序放到中间也是不能当作点查的,左一点查,左二排序,左三点查,只能走到左二!
keylen的长度不算排序列!
测试准备
sysbench oltp_common --mysql-socket=/home/mingjie.gmj/databases/data/mydata5470/tmp/mysql.sock --mysql-user=root --mysql-db=idxdb --db-driver=mysql --tables=4 --table-size=10000000 --threads=128 prepare
alter table sbtest1 add (t1 int not null);
update sbtest1 set t1=FLOOR(RAND() * 1000000);
alter table sbtest1 add (t2 int not null);
update sbtest1 set t2=FLOOR(RAND() * 1000000);
alter table sbtest1 add index(k,t1,t2,pad);
CREATE TABLE `sbtest1` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`k` int(11) NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
`t1` int(11) NOT NULL,
`t2` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `k_1` (`k`),
KEY `k` (`k`,`t1`,`t2`,`pad`)
);
–k--t1–t2–order by k:走索引排序
-- KEY `k` (`k`,`t1`,`t2`,`pad`)
explain select * from sbtest1 force index(k) where k =4983851 and t1=998800 and t2=145174 order by k;
--ref
+------+---------+-------------------+------+----------+-------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------------------+------+----------+-------+
| k | 12 | const,const,const | 1 | 100.00 | NULL |
+------+---------+-------------------+------+----------+-------+
–k--t1–t2–order by t1:走索引排序
-- KEY `k` (`k`,`t1`,`t2`,`pad`)
explain select * from sbtest1 force index(k) where k =4983851 and t1=998800 and t2=145174 order by t1;
+------+---------+-------------------+------+----------+-------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------------------+------+----------+-------+
| k | 12 | const,const,const | 1 | 100.00 | NULL |
+------+---------+-------------------+------+----------+-------+
–k--order by t2:不走
-- KEY `k` (`k`,`t1`,`t2`,`pad`)
explain select * from sbtest1 force index(k) where k =4983851 order by t2;
+------+---------+-------+------+----------+---------------------------------------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------+------+----------+---------------------------------------+
| k | 4 | const | 94 | 100.00 | Using index condition; Using filesort |
+------+---------+-------+------+----------+---------------------------------------+
–order by k–t1:不走
-- KEY `k` (`k`,`t1`,`t2`,`pad`)
explain select * from sbtest1 force index(k) where t1=998800 order by k;
+------+---------+------+---------+----------+-------------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+------+---------+----------+-------------+
| k | 252 | NULL | 9857202 | 10.00 | Using where |
+------+---------+------+---------+----------+-------------+
–k--t1–order by k–t1:走索引排序
-- KEY `k` (`k`,`t1`,`t2`,`pad`)
explain select * from sbtest1 force index(k) where k =4983851 and t1=998800 order by k;
+---------------+------+---------+-------------+------+----------+-------+
| possible_keys | key | key_len | ref | rows | filtered | Extra |
+---------------+------+---------+-------------+------+----------+-------+
| k | k | 8 | const,const | 1 | 100.00 | NULL |
+---------------+------+---------+-------------+------+----------+-------+
–k--order by t1–t2–qad–:只能走到k和t1
-- KEY `k` (`k`,`t1`,`t2`,`pad`)
explain select * from sbtest1 force index(k) where k =4983851 and t2=145174 order by t1;
+------+---------+-------+------+----------+-----------------------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------+------+----------+-----------------------+
| k | 4 | const | 94 | 10.00 | Using index condition |
+------+---------+-------+------+----------+-----------------------+
–k--t1–order by t2:走索引排序
-- KEY `k` (`k`,`t1`,`t2`,`pad`)
explain select * from sbtest1 force index(k) where k =4983851 and t1=998800 order by t2;
+------+---------+-------------+------+----------+-----------------------+
| key | key_len | ref | rows | filtered | Extra |
+------+---------+-------------+------+----------+-----------------------+
| k | 8 | const,const | 1 | 100.00 | Using index condition |
+------+---------+-------------+------+----------+-----------------------+