在上一篇文章中,通过分析执行计划的字段说明,大体说了一下索引优化过程中的一些注意点,那么如何才能避免索引失效呢?本篇文章将来讨论这个问题。

避免索引失效的常见方法

1.对于复合索引的使用,应按照索引建立的顺序使用,尽量不要跨列(最佳左前缀原则)

为了说明问题,我们仍然使用上一篇文章中的test01表,其表结构如下所示:

mysql> desc test01;

+--------+-------------+------+-----+---------+-------+

| Field | Type | Null | Key | Default | Extra |

+--------+-------------+------+-----+---------+-------+

| id | int(4) | YES | MUL | NULL | |

| name | varchar(20) | YES | | NULL | |

| passwd | char(20) | YES | | NULL | |

| inf | char(50) | YES | | NULL | |

+--------+-------------+------+-----+---------+-------+

4 rows in set (0.01 sec)

mysql> show index from test01;

+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+

---------+---------------+

| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type |

Comment | Index_comment |

+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+

---------+---------------+

| test01 | 1 | t_idx1 | 1 | id | A | 0 | NULL | NULL | YES | BTREE |

| |

| test01 | 1 | t_idx1 | 2 | name | A | 0 | NULL | NULL | YES | BTREE |

| |

| test01 | 1 | t_idx1 | 3 | passwd | A | 0 | NULL | NULL | YES | BTREE |

| |

+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+

---------+---------------+

3 rows in set (0.01 sec)

如果常规的SQL写法,三个索引全覆盖,没有任何问题:

mysql> explain select * from test01 where id = 1 and name = 'zz' and passwd = '123';

+----+-------------+--------+------------+------+---------------+--------+---------+-------------------+------+----------+-------

+

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra

|

+----+-------------+--------+------------+------+---------------+--------+---------+-------------------+------+----------+-------

+

| 1 | SIMPLE | test01 | NULL | ref | t_idx1 | t_idx1 | 129 | const,const,const | 1 | 100.00 | NULL

|

+----+-------------+--------+------------+------+---------------+--------+---------+-------------------+------+----------+-------

+

1 row in set, 1 warning (0.00 sec)

但是如果跨列使用,如下所示:

mysql> explain select * from test01 where id = 1 and passwd = '123';

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

----+

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra

|

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

----+

| 1 | SIMPLE | test01 | NULL | ref | t_idx1 | t_idx1 | 5 | const | 1 | 100.00 | Using index condit

ion |

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

----+

1 row in set, 1 warning (0.00 sec)

通过观察,发现key_len已经从129变成5了,说明只有id使用到了索引,而passwd并没有用到索引。

接下来我们看一种更糟糕的情况:

mysql> explain select * from test01 where id = 1 order by passwd;

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

--------------------+

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra

|

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

--------------------+

| 1 | SIMPLE | test01 | NULL | ref | t_idx1 | t_idx1 | 5 | const | 1 | 100.00 | Using index condit

ion; Using filesort |

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

--------------------+

1 row in set, 1 warning (0.00 sec)

上述语句中,Extra字段中出现了Using filesort,之前说过,这是非常差的一种写法,如果我们做一下改动:

mysql> explain select * from test01 where id = 1 order by name,passwd;

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

----+

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra

|

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

----+

| 1 | SIMPLE | test01 | NULL | ref | t_idx1 | t_idx1 | 5 | const | 1 | 100.00 | Using index condit

ion |

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

----+

1 row in set, 1 warning (0.00 sec)

与上面的SQL相比较,只是在order by 里,加上了name,Using filesort就去掉了,所以这里的不能跨列,指的是where 和order by之间不能跨列,否则会出现很糟糕的情况。

2.不要在索引上进行任何函数操作

包括但不限于sum、trim、甚至对字段进行加减乘除计算。

mysql> explain select id from test01 where id = 1 and trim(name) ='zz' and passwd = '123';

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

-------+

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra

|

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

-------+

| 1 | SIMPLE | test01 | NULL | ref | t_idx1 | t_idx1 | 5 | const | 1 | 100.00 | Using where; Using

index |

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

-------+

1 row in set, 1 warning (0.00 sec)

因为在name字段上,进行了trim函数操作,所以name失效,连带着后面的passwd也失效了,因为key_len = 5。

所以此处透露出两个信息点:

在索引字段上进行函数操作,会导致索引失效;

复合索引如果前面的字段失效,其后面的所有字段索引都会失效。

3.复合索引不要使用is null或is not null,否则其自身和其后面的索引全部失效。

仍然是看一个例子:

mysql> explain select id from test01 where id = 1 and name is not null and passwd = '123';

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

-------+

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra

|

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

-------+

| 1 | SIMPLE | test01 | NULL | ref | t_idx1 | t_idx1 | 5 | const | 1 | 100.00 | Using where; Using

index |

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

-------+

1 row in set, 1 warning (0.00 sec)

因为name使用了is not null,所以导致name和passwd都失效了,从key_len = 5可以看出。

4.like尽量不要在前面加%

这一点之前在说明range级别的时候有提到过,此处再次说明一下。

mysql> explain select * from test01 where id = 1 and name like '%a%' and passwd = '123';

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

----+

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra

|

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

----+

| 1 | SIMPLE | test01 | NULL | ref | t_idx1 | t_idx1 | 5 | const | 1 | 100.00 | Using index condit

ion |

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

----+

1 row in set, 1 warning (0.00 sec)

在上例中,name字段使用了like '%a%',所以导致name和passwd都失效,只有id使用到了索引。

为了对比,如果把前面的%去掉,看看什么结果:

mysql> explain select * from test01 where id = 1 and name like 'a%' and passwd = '123';

+----+-------------+--------+------------+-------+---------------+--------+---------+------+------+----------+-------------------

----+

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra

|

+----+-------------+--------+------------+-------+---------------+--------+---------+------+------+----------+-------------------

----+

| 1 | SIMPLE | test01 | NULL | range | t_idx1 | t_idx1 | 129 | NULL | 1 | 100.00 | Using index condit

ion |

+----+-------------+--------+------------+-------+---------------+--------+---------+------+------+----------+-------------------

----+

1 row in set, 1 warning (0.00 sec)

可以看到,此时key_len = 129,说明三个字段都用到了。

5.尽量不要使用类型转换,包括显式的和隐式的

正常的索引应该是这样:

mysql> explain select * from test01 where id = 1 and name = 'zz' and passwd = '123';

+----+-------------+--------+------------+------+---------------+--------+---------+-------------------+------+----------+-------

+

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra

|

+----+-------------+--------+------------+------+---------------+--------+---------+-------------------+------+----------+-------

+

| 1 | SIMPLE | test01 | NULL | ref | t_idx1 | t_idx1 | 129 | const,const,const | 1 | 100.00 | NULL

|

+----+-------------+--------+------------+------+---------------+--------+---------+-------------------+------+----------+-------

+

1 row in set, 1 warning (0.00 sec)

但是如果改一下,把passwd = '123'改成passwd = 123,让MySQL自己去做类型转换,将123转换成'123',那结果是怎样的呢?

mysql> explain select * from test01 where id = 1 and name = 'zz' and passwd = 123;

+----+-------------+--------+------------+------+---------------+--------+---------+-------------+------+----------+-------------

----------+

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra

|

+----+-------------+--------+------------+------+---------------+--------+---------+-------------+------+----------+-------------

----------+

| 1 | SIMPLE | test01 | NULL | ref | t_idx1 | t_idx1 | 68 | const,const | 1 | 100.00 | Using index

condition |

+----+-------------+--------+------------+------+---------------+--------+---------+-------------+------+----------+-------------

----------+

1 row in set, 2 warnings (0.00 sec)

发现key_len = 68,最后一个passwd失效了。

6.尽量不要使用or

or会使or前面的和后面的索引同时失效,这点比较变态,所以要特别注意:

mysql> explain select * from test01 where id = 1 or name = 'zz';

+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |

+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+

| 1 | SIMPLE | test01 | NULL | ALL | t_idx1 | NULL | NULL | NULL | 1 | 100.00 | Using where |

+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+

1 row in set, 1 warning (0.00 sec)

可以看到因为使用了or,导致索引为NULL,type级别为ALL。

如果一定要使用or,应该怎样补救呢?

补救的办法是尽量用到索引覆盖。比如我们把原来SQL中的select * 中的 * 号替换成具体的字段,这些字段能够覆盖索引,那么对索引优化也有一定的提升:

mysql> explain select id, name, passwd from test01 where id = 1 or name = 'zz';

+----+-------------+--------+------------+-------+---------------+--------+---------+------+------+----------+-------------------

-------+

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra

|

+----+-------------+--------+------------+-------+---------------+--------+---------+------+------+----------+-------------------

-------+

| 1 | SIMPLE | test01 | NULL | index | t_idx1 | t_idx1 | 129 | NULL | 1 | 100.00 | Using where; Using

index |

+----+-------------+--------+------------+-------+---------------+--------+---------+------+------+----------+-------------------

-------+

1 row in set, 1 warning (0.00 sec)

从上例中可以看到,where条件没做任何改变,但是type级别已经提升到了index,也是用到了索引。

7.in经常会使索引失效,应该慎用

mysql> explain select id, name, passwd from test01 where id = 1 and name in ('zz', 'aa');

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

-------+

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra

|

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

-------+

| 1 | SIMPLE | test01 | NULL | ref | t_idx1 | t_idx1 | 5 | const | 2 | 100.00 | Using where; Using

index |

+----+-------------+--------+------------+------+---------------+--------+---------+-------+------+----------+-------------------

-------+

1 row in set, 1 warning (0.00 sec)

从上例中,不难看出,key_len = 5,所以name索引失效了,原因就是name使用了in。

为什么说经常会使索引失效呢?因为in也不一定总使索引失效,如下面的例子:

mysql> explain select id, name, passwd from test01 where id in (1,2,3) and name = 'zz';

+----+-------------+--------+------------+-------+---------------+--------+---------+------+------+----------+-------------------

-------+

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra

|

+----+-------------+--------+------------+-------+---------------+--------+---------+------+------+----------+-------------------

-------+

| 1 | SIMPLE | test01 | NULL | index | t_idx1 | t_idx1 | 129 | NULL | 1 | 100.00 | Using where; Using

index |

+----+-------------+--------+------------+-------+---------------+--------+---------+------+------+----------+-------------------

-------+

1 row in set, 1 warning (0.00 sec)

可以看到此时key_len = 129,说明用到了全部索引。以上情况之所以出现,其实还是索引失效了,但是虽然id索引失效,但是name索引并没有失效,所以上面的句子等价于:select id, name, passwd from test01 where name = 'zz';。这与之前说的复合索引只要前面的失效,后面都失效并不太一致,所以对于in,应该谨慎使用。

对于关联表查询的情况,应该遵循“小表驱动大表”的原则

总而言之,就是左连接给左表建索引,右连接给右表建索引,内连接的话给数据量小的表建索引。

还需要说明一点的是,索引并不是越多越好

因为索引的数据结构是B树,毕竟要占内存空间,所以如果索引越多,索引越大,对硬盘空间的消耗其实是巨大的,而且如果表结构需要调整,意味着索引也要同步做调整,否则会导致不可预计的问题出现。

因此,在实际开发中,对于创建索引,应充分考虑到具体的业务情况,根据业务实现来创建索引,对于有些比较特殊的复杂SQL,建议在代码里进行一定的逻辑处理后再进行常规的索引查询。

举个例子,比如test01表中,需要判断inf字段是否包含“上海”字段,如果在SQL里实现,则必然是如下的逻辑:

select id,name,passwd,inf from test01 where id = 1 and name = 'zz' and passwd = '123' and inf like '%上海%'

这条sql就比较恐怖了,且不说inf本来不是索引,而且有like '%上海%'这种糟糕的写法,所以我们完全可以使用下面的方法代替。

先使用下面的SQL查出所有字段:

select id,name,passwd,inf from test01 where id = 1 and name = 'zz' and passwd = '123'

然后在代码里判断inf字段是否包含上海字段,如C语言实现如下:

if (strstr(inf, "上海") != NULL)

{

//do something

}

这样一来,虽然只是多了一步简单的逻辑判断,但是对于SQL优化的帮助其实是巨大的。

mysql防止索引崩溃_MySQL优化之避免索引失效的方法相关推荐

  1. mysql索引优化规则_Mysql优化选择最佳索引规则

    索引的目的在于提高查询效率,其功能可类比字典,通过该索引可以查询到我们想要查询的信息,因此,选择建立好的索引十分重要,以下是为Mysql优化选择最佳索引的方法步骤: 1. 首先列出查询中所有使用的表, ...

  2. mysql 回表查询优化_MySQL优化:如何避免回表查询?什么是索引覆盖?

    转自:https://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=2651962609&idx=1&sn=46e59691257 ...

  3. mysql优化varchar索引_MySQL优化--概述以及索引优化分析

    一.MySQL概述 1.1.MySQL文件含义 通过如下命令查看 show variables like '%dir%'; MySQL文件位置及含义 名称 值 备注 basedir /usr/ 安装路 ...

  4. mysql 创建索引失败_mysql创建多列索引及优化 - 没有所谓的失败!除非你不再尝试! - PHPChina ......

    什么是索引? 索引用来快速地寻找那些具有特定值的记录,所有MySQL索引都以B-树的形式保存.如果没有索引,执行查询时MySQL必须从第一个记录开始扫描整个表的所有记录,直至找到符合要求的记录.表里面 ...

  5. mysql btree索引_MySQL优化之BTree索引使用规则

    MySQL优化之BTree索引使用规则 从一道题开始分析: 假设某个表有一个联合索引(c1,c2,c3,c4)一下--只能使用该联合索引的c1,c2,c3部分 A where c1=x and c2= ...

  6. mysql删除表崩溃_MySQL在删除表时I/O错误原因分析

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 问题现象 最近使用sysbench测试MySQL,由于测试时间较长,写了一个脚本按prepare->run->cleanup的顺 ...

  7. mysql 查询慢 分析_MySQL优化:定位慢查询的两种方法以及使用explain分析SQL

    一条SQL查询语句在经过MySQL查询优化器处理后会生成一个所谓的执行计划,这个执行计划展示了具体执行查询的方式,比如多表连接的顺序是什么,对于每个表采用什么访问方法来具体执行查询等等. 本章的内容就 ...

  8. mysql的学习要点_MySQL中的联合索引的学习要点总结

    MySQL中的联合索引的学习要点总结 联合索引又叫复合索引.对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分.例如索引是key index (a ...

  9. mysql ip比较大小_MySQL优化/面试,看这一篇就够了

    原文链接:http://www.zhenganwen.top/articles/2018/12/25/1565048860202.html 作者:Anwen~ 链接:https://www.nowco ...

最新文章

  1. 学习利器:工欲善其事,必先利其器
  2. R语言stringr包str_extract函数检查数据是否包含特定模式实战
  3. linux规则及别名设置
  4. linux 打包压缩工具
  5. 【云周刊】第132期:走近40+世界级AI专家!第三届中国人工智能大会资料分享...
  6. mysql百万级分页优化
  7. 罗永浩直播翻车频现,抖音选错了吗?
  8. 18B树、B++树和Trie树
  9. 然而毕博淮安的毕博淮安
  10. mongoDB高级查询
  11. 华为交换机关闭接口命令_华为交换机常用命令及技巧
  12. Git -- 搭建git服务器
  13. 深入浅出Docker(六):像谷歌一样部署你的应用
  14. Android RecyclerView网格布局动画
  15. 每日一句20200119
  16. mysql 多级主从_mysql主从复制-二级主从
  17. php 公众号推送图片尺寸,『微信公众号运营技巧』推送文图片什么尺寸最佳?...
  18. 东财《组织行为学B》综合作业
  19. 传奇地图时间限制脚本_脚本第一课限制进入次数地图设置方法
  20. C#Assembly详解

热门文章

  1. 不错!基于Springboot 2.0 + LayUI开发的物流管理系统(已开源)
  2. 皮一皮:当群聊被封,大家是如何聊天的...
  3. 皮一皮:所以说,快乐水才会让人变胖...
  4. 每日一皮:上线一切正常的顺畅感就是如此赏心悦目!
  5. 这款 20 万开发者使用的开发插件到底经历了什么?
  6. 微信小程序设置文本左对齐居中对齐右对齐setTextAlign的使用说明
  7. pytorch cpu占用较高
  8. permission denied for window type 2003
  9. 背景减除(Background Segment)
  10. sqlalchemy根据表名动态创建model类