MySQL调优之索引在什么情况下会失效详解

目录
  • 前言
  • 【1】全值匹配我最爱
  • 【2】最佳左前缀法则
  • 【3】主键插入顺序
  • 【4】计算、函数、类型转换(自动或手动)导致索引失效
  • 【5】范围条件右边的列索引失效
  • 【6】不等于(!=或者 <>) 索引失效
  • 【7】is null可以使用索引,is not null无法使用索引
  • 【8】like以通配符%开头索引失效
  • 【9】OR前后存在非索引的列,索引失效
  • 【10】数据库和表的字符集统一使用utf8mb4
  • 总结

前言

MySQL中提高性能的一个最有效的方式是对数据表设计合理的索引。索引提供了高效访问数据的方法,并且加快查询的速度,因此索引对查询的速度有着至关重要的影响。

  • 使用索引可以快速地定位表中的某条记录,从而提高数据库查询的速度,提高数据库的性能。
  • 如果查询时没有使用索引,查询语句就会扫描表中的所有记录。在数据量大的情况下,这样查询的速度回很慢。

大多数情况下都(默认)采用B+树来构建索引。只是空间列类型的索引使用R-树,并且MEMORY表还支持hash索引。

其实,用不用索引,最终都是优化器说了算。优化器是基于什么的考虑?基于cost开销(CostBaseOptimizer),它不是基于规则(Rule-BasedOptimizer),也不是基于语义,只是依据数值大小。另外,SQL语句是否使用索引,跟数据库版本、数据量、数据选择度都有关系。

本文我们尝试总结索引失效的一些场景。我们会准备class和student两个表,class插入一万条数据,student插入50万条数据。环境是MySQL8.0,InnoDB。

【1】全值匹配我最爱

系统中经常出现的SQL语句如下:

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age=30;
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age=30 AND classId=4;
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age=30 AND classId=4 AND NAME = 'abcd';

建立索引前执行:

[SQL]SELECT SQL_NO_CACHE * FROM student WHERE age=30 AND classId=4 AND NAME = 'abcd';
受影响的行: 0
时间: 0.308s

建立索引(age):

CREATE INDEX idx_age ON student(age);

建立索引后执行:

[SQL]SELECT SQL_NO_CACHE * FROM student WHERE age=30 AND classId=4 AND NAME = 'abcd';
受影响的行: 0
时间: 0.113s

继续创建索引(age,classId):

CREATE INDEX idx_age_classid ON student(age,classId);

建立索引后执行:

[SQL]SELECT SQL_NO_CACHE * FROM student WHERE age=30 AND classId=4 AND NAME = 'abcd';
受影响的行: 0
时间: 0.007s

继续创建索引(age,classId,NAME):

CREATE INDEX idx_age_classid_name ON student(age,classId,NAME);

建立索引后执行:

[SQL]SELECT SQL_NO_CACHE * FROM student WHERE age=30 AND classId=4 AND NAME = 'abcd';
受影响的行: 0
时间: 0.000s  # 其实必然不是0,只是更小了

从执行计划可以看到,MySQL会帮我们选择最多包含查询列的联合索引。

【2】最佳左前缀法则

在MySQL建立联合索引时会遵守最佳左前缀匹配原则,即最左优先,在检索数据时从联合索引的最左边开始匹配。

举例:age、name可以用到索引。

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.age=30 AND student.name = 'abcd' ;

虽然可以正常使用,但是只有部分被使用到了。而且MySQL优化器考虑的索引是idx_age,而非idx_age_classid_name。

举例2:

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.classid=1 AND student.name = 'abcd';

可以看到,没有age开头 ,完全没有用到索引。

举例3:索引idx_age_classid_name还能否正常使用?

# MySQL会进行优化,形成age,classid,name以符合联合索引idx_age_classid_name
EXPLAIN SELECT SQL_NO_CACHE * FROM student
WHERE classid=4 AND student.age=30 AND student.name = 'abcd';

如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。

我们删掉索引idx_age 、idx_age_classid 再次执行查询age and name,没有中间的classid。

DROP INDEX idx_age ON student;
DROP INDEX idx_age_classid ON student;

EXPLAIN SELECT SQL_NO_CACHE * FROM student
WHERE student.age=30 AND student.name = 'abcd';

这里key_len=5,说明只用到了联合索引的一部分–age用到了索引。因为其中间环节 classid不存在, 故而不能完全使用联合索引。

结论 : MySQL可以为多个字段创建索引,一个索引可以包括16个字段。对于多列索引,过滤条件要使用索引必须按照索引建立时的顺序,依次满足,一旦跳过某个字段,索引后面的字段都无法被使用。如果查询条件中没有使用这些字段中第一个字段时,多列(或联合)索引不会被使用。

对于=值查询,如果where中条件查询没有按照联合索引字段顺序编写,MySQL优化器会进行调优以使其满足联合索引字段顺序。

【3】主键插入顺序

对于一个使用InnoDB存储引擎的表来说,在我们没有显示的创建索引时,表中的数据实际上都是存储在聚簇索引的叶子节点的。而记录又是存储在数据页中的,数据页和记录又是按照记录主键值从小到大的顺序进行排序。所以如果我们插入的记录的主键值是依次增大的话,那我们每插满一个数据页就换到下一个数据页继续插。

而如果我们插入的主键值忽大忽小的话,就比较麻烦了。假设某个数据页存储的记录已经满了,它存储的主键值在1~100之间:

如果此时再插入一条主键值为9的记录,那它插入的位置就如下图:

可这个数据页已经满了,再插进来咋办呢?我们需要把当前页面分裂成两个页面,把本页中的一些记录移动到新创建的这个页中。页面分裂和记录移位意味着什么?意味着性能损耗! 所以如果我们想进来避免这样无谓的性能损耗,最好让插入的记录的主键值依次递增,这样就不会发生这样的性能损耗了。

所以我们建议:让主键具有AUTO_INCREMENT,让存储引擎自己为表生成主键,而不是我们手动插入,比如person_info表:

create table person_info(
	id int unsigned not null auto_increment,
	name varchar(100) not null,
	birthday date not null,
	phone_numnber char(11) not null,
	country varchar(100) not null,
	primary key (id),
	key idx_name_bd_ph_num(name(10),birthday,phone_number)
)

我们自定义的主键列id拥有AUTO_INCREMENT属性,在插入记录时存储引擎会自动为我们填入自增的主键值。这样的主键占用空间小,顺序写入,减少页分裂。

【4】计算、函数、类型转换(自动或手动)导致索引失效

如下两条SQL,哪个更好呢?其实是第一条,能够使用到索引,第二条有了函数计算。

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE 'abc%';

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE LEFT(student.name,3) = 'abc';

我们创建索引(NAME):

CREATE INDEX idx_name ON student(NAME);

查看第一条SQL的执行计划:

查看第二条SQL的执行计划:

对比执行计划可以看到,第一条SQL使用到了索引,第二条SQL的type=all表示全表扫描。说明函数计算或导致索引失效。

我们再看一下数学计算:

CREATE INDEX idx_sno ON student(stuno);

EXPLAIN SELECT SQL_NO_CACHE id, stuno, NAME FROM student WHERE stuno+1 = 900001;

如上图所示,SQL中有数学计算,执行计划中 type=all表示没有使用索引进行了全表扫描。我们再看下面这个SQL,很显然其会使用到索引。这就说明数学计算会导致索引失效。

EXPLAIN SELECT SQL_NO_CACHE id, stuno, NAME FROM student WHERE stuno = 900000;

最后我们再看一下类型转换

字符串类型一定不要忘记单引号,否则索引失效。

# 会进行隐式类型转换 ,索引失效
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE NAME = 123;

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE NAME = '123';


对比二者的执行计划可知,类型转换会导致索引失效。

【5】范围条件右边的列索引失效

首先删除表student的索引:

alter table student drop index idx_name;
alter table student drop index idx_age;
alter table student drop index idx_age_classid;

查看当前索引:show index from student;

对于如下SQL,索引idx_age_classid_name还能够正常使用吗?

EXPLAIN SELECT SQL_NO_CACHE * FROM student
WHERE student.age=30 AND student.classId > 20 AND student.name = 'abc' ;

执行计划如下所示,key_len=10,说明只有age和classid用到了索引。

这时候即使交换次序,也是没有意义的,如下所示:

EXPLAIN SELECT SQL_NO_CACHE * FROM student
WHERE student.age=30 AND student.name = 'abc' AND student.classId>20;

那么如何使其能够使用到索引呢?如下所示创建索引(age,NAME,classId)。

CREATE INDEX idx_age_name_cid ON student(age,NAME,classId);

这时再执行上面SQL,可以看到充分用到了联合索引。

对于 下面这个SQL,执行计划是一样的。查询优化器对于and条件会进行顺序的调整,以满足联合索引的顺序。

EXPLAIN SELECT SQL_NO_CACHE * FROM student
WHERE student.age=30 AND student.classId > 20 AND student.name = 'abc' ;

总结

  • 范围右边的列不能使用索引。比如 < 、<=、 >、 >=、 between。
  • 这个右边指的是联合索引字段的右边,至于SQL where中的and条件,查询优化器是可以进行调整的。
  • 创建的联合索引中,务必把范围涉及到的字段写在最后。

【6】不等于(!=或者 <>) 索引失效

为name字段创建索引:

CREATE INDEX idx_name ON student(NAME);

进行等值判断,正常使用索引:

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name = 'abc' ;

对于不等判断,查看索引是否失效:

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name <> 'abc' ;

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name != 'abc' ;

可以看到,两条SQL均为使用到索引。

【7】is null可以使用索引,is not null无法使用索引

is null可以触发索引

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age IS NULL; 

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE NAME IS NULL; 

is not null无法使用索引

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age IS NOT NULL; 

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE NAME IS NOT NULL; 

结论: 最好在设计数据表的时候就将字段设置为not null约束,比如你可以将int类型的字段,默认值设置为0.将字符类型的默认值设置为空字符('') 。同理,在查询中使用 not like 也无法使用索引,导致全表扫描。

【8】like以通配符%开头索引失效

在使用like关键字进行查询的查询语句中,如果匹配字符串的第一个字符为“%”,索引就不会起作用。只有"%"不在第一个位置,索引才会起作用。

使用到索引

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE NAME LIKE 'ab%'; 

没有用到索引

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE NAME LIKE '%ab%';

【9】OR前后存在非索引的列,索引失效

在where子句中,如果在or前的条件列进行了索引,而在or后的条件列没有进行索引,那么索引会失效。也就是说,OR 前后的两个条件中的列都是索引列时,查询中才会使用到索引。

因为OR的含义就是两个只要满足一个即可,因此只有一个条件列进行了索引是没有意义的。只要有条件列没有进行索引,就会进行全表扫描,因此索引的条件列也会失效。

SHOW INDEX FROM student;

# 删除索引
alter table student drop index idx_age_classid_name;
alter table student drop index idx_age_name_cid;
alter table student drop index idx_sno;
alter table student drop index idx_name;

#创建索引
CREATE INDEX idx_age ON student(age);

这时我们查询语句使用OR关键字的情况(age有索引,classid没有索引)

EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 10 OR classid = 100;

可以看到,是没有使用到索引的。如果我们为classid创建索引呢?

CREATE INDEX idx_cid ON student(classid);
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 10 OR classid = 100;


可以看到,其使用到了索引,type=index_merge。简单来说,index_merge就是对age和classid分别进行了扫描,然后将这两个结果集进行了合并。这样做的好处就是避免了全表扫描。

【10】数据库和表的字符集统一使用utf8mb4

统一使用utf8mb4(5.5.3版本以上支持)兼容性更好,统一字符集可以避免由于字符集转换产生的乱码。

不同的字符集进行比较前需要进行转换会造成索引失效。

一般性建议:

  • 对于单列索引,尽量选择针对当前query过滤性更好的索引;
  • 在选择组合索引的时候,当前query中过滤性最好的字段在索引字段顺序中,位置越靠前越好;
  • 在选择组合索引的时候,尽量选择能够包含当前query中的where子句中更多字段的索引;
  • 在选择组合索引的时候,如果某个字段可能出现范围查询时,尽量把这个字段放在索引次序的最后面。

总结

到此这篇关于MySQL调优之索引在什么情况下会失效的文章就介绍到这了,更多相关MySQL索引失效内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • MySQL索引失效场景及解决方案

    目录 一.前言 二.最左前缀匹配原则 三.MySQL逻辑架构和优化器 四.索引失效场景以及为何会失效 五.总结 一.前言 在对SQL语句进行索引查询时会遇到索引失效的时候,对于该语句的可行性以及性能效率方面有至关重要的影响,本篇剖析索引为何失效,有哪些情况会导致索引失效以及对于索引失效时的优化解决方案,其中着重介绍最左前缀匹配原则.MySQL逻辑架构和优化器.索引失效场景以及为何会失效. 二.最左前缀匹配原则 之前有写了一篇关于MySQL添加索引特点及优化问题方面的文章,下面将介绍索引失效的相关

  • MySQL中因字段字符集不同导致索引不能命中的解决方法

    什么是索引?为什么要建立索引? 索引用于快速找出在某个列中有一特定值的行,不使用索引,MySQL必须从第一条记录开始读完整个表,直到找出相关的行,表越大,查询数据所花费的时间就越多,如果表中查询的列有一个索引,MySQL能够快速到达一个位置去搜索数据文件,而不必查看所有数据,那么将会节省很大一部分时间. 例如:有一张person表,其中有2W条记录,记录着2W个人的信息.有一个Phone的字段记录每个人的电话号码,现在想要查询出电话号码为xxxx的人的信息. 如果没有索引,那么将从表中第一条记录

  • 关于Mysql5.7及8.0版本索引失效情况汇总

    目录 一个独立索引 多个独立索引 总结 TIPS: 没有特殊说明,测试环境均为MySQL8.0,早期版本可能会有更多情况导致索引失效.8.0失效的情况,早期版本也失效:8.0不失效的情况,早期版本可能失效. 所有测试默认不考虑表为空的情况,特殊情况文中会有说明. 本文只介绍Innodb引擎下的索引失效情况. -- 创建测试表 DROP TABLE IF EXISTS `test_idx`; CREATE TABLE `test_idx` ( `id` int(11) NOT NULL AUTO_

  • MySQL索引命中与失效代码实现

    目录 创建表 MySQL执行优化器 第一种情况:针对联合索引,是否遵循最左匹配原则: 第二种情况:去掉大哥,看看索引是否命中: 第三种情况:在索引列上做了函数操作,会导致索引失效而导致全表扫描 第四种情况:模糊查询前缀是以%开头的,索引失效 第五种情况:模糊查询中后缀是以%,可以命中索引 第六种情况:使用is not null 会导致索引失效 第六种情况:使用and时,其中有一个条件查询带有索引而另一个不带索引,不会导致索引失效.而使用or时,如果条件查询中其中一个不带索引,导致索引失效,必须全

  • 从MySQL的源码剖析Innodb buffer的命中率计算

    按官方手册推荐Innodb buffer Hit Ratios的计算是: 100-((iReads / iReadRequests)*100) iReads : mysql->status->Innodb_buffer_pool_reads iReadRequests: mysql->status->Innodb_buffer_pool_read_requests 出处: http://dev.mysql.com/doc/mysql-monitor/2.0/en/mem_graph

  • MySQL之主键索引排序失效问题

    目录 主键索引排序失效 现在初始化几行数据 查一下所有记录 查看一下执行计划 总结 主键索引排序失效 环境:MySQL8 有一张用户信息表user_info,建表DDL如下: CREATE TABLE `user_info` ( `id` int(11) NOT NULL COMMENT '用户编号', `age` int(11) NOT NULL COMMENT '用户年龄', PRIMARY KEY (`id`), KEY `idx_age` (`age`) USING BTREE COMM

  • MySQL细数发生索引失效的情况

    目录 索引的存储结构 不合理的模糊查询条件 对索引使用函数 对索引进行表达式计算 对索引使用隐式转换 联合索引非最左匹配 where子句中的or 总结 索引的存储结构 首先了解一下索引的存储结构,知道了索引的存储结构,才方便我们更好地理解索引失效的问题. 索引的存储结构跟MySQL的存储引擎有关,存储引擎的不同采用的结构也会不同. MySQL默认的存储引擎InnoDB采用B+Tree作为索引的数据结构,在创建表时,InnoDB会默认创建一个主键索引,这是一个聚簇索引,其他索引都属于二级索引. M

  • MySQL索引优化之不适合构建索引及索引失效的几种情况详解

    目录 结论 不建议建立索引的场景 索引失效的场景 小结 结论 具体案例下文有详尽描述 不适合建立索引的场景: 数据量比较小的表不建议建立索引 有大量重复数据的字段上不建议建立索引(类似:性别字段) 需要进行频繁更新的表不建议建立索引 where.group by.order by后面的没有使用到的字段不建立索引 不要定义冗余索引 索引失效的场景: 过滤条件使用不等于(!=.<>) 过滤条件使用is not null 在索引字段上使用函数或进行计算 在使用联合索引的时候,需要满足“最佳左前缀法则

  • J2ee 高并发情况下监听器实例详解

    J2ee 高并发情况下监听器实例详解 引言:在高并发下限制最大并发次数,在web.xml中用过滤器设置参数(最大并发数),并设置其他相关参数.详细见代码. 第一步:配置web.xml配置,不懂的地方解释一下:参数50通过参数名maxConcurrent用在filter的实现类中获取,filter-class就是写的实现类, url-pattern就是限制并发时间的url,结束! <filter> <filter-name>ConcurrentCountFilter</filt

  • MySQL在不知道列名情况下的注入详解

    前言 最近感觉脑子空空,全在为了刷洞去挖洞,还是回归技术的本身让自己舒服些.好了,下面话不多说了,来一起看看详细的介绍吧 前提 以下情况适用于 MySQL < 5版本,或者在 MySQL >= 5 的版本[存在information_schema库],且已获取到库名和表名 ① 当只能获取到表名,获取不到列名或者只能获取到无有效内容的列名情况[例如 id] ② 当希望通过information_schema库中的表去获取其他表的结构,即表名.列名等,但是这个库被WAF过滤掉的情况 其实个人感觉这

  • Mysql调优Explain工具详解及实战演练(推荐)

    Mysql调优Explain工具详解以及实战演练 Explain工具介绍Explain分析示例explain 两个变种explain中的列 索引最佳实战索引使用总结: Mysql安装文档参考 Explain工具介绍 使用EXPLAIN关键字可以模拟优化器执行SQL语句,分析你的查询语句或是结构的性能瓶颈 在 select 语句之前增加 explain 关键字,MySQL 会在查询上设置一个标记,执行查询会返回执行计划的信息,而不是 执行这条SQL 注意:如果 from 中包含子查询,仍会执行该子

  • MySQL调优之SQL查询深度分页问题

    目录 一.问题引入 二.MySQL中的limit用法 三.深度分页优化策略 方法一:用主键id或者唯一索引优化 方法二:利用索引覆盖优化 方法三:基于索引再排序 方法四:基于索引使用prepare 方法五:利用"子查询+索引"快速定位数据 方法六:利用复合索引进行优化 一.问题引入 例如当前存在一张表test_user,然后往这个表里面插入3百万的数据: CREATE TABLE `test_user` ( `id` int(11) NOT NULL AUTO_INCREMENT CO

  • MySQL锁(表锁,行锁,共享锁,排它锁,间隙锁)使用详解

    锁,在现实生活中是为我们想要隐藏于外界所使用的一种工具.在计算机中,是协调多个进程或县城并发访问某一资源的一种机制.在数据库当中,除了传统的计算资源(CPU.RAM.I/O等等)的争用之外,数据也是一种供许多用户共享访问的资源.如何保证数据并发访问的一致性.有效性,是所有数据库必须解决的一个问题,锁的冲突也是影响数据库并发访问性能的一个重要因素.从这一角度来说,锁对于数据库而言就显得尤为重要. MySQL锁 相对于其他的数据库而言,MySQL的锁机制比较简单,最显著的特点就是不同的存储引擎支持不

  • MySql分表、分库、分片和分区知识深入详解

    一.前言 数据库的数据量达到一定程度之后,为避免带来系统性能上的瓶颈.需要进行数据的处理,采用的手段是分区.分片.分库.分表. 二.分片(类似分库) 分片是把数据库横向扩展(Scale Out)到多个物理节点上的一种有效的方式,其主要目的是为突破单节点数据库服务器的 I/O 能力限制,解决数据库扩展性问题.Shard这个词的意思是"碎片".如果将一个数据库当作一块大玻璃,将这块玻璃打碎,那么每一小块都称为数据库的碎片(DatabaseShard).将整个数据库打碎的过程就叫做分片,可以

  • MySQL null与not null和null与空值''''的区别详解

    相信很多用了MySQL很久的人,对这两个字段属性的概念还不是很清楚,一般会有以下疑问: 我字段类型是not null,为什么我可以插入空值 为毛not null的效率比null高 判断字段不为空的时候,到底要 select * from table where column <> '' 还是要用 select * from table wherecolumn is not null 呢. 带着上面几个疑问,我们来深入研究一下null 和 not null 到底有什么不一样. 首先,我们要搞清楚

  • MySQL系列多表连接查询92及99语法示例详解教程

    目录 1.笛卡尔积现象 2.连接查询知识点概括 1)什么是连接查询? 2)连接查询的分类 3.内连接讲解 1)等值连接:最大特点是,连接条件为等量关系. 2)sql92语法和sql99语法的区别. 3)非等值连接:最大特点是,连接条件为非等量关系. 4)自连接:最大特点是,一张表看作两张表. 4.外连接讲解 1)什么是外连接,和内连接有什么区别? 2)外连接的分类 前面两天带着大家换了一个口味,带着大家学习了pyecharts的原理和部分图形制作.今天我们继续回归带你学MySQL系列,带着大家继

  • Mysql数据库事务的脏读幻读及不可重复读详解

    目录 一.什么是数据库事务 二.事务的ACID原则 1. 原子性(Atomicity) 2. 一致性(Consistency) 3. 持久性(Durability) 4. 隔离性(Isolation) 三.隔离带来的问题 1. 脏读 2. 不可重复读 3.幻读 四.手动测试下事务的过程 一.什么是数据库事务 数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位.事务由事务开始与事务结束之间执行的全部数

  • Linux监控cpu以及内存使用情况之top命令(详解)

    top命令是Linux下常用的性能分析工具,比如cpu.内存的使用,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器. top显示系统当前的进程和其他状况,是一个动态显示过程,即可以通过用户按键来不断刷新当前状态.如果在前台执行该命令,它将独占前台,直到用户终止该程序为止. 比较准确的说,top命令提供了实时的对系统处理器的状态监视.它将显示系统中CPU最"敏感"的任务列表.该命令可以按CPU使用.内存使用和执行时间对任务进行排序:而且该命令的很多特性都可以通

随机推荐