MySQL基础 - InnoDB统计数据是如何收集的
统计数据的存储方式
InnoDB有两种存储统计数据的方式:
1. 永久性地存储统计数据: 统计数据存在磁盘上,在服务器重启之后这些统计数据依然存在。
2. 非永久性的存储统计数据: 统计数据存储在内存中,当服务器关闭时这些统计数据就被清除掉。等到服务器重启后,在某些适当场景下会重新收集这些统计数据。
innodb_stats_persitent : 用来控制将统计数据存在何处。在MySQL 5.6.6版本之前,innodb_stats_persitent默认值是OFF,会默认存储到内存中。自MySQL 5.6.6版本起,innodb_stats_persitent的值默认是ON,默认统计在磁盘上。
因为MySQL是按表为单位收集和存储统计信息,因此可以单独指定表的统计数据存储方式:
CREATE TABLE 表名 (...) Engine=Innodb,STATS_PERSITENT = (1 | 0); ALTER TABLE 表名 Engine=Innodb,STATS_PERSITENT = (1 | 0);
基于磁盘的永久性统计数据
当我们选择把某个表以及该表索引的统计数据存放在磁盘上,实际是把这些数据存储到两个表中:
innodb_table_stats 存储了关于表的统计数据,每一条记录对应着一个表的统计数据。
innodb_index_stats 存储了关于索引的统计数据,每一条记录对应着一个索引的一个统计项的统计数据。
innodb_table_stats
字段名 | 描述 |
database_name | 数据库名 |
table_name | 表名 |
last_uptime | 本表记录的最后更新时间 |
n_rows | 表中记录的条数 |
clustered_index_size | 表中聚簇索引占用的页面数量 |
sum_of_other_index_sizes | 表的其他索引占用的页面数量 |
1. n_rows统计项的收集
InnoDB在计算表中有多少行记录时,按照一定的算法(并不是纯粹随机的),从聚簇索引中选取几个叶子节点页面,统计每个页面中包含的记录数量,然后计算一个页面中平均包含的记录数量,并将其乘以全部的叶子节点的数量,结果就是该表的 n_rows值。
通过修改innodb_stats_persistent_sample_pages 可以单独指定表的采样页面数量
CREATE TABLE 表名 (...) Engine=InnoDB,STATS_SAMPLE_PAGES=具体采样页面数量; ALTER TABLE 表名 Engine=InnoDB,STATS_SAMPLE_PAGES=具体采样页面数量;
如果在创建表的语句中没有指定STATS_SAMPLE_PAGES属性,将默认采用系统变量innodb_stats_persistent_sample_pages的值作为该属性的值。
2. clustered_index_size 和 sum_of_other_index_sizes统计项的收集
clustered_index_size 代表聚簇索引占用的页面数量。sum_of_other_index_sizes代表其他索引总共占用的页面数量。
所以在收集这两个统计项的数据时,需要统计各个索引对应的叶子节点段和非叶子节点段分别占用的页面数量,而统计一个段占用的页面数量步骤如下:
1) 从数据字典中找到表的各个索引对应的根页面位置。系统表SYS_INDEXES中存储了各个索引对应的根页面信息。
2) 从根页面的PageHeader中找到叶子节点段和非叶子节点段对应的Segment Header ,在每个索引的根页面的Page Header部分都有两个字段。
PAGE_BTR_SEG_LEAF: 表示B+树叶子节点段的Segment Header信息。
PAGE_BTR_SEG_TOP: 表示B+树非叶子节点段的Segment Header信息。
3) 从叶子节点段和非叶子节点段的Segment Header中找到这两个段对应的INODE Entry结构。
4) 针对某个段对应的INODE Entry 结构,从中找出该段对应的所有零散页面的地址以及FREE、NOT_FULL、和FULL链表的基节点。
5) 直接统计零散的页面有多少个,然后从FREE、NOT_FULL、FULL 页面 这三个链表的LIst Length字段中读取该段占用的区的数量。每个区占用64个页,所以就可以统计整个段占用的页面。
6) 将某个段占用的页面数量统计出来,然后分别计算聚簇索引的叶子节点和非叶子节点占用的页面数,它们的和就是clustered_index_size的值。
7) 同样的方式计算其余索引占用的页面数 得到 sum_of_other_index_sizes的值
需要注意:当一个段超过32个页面就会按照区来申请空间,有一些页可能还未使用,也会被计入clustered_index_size 和 sum_of_other_index_sizes,所以实际占用可能要比这俩值小一些。
innodb_index_stats
字段名 描述 database_name 数据库名 table_name 表名 index_name 索引名 last_uptime 本条记录最后更新时间 stat_name 统计项名称 stat_value 统计项的值 sample_size 为生成统计数据而采样的页面数量 stat_description
对应的统计项的描述
1. 先查看index_name列,该列用来说明该记录是哪个索引的统计信息
2. 针对index_name列相同的记录,stat_name表示针对该索引的统计项名称,stata_value表示该索引在该统计项的值,stat_description表示该统计项的含义。
一条索引都有哪些统计项?
n_leaf_pages : 表示该索引的叶子节点实际占用多少页面。
size: 表示该索引共占用多少页面。
n_dff_pfxNN : 表示对应的索引列不重复的值有多少。
3. 在计算某个索引列包含多少个不重复值时,需要对一些叶子节点进行采样,sample_size列就表明了采样的页面数量的多少。
定期更新统计数据
innodb_stats_auto_recalc 决定了服务器是否自动重新计算统计数据,默认是ON开启。
CREATE TABLE 表名(...) Engine=InnoDB,STATS_AUTO_RECALC=(1|0) ALTER TABLE 表名 Engine=InnoDB,STATS_AUTO_RECALC=(1|0)
如果发生变动的记录数量超过10%,并且innodb_stats_auto_recalc 开启,那么服务器会重新计算一次统计数据,并且更新innodb_table_stats 和 innodb_index_stats ,因为是更新是异步的,所以即使超过10% 可能也不会立即更新,因此可能存在一定延迟。
也可以手动调用 ANALYZE TABLE 来重新计算统计数据。
ANALYZE TABLE table;还可以贱贱的修改 innodb_table_stats 和 innodb_index_stats 的数据,
UPDATE innodb_table_stats SET n_rows=1 WHERE table_name="test"; FLUSH TABLE test;
基于内存的非永久性统计
非永久性统计采用的页面数量是由系统变量 innodb_stats_transient_sample_pages来控制,默认为8;
因为每次服务器关闭以及执行某些操作会导致统计数据被清除,并在下次访问表时重新计算,可能导致在重新计算统计数据时得到不同的结果,从而可能生成经常变化的执行计划,让你懵逼。
innodb_stats_method
这个系统变量的作用是 在计算某个索引列中的不重复值数量时,如何对待NULL值。
三个值:
nulls_equal(默认值): 认为所有的NULL都是相等的,
nulls_unequal:认为所有的NULL都是不相等的
nulls_ignored: 不管NULL,
总结:
InnoDB是以表为单位来收集统计数据,可以基于磁盘永久性统计也可以基于内存非永久性统计。innodb_stats_persistent 来控制。
innodb_persistent_sample_pages控制永久性统计数据的采样页面。
innodb_stats_transient_sample_pages 控制非永久性数据的采样页面。
innodb_stats_auto_recalc控制着是否自动重新计算统计数据。
这三都能基于表来调整。
innodb_stats_method 决定 统计某个索引列中不重复值的数量如何对待NULL。