这种sql写法真的会导致索引失效吗
前言
网上经常能看到一些文章总结在mysql中不能命中索引的各种情况,其中有一种说法就是指使用了or的语句都不能命中索引。
这种说法其实是不够正确的,正确的结论应该是,从mysql5.0后,如果在or连接的字段上都有独立的索引的话,是可以命中索引的,这里就是用到了index_merge特性。
在mysql5.0版本以前一条sql只能选择使用一个索引,而且如果sql中使用了or关键字,那么已有的索引就会失效,会走全表扫描。因为无论走哪个索引,mysql都不能一次性查找出符合条件的数据,所以只能放弃索引。
mysql也是一直在不断升级更新,所以在mysql5.0版本后,增加了index_merge索引合并这个特性,也因此支持了一条sql使用多个索引。
index_merge核心思想就是先分别使用单个索引查出满足要求的数据,然后再将这些数据合并到一起返回。
我们可以看一个的例子。
这里依然沿用我们前面文章中创建的表和测试数据,表中插入了10w条测试数据,表结构如下。
CREATETABLE`t`( `id`int(11)NOTNULL, `a`int(11)DEFAULTNULL, `b`int(11)DEFAULTNULL, PRIMARYKEY(`id`) )ENGINE=InnoDB;
我们先来给 a字段添加一个索引,然后执行一条带or的查询语句看看。
mysql>altertabletaddindexa_index(a); QueryOK,0rowsaffected(0.17sec) Records:0Duplicates:0Warnings:0
mysql>explainselectafromtwherea=100orb=6000; +----+-------------+-------+------+---------------+------+---------+------+--------+-------------+ |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra| +----+-------------+-------+------+---------------+------+---------+------+--------+-------------+ |1|SIMPLE|t|ALL|a_index|NULL|NULL|NULL|100332|Usingwhere| +----+-------------+-------+------+---------------+------+---------+------+--------+-------------+ 1rowinset(0.00sec)
因为字段b上没有索引,mysql认为走全表扫描代价更低一些,因为可以免去回表过程。
那么我们给b字段也加上索引试试,然后再执行刚刚那条sql。
mysql>altertabletaddindexb_index(b); QueryOK,0rowsaffected(0.17sec) Records:0Duplicates:0Warnings:0
mysql>explainselectafromtwherea=100orb=6000; +----+-------------+-------+-------------+-----------------+-----------------+---------+------+------+-------------------------------------------+ |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra| +----+-------------+-------+-------------+-----------------+-----------------+---------+------+------+-------------------------------------------+ |1|SIMPLE|t|index_merge|a_index,b_index|a_index,b_index|5,5|NULL|2|Usingunion(a_index,b_index);Usingwhere| +----+-------------+-------+-------------+-----------------+-----------------+---------+------+------+-------------------------------------------+ 1rowinset(0.00sec)
这回可以看到mysql同时使用了a、b两个索引,并且看到type字段的值为index_merge。
接下来再来看另一条sql,看看结果又是怎样的。
mysql>explainselectafromtwherea>100orb>6000; +----+-------------+-------+------+-----------------+------+---------+------+--------+-------------+ |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra| +----+-------------+-------+------+-----------------+------+---------+------+--------+-------------+ |1|SIMPLE|t|ALL|a_index,b_index|NULL|NULL|NULL|100332|Usingwhere| +----+-------------+-------+------+-----------------+------+---------+------+--------+-------------+ 1rowinset(0.00sec)
这条sql仅仅是把等号改成了大于号,也就是说返回的结果集是一个区间集,mysql在这里又放弃了索引,走的全表扫描,不过有看文章说在mysql5.7版本后优化了这个问题,即在区间查询中也支持使用index_merge,我的版本是5.6,暂未验证这个优化,有兴趣的可以去验证下。
其实在mysql中很多东西都是不绝对的,对于同一条sql不同mysql版本的内部处理方式有可能是不太一样的,同时也可以看到mysql一直在不断优化升级,一些老旧的知识点很容易就会不再适用了。
希望文章对你有帮助,欢迎关注,点个赞是对我最好的支持,感谢。
另外,关于mysql的底层数据结构,大家可以参考我前面写的其他文章,对你理解这篇文章或许有帮助。