我们都是小青蛙,呱呱呱呱呱!喜欢快乐的生活,最爱说笑话~
在阅读本文前最好越多过下列文章,否则文中出现的部分名词大家可能会有点儿陌生:
这一系列文章主要说明了一个道理:MySQL查询优化器决策是否使用某个索引执行查询时的依据是使用该索引的成本是否足够低,而成本很大程度上取决于需要扫描的二级索引记录数量占表中所有记录数量的比例。
innodb_stats_method的作用
我们知道索引列不重复的值的数量这个统计数据对于MySQL查询优化器十分重要,因为通过它可以计算出在索引列中平均一个值重复多少行,它的应用场景主要有两个:
在统计索引列不重复的值的数量时,有一个比较烦的问题就是索引列中出现NULL值怎么办,比方说某个索引列的内容是这样:
+------+ | col | +------+ | 1 | | 2 | | NULL | | NULL | +------+
此时计算这个col列中不重复的值的数量就有下边的分歧:
设计MySQL的大叔蛮贴心的,他们提供了一个名为innodb_stats_method的系统变量,相当于在计算某个索引列不重复值的数量时如何对待NULL值这个锅甩给了用户,这个系统变量有三个候选值:
反正这个锅是甩给用户了,当你选定了innodb_stats_method值之后,优化器即使选择了不是最优的执行计划,那也跟设计MySQL的大叔们没关系了哈~ 当然对于用户的我们来说,最好不在索引列中存放NULL值才是正解。
两种不同的统计数据存储方式
InnoDB提供了两种存储统计数据的方式:
设计MySQL的大叔们给我们提供了系统变量innodb_stats_persistent来控制到底采用哪种方式去存储统计数据。在MySQL 5.6.6之前,innodb_stats_persistent的值默认是OFF,也就是说InnoDB的统计数据默认是存储到内存的,之后的版本中innodb_stats_persistent的值默认是ON,也就是统计数据默认被存储到磁盘中。
不过InnoDB默认是以表为单位来收集和存储统计数据的,也就是说我们可以把某些表的统计数据(以及该表的索引统计数据)存储在磁盘上,把另一些表的统计数据存储在内存中。怎么做到的呢?我们可以在创建和修改表的时候通过指定STATS_PERSISTENT属性来指明该表的统计数据存储方式:
CREATE TABLE 表名 (...) Engine=InnoDB, STATS_PERSISTENT = (1|0); ALTER TABLE 表名 Engine=InnoDB, STATS_PERSISTENT = (1|0);
当STATS_PERSISTENT=1时,表明我们想把该表的统计数据永久的存储到磁盘上,当STATS_PERSISTENT=0时,表明我们想把该表的统计数据临时的存储到内存中。如果我们在创建表时未指定STATS_PERSISTENT属性,那默认采用系统变量innodb_stats_persistent的值作为该属性的值。
问题
有同学在小册群中反应在使用基于磁盘的统计数据时,将innodb_stats_method系统变量设置成不同的值,但是发现对应的统计数据却并未发生预想的变化(可以通过SHOW INDEX FROM tbl_name或者查看mysql数据库下的innodb_index_stats表),这到底是因为啥呢?
原因
我一开始也对这个现象有点儿疑惑,于是不得不再次打开看吐了的源码来看。
比较两条记录是否相同的函数是cmp_rec_rec_with_match,如下图所示:
其中的nulls_unequal参数是用来区别是否将两个null值认为是相等的。
在计算基于磁盘的统计数据时,是这样调用该函数的:
可以看到nulls_unequal参数是硬编码为FALSE。
在计算基于内存的统计数据时,是这样调用该函数的:
可以看到这种调用的方式就是正常的。
从实践来看,在计算基于内存的统计数据时,改变系统变量innodb_stats_method的值是起作用的,但是在计算基于磁盘的统计数据时,改变该系统变量的值是无效的。我也并不知道设计InnoDB的大叔为什么这么写,翻了翻代码也没看见这么写有什么特别的注释,之后还特意去看了MySQL文档中关于统计数据收集的相关章节,也没发现有特别声明这两者的区别。可能是一个bug?或者有啥深层次的含义?有知道的同学可以留言哈~
小青蛙历史文章(历史文章,不容错过):