mysql null对索引的影响中的问题

  1. NULL值代表一个未确定的值每一个NULL徝都是独一无二的,在统计列不重复值的时候应该都当作独立的
  2. NULL值在业务上就是代表没有,所有的NULL值代表的意义是一样的所以所有的NULL徝都一样,在统计列不重复值的时候应该只算一个
  3. NULL完全没有意义,在统计列不重复值的时候应该忽略NULL

最好不在索引列中存放NULL值1. nulls_equal:认为所有NULL值都是相等的。这个值也是innodb_stats_method的默认值如果某个索引列中NULL值特别多的话,这种统计方式会让优化器认为某个列中平均一个值重复次数特别多所以倾向于不使用索引进行访问。
2. nulls_unequal:认为所有NULL值都是不相等的如果某个索引列中NULL值特别多的话,这种统计方式会让优化器认为某个列中平均一个值重复次数特别少所以倾向于使用索引进行访问。

因此, 最好不在索引列中存放NULL值~

谈谈InnoDB辅助索引的几个特征

0. 初始囮测试表、数据

1. 问题1:索引列允许为NULL,对性能影响有多少

    结论1存储大量的NULL值,除了计算更复杂之外数据扫描的代价也会更高一些

2. 问题2:辅助索引需要MVCC多版本读的时候,为什么需要依赖聚集索引

3. 问题3:为什么查找数据时一定要读取叶子节点,只读非叶子节点不行吗

    结论3在索引树中查找数据时,最终一定是要读取叶子节点才行

4. 问题4:索引列允许为NULL会额外存储更多字节吗

  结论4,定义列值允许为NULL并不会增加物理存储代价但对索引效率的影响要另外考虑

本文开始之前,有几篇文章建议先复习一下

接下来我们一起测试验证关于辅助索引的幾个特点。

除了主键索引外还有个 c1 列上的辅助索引。

把辅助索引列 c1 修改为允许NULL并且随机更新5万条数据,将 c1 列设置为NULL

好现在观察辅助索引的索引数据页结构。

观察辅助索引的根节点里的数据

经过统计根节点中c1列值为NULL的记录共有33条,其余476条是c1列值为非NULL共509条记录。

叶子節点中每个page大约可以存储1547条记录,共有5万条记录值为NULL因此需要至少33个page来保存(ceiling() = 33)。

看下这个SQL的查询计划

从上面的输出中我们能看到,當索引列设置允许为NULL时是会对其纳入索引统计信息,并且值为NULL的记录都是存储在索引树的最左边。

接下来跑几个SQL查询。

这次的查询需要扫描12个page除去1个根节点外,还需要扫描12个叶子节点只是为了返回一条数据而已,这代价有点大

如果把SQL微调改成下面这样

可以看到還是需要扫描12个page。

如果把 c1列条件改成正常的int值结果就不太一样了

可以看到,只需要扫描2个page这个看起来就正常了。

结论1存储大量的NULL值,除了计算更复杂之外数据扫描的代价也会更高一些

另外,如果要查询的c1值正好介于两个page的临界位置那么需要多读取一个page。

扫描第31号page确认该数据页中的最小和最大物理记录

指定c1的值为 、 执行查询,查看slow log确认都需要扫描3个page,而如果换成介于这两个值之间的数据则只需要扫描2个page。

这是因为辅助索引是非唯一的即便是在等值查询时,也需要再读取下一条记录以确认已获取所有符合条件的数据。

还有当利用辅助索引读取数据时,如果要读取整行数据则需要回表。

也就是说除了扫描辅助索引数据页之外,还需要扫描聚集索引数据頁

来个例子看看就知道了。

需要回表时除了扫描辅助索引页2个page外,还需要回表扫描聚集索引页而聚集索引是个3层树,因此总共需要掃描5个page

但是我们从上面page dump出来的结果也很明显能看到,附注索引页是不存储DB_TRX_ID信息的

所以说,辅助索引上如果想要实现MVCC需要通过回表读聚集索引来实现。

结论2辅助索引中不存储DB_TRX_ID,需要依托聚集索引实现MVCC

在辅助索引的根节点这个页面中(pageno=4)我们注意到它记录的最小记录(min_rec)对应嘚是(c1=NULL, id=9)这条记录。

在它指向的叶子节点页面中(pageno=18)也确认了这个情况

现在把id=9的记录删掉,看看辅助索引数据页会发生什么变化

看到第四号数據页中,最小记录还是 id=9没有更新。

再查看第18号数据页

在这个数据页(叶子节点)中,最小记录已经被更新成 id=30 这条数据了

可见,索引樹中的非叶子节点数据不是实时更新的只有叶子节点的数据才是最准确的。

结论3在索引树中查找数据时,最终一定是要读取叶子节点財行

之前流传有一种说法不允许设置列值允许NULL,是因为会额外多存储一个字节事实是这样吗?

在修改之前每条索引记录长度都是10字節,更新之后却变成了13个字节
直接对比索引页中的数据,发现不同之处

#允许为NULL且默认值为NULL时
#不允许为NULL,默认值为0时

可以看到原先允許为NULL时,record header需要多一个字节(共6字节)但实际物理存储中无需存储NULL值。

而当设置为NOT NULL DEFAULT 0时record header只需要5字节,但实际物理存储却多了4字节总共多叻3字节,所以索引记录以前是10字节更新后变成了13字节,实际上代价反倒变大了

列值允许为NULL更多的是计算代价变大了,以及索引对索引效率的影响反倒可以说是节省了物理存储开销。

结论4定义列值允许为NULL并不会增加物理存储代价,但对索引效率的影响要另外考虑

最后針对InnoDB辅助索引总结几条建议吧。
a) 索引列最好不要设置允许NULL
b) 如果是非索引列,设置允许为NULL基本上无所谓
c) 辅助索引需要依托聚集索引实現MVCC。
d) 叶子节点总是存储最新数据而非叶子节点则不一定。
e) 尽可能不SELECT *尽量利用覆盖索引完成查询,能不回表就不回表


  • 由叶老师主讲的「mysql null对索引的影响优化」课已升级到mysql null对索引的影响 8.0版本,扫码开启mysql null对索引的影响 8.0的修行之旅吧

今天看到一篇关于mysql null对索引的影响嘚IN子查询优化的案例

一开始感觉有点半信半疑(如果是换做在SQL Server中,这种情况是绝对不可能的后面会做一个简单的测试。)

随后动手按照他說的做了一个表来测试验证发现mysql null对索引的影响的IN子查询做的不好,确实会导致无法使用索引的情况(IN子查询无法使用所以场景是mysql null对索引嘚影响,截止的版本是5.7.18)

建一个存储过程插入测试数据测试数据的特点是pay_id可重复,这里在存储过程处理成循环插入300W条数据的过程中,每隔100条数据插入一条重复的pay_id时间字段在一定范围内随机

查询大概的意思是查询某个时间段之内的业务Id大于1的数据,于是就出现两种写法

苐一种写法如下:IN子查询中是某段时间内业务统计大于1的业务Id,外层按照IN子查询的结果进行查询业务Id的列pay_id上有索引,逻辑也比较简单這种写法,在数据量大的时候确实效率比较低用不到索引

第二种写法,与子查询进行join关联这种写法相当于上面的IN子查询写法,下面测試发现效率确实有不少的提高

In子查询的执行计划,发现外层查询是一个全表扫描的方式没有用到pay_id上的索引

join自查的执行计划,外层(tpp1别名嘚查询)是用到pay_id上的索引的

后面想对第一种查询方式使用强制索引,虽然是不报错的但是发现根本没用

如果子查询是直接的值,则是可鉯正常使用索引的

可见mysql null对索引的影响对IN子查询的支持,做的确实不怎么样

另外:加一个使用临时表的情况,虽然比不少join方式查询的泹是也比直接使用IN子查询效率要高,这种情况下也是可以使用到索引的,不过这种简单的情况是没有必要使用临时表的。

下面是类似案例在sqlserver 2014中的测试几万完全一样的测试表结构和数量,可见这种情况下两种写法,在SQL Server中可以认为是完全一样的(执行计划+效率)这一点SQL Server要仳mysql null对索引的影响强不少

下面是sqlserver中的测试环境脚本。

总结:在mysql null对索引的影响数据中截止5.7.18版本,对IN子查询仍要慎用

我要回帖

更多关于 mysql null对索引的影响 的文章

 

随机推荐