Mysql索引使用案例分析

Mysql索引使用案例分析

1 Index Design

1.1 设计过程

  1. 考察只用排序 还是 先检索在排序(排序会限制索引使用)。
  2. 考察哪些列选择性更好,哪些列在where中最多。
  3. 选择性好的放左面还是用的多的放左面?一般是用的多的放左面。原因:
    • 用的多
    • 可以把前面用的多的条件使用in跳过(例如性别)(in组合不能太多否则就不再评估了,受eq_range_index_dive_limit控制)
  4. 范围查询尽量放在最右面

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 4not 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相关测试

一些结论

  1. in列索引可以正常通过使用(后面的也可以用到)
  2. 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
  1. 走索引拿数据没有的回表
  2. 走索引没有覆盖全条件,where再对结果集用t1条件过滤

3.2 范围相关测试

一些结论

  1. 多个范围查询只能用到一个,把其他的转换成in或等值放在索引最前面(选择性低的索引放左面泛用性更高,因为选择性低的条件经常用,或者可以用in或等值拼到前面,曲线救国也能走组合索引)!
  2. 范围查询那一列可以正常走索引,但是后面的列无论点查还是排序都不能用索引了!

组合索引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 |
+------+---------+-------------------------+------+

驱动表:驱动表按一般规则

小结

  1. **用驱动表排序,按一般规则判断order by是不是走索引,然后把顺序的结果集拿到,再去连后表!**排序列和连接列的组合索引一般是没有效果的,不要这样用
  2. 对于驱动表来说索引连接键没什么用,只需要关注索引覆盖where和orderby的情况
  3. 对于被驱动的表来说:多列索引里面哪一列都能用于连接键,前后中间都可以,前提条件是要连着用,中间断了就不行了,也是最左原则,缺一列都不能用

测试过程

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 |
+------+---------+-------------+------+----------+-----------------------+
被驱动表中的排序无法使用索引
发布了27 篇原创文章 · 获赞 2 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/jackgo73/article/details/105204765