MySQL 索引优化及失效场景

MySQL 索引失效场景

左侧模糊查询

模糊查询时(like语句),模糊匹配的占位符位于条件的首部

B+树索引的键值都是排序的,而条件的左侧使用了占位符,会导致无法按照正常的目录进行匹配,从而导致索引失效

select * from tmp where nick like '%张';

OR操作

  1. OR查询中有一个非索引字段
---id为主键,nick未创建索引
select * from tmp where id = 1 or nick = '张一';
  1. OR查询同时使用><
select * from tmp where id  > 1 or id  < 100;

联合索引不满足最左前缀原则

在联合索引的场景下,查询条件不满足最左匹配原则

KEY `idx_uid_nick` (`uid`,`nick`)

走联合索引

---满足最左匹配原则
select * from tmp where uid = 1;
select * from tmp where uid = 1 and nick = '张一';

全表查询

select * from tmp where nick = '张一';

索引列参与运算

索引列参与运算,如加减乘除,会导致数据库需要全表查询出所有字段值,对其计算后与参数值进行比较。

select * from tmp where id + 1 = 2;

优化方式:进程内存计算/SQL查询条件右侧计算

--- 内存计算,得知要查询的id为1
select * from tmp where id = 1;
--- 参数侧计算
select * from tmp where id = 2 - 1;

不等于比较

查询条件使用不等进行比较时,需要慎重,普通索引会查询结果集占比较大时索引会失效

---走索引
select * from tmp where id != 1;
---不走索引
select * from tmp where uid <> 1;

NULL值

MySQL不对NULL值进行索引创建,判断IS NULLIS NOT NULL可能会导致索引失效

MySQL建议最好设置为NOT NULL,需要使用NULL的情况使用DEFAULT VALUE代替

select * from tmp where nick IS NOT NULL;

内置函数

索引列参与了MySQL内置函数,会导致全表扫描,索引失效

select * from tmp where date_add(create_time, INTERVAL 1 DAY) = '2022-07-30 00:00:00';

类型隐式转换

参数类型与字段类型不匹配,导致MySQL使用了convert函数进行了类型隐式转换,导致索引失效

---nick为varchar,SQL语句中使用int类型,将参数加上单引号可避免此情况
select * from tmp where nick = 2022;

SELECT *

在《阿里巴巴开发手册》的ORM映射章节中有一条【强制】的规范:
【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。 说明:

  1. 增加查询分析器解析成本。
  2. 增减字段容易与resultMap配置不一致。
  3. 无用字段增加网络消耗,尤其是text类型的字段。
KEY `idx_uid_nick` (`uid`,`nick`)

需要回表查询

select * from tmp where uid = 1 and nick = '张一';

走覆盖索引,无需回表进行主键查询

select uid, nick from tmp where uid = 1 and nick = '张一';

MySQL优化器策略

Mysql优化器的其他优化策略,比如优化器认为在某些情况下,全表扫描比走索引快,则它就会放弃索引

索引相关

key_len

表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的)

不损失精确性的情况下,长度越短越好

key_len的计算:id_no类型为varchar(18),字符集为utf8mb4_bin,也就是使用4个字节来表示一个完整的UTF-8。此时,key_len= 18*4=72;
由于该字段类型varchar为变长数据类型,需要再额外添加2个字节。此时,key_len = 72 + 2 = 74;
由于该字段运行为NULL(default NULL),需要再添加1个字节。此时,key_len = 74 + 1 = 75;

离散读

MySQL优化器使用了全表扫描,而非辅助索引

select * from tmp where uid > 1 and uid < 100000;

具体原因是查询结果为整行信息,若走辅助索引需要回表操作到聚集索引中查询完整数据。虽然辅助索引中数据是顺序存放的,但是再一次进行书签查找的数据则是无序的,因此变为了磁盘上的离散读操作。

如果要求访问的数据量很小,则优化器还是会选择辅助索引,但是当访问的数据占整个表中数据的蛮大一部分时(一般是20%左右),优化器会选择通过聚集索引来查找数据。

优化器优化离散读

MySQL 5.6之前,优化器在进行离散读决策的时候,如果数据量比较大,会选择使用聚集索引,全表扫描。
MySQL5.6版本开始支持Multi-Range Read(MRR/MR2)优化。

Multi-Range Read优化的目的就是为了减少磁盘的随机访问,并且将随机访问转化为较为顺序的数据访问,这对于IO-bound类型的SQL查询语句可带来性能极大的提升。Multi-Range Read优化可适用于range,ref,eq_ref类型的查询。

MR2优化的优点:

  • 使数据访问变得较为顺序
    在查询辅助索引时,首先根据得到的查询结果,按照主键进行排序,并按照主键排序的顺序进行书签查找。
  • 减少缓冲池中页被替换的次数
    顺序查找可以对一个页进行顺序查找,无需离散加载数据页
  • 批量处理对键值的查询操作

对于InnoDB和MyISAM存储引擎的范围查询和JOIN查询操作,MR2的工作方式如下:

  1. 将查询得到的辅助索引键值存放于一个缓存中,这时缓存中的数据是根据辅助索引键值排序的
  2. 将缓存中的键值根据RowID进行排序
  3. 据RowID的排序顺序来访问实际的数据文件

ICP优化

Index Condition Pushdown(ICP)同样是MySQL 5.6开始支持的一种根据索引进行查询的优化方式。

在支持ICP后,MySQL数据库会在取出索引的同时,判断是否可以进行WHERE条件的过滤,也就是将WHERE的部分过滤操作放在了存储引擎层。
在某些查询下,可以大大减少上层SQL层对记录的索取(fetch),从而提高数据库的整体性能。ICP优化支持range、ref、eq_ref、ref_or_null类型的查询,当前支持MyISAM和InnoDB存储引擎。
当优化器选择ICP优化时,可在执行计划的Extra列看到Using index condition提示。

不适合建索引的字段

  1. 数据唯一性差的字段
    比如性别,只有两种可能数据。意味着索引的二叉树级别少,多是平级。这样的二叉树查找无异于全表扫描。

  2. 频繁更新的字段
    比如loginCount登录次数,频繁变化导致索引也频繁变化,增大数据库工作量,降低效率。

  3. 字段不在查询条件内
    字段不在where语句出现时不要添加索引。如果where后含IS NULL/IS NOT NULL/like '%s%'等条件,不建议使用索引


参考资料:

  1. 《MySQL技术内幕 InnoDB存储引擎》
  2. MySQL 索引的数据结构及类型
  3. 15个必知的Mysql索引失效场景,别再踩坑了!
  4. MySQL第二弹
  5. 离散读、ICP优化、全文检索

你可能感兴趣的:(MySQL,mysql,数据库,sql)