mysql statistics 多_Mysql5.6 innodb如何 统计table statistics

Table statistics 包含两个方面:

1) table stat 比如表内一共有多少条记录(n_rows)。

此类信息保存在mysql.innodb_table_stats表中。

2) index stat 比如index中包含多少个page, 多少个leaf page,共有多少条完全不同的索引

此信息保存在mysql.innodb_index_stats表中。

在innodb中,一切都是基于index的,table stat 中n_rows的计算也是基于index的。

下面就分析一下innodb是如何对index进行统计的。

哎,还是先简单介绍一下index的结构—B+ tree

B+tree的结构如下图:

8c657b127dac0339a486a1dc483eddff.png

这张图是从 Wikipedia copy过来的,不必太执着于它。

Index在硬盘文件中的最基本保存单位是page(默认16K一页),这些page在一起组成了B+ tree。 表中的每一个index,对应一个B+tree。

根据page的类型,可以分为三类:

1) Root page 最上面的一个page, 对B+ tree的访问总是从它开始的。

2) leaf page 最下层的page. 储存了真正的索引数据。如果是主键的话,还储存了其它全部的字段。

3) non-leaf page 界于Root page与leaf page之间的page.

另外还有一个level的概念:

Leaf page 的level 为0, 每往上走一层,level就+1。

Root 的level最大,表示这个tree 的高度。

Leaf page 的简单结构:

82c3705d1b028d05a0e9baadd8976e64.png

此page中的记录,从Infimum开始,到Supremum结束,每个记录都有一条指针指向下一条记录,组成了一条单向升序的链表。 Infimum与Supremum为system记录,表示page中记录的开始和结束。它们之间的记录为User记录,保存了key(索引的值)与value(如果为主键索引,那么value就是其它的全部字段的值;如果是非主键索引,value就是主键的值(Primary Key Value , PKV))。

Non-leaf page的简单结构:

6811e61bec7d7b84777e841969186af3.png

与leaf page的结构相似,只不过在每条记录中,它保存的key的意思是:child page中最小的索引值。 然后也保存了child 的page num(相当于一个指向child page的指针)。每一条记录,只有一个child page。

再来看一下level的概念:

461796fd40f1c20a2afc12df5fc155e1.png

同一level中的page由二条指针组成了双向的链表, 同时有了升序和降序。

最后从整体上把握一下:

CREATE TABLE t (

i INT NOT NULL,

s CHAR(10) NOT NULL,

PRIMARY KEY(i)

) ENGINE=InnoDB;

INSERT INTO t (i, s)

VALUES (0, "A"), (1, "B"), (2, "C"), (3, “D”),(4,”E”),(5,”F”),(6,”G”),(7,”H”);

b1535e2c5fd9bb9a43fc0c45a6fc851b.png

如果需要对index进行统计,最简单的办法就是从头到尾扫描此index对应的B+tree全部的leaf page中的全部记录,看一下有多少条不相同的索引。优点是非常精确, 缺点是如果page相当多的话,扫描的速度会相当慢。

Mysql的做法是采样,从全部leaf page中取出20个page 进行分析,

mysql> SELECT @@global.innodb_stats_persistent_sample_pages;

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

| @@global.innodb_stats_persistent_sample_pages |

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

| 20 |

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

1 ROW IN SET (0.00 sec)

当然这个变量是可以配置的, 它的值越大,统计的结果越精确,统计的速度也就越慢。把它记为A。

还是先用简单的主键索引(clustered index )来分析吧。。。越来越乱了。

之所以说主键索引比较简单,是因为在任意一个level中,全部page中的全部记录的key都是不相同的。

从Root page开始,向下面的level遍历它的全部child page,直到遇到这样的一个level:

此level中至少包含A * 10条不相同的key。

把此level标记为LA。

然后从此level的全部记录中随机选出A条记录。

顺着这A条记录向下,找到A个leaf page。 分析每个leaf page中含有多少个不同的key。对于主键索引来说,leaf page中有多少条记录,就有多少个不同的key。

分析完这A个leaf page, 每个page中不同的key的数量保存到 P1, P2, P3, P4 … PA中。

那么平均值 N_DIFF_AVG_LEAF 就是 (P1 + P2 + … + PA) / A。

再假设全部leaf page的数量为N,

那么全部leaf page中不同的key的数量为 N * N_DIFF_AVG_LEAF。

对于主键索引,这个值刚好就是全部leaf page中的记录数,也就是此表中的记录数n_rows。

mysql> SELECT * FROM innodb_index_stats WHERE TABLE_NAME='xxxx2' AND index_name='PRIMARY';

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

| database_name | TABLE_NAME | index_name | last_update | stat_name | stat_value | sample_size | stat_description |

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

| kaixin | xxxx2 | PRIMARY | 2013-05-08 15:55:04 | n_diff_pfx01 | 5207 | 20 | id |

| kaixin | xxxx2 | PRIMARY | 2013-05-08 15:55:04 | n_leaf_pages | 277 | NULL | NUMBER OF leaf pages IN the INDEX |

| kaixin | xxxx2 | PRIMARY | 2013-05-08 15:55:04 | SIZE | 353 | NULL | NUMBER OF pages IN the INDEX |

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

3 ROWS IN SET (0.00 sec)

mysql> SELECT * FROM innodb_table_stats WHERE TABLE_NAME='xxxx2';

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

| database_name | TABLE_NAME | last_update | n_rows | clustered_index_size | sum_of_other_index_sizes |

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

| kaixin | xxxx2 | 2013-05-08 15:55:04 | 5207 | 353 | 177 |

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

由于只是一个随机的采样,且样本只有20个page, 准确度确实难以保证,但还是有一定的参考价值的。

mysql> SELECT COUNT(*) FROM kaixin.xxxx2;

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

| COUNT(*) |

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

| 5432 |

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

1 ROW IN SET (0.00 sec)

最后来看比较复杂一点的secondary index。

假设表结构为

CREATE TABLE t (

i INT NOT NULL,

s CHAR(10) NOT NULL,

a INT NOT NULL,

b INT NOT NULL,

c INT NOT NULL,

PRIMARY KEY(i),

KEY (a,b,c)

) ENGINE=InnoDB;

在a b c 三个字段上定义了一个索引,不唯一,允许重复的值出现。

反映到此索引的B+tree中, 某一条记录可能与它相邻记录的key值是相同的, 甚至某一个page中,key值都是相同的。

与Non-leaf page和level相关的一个概念:boring record:

如果non-leaf page中的一条记录R的key与它右侧(可跨越page,但要保证在同一个level上面)的记录的key值相同,那么认为此记录R为boring record。

Boring record的特性:

在Boring record的全部child page,直至leaf page中,key值都是相同的,都与boring record的key值相同。

所以这种boring record就没有分析的必要, 因为key值不同的记录数肯定为1。

流程开始:

仍然是从Root page开始,向下面的level遍历它的全部child page,直到遇到这样的一个level:

此level中至少包含A * 10条不相同的key。

把此level标记为LA。

先定义一个数组boundaries[]

对于 level LA中具有相同key的记录,只把最后一个记录保存到数组中。遍历此level之后,boundaries[]就包含了全部的不同的key的记录,总数量记为n_diff_for_this_prefix。

#如果 A > n_diff_for_this_prefix, 则 A=n_diff_for_this_prefix。

然后把它们等分成A组,每组数量为 n_diff_for_this_prefix除以A。

然后从每一组中随机选出一个记录, 共选出A条记录。

顺着这A条记录向下,找到A个leaf page。在这个向下的过程中, 遇到boring record就跳过。然后分析每个leaf page中含有多少个不同的key。

分析完这A个leaf page, 每个page中不同的key的数量保存到 P1, P2, P3, P4 … PA中。

那么平均值 N_DIFF_AVG_LEAF 就是 (P1 + P2 + … + PA) / A。

Level LA中全部记录总数为TOTAL_LA,

Level LA中不同记录的数量为N_DIFF_LA

再假设全部leaf page的数量为N,

那么全部leaf page中不同的key的数量为 (N_DIFF_LA / TOTAL_LA ) *N * N_DIFF_AVG_LEAF。

其实对于index(a,b,c) ,需要执行4次上面的分析过程。第一次是a, 第二次是(a,b),第三次是(a,b,c), 第四次是(a,b,c,i)。

mysql> SELECT * FROM innodb_index_stats WHERE TABLE_NAME='t' ;

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

| database_name | TABLE_NAME | index_name | last_update | stat_name | stat_value | sample_size | stat_description |

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

| mysql | t | PRIMARY | 2013-05-08 16:23:05 | n_diff_pfx01 | 0 | 1 | i |

| mysql | t | PRIMARY | 2013-05-08 16:23:05 | n_leaf_pages | 1 | NULL | NUMBER OF leaf pages IN the INDEX |

| mysql | t | PRIMARY | 2013-05-08 16:23:05 | SIZE | 1 | NULL | NUMBER OF pages IN the INDEX |

| mysql | t | a | 2013-05-08 16:23:05 | n_diff_pfx01 | 0 | 1 | a |

| mysql | t | a | 2013-05-08 16:23:05 | n_diff_pfx02 | 0 | 1 | a,b |

| mysql | t | a | 2013-05-08 16:23:05 | n_diff_pfx03 | 0 | 1 | a,b,c |

| mysql | t | a | 2013-05-08 16:23:05 | n_diff_pfx04 | 0 | 1 | a,b,c,i |

| mysql | t | a | 2013-05-08 16:23:05 | n_leaf_pages | 1 | NULL | NUMBER OF leaf pages IN the INDEX |

| mysql | t | a | 2013-05-08 16:23:05 | SIZE | 1 | NULL | NUMBER OF pages IN the INDEX |

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

9 ROWS IN SET (0.00 sec)

还有一些优化和细节, 请看 代码 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值