MySQL中 IS NULL、IS NOT NULL、不等于, 能用上索引吗?

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

告诉大家结论:

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

自己做了个验证:
一个大概3万数据的表,如果只有10多个记录是null值,is null走索引,not null!=没走索引,如果大部分都是null值,只有部分几条数据有值,is nullnot 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)

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

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

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

新建一个称之为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个列c1c3c4都是允许存储NULL值的,而c2列是被NOT NULL修饰,不允许存储NULL值。

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

因为表record_format_demo有3个值允许为NULL的列,所以这3个列和二进制位的对应关系就是这样:
img
再一次强调,二进制位按照列的顺序逆序排列,所以第一个列c1和最后一个二进制位对应。

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

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

record_format_demo只有3个值允许为NULL的列,对应3个二进制位,不足一个字节,所以在字节的高位补0,效果就是这样:
img
以此类推,如果一个表中有9个允许为NULL,那这个记录的NULL值列表部分就需要2个字节来表示了。

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

复制代码

INSERT INTO record_format_demo(c1, c2, c3, c4)VALUES('eeee', 'fff', NULL, NULL);

这条记录的c1c3c4这3个列中c3c4的值都为NULL,所以这3个列对应的二进制位的情况就是:
img
所以这记录的NULL值列表用十六进制表示就是:0x06

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

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

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

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

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

复制代码

SELECT * FROM tbl_name WHERE primary_key IS NULL;

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

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

复制代码

SELECT * FROM s1 WHERE key1 IS NULL;

那它的查询示意图就如下所示:
img
从图中可以看出,对于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 NULLIS 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 NULLIS NOT NULL!=这些条件仍然可以使用索引,本质上都是优化器去计算一下对应的二级索引数量占所有记录数量的比值而已。

结论

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/555235.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java EE 企业网站_基于jsp的企业网站系统-JavaEE实现企业网站系统 - java项目源码...

基于jspservletpojomysql实现一个javaee/javaweb的企业网站系统, 该项目可用各类java课程设计大作业中, 企业网站系统的系统架构分为前后台两部分, 最终实现在线上进行企业网站系统各项功能,实现了诸如用户管理, 登录注册, 权限管理等功能, 并实现对各类企业网站系统相关的实体…

MySQL创建联合索引,字段的先后顺序,对查询的影响分析

文章目录前言最左匹配原则为什么会有最左前缀呢?联合索引的存储结构联合索引字段的先后顺序b树可以存储的数据条数总结前言 ​ 对于联合索引我们知道,在使用的时候有一个最左前缀的原则,除了这些呢,比如字段放置的位置&#xff0…

php oracle 操作 sql语句中能不能添加数组_如何在PHP中使用Oracle数据库_php

在php3.0以上版本中,php内置了几乎目前所有的数据库处理函数,包括oracle;在本文中我们通过一个实例来介绍了如何使用这些函数来操作Oracle数据库。PHP提供了2大类API(应用程序接口)来操作Oracle数据库。一个是标准的Oracle处理函数(ORA) 另一个是Oracle …

mysql where过滤条件中and连接的两个条件的顺序不必和建立的联合索引的字段顺序一致_mysql and 顺序_mysql执行过程以及顺序

mysql中and的判断顺序 select * from a join b on 条件一 and 条件二条件一和二都是判断 id 字段, 条件一和条件二哪个先执行? 解析器会自动选择最优的流程执行的 这两个都是平级条件,理论上是没有先后顺序的! 没有顺序&#xff…

MySQL优化器_MySQL查询优化器

MySQL优化器 MySQL架构图 讲到MySQL,就绕不开他的架构图。MySQL是一个经典的C/S架构。服务器这边分两层:第一层是Server层,第二层是存储引擎。Server层处理主要的业务操作流程,但不关心具体的存储逻辑。存储逻辑由存储引擎层去…

MySQL性能优化(一)MySQL中SQL语句是如何执行的?

该篇章将开始整理MySQL的优化,不过开始之前,我们想了解清楚那就是MySQL是怎么执行的。 文章目录1.MySQL驱动2.应用系统数据库连接池3.MySQL数据库连接池4.SQL执行过程4.1.线程监听:监听网络请求中的SQL语句4.2.SQL接口:负责处理接…

2023_Spark_实验三十三:配置Standalone模式Spark3.4.2集群

实验目的:掌握Spark Standalone部署模式 实验方法:基于centos7部署Spark standalone模式集群 实验步骤: 一、下载spark软件 下载的时候下载与自己idea里对应版本的spark News | Apache Spark 选择任意一个下载即可 - spark 3.4.1 - spark …

MySQL性能优化(二)InnoDB之日志文件

文章目录1.MySQL日志记录文件1.1.回顾SQL语句的执行1.2.InnoDB内存结构:缓冲池1.3.记录日志:Undo和Redo1.3.1.Undo日志文件:记录数据修改前的值1.3.2.Redo日志文件:记录数据即将修改值1.3.3.Undo和Redo的区别(记录、前…

php正则检查QQ,PHP 正则匹配手机号的QQ号

//匹配手机号码//$mode /\d{3}-\d{8}|\d{4}-\d{7}|\d{11}/;//不匹配以-开头的手机号码//$mode /^((?!-).)*\d{3}-\d{8}|^((?!-).)*\d{4}-\d{7}|^((?!-).)*\d{11}/;//不匹配以 / 开头或结束的手机号码$mode /^((?!\/).)*\d{3}-\d{8}((?!\/).)*$|^((?!\/).)*\d{4}-\d{7}…

java获取单击内容,java – 单击菜单链接时,获取元素不是可点击的异常

我想点击菜单链接但没有运气.它总是显示异常 –Exception in thread “main” org.openqa.selenium.WebDriverException:unknown error: Element is not clickable at point (64, 64). Otherelement would receive the click: <div style”position: absolute; left:0px; to…

MySQL性能优化(三)Buffer Pool实现原理

文章目录1.回顾缓冲池 Buffer Pool2.配置Buffer Pool的大小3.Buffer Pool&#xff1a;数据结构3.1.磁盘数据结构&#xff1a;数据页3.2.缓冲池数据结构&#xff1a;数据页(缓存页)3.3.缓存页对应的描述信息4.Buffer Pool&#xff1a;初始化5.Buffer Pool&#xff1a;free链表6.…

MySQL性能优化(四)redo log实现原理

文章目录1.redo log的作用2.redo log的结构2.1.redo log 记录2.2.redo log block2.3.redo log buffer3.redo log buffer 刷盘1.redo log的作用 首先我们都知道&#xff0c;执行增删改SQL语句的时候&#xff0c;都是针对一个表中的某些数据去执行的&#xff0c;此时的话&#x…

MySQL性能优化(五)undo log是如何实现MVCC的?

之前我们最开始的几篇文章就讲过&#xff0c;你除了写redolog日志还必须要写undo log日志&#xff0c;这个undo log日志是至关重要的&#xff0c;没有他&#xff0c;你根本都没办法回滚事务&#xff01; 1.事务 1.1.多线程并发执行多个事务 对于我们的业务系统去访问数据库而…

Linux中Shell脚本--awk的用法

语法格式&#xff1a;awk [选项] ‘指令’ 操作文件 常用选项&#xff1a;-F 指定分隔符&#xff0c;分隔符用""引起来 -v&#xff1a;varvalue在awk程序开始之前指定一个值valu给变量var&#xff0c;这些变量值用于awk程序的BEGIN快 -f&#xff1a;后面跟一个保存…

linux下的shell脚本(基础)

Shell是一种脚本语言&#xff0c;那么&#xff0c;就必须有解释器来执行这些脚本&#xff0c;常见的脚本解释器有&#xff1a; bash&#xff1a;是Linux标准默认的shell。bash由Brian Fox和Chet Ramey共同完成&#xff0c;是BourneAgain Shell的缩写&#xff0c;内部命令一共有…

mysql rsync复制,mysql复制又同步

mysql复制再同步由于一个老旧系统没有使用LVM分区,导致mylvmbackup不能使用。为了重新全量同步数据库&#xff0c;发现rsync可以使用&#xff0c;并且锁住数据库的时间不长。1. 首先刷新数据库到文件flush tables with read lock;unlock tables;2. 执行rsync进行数据库同步/usr…

解决springboot中只支持get请求,无法支持post请求

解决springboot中只支持get请求&#xff0c;无法支持post请求 报错信息如下&#xff1a; 405 相关类如下&#xff1a; RestController RequestMapping public class HttpServiceController {Autowiredprivate HttpSecretReport httpSecretReport;Autowiredprivate HttpSecret…

mysql2005卸载步骤,二次安装mysql步骤

1.先将mysql服务停止&#xff0c;并关闭服务页面(否则后面报错&#xff1a;“指定的服务已经标记为删除”)&#xff1a;打开“任务管理器”可以找到服务页面2.在控制面板对mysql进行删除。找到并将其卸载&#xff0c;如果没有直接跳过3.在注册表中删除相关目录。(使用组合键&qu…

Spring Boot 默认数据源 HikariDataSource_Spring Boot 中使用 Hikari

Spring Boot 默认数据源 HikariDataSource springboot2.x之后&#xff0c;系统的默认数据源由原来的的org.apache.tomcat.jdbc.pool.DataSource更改为com.zaxxer.hikari.HikariDataSource。 HikariDataSource 号称 Java WEB 当前速度最快的数据源&#xff0c;相比于传统的 C3…

matlab取出等高线上的数据,在Python或MATLAB中从等高线图中提取数据

这是一个小型的Matlab脚本&#xff0c;可以完成这项工作(使用一些GUI&#xff0c;在图的斜角处读取guidlines)&#xff1a;%// Import the data:imdata importdata(your_picture_file);Gray rgb2gray(imdata.cdata);colorLim [-1 1]; %// this should be set manually%// Ge…