看面试题的时候,总能看到MySQL在什么情况下用不上索引,如下:

MySQL的WHERE子句中包含 IS NULL、IS NOT NULL、!= 这些条件时便不能使用索引查询,只能使用全表扫描。

不耽误大家时间,告诉大家结论:

MySQL中决定使不使用某个索引执行查询的依据就是成本够不够小,如果null值很多,还是会用到索引的。

自己做了个验证:

一个大概3万数据的表,如果只有10多个记录是null值,is null走索引,not null和!=没走索引,如果大部分都是null值,只有部分几条数据有值,is null,not null和!=都走索引。

以下是搬过来网上的验证,让大家看看,结构如下:

CREATE TABLE s1 (

id INT NOT NULL AUTO_INCREMENT,

key1 VARCHAR(100),

key2 VARCHAR(100),

key3 VARCHAR(100),

key_part1 VARCHAR(100),

key_part2 VARCHAR(100),

key_part3 VARCHAR(100),

common_field VARCHAR(100),

PRIMARY KEY (id),

KEY idx_key1 (key1),

KEY idx_key2 (key2),

KEY idx_key3 (key3),

KEY idx_key_part(key_part1, key_part2, key_part3)

) Engine=InnoDB CHARSET=utf8;

这个表里有10000条记录:

mysql> SELECT COUNT(*) FROM s1;

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

| COUNT(*) |

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

| 10000 |

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

1 row in set (0.00 sec)

下边贴几个图:

上边几个查询语句的WHERE子句中用了IS NULL、IS NOT NULL、!=这些条件,但是从它们的执行计划中可以看出来,这些语句都采用了相应的二级索引执行查询,而不是使用所谓的全表扫描,谣言不攻自破。我们来更细致的分析一下这些查询到底是怎么执行的。

NULL值是怎么在记录中存储的

在MySQL中,每一条记录都有它固定的格式,我们以InnoDB存储引擎的Compact行格式为例,来看一下NULL值是怎样存储的。在Compact行格式下,一条记录是由下边这几个部分构成的:

新建一个称之为record_format_demo的表:

CREATE TABLE record_format_demo (

c1 VARCHAR(10),

c2 VARCHAR(10) NOT NULL,

c3 CHAR(10),

c4 VARCHAR(10)

) CHARSET=ascii ROW_FORMAT=COMPACT;

因为我们的重点是NULL值是如何存储在记录中的,所以重点唠叨一下行格式的NULL值列表部分。存储NULL值的过程如下:

1.首先统计表中允许存储NULL的列有哪些。

我们前边说过,主键列、被NOT NULL修饰的列都是不可以存储NULL值的,所以在统计的时候不会把这些列算进去。比方说表record_format_demo的3个列c1、c3、c4都是允许存储NULL值的,而c2列是被NOT NULL修饰,不允许存储NULL值。

2.如果表中没有允许存储NULL的列,则NULL值列表也不存在了,否则将每个允许存储NULL的列对应一个二进制位,二进制位按照列的顺序逆序排列,二进制位表示的意义如下:

因为表record_format_demo有3个值允许为NULL的列,所以这3个列和二进制位的对应关系就是这样:

再一次强调,二进制位按照列的顺序逆序排列,所以第一个列c1和最后一个二进制位对应。

二进制位的值为1时,代表该列的值为NULL。

二进制位的值为0时,代表该列的值不为NULL。

3.设计InnoDB的大叔规定NULL值列表必须用整数个字节的位表示,如果使用的二进制位个数不是整数个字节,则在字节的高位补0。

表record_format_demo只有3个值允许为NULL的列,对应3个二进制位,不足一个字节,所以在字节的高位补0,效果就是这样:

以此类推,如果一个表中有9个允许为NULL,那这个记录的NULL值列表部分就需要2个字节来表示了。

假设我们现在向record_format_demo表中插入一条记录:

INSERT INTO record_format_demo(c1, c2, c3, c4)

VALUES('eeee', 'fff', NULL, NULL);

这条记录的c1、c3、c4这3个列中c3和c4的值都为NULL,所以这3个列对应的二进制位的情况就是:

所以这记录的NULL值列表用十六进制表示就是:0x06。

键值为NULL的记录是怎么在B+树中存放的

对于InnoDB存储引擎来说,记录都是存储在页面中的(一个页面默认是16KB大小),这些页面可以作为B+树的节点而组成一个索引,类似这种样子(只是用下边的图举个B+树的例子而已,跟我们上边列举的表没关系):

聚簇索引和二级索引都对应着像上图一样的B+树(也就是说有多少个索引就有多少棵对应的B+树),不过:

对于聚簇索引索引来说,页面中的记录是按照主键值进行排序的;而对于二级索引来说,页面中的记录是按照给定的索引列的值进行排序的。

对于聚簇索引来说,B+树每一层节点(页面)都是按照页中记录的主键值大小进行排序的;而对于二级索引来说,B+树每一层节点(页面)都是按照页中记录的给定的索引列的值进行排序的。

对于聚簇索引来说,B+树叶子节点对应的页面中存储的是完整的用户记录(就是一条记录中包含我们定义的所有列值,还包含一些InnoDB自己添加的一些隐藏列);而对于二级索引来说,B+树叶子节点对应的页面中存储的只是索引列的值 + 主键值。

按规定,一条记录的主键值不允许存储NULL值,所以下边语句中的WHERE子句结果肯定为FALSE:

SELECT * FROM tbl_name WHERE primary_key IS NULL;

像这样的语句优化器自己就能判定出WHERE子句必定为NULL,所以压根儿不会去执行它,不信我们看(Extra信息提示WHERE子句压根儿不成立):

对于二级索引来说,索引列的值可能为NULL。那对于索引列值为NULL的二级索引记录来说,它们被放在B+树的哪里呢?答案是:放在B+树的最左边。比方说我们有如下查询语句:

SELECT * FROM s1 WHERE key1 IS NULL;

那它的查询示意图就如下所示:

从图中可以看出,对于s1表的二级索引idx_key1来说,值为NULL的二级索引记录都被放在了B+树的最左边,这是因为设计InnoDB的大叔有这样的规定:

We define the SQL null to be the smallest possible value of a field.

也就是说他们把SQL中的NULL值认为是列中最小的值。

在通过二级索引idx_key1对应的B+树快速定位到叶子节点中符合条件的最左边的那条记录后,也就是本例中id值为521的那条记录之后,就可以顺着每条记录都有的next_record属性沿着由记录组成的单向链表去获取记录了,直到某条记录的key1列不为NULL。

小贴士: 通过B+树快速定位到叶子节点的记录的过程是靠一个所谓的页目录(Page Directory)做到的,不过这不是本文的重点,大家可以到小册中翻看,都有详细解释。

使不使用索引的依据到底是什么?

那既然IS NULL、IS NOT NULL、!=这些条件都可能使用到索引,那到底什么时候索引,什么时候采用全表扫描呢?

答案很简单:成本。因为篇幅有限,我们在这里只准备定性的分析一下。对于使用二级索引进行查询来说,成本组成主要有两个方面:

读取二级索引记录的成本

将二级索引记录执行回表操作,也就是到聚簇索引中找到完整的用户记录的操作所付出的成本。

很显然,要扫描的二级索引记录条数越多,那么需要执行的回表操作的次数也就越多,达到了某个比例时,使用二级索引执行查询的成本也就超过了全表扫描的成本(举一个极端的例子,比方说要扫描的全部的二级索引记录,那就要对每条记录执行一遍回表操作,自然不如直接扫描聚簇索引来的快)。

所以MySQL优化器在真正执行查询之前,对于每个可能使用到的索引来说,都会预先计算一下需要扫描的二级索引记录的数量,比方说对于下边这个查询:

SELECT * FROM s1 WHERE key1 IS NULL;

优化器会分析出此查询只需要查找key1值为NULL的记录,然后访问一下二级索引idx_key1,看一下值为NULL的记录有多少(如果符合条件的二级索引记录数量较少,那么统计结果是精确的,如果太多的话,会采用一定的手段计算一个模糊的值,当然算法也比较麻烦,我们就不展开说了,小册里有说),这种在查询真正执行前优化器就率先访问索引来计算需要扫描的索引记录数量的方式称之为index dive。当然,对于某些查询,比方说WHERE子句中有IN条件,并且IN条件中包含许多参数的话,比方说这样:

SELECT * FROM s1 WHERE key1 IN ('a', 'b', 'c', ... , 'zzzzzzz');

这样的话需要统计的key1值所在的区间就太多了,这样就不能采用index dive的方式去真正的访问二级索引idx_key1,而是需要采用之前在背地里产生的一些统计数据去估算匹配的二级索引记录有多少条(很显然根据统计数据去估算记录条数比index dive的方式精确性差了很多)。

反正不论采用index dive还是依据统计数据估算,最终要得到一个需要扫描的二级索引记录条数,如果这个条数占整个记录条数的比例特别大,那么就趋向于使用全表扫描执行查询,否则趋向于使用这个索引执行查询。

理解了这个也就好理解为什么在WHERE子句中出现IS NULL、IS NOT NULL、!=这些条件仍然可以使用索引,本质上都是优化器去计算一下对应的二级索引数量占所有记录数量的比值而已。

结论

大家可以看到,MySQL中决定使不使用某个索引执行查询的依据很简单:就是成本够不够小。而不是是否在WHERE子句中用了IS NULL、IS NOT NULL、!=这些条件。

mysql not null 索引_MySQL中 IS NULL、IS NOT NULL、!= 能用上索引吗?相关推荐

  1. mysql如何创建日志_mysql中的日志

    关键词:mysql日志,mysql四种日志,删除binlog,binlog删除 一.mysql日志的种类 (1)一般来说,日志有四种,分别为: 1.错误日志:log-err (记录启动,运行,停止my ...

  2. mysql获取当月最后一天_mysql中获取本月第一天、本月最后一天、上月第一天、上月最后一天

    mysql获取当月最后一天_mysql中获取本月第一天.本月最后一天.上月第一天.上月最后一天等等 转自: https://blog.csdn.net/min996358312/article/det ...

  3. mysql is null走索引_mysql中IS NULL、IS NOT NULL不能走索引?

    不知道是啥原因也不知道啥时候, 江湖上流传着这么一个说法 mysql查询条件包含IS NULL.IS NOT NULL.!=.like %* .like %*%,不能使用索引查询,只能使用全表扫描. ...

  4. mysql null处理_MySQL中处理Null时要注意两大陷阱

    MySQL数据库是一个基于结构化数据的开源数据库.SQL语句是MySQL数据库中核心语言.不过在MySQL数据库中执行SQL语句,需要小心两个陷阱. 陷阱一:空值不一定为空 空值是一个比较特殊的字段. ...

  5. mysql教程联合索引_MySQL中的联合索引学习教程

    联合索引又叫复合索引.对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分.例如索引是key index (a,b,c). 可以支持a | a,b| ...

  6. mysql 回表查询优化_MySQL中的回表查询与索引覆盖:一次百万级别分页查询使用Limit 从90秒到0.6毫秒的优化...

    这里写目录标题 事故现场 解决方案 提到的"回表查询" InnoDB的索引 什么是回表查询 怎么优化回表查询 事故现场 数据库使用的MySQL,有一个日志表,需要进行分页查询,于是 ...

  7. mysql explain 索引_MySql中Explain详解与索引最佳实践

    使用EXPLAIN关键字可以模拟优化器执行SQL语句,从而知道MySQL是 如何处理你的SQL语句的.分析你的查询语句或是结构的性能瓶颈 下面是使用 explain 的例子: 在 select 语句之 ...

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

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

  9. mysql添加临时索引_mysql 中添加索引的三种方法

    在mysql中有多种索引,有普通索引,全文索引,唯一索引,多列索引,小伙伴们可以通过不同的应用场景来进行索引的新建,在此列出三种新建索引的方法 mysql 中添加索引的三种方法 1.1 新建表中添加索 ...

最新文章

  1. python导入模块有同名_Python:导入与函数同名的模块
  2. 一种C#读写二进制文件的通用方法
  3. spring cloud java b2b2c o2o分布式 微服务电子商务平台
  4. ASP.NET MVC网站学习问题积累(一)
  5. 关于Dalvik虚拟机你需要知道的15个问题
  6. WPF之无法触发KeyDown或者KeyUp键盘事件
  7. php 流(Stream)
  8. python中的try与if,python中if和try的区别是什么
  9. 浅谈前端自动化构建 -- Grunt、Gulp、FIS
  10. oracle临时表经常被锁_5.性能测试 - Oracle体系结构和性能优化简介
  11. OSChina 周二乱弹 ——胸大的烦恼!
  12. php 包含因子,轻松掌握测量不确定度——(4)测量不确定度的评定和报告(完)...
  13. 员工评分系统现场发布小感
  14. onenote for win 10无法正常启动(修复或者重装可解决)
  15. 客户端SDK测试是什么?如何测?
  16. 如果心酸就会心动也应行动
  17. Java修仙,法力无边(光速回顾Java基础~)
  18. [Vue] TodoList 案例
  19. 【赠书】曾因「抢车位」出圈儿,神奇的Mask R-CNN了解一下?
  20. Listen 1添加下载功能(列表下载方法)(v2.6)

热门文章

  1. 算法设计 - LCS 最长公共子序列最长公共子串 LIS 最长递增子序列
  2. HBASE学习笔记--概述
  3. 对于已经做“大”了的企业,最需要什么?
  4. OpenCV-绘制旋转矩形
  5. 按周汇总_有合并单格及空行的数据如何快速汇总?简单几步快速搞定
  6. php 创建文本文件,php-创建文本文件以进行实时下载
  7. 占用率_有问有答:任务管理器里面的GPU占用率到底是怎么算的?
  8. 为什么手机网速太慢_为什么不同的5G手机网速差别高达47%!
  9. c语言求20项FBNC,大学C语言 结业作业 暗影行者
  10. ubuntu16.04开机只显示桌面背景鼠标解决方法