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

简介: MySQL调优之索引在什么情况下会失效?

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

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

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

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


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

【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


37c9eb0a682549d2acee9268e1554ad2.png

继续创建索引(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

c4ac0626fa4e40fe8a634bf903b811e5.png


继续创建索引(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之间:

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

所以我们建议:让主键具有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均为使用到索引。

那么如果不是select *时,<>一定使用不到索引吗?

EXPLAIN SELECT age,NAME FROM student WHERE age <> 20;

如下执行计划所示,其使用到了索引!是不是矛盾了?其实不是。我们这里所说的规则并不是一种真理,而是一种相对的情况。具体MySQL优化器是否使用索引要看其成本考虑。比如上面这条SQL( age name)有联合索引,那么就可以实现索引覆盖,MySQL优化器认为这里使用索引将会更好!

也就是说,MySQL优化器是否使用索引,要根据具体存储引擎、数据表、数据量级等情况综合考虑!

【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子句中更多字段的索引;
  • 在选择组合索引的时候,如果某个字段可能出现范围查询时,尽量把这个字段放在索引次序的最后面。


相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
目录
相关文章
|
存储 SQL 关系型数据库
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
|
存储 关系型数据库 MySQL
MySQL数据库索引的数据结构?
MySQL中默认使用B+tree索引,它是一种多路平衡搜索树,具有树高较低、检索速度快的特点。所有数据存储在叶子节点,非叶子节点仅作索引,且叶子节点形成双向链表,便于区间查询。
346 4
|
存储 关系型数据库 MySQL
阿里面试:MySQL 一个表最多 加几个索引? 6个?64个?还是多少?
阿里面试:MySQL 一个表最多 加几个索引? 6个?64个?还是多少?
阿里面试:MySQL 一个表最多 加几个索引? 6个?64个?还是多少?
|
关系型数据库 MySQL 数据库
Mysql的索引
MYSQL索引主要有 : 单列索引 , 组合索引和空间索引 , 用的比较多的就是单列索引和组合索引 , 空间索引我这边没有用到过 单列索引 : 在MYSQL数据库表的某一列上面创建的索引叫单列索引 , 单列索引又分为 ● 普通索引:MySQL中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值,纯粹为了查询数据更快一点。 ● 唯一索引:索引列中的值必须是唯一的,但是允许为空值 ● 主键索引:是一种特殊的唯一索引,不允许有空值 ● 全文索引: 只有在MyISAM引擎、InnoDB(5.6以后)上才能使⽤用,而且只能在CHAR,VARCHAR,TEXT类型字段上使⽤用全⽂文索引。
|
存储 SQL 关系型数据库
MySQL 核心知识与索引优化全解析
本文系统梳理了 MySQL 的核心知识与索引优化策略。在基础概念部分,阐述了 char 与 varchar 在存储方式和性能上的差异,以及事务的 ACID 特性、并发事务问题及对应的隔离级别(MySQL 默认 REPEATABLE READ)。 索引基础部分,详解了 InnoDB 默认的 B+tree 索引结构(多路平衡树、叶子节点存数据、双向链表支持区间查询),区分了聚簇索引(数据与索引共存,唯一)和二级索引(数据与索引分离,多个),解释了回表查询的概念及优化方法,并分析了 B+tree 作为索引结构的优势(树高低、效率稳、支持区间查询)。 索引优化部分,列出了索引创建的六大原则
379 2
|
存储 关系型数据库 MySQL
MySQL覆盖索引解释
总之,覆盖索引就像是图书馆中那些使得搜索变得极为迅速和简单的工具,一旦正确使用,就会让你的数据库查询飞快而轻便。让数据检索就像是读者在图书目录中以最快速度找到所需信息一样简便。这样的效率和速度,让覆盖索引成为数据库优化师傅们手中的尚方宝剑,既能够提升性能,又能够保持系统的整洁高效。
448 9
|
机器学习/深度学习 关系型数据库 MySQL
对比MySQL全文索引与常规索引的互异性
现在,你或许明白了这两种索引的差异,但任何技术决策都不应仅仅基于理论之上。你可以创建你的数据库实验环境,尝试不同类型的索引,看看它们如何影响性能,感受它们真实的力量。只有这样,你才能熟悉它们,掌握什么时候使用全文索引,什么时候使用常规索引,以适应复杂多变的业务需求。
413 12
|
SQL 关系型数据库 MySQL
大数据新视界--大数据大厂之MySQL数据库课程设计:MySQL 数据库 SQL 语句调优方法详解(2-1)
本文深入介绍 MySQL 数据库 SQL 语句调优方法。涵盖分析查询执行计划,如使用 EXPLAIN 命令及理解关键指标;优化查询语句结构,包括避免子查询、减少函数使用、合理用索引列及避免 “OR”。还介绍了索引类型知识,如 B 树索引、哈希索引等。结合与 MySQL 数据库课程设计相关文章,强调 SQL 语句调优重要性。为提升数据库性能提供实用方法,适合数据库管理员和开发人员。
|
关系型数据库 MySQL 大数据
大数据新视界--大数据大厂之MySQL 数据库课程设计:MySQL 数据库 SQL 语句调优的进阶策略与实际案例(2-2)
本文延续前篇,深入探讨 MySQL 数据库 SQL 语句调优进阶策略。包括优化索引使用,介绍多种索引类型及避免索引失效等;调整数据库参数,如缓冲池、连接数和日志参数;还有分区表、垂直拆分等其他优化方法。通过实际案例分析展示调优效果。回顾与数据库课程设计相关文章,强调全面认识 MySQL 数据库重要性。为读者提供综合调优指导,确保数据库高效运行。
|
SQL 存储 关系型数据库
MySQL选错索引了怎么办?
本文探讨了MySQL中因索引选择不当导致查询性能下降的问题。通过创建包含10万行数据的表并插入数据,分析了一条简单SQL语句在不同场景下的执行情况。实验表明,当数据频繁更新时,MySQL可能因统计信息不准确而选错索引,导致全表扫描。文章深入解析了优化器判断扫描行数的机制,指出基数统计误差是主要原因,并提供了通过`analyze table`重新统计索引信息的解决方法。
448 3

推荐镜像

更多