MySQL--a>? and b=? order by c该如何建索引效率最高

最近遇到一个这么一个问题,有一个语句,查询的where条件为,select a,b,c from table_name where a>xxx and b=xxx order by c;那这种情况该如何正确建造联合索引?有人说建联合索引(a,b,c)。也有说(a,c,b)、(b,a,c)、(b,c,a)甚至还有(b,c)。还各有各的说法,有说等值优先,又有说。开销最小的放在最后面。等等,可是到底应该如何建造联合索引,才能有最小的开销,最好的效率,看来只能实际测试一下了。

准备环境

1.先使用sysbench创建一个测试表,先写入100w条数据。

root # sysbench /usr/share/sysbench/oltp_update_index.lua --mysql-host=127.0.0.1 --mysql-port=3306 --mysql-user=test --mysql-password=123456 --mysql-db=sbtest --table-size=1000000 --tables=1 --threads=10 --report-interval=3 --time=20  prepare

2.先看一下表结构,和数据。

mysql> show create table sbtest1\G
*************************** 1. row ***************************
       Table: sbtest1
Create Table: 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 '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8mb4


mysql> select * from sbtest1 limit 10;
+----+--------+-------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------+
| id | k      | c                                                                                                                       | pad                                                         |
+----+--------+-------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------+
|  1 | 504773 | 46953504341-24827434769-14900357301-22216603386-59015331152-64421594353-91063882286-20886127030-43555962287-78841524137 | 93637428753-19078512484-07499046167-90467648315-23800958676 |
|  2 | 383019 | 95718881762-40242328339-05770398751-81456272523-88750816056-12849082499-99426386575-67936738491-05967978445-48490321969 | 28062089678-53016824652-82043772113-00345737256-89531328849 |
|  3 | 503847 | 01038385872-50361705817-46318905997-76679583539-88685166918-98634648550-40413039627-46247204716-09242720804-66101780709 | 24034894203-99292672185-45043562307-83346840810-30785340141 |
|  4 | 499704 | 93953438013-23388274922-95983341481-57221053014-04363900317-67253124803-28970744159-73839965368-87044223191-51910643768 | 06048066221-15243579179-69120208226-58470567091-17594624310 |
|  5 | 504333 | 71261094016-71756798329-41630209368-07219841001-81466827148-40549506058-53017560040-16501082010-89928032477-46063916453 | 00213142226-22736525923-92307256421-88250677601-39977405598 |
|  6 | 503552 | 32753709874-02382056451-81693154231-27180260479-19599038117-11097832377-93022647238-28037030398-75145265570-28677929900 | 72055222201-67390314106-05261498936-44609701627-90239230652 |
|  7 | 406374 | 64005369708-01328903521-83628938492-58715179738-86878139875-08682365457-20667146951-39733142900-50703403349-70540498661 | 51830182144-85315866878-16093318939-33670981339-30608544097 |
|  8 | 499282 | 49820770300-84763632943-74879578402-29401740516-28978918607-99083669177-42065952807-09235614265-01166968728-26325800927 | 85626280648-53691844105-62670548340-12193503415-17217200408 |
|  9 | 501253 | 97138737797-22390438815-38895931199-24202958945-61107229413-58522735620-64288660669-68685322850-20820693122-52956464894 | 36554471751-65919787106-00809866023-48790163561-33909214861 |
| 10 | 497864 | 09547967219-39906583629-69077413839-69484437464-13418798478-47928808080-54901501970-58233953813-72479608919-63475855320 | 59346922262-65082899599-23058506484-38409416453-58642527076 |
+----+--------+-------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------+

根据上方的条件,我们创造一条本案例一样的语句:select k,pad,c from sbtest1 where k>383019 and pad=‘93637428753-19078512484-07499046167-90467648315-23800958676’ order by c;也就是说这条语句只有一个结果集,比较直观一些,k代表a、pad代表b、c代表c。

没有索引的情况

从下方可以看出,没有索引必然是全表扫描,100w的数据,扫描大概98w行。时间耗时0.32s,Extra为Using where; Using filesort
Using where:表示不一定是没用到索引,而是就算有索引,但是where条件的需求满足不了,需要获取所需要的数据行,这种情况效率是有一些糟糕的。
Using filesort:这个表示本次查询使用到了额外的排序,就是当索引中无法完成排序,就需要在临时文件中进行额外的排序,因为我们语句中有order by,如果需要排序的数据比较多,这种情况也是比较糟糕的。

mysql> select k,pad,c from sbtest1 where k>383019 and pad='93637428753-19078512484-07499046167-90467648315-23800958676' order by c;
1 row in set (0.32 sec)

mysql> explain select k,c,pad from sbtest1 where k>383019 and pad='93637428753-19078512484-07499046167-90467648315-23800958676' order by c;
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-----------------------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra                       |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-----------------------------+
|  1 | SIMPLE      | sbtest1 | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 986328 |     3.33 | Using where; Using filesort |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-----------------------------+
1 row in set, 1 warning (0.00 sec)

增加索引(a,b,c)

从下方测试可以看出,加了索引确实好了一点,执行时间0.06s,变成了索引范围扫描(range),但是还是用到了额外的排序,而且扫描行数为20多w。
Using index:表示索引覆盖,因为我们是select a,b,c而不是select * 所以可能会用到索引覆盖,不知道索引覆盖是什么的可以看我另外一篇文章有详细介绍。

https://blog.csdn.net/h_3369/article/details/141357887?spm=1001.2014.3001.5502

mysql> alter table sbtest1 add index id_test(k,pad,c);
mysql> explain select k,pad,c from sbtest1 where k>383019 and pad='93637428753-19078512484-07499046167-90467648315-23800958676' order by c;
+----+-------------+---------+------------+-------+---------------+---------+---------+------+--------+----------+------------------------------------------+
| id | select_type | table   | partitions | type  | possible_keys | key     | key_len | ref  | rows   | filtered | Extra                                    |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+--------+----------+------------------------------------------+
|  1 | SIMPLE      | sbtest1 | NULL       | range | id_test       | id_test | 244     | NULL | 256480 |    10.00 | Using where; Using index; Using filesort |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+--------+----------+------------------------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> select k,pad,c from sbtest1 where k>383019 and pad='93637428753-19078512484-07499046167-90467648315-23800958676' order by c;
1 row in set (0.06 sec)

增加索引(a,c,b)

从下方结果来看,(a,c,b)和(a,b,c)基本上区别不大…

mysql> alter table sbtest1 add index id_test(k,c,pad);
mysql> OPTIMIZE TABLE sbtest1;
mysql> select k,pad,c from sbtest1 where k>383019 and pad='93637428753-19078512484-07499046167-90467648315-23800958676' order by c;
1 row in set (0.05 sec)

mysql>  explain select k,pad,c from sbtest1 where k>383019 and pad='93637428753-19078512484-07499046167-90467648315-23800958676' order by c;
+----+-------------+---------+------------+-------+---------------+---------+---------+------+--------+----------+------------------------------------------+
| id | select_type | table   | partitions | type  | possible_keys | key     | key_len | ref  | rows   | filtered | Extra                                    |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+--------+----------+------------------------------------------+
|  1 | SIMPLE      | sbtest1 | NULL       | range | id_test       | id_test | 4       | NULL | 254162 |    10.00 | Using where; Using index; Using filesort |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+--------+----------+------------------------------------------+
1 row in set, 1 warning (0.00 sec)

增加索引(b,a,c)

从这里看效率已经很高了,扫描行数1行,但是索引是range范围扫描,并且依然有临时表排序。

mysql> alter table sbtest1 add index id_test(pad,k,c);
mysql> OPTIMIZE TABLE sbtest1;
mysql> select k,pad,c from sbtest1 where k>383019  and pad='93637428753-19078512484-07499046167-90467648315-23800958676' order by c;
1 row in set (0.00 sec)

mysql> explain select k,pad,c from sbtest1 where k>383019 and pad='93637428753-19078512484-07499046167-90467648315-23800958676' order by c;
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+------------------------------------------+
| id | select_type | table   | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra                                    |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+------------------------------------------+
|  1 | SIMPLE      | sbtest1 | NULL       | range | id_test       | id_test | 244     | NULL |    1 |   100.00 | Using where; Using index; Using filesort |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+------------------------------------------+
1 row in set, 1 warning (0.00 sec)

增加索引(b,c,a)

此时可以看到,索引的效率已经非常高了,ref,const,唯一查询,并且没有临时排序,因为b值相等于c,所以无需排序,因为索引中本来就是有序无需再进行排序。

mysql> alter table sbtest1 add index id_test(pad,c,k);
mysql> explain select k,pad,c from sbtest1 where k>383019 and pad='93637428753-19078512484-07499046167-90467648315-23800958676' order by c;
+----+-------------+---------+------------+------+---------------+---------+---------+-------+------+----------+--------------------------+
| id | select_type | table   | partitions | type | possible_keys | key     | key_len | ref   | rows | filtered | Extra                    |
+----+-------------+---------+------------+------+---------------+---------+---------+-------+------+----------+--------------------------+
|  1 | SIMPLE      | sbtest1 | NULL       | ref  | id_test       | id_test | 240     | const |    1 |    33.33 | Using where; Using index |
+----+-------------+---------+------------+------+---------------+---------+---------+-------+------+----------+--------------------------+
1 row in set, 1 warning (0.01 sec)

mysql> select k,pad,c from sbtest1 where k>383019 and pad='93637428753-19078512484-07499046167-90467648315-23800958676' order by c;
1 row in set (0.00 sec)

增加索引(b,c)

这里效率整体来看和(b,c,a)一样,而且同样没有额外的排序,但是因为我们是要查,slect a,b,c,所以如果不加a的索引,就用不到覆盖索引,相比较(b,c,a)多一次通过主键回表找a列数据的操作。但是因为是主键其实效率还好。因为建了(b,c,a)走到最后a的时候也是需要在辅助索引树中,进行for循环判断a列的。

mysql> drop index id_test on sbtest1;
mysql> alter table sbtest1 add index id_test(pad,c);
mysql> OPTIMIZE TABLE sbtest1;
mysql> explain select k,pad,c from sbtest1 where k>383019 and pad='93637428753-19078512484-07499046167-90467648315-23800958676' order by c;
+----+-------------+---------+------------+------+---------------+---------+---------+-------+------+----------+------------------------------------+
| id | select_type | table   | partitions | type | possible_keys | key     | key_len | ref   | rows | filtered | Extra                              |
+----+-------------+---------+------------+------+---------------+---------+---------+-------+------+----------+------------------------------------+
|  1 | SIMPLE      | sbtest1 | NULL       | ref  | id_test       | id_test | 240     | const |    1 |    33.33 | Using where                        |
+----+-------------+---------+------------+------+---------------+---------+---------+-------+------+----------+------------------------------------+
1 row in set, 1 warning (0.01 sec)

mysql> select k,pad,c from sbtest1 where k>383019 and pad='93637428753-19078512484-07499046167-90467648315-23800958676' order by c;
1 row in set (0.00 sec)

B+tree索引概念

在解释之前,我们需要了解一下B+tree索引中比较重要的两个概念:

  • 第一个:B+tree在创建索引时,是按照最左侧进行排序,例如(a,b,c)三列建造联合索引,先按照a排序,如过a的值一样,那就按照第二列b进行排序,b是相等的话,在按照c排序…

  • 第二个:因为根据上方的排序方式,所以就导致了,B+tree的联合索引,在select查询时,如果查询条件,是范围查询如a>?或者a<?,就会导致右侧,也就是后面的where条件无法走索引。例如,索引(a,b,c),查询条件为a>? and b=? c=?这样的话,后面的b和c就无法使用到索引了,因为B+tree遵循,最左原则,先按照索引查a,a在范围查找后,b和c在索引树中就是无序的,无序的自然也就没办法使用索引了。
    请看下图,a,b两列建造索引,先按照a排序,所以a是有序的,b在a相同的情况下,b是有序的。
    image.png

结论

(b,c,a)≈(b,c)>(b,a,c)>(a,c,b)≈(a,b,c)
根据上面的两个核心点,我们现在整理理解一下本篇文章的结论,为什么b,c没有建造a的索引,也能和b,c,a效率一样呢?

  • 那是因为就算建造了a,也是用不上的,因为b是等值,c是做order排序,B+tree索引中的联合索引,是先排序再创建的,所以结果是有序的,无需再进行排序,也就没有了filesort。但走到最后判断a的时候,此时a是无序的,所以不满足使用B+tree索引的条件。

那在(b,c)索引中,最后是怎么获取a这列数据的?

  • b等值查询后,因为是等值,所以c也是有序的,无需排序,最后到a的时候,因为索引树中没有a这个列,所以需要通过主键id获取a列数据进行比对。因为是通过主键,效率其实还好。但是因为所需要查询的数据是select a,b,c所以在索引树中无法直接获取到所有数据,也就没了using index覆盖索引。

那在(b,c,a)索引中,最后是怎么获取a这列数据的?

  • 前面查询的数据和(b,c)索引一样,到了最后a的时候,因为(b,c,a)索引中也有a这个列,所以可以在这个索引页中进行for循环,加if判断a的查询条件,看是否满足,判断完毕后返回数据,因为直接从索引树中获取到的数据,所以用到了using index覆盖索引。

如果不建造a列索引只是建造(b,c)效果也是差不多的,查到最后a的时候通过主键回表效率也不是很低,所以整体来说建造(b,c,a)或者(b,c)都可以,如果可以确认语句永远只查a,b,c三列,建造(b,c,a)没有问题,因为可以用到覆盖索引,否则为了存储空间和写入效率等,建造(b,c)就可以了。

小知识

前面提到了,当联合索引where条件,用到范围查询后,右侧也就是后面的条件因为是无序的,所以无法使用索引,这句话也对,但不全对,因为范围查询还包括>=、<=、BETWEEN、like。这些条件的后面,有可能也会用上索引,为什么这么说?

  • 比如有(a,b)两列索引,a列数据为1、1、2、3、4,那此时可以发现a=1的数据有两条,这两条数据的b列一定是局部有序的,如果我的查询条件是a>=1 and b=?,如果是a>1查询条件,那肯定不会看到1这个结果,所以无法b无法走索引,但是如果是a>=1,那么结果中此时有两个1,这时候优化器就会把他看成a=1 and b=?,因为有相同的等值,结果集a列的两个1。那此时b列就是有序的,就能用到索引先过滤掉这一部分数据。所以还是有可能会用到索引的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值