一文帶你了解MySQL之InnoDB統(tǒng)計(jì)數(shù)據(jù)是如何收集的
一、兩種不同的統(tǒng)計(jì)數(shù)據(jù)存儲(chǔ)方式
InnoDB提供了兩種存儲(chǔ)統(tǒng)計(jì)數(shù)據(jù)的方式:
永久性的統(tǒng)計(jì)數(shù)據(jù):這種統(tǒng)計(jì)數(shù)據(jù)存儲(chǔ)在磁盤(pán)上,也就是服務(wù)器重啟之后這些統(tǒng)計(jì)數(shù)據(jù)還在非永久性的統(tǒng)計(jì)數(shù)據(jù):這種統(tǒng)計(jì)數(shù)據(jù)存儲(chǔ)在內(nèi)存中,當(dāng)服務(wù)器關(guān)閉時(shí)這些這些統(tǒng)計(jì)數(shù)據(jù)就都被清除掉了,等到服務(wù)器重啟之后,在某些適當(dāng)?shù)膱?chǎng)景下才會(huì)重新收集這些統(tǒng)計(jì)數(shù)據(jù)
MySQL給我們提供了系統(tǒng)變量innodb_stats_persistent來(lái)控制到底采用哪種方式去存儲(chǔ)統(tǒng)計(jì)數(shù)據(jù)。在MySQL 5.6.6之前,innodb_stats_persistent的值默認(rèn)是OFF,也就是說(shuō)InnoDB的統(tǒng)計(jì)數(shù)據(jù)默認(rèn)是存儲(chǔ)到內(nèi)存的,之后的版本中innodb_stats_persistent的值默認(rèn)是ON,也就是統(tǒng)計(jì)數(shù)據(jù)默認(rèn)被存儲(chǔ)到磁盤(pán)中。
不過(guò)InnoDB默認(rèn)是以表為單位來(lái)收集和存儲(chǔ)統(tǒng)計(jì)數(shù)據(jù)的,也就是說(shuō)我們可以把某些表的統(tǒng)計(jì)數(shù)據(jù)(以及該表的索引統(tǒng)計(jì)數(shù)據(jù))存儲(chǔ)在磁盤(pán)上,把另一些表的統(tǒng)計(jì)數(shù)據(jù)存儲(chǔ)在內(nèi)存中。怎么做到的呢?我們可以在創(chuàng)建和修改表的時(shí)候通過(guò)指定stats_persistent屬性來(lái)指明該表的統(tǒng)計(jì)數(shù)據(jù)存儲(chǔ)方式:
create table 表名 (...) engine=innodb, stats_persistent = (1|0); alter table 表名 engine=innodb, stats_persistent = (1|0);
- 當(dāng)
stats_persistent=1時(shí),表明我們想把該表的統(tǒng)計(jì)數(shù)據(jù)永久的存儲(chǔ)到磁盤(pán)上 - 當(dāng)
stats_persistent=0時(shí),表明我們想把該表的統(tǒng)計(jì)數(shù)據(jù)臨時(shí)的存儲(chǔ)到內(nèi)存中
需要注意的是如果我們?cè)趧?chuàng)建表時(shí)未指定stats_persistent屬性,那默認(rèn)采用系統(tǒng)變量innodb_stats_persistent的值作為該屬性的值
mysql> show variables like 'innodb_stats_persistent'; +-------------------------+-------+ | Variable_name | Value | +-------------------------+-------+ | innodb_stats_persistent | ON | +-------------------------+-------+ 1 row in set (0.01 sec) mysql> set persist innodb_stats_persistent =1; Query OK, 0 rows affected (0.01 sec)
二、基于磁盤(pán)的永久性統(tǒng)計(jì)數(shù)據(jù)
當(dāng)我們選擇把某個(gè)表以及該表索引的統(tǒng)計(jì)數(shù)據(jù)存放到磁盤(pán)上時(shí),實(shí)際上是把這些統(tǒng)計(jì)數(shù)據(jù)存儲(chǔ)到了兩個(gè)表里:
mysql> show tables from mysql like 'innodb%'; +---------------------------+ | Tables_in_mysql (innodb%) | +---------------------------+ | innodb_index_stats | | innodb_table_stats | +---------------------------+ 2 rows in set (0.03 sec)
可以看到,這兩個(gè)表都位于mysql系統(tǒng)數(shù)據(jù)庫(kù)下邊,其中:
innodb_table_stats存儲(chǔ)了關(guān)于表的統(tǒng)計(jì)數(shù)據(jù),每一條記錄對(duì)應(yīng)著一個(gè)表的統(tǒng)計(jì)數(shù)據(jù)innodb_index_stats存儲(chǔ)了關(guān)于索引的統(tǒng)計(jì)數(shù)據(jù),每一條記錄對(duì)應(yīng)著一個(gè)索引的一個(gè)統(tǒng)計(jì)項(xiàng)的統(tǒng)計(jì)數(shù)據(jù)
我們下邊的就是看?下這兩個(gè)表里邊都有什么以及表里的數(shù)據(jù)是如何生成的
2.1 innodb_table_stats
這里直接看一下innodb_table_stats表中的各個(gè)列都是干嘛的:
| 字段名 | 描述 |
|---|---|
| database_name | 數(shù)據(jù)庫(kù)名 |
| table_name | 表名 |
| last_update | 本條記錄最后更新時(shí)間 |
| n_rows | 表中記錄的條數(shù) |
| clustered_index_size | 表的聚簇索引占用的頁(yè)面數(shù)量 |
| sum_of_other_index_sizes | 表的其他索引占用的頁(yè)面數(shù)量 |
注意這個(gè)表的主鍵是(database_name,table_name),也就是innodb_table_stats表的每條記錄代表著一個(gè)表的統(tǒng)計(jì)信息。我們直接看一下這個(gè)表里的內(nèi)容:
mysql> select * from mysql.innodb_table_stats where database_name= 'testdb' limit 2; +---------------+---------------+---------------------+--------+----------------------+--------------------------+ | database_name | table_name | last_update | n_rows | clustered_index_size | sum_of_other_index_sizes | +---------------+---------------+---------------------+--------+----------------------+--------------------------+ | testdb | demo1 | 2023-05-06 18:20:56 | 1 | 1 | 0 | | testdb | demo8 | 2023-05-16 16:38:16 | 18758 | 97 | 170 | +---------------+---------------+---------------------+--------+----------------------+--------------------------+ 2 rows in set (0.01 sec)
可以看到我們熟悉的demo8表的統(tǒng)計(jì)信息就對(duì)應(yīng)著mysql.innodb_table_stats的第二條記錄。幾個(gè)重要統(tǒng)計(jì)信息項(xiàng)的值如下:
n_rows的值是18758,表明demo8表中大約有18758條記錄,注意這個(gè)數(shù)據(jù)是估計(jì)值clustered_index_size的值是97,表明demo8表的聚簇索引占用97個(gè)頁(yè)面,這個(gè)值是也是一個(gè)估計(jì)值sum_of_other_index_sizes的值是170,表明demo8表的其他索引一共占用170個(gè)頁(yè)面,這個(gè)值是也是一個(gè)估計(jì)值
2.1.1 n_rows 統(tǒng)計(jì)項(xiàng)的收集
為啥一直強(qiáng)調(diào)n_rows這個(gè)統(tǒng)計(jì)項(xiàng)的值是估計(jì)值呢?現(xiàn)在就來(lái)揭曉答案。InnoDB統(tǒng)計(jì)一個(gè)表中有多少條記錄是這樣的:
- 按照一定算法(并不是純粹隨機(jī)的)選取每個(gè)葉自節(jié)點(diǎn)頁(yè)面,計(jì)算每個(gè)頁(yè)面中主鍵值記錄數(shù)量,然后計(jì)算平均一個(gè)頁(yè)面中主鍵值的記錄數(shù)量乘以全部葉自節(jié)點(diǎn)的數(shù)量就算是該表的n_rows值
小提示:真實(shí)的計(jì)算過(guò)程比這個(gè)稍微復(fù)雜一些,不過(guò)大致上就是這樣的意思
可以看出來(lái)這個(gè)n_rows值精確與否取決于統(tǒng)計(jì)時(shí)采樣的頁(yè)面數(shù)量,MySQL為我們準(zhǔn)備了一個(gè)名為innodb_stats_persistent_sample_pages的系統(tǒng)變量來(lái)控制使用永久性的統(tǒng)計(jì)數(shù)據(jù)時(shí),計(jì)算統(tǒng)計(jì)數(shù)據(jù)時(shí)采樣的頁(yè)面數(shù)量。
- 該值設(shè)置的越大,統(tǒng)計(jì)出的n_rows值越精確,但是統(tǒng)計(jì)耗時(shí)也就最久
- 該值設(shè)置的越小,統(tǒng)計(jì)出的n_rows值越不精確,但是統(tǒng)計(jì)耗時(shí)特別少。
所以在實(shí)際使用是需要我們?nèi)?quán)衡利弊,該系統(tǒng)變量的默認(rèn)值是20。
mysql> show variables like 'innodb_stats_persistent_sample_pages'; +--------------------------------------+-------+ | Variable_name | Value | +--------------------------------------+-------+ | innodb_stats_persistent_sample_pages | 20 | +--------------------------------------+-------+ 1 row in set (0.01 sec) mysql> set persist innodb_stats_persistent_sample_pages =20; Query OK, 0 rows affected (0.02 sec)
我們前邊說(shuō)過(guò),不過(guò)InnoDB默認(rèn)是以表為單位來(lái)收集和存儲(chǔ)統(tǒng)計(jì)數(shù)據(jù)的,我們也可以單獨(dú)設(shè)置某個(gè)表的采樣頁(yè)面的數(shù)量,設(shè)置方式就是在創(chuàng)建或修改表的時(shí)候通過(guò)指定stats_sample_pages 屬性來(lái)指明該表的統(tǒng)計(jì)數(shù)據(jù)存儲(chǔ)方式:
create table 表名 (...) engine=innodb, stats_sample_pages = 具體的采樣頁(yè)面數(shù)量; alter table 表名 engine=innodb, stats_sample_pages = 具體的采樣頁(yè)面數(shù)量;
如果我們?cè)趧?chuàng)建表的語(yǔ)句中并沒(méi)有指定stats_sample_pages屬性的話,將默認(rèn)使用系統(tǒng)變量innodb_stats_persistent_sample_pages的值作為該屬性的值。
2.1.2 clustered_index_size 和 sum_of_other_index_sizes統(tǒng)計(jì)項(xiàng)的收集
統(tǒng)計(jì)這兩個(gè)數(shù)據(jù)需要大量用到我們之前學(xué)習(xí)的InnoDB表空間的知識(shí),如果大家壓根就沒(méi)有看那一章,那下邊的計(jì)算過(guò)程大家還是不要看了(看也看不懂);如果看過(guò)了,那大家就會(huì)發(fā)現(xiàn)InnoDB表空間的知識(shí)真是有用。
這兩個(gè)統(tǒng)計(jì)項(xiàng)的收集過(guò)程如下:
步驟一: 從數(shù)據(jù)字典中找到表的各個(gè)索引對(duì)應(yīng)的根頁(yè)面位置(系統(tǒng)表sys_indexes里存儲(chǔ)了各個(gè)索引對(duì)應(yīng)的根頁(yè)面信息)
步驟二: 從根頁(yè)面的Page Header里找到葉子節(jié)點(diǎn)段和非葉子節(jié)點(diǎn)段對(duì)應(yīng)的Segment Header。在每個(gè)索引的根??的Page Header部分都有兩個(gè)字段:
Page_btr_seg_leaf:表示B+樹(shù)葉?段的Segment Header信息Page_btr_seg_top:表示B+樹(shù)?葉?段的Segment Header信息
步驟三: 從葉子節(jié)點(diǎn)段和非葉子節(jié)點(diǎn)段的Segment Header中找到這兩個(gè)段對(duì)應(yīng)的INODE Entry結(jié)構(gòu)。
Segment Header結(jié)構(gòu)如下:

從對(duì)應(yīng)的INODE Entry結(jié)構(gòu)中可以找到該段對(duì)應(yīng)所有零散的頁(yè)面地址以及free、not_full、full鏈表的基節(jié)點(diǎn)。
INODE Entry結(jié)構(gòu)如下:

直接統(tǒng)計(jì)零散的頁(yè)面有多少個(gè),然后從那三個(gè)鏈表的List Length字段中讀出該段占用的區(qū)的大小,每個(gè)區(qū)占用64個(gè)頁(yè),所以就可以統(tǒng)計(jì)出整個(gè)段占用的頁(yè)面。
這個(gè)是鏈表基節(jié)點(diǎn)的示意圖:

步驟四: 分別計(jì)算聚簇索引的葉子結(jié)點(diǎn)段和非葉子節(jié)點(diǎn)段占用的頁(yè)面數(shù),它們的和就是clustered_index_size的值,按照同樣的套路把其余索引占用的頁(yè)面數(shù)都算出來(lái),加起來(lái)之后就是sum_of_other_index_sizes的值
這里需要大家注意一個(gè)問(wèn)題,我們說(shuō)一個(gè)段的數(shù)據(jù)在非常多時(shí)(超過(guò)32個(gè)頁(yè)面),會(huì)以區(qū)為單位來(lái)申請(qǐng)空間,這里的問(wèn)題是以區(qū)為單位申請(qǐng)空間中有一些頁(yè)可能并沒(méi)有使用,但是在統(tǒng)計(jì)clustered_index_size和sum_of_other_index_sizes時(shí)都把它們算進(jìn)去了,所以說(shuō)聚簇索引和其他的索引占用的頁(yè)面數(shù)可能比這兩個(gè)值要大一些。
2.2 innodb_index_stats
直接看一下這個(gè)innodb_index_stats表中的各個(gè)列都是干嘛的:
| 字段名 | 描述 |
|---|---|
| database_name | 數(shù)據(jù)庫(kù)名 |
| table_name | 表名 |
| index_name | 索引名 |
| last_update | 本條記錄最后更新時(shí)間 |
| stat_name | 統(tǒng)計(jì)項(xiàng)的名稱(chēng) |
| stat_value | 對(duì)應(yīng)的統(tǒng)計(jì)項(xiàng)的值 |
| sample_size | 為生成統(tǒng)計(jì)數(shù)據(jù)而采樣的頁(yè)面數(shù)量 |
| stat_description | 對(duì)應(yīng)的統(tǒng)計(jì)項(xiàng)的描述 |
注意這個(gè)表的主鍵是(database_name,table_name,index_name,stat_name),其中的stat_name是指統(tǒng)計(jì)項(xiàng)的名稱(chēng),也就是說(shuō)innodb_index_stats表的每條記錄代表著一個(gè)索引的一個(gè)統(tǒng)計(jì)項(xiàng)。可能這會(huì)大家有些懵逼這個(gè)統(tǒng)計(jì)項(xiàng)到底指什么,別著急,我們直接看一下關(guān)于demo8表的索引統(tǒng)計(jì)數(shù)據(jù)都有些什么:
mysql> select * from mysql.innodb_index_stats where table_name='demo8'; +---------------+------------+--------------+---------------------+--------------+------------+-------------+-----------------------------------+ | database_name | table_name | index_name | last_update | stat_name | stat_value | sample_size | stat_description | +---------------+------------+--------------+---------------------+--------------+------------+-------------+-----------------------------------+ | testdb | demo8 | PRIMARY | 2023-05-16 16:38:16 | n_diff_pfx01 | 18750 | 20 | id | | testdb | demo8 | PRIMARY | 2023-05-16 16:38:16 | n_leaf_pages | 75 | NULL | Number of leaf pages in the index | | testdb | demo8 | PRIMARY | 2023-05-16 16:38:16 | size | 97 | NULL | Number of pages in the index | | testdb | demo8 | idx_key1 | 2023-05-16 16:38:16 | n_diff_pfx01 | 256 | 22 | key1 | | testdb | demo8 | idx_key1 | 2023-05-16 16:38:16 | n_diff_pfx02 | 18567 | 22 | key1,id | | testdb | demo8 | idx_key1 | 2023-05-16 16:38:16 | n_leaf_pages | 22 | NULL | Number of leaf pages in the index | | testdb | demo8 | idx_key1 | 2023-05-16 16:38:16 | size | 23 | NULL | Number of pages in the index | | testdb | demo8 | idx_key2 | 2023-05-16 16:38:16 | n_diff_pfx01 | 18565 | 18 | key2 | | testdb | demo8 | idx_key2 | 2023-05-16 16:38:16 | n_leaf_pages | 18 | NULL | Number of leaf pages in the index | | testdb | demo8 | idx_key2 | 2023-05-16 16:38:16 | size | 19 | NULL | Number of pages in the index | | testdb | demo8 | idx_key3 | 2023-05-16 16:38:16 | n_diff_pfx01 | 4053 | 30 | key3 | | testdb | demo8 | idx_key3 | 2023-05-16 16:38:16 | n_diff_pfx02 | 18568 | 30 | key3,id | | testdb | demo8 | idx_key3 | 2023-05-16 16:38:16 | n_leaf_pages | 30 | NULL | Number of leaf pages in the index | | testdb | demo8 | idx_key3 | 2023-05-16 16:38:16 | size | 31 | NULL | Number of pages in the index | | testdb | demo8 | idx_key_part | 2023-05-16 16:38:16 | n_diff_pfx01 | 16122 | 50 | key_part1 | | testdb | demo8 | idx_key_part | 2023-05-16 16:38:16 | n_diff_pfx02 | 18570 | 50 | key_part1,key_part2 | | testdb | demo8 | idx_key_part | 2023-05-16 16:38:16 | n_diff_pfx03 | 18570 | 50 | key_part1,key_part2,key_part3 | | testdb | demo8 | idx_key_part | 2023-05-16 16:38:16 | n_diff_pfx04 | 18570 | 50 | key_part1,key_part2,key_part3,id | | testdb | demo8 | idx_key_part | 2023-05-16 16:38:16 | n_leaf_pages | 50 | NULL | Number of leaf pages in the index | | testdb | demo8 | idx_key_part | 2023-05-16 16:38:16 | size | 97 | NULL | Number of pages in the index | +---------------+------------+--------------+---------------------+--------------+------------+-------------+-----------------------------------+ 20 rows in set (0.03 sec)
這個(gè)結(jié)果有點(diǎn)多,正確查看這個(gè)結(jié)果的方式是這樣的:
- 先查看index_name列,這個(gè)列說(shuō)明該記錄是哪個(gè)索引的統(tǒng)計(jì)信息,從結(jié)果中我們可以看出來(lái),PRIMARY索引(也就是主鍵)占了3條記錄,idx_key_part索引占了6條記錄。
- 針對(duì)index_name列相同的記錄,stat_name表示針對(duì)該索引的統(tǒng)計(jì)項(xiàng)名稱(chēng),stat_value展示的是該索引在該統(tǒng)計(jì)項(xiàng)上的值,stat_description指的是來(lái)描述該統(tǒng)計(jì)項(xiàng)的含義的。我們來(lái)具體看一下一個(gè)索引都有哪些統(tǒng)計(jì)項(xiàng):
n_leaf_pages:表示該索引的葉子節(jié)點(diǎn)占用多少頁(yè)面size:表示該索引共占用多少頁(yè)面-
n_diff_pfxNN:表示對(duì)應(yīng)的索引列不重復(fù)的值有多少。其中的NN長(zhǎng)得有點(diǎn)?怪呀,啥意思呢? 其實(shí)NN可以被替換為01、02、03… 這樣的數(shù)字。比如對(duì)于idx_key_part來(lái)說(shuō):- n_diff_pfx01表示的是統(tǒng)計(jì)key_part1這單單一個(gè)列不重復(fù)的值有多少
- n_diff_pfx02表示的是統(tǒng)計(jì)key_part1、key_part2這兩個(gè)列組合起來(lái)不重復(fù)的值有多少
- n_diff_pfx03表示的是統(tǒng)計(jì)key_part1、key_part2、key_part3這三個(gè)列組合起來(lái)不重復(fù)的值有多少
- n_diff_pfx04表示的是統(tǒng)計(jì)key_part1、key_part2、key_part3、id這四個(gè)列組合起來(lái)不重復(fù)的值有多少
小提示:
這里需要注意的是,對(duì)于普通的二級(jí)索引,并不能保證它的索引列值是唯一的,比如對(duì)于idx_key1來(lái)說(shuō),key1列就可能有很多值重復(fù)的記錄。此時(shí)只有在索引列上加上主鍵值才可以區(qū)分兩條索引列值都一樣的二級(jí)索引記錄。對(duì)于主鍵和唯一二級(jí)索引則沒(méi)有這個(gè)問(wèn)題,它們本身就可以保證索引列值的不重復(fù),所以也不需要再統(tǒng)計(jì)一遍在索引列后加上主鍵值的不重復(fù)值有多少。比如上邊的idx_key1有n_diff_pfx01、n_diff_pfx02兩個(gè)統(tǒng)計(jì)項(xiàng),而idx_key2卻只有n_diff_pfx01一個(gè)統(tǒng)計(jì)項(xiàng)。
- 在計(jì)算某些索引列中包含多少不重復(fù)值時(shí),需要對(duì)這些葉子節(jié)點(diǎn)頁(yè)面進(jìn)行采樣,
size列就表明了采樣的頁(yè)面數(shù)量是多少
小提示:
對(duì)于有多個(gè)列的聯(lián)合索引來(lái)說(shuō),采樣的頁(yè)面數(shù)量是:innodb_stats_persistent_sample_pages × 索引列的個(gè)數(shù)。當(dāng)需要采樣的頁(yè)面數(shù)量大于該索引的葉子節(jié)點(diǎn)數(shù)量的話,就直接采用全表掃描來(lái)統(tǒng)計(jì)索引列的不重復(fù)值數(shù)量了。所以大家可以在查詢(xún)結(jié)果中看到不同索引對(duì)應(yīng)的size列的值可能是不同的。
2.3 定期更新統(tǒng)計(jì)數(shù)據(jù)
隨著我們不斷的對(duì)表進(jìn)行增刪改操作,表中的數(shù)據(jù)也一直在變化,innodb_table_stats和innodb_index_stats表立的統(tǒng)計(jì)數(shù)據(jù)是不是也應(yīng)該跟著變一變了?當(dāng)然要變了,不變的話MySQL查詢(xún)優(yōu)化器計(jì)算的成本可就差老遠(yuǎn)了。MySQL的提供了如下兩種更新統(tǒng)計(jì)數(shù)據(jù)的方式:
- 開(kāi)啟
innodb_stats_auto_recalc
系統(tǒng)變量innodb_stats_auto_recalc決定著服務(wù)器是自動(dòng)重新計(jì)算統(tǒng)計(jì)數(shù)據(jù),它的默認(rèn)值是ON,也就是該功能默認(rèn)是開(kāi)啟的。每個(gè)表都維護(hù)了一個(gè)變量,該變量記錄著對(duì)該表進(jìn)行增刪改的記錄條數(shù),如果發(fā)生變動(dòng)的記錄數(shù)量超過(guò)了表大小的10%,并且自動(dòng)重新計(jì)算統(tǒng)計(jì)數(shù)據(jù)的功能是打開(kāi)的,那么服務(wù)器會(huì)重新進(jìn)行一次統(tǒng)計(jì)數(shù)據(jù)的計(jì)算,并且更新innodb_table_stats和innodb_index_stats表。不過(guò)自動(dòng)重新計(jì)算統(tǒng)計(jì)數(shù)據(jù)的過(guò)程是異步發(fā)生的,也就是即使表中變動(dòng)的記錄數(shù)超過(guò)了10%,自動(dòng)重新計(jì)算統(tǒng)計(jì)數(shù)據(jù)也不會(huì)立即發(fā)生,可能會(huì)延遲幾秒才會(huì)進(jìn)行計(jì)算。
mysql> show variables like 'innodb_stats_auto_recalc'; +--------------------------+-------+ | Variable_name | Value | +--------------------------+-------+ | innodb_stats_auto_recalc | ON | +--------------------------+-------+ 1 row in set (0.00 sec) mysql> set persist innodb_stats_auto_recalc =1; Query OK, 0 rows affected (0.00 sec)
再一次強(qiáng)調(diào),InnoDB默認(rèn)是以表為單位來(lái)收集和存儲(chǔ)統(tǒng)計(jì)數(shù)據(jù)的,我們也可以單獨(dú)為某個(gè)表設(shè)置是否自動(dòng)重新計(jì)算統(tǒng)計(jì)數(shù)的屬性,設(shè)置方式就是在創(chuàng)建或修改表的時(shí)候通過(guò)指定stats_auto_recalc屬性來(lái)指明該表的統(tǒng)計(jì)數(shù)據(jù)存儲(chǔ)方式:
create table 表名 (...) engine=innodb, stats_auto_recalc = (1|0); alter table 表名 engine=innodb, stats_auto_recalc = (1|0);
當(dāng)stats_auto_recalc=1時(shí),表明我們想讓該表自動(dòng)重新計(jì)算統(tǒng)計(jì)數(shù)據(jù)當(dāng)stats_persistent=0時(shí),表明不想讓該表自動(dòng)重新計(jì)算統(tǒng)計(jì)數(shù)據(jù)
如果我們?cè)趧?chuàng)建表時(shí)未指定stats_auto_recalc屬性,那默認(rèn)采用系統(tǒng)變量innodb_stats_auto_recalc的值作為該屬性的值。
手動(dòng)調(diào)用
analyze table語(yǔ)句來(lái)更新統(tǒng)計(jì)信息如果
innodb_stats_auto_recalc系統(tǒng)變量的值為OFF的話,我們也可以手動(dòng)調(diào)用analyze table語(yǔ)句來(lái)重新計(jì)算統(tǒng)計(jì)數(shù)據(jù),比如我們可以這樣更新關(guān)于demo8表的統(tǒng)計(jì)數(shù)據(jù):
mysql> analyze table demo8;
需要注意的是,analyze table語(yǔ)句會(huì)立即重新計(jì)算統(tǒng)計(jì)數(shù)據(jù),也就是這個(gè)過(guò)程是同步的,在表中索引多或者采樣頁(yè)面特別多時(shí)這個(gè)過(guò)程可能會(huì)特別慢,請(qǐng)不要沒(méi)事就運(yùn)行一下analyze table語(yǔ)句,最好在業(yè)務(wù)不是很繁忙的時(shí)候再運(yùn)行。
2.4 手動(dòng)動(dòng)更新innodb_table_stats 和 innodb_index_stats表
其實(shí)innodb_table_stats和innodb_index_stats表就相當(dāng)于一個(gè)普通的表一樣,我們能對(duì)它們做增刪改查操作。這也就意味著我們可以手動(dòng)更新某個(gè)表或者索引的統(tǒng)計(jì)數(shù)據(jù)。比如說(shuō)我們想把demo8表關(guān)于行數(shù)的統(tǒng)計(jì)數(shù)據(jù)更改一下可以這么做:
步驟一: 更新demo8表
update innodb_table_stats set n_rows = 1 where table_name = 'demo8';
步驟二: 讓MySQL查詢(xún)優(yōu)化器重新加載我們更改過(guò)的數(shù)據(jù)
更新完innodb_table_stats只是單純的修改了一個(gè)表的數(shù)據(jù),需要讓MySQL查詢(xún)優(yōu)化器重新加載我們更改過(guò)的數(shù)據(jù),運(yùn)行下邊的命令就可以了:
flush table demo8;
之后我們使用show table status語(yǔ)句查看表的統(tǒng)計(jì)數(shù)據(jù)時(shí)就看到Rows行變?yōu)榱?。
三、基于內(nèi)存的非永久性統(tǒng)計(jì)數(shù)據(jù)
當(dāng)我們把系統(tǒng)變量innodb_stats_persistent的值設(shè)置為OFF時(shí),之后創(chuàng)建的表的統(tǒng)計(jì)數(shù)據(jù)默認(rèn)就都是非永久性的了,或者我們直接在創(chuàng)建表或修改表時(shí)設(shè)置stats_persistent屬性的值為0,那么該表的統(tǒng)計(jì)數(shù)據(jù)就是非永久性的了。
與永久性的統(tǒng)計(jì)數(shù)據(jù)不同,非永久性的統(tǒng)計(jì)數(shù)據(jù)采樣的頁(yè)面數(shù)量是由innodb_stats_transient_sample_pages控制的,這個(gè)系統(tǒng)變量的默認(rèn)值是8。另外,由于非永久性的統(tǒng)計(jì)數(shù)據(jù)經(jīng)常更新,所以導(dǎo)致MySQL查詢(xún)優(yōu)化器計(jì)算查詢(xún)成本的時(shí)候依賴(lài)的是經(jīng)常變化的統(tǒng)計(jì)數(shù)據(jù),也就會(huì)生成經(jīng)常變化的執(zhí)行計(jì)劃,這個(gè)可能讓大家有些懵逼。不過(guò)最近的MySQL版本都不咋用這種基于內(nèi)存的非永久性統(tǒng)計(jì)數(shù)據(jù)了,所以我們也就不深入了解了。
mysql> show variables like 'innodb_stats_transient_sample_pages'; +-------------------------------------+-------+ | Variable_name | Value | +-------------------------------------+-------+ | innodb_stats_transient_sample_pages | 8 | +-------------------------------------+-------+ 1 row in set (0.01 sec) mysql> set persist innodb_stats_transient_sample_pages =1; Query OK, 0 rows affected (0.00 sec)
四、innodb_stats_method 的使用
我們知道索引列不重復(fù)的值的數(shù)量這個(gè)統(tǒng)計(jì)數(shù)據(jù)對(duì)于MySQL查詢(xún)優(yōu)化器十分重要,因?yàn)橥ㄟ^(guò)它可以計(jì)算出在索引列中平均一個(gè)值重復(fù)多少行,它的應(yīng)用場(chǎng)景主要有兩個(gè):
- 單表查詢(xún)中單點(diǎn)區(qū)間太多,比如說(shuō)這樣: 當(dāng)IN里的參數(shù)數(shù)量過(guò)多時(shí),采用index dive的方式直接訪問(wèn)B+樹(shù)索引去統(tǒng)計(jì)每個(gè)單點(diǎn)區(qū)間對(duì)應(yīng)的記錄的數(shù)量就太耗費(fèi)性能了,所以直接依賴(lài)統(tǒng)計(jì)數(shù)據(jù)中的平均一個(gè)值重復(fù)多少行來(lái)計(jì)算單點(diǎn)區(qū)間對(duì)應(yīng)的記錄數(shù)量。
select * from tbl_name where key in ('xx1', 'xx2', ..., 'xxn');
- 連接查詢(xún)時(shí),如果有涉及兩個(gè)表的等值匹配連接條件,該連接條件對(duì)應(yīng)的被驅(qū)動(dòng)表中的列又擁有索引時(shí),則可以使用ref訪問(wèn)方法來(lái)對(duì)被驅(qū)動(dòng)表進(jìn)行查詢(xún),比如說(shuō)這樣:
select * from t1 join t2 on t1.column = t2.key where ...;
在真正執(zhí)行對(duì)t2表的查詢(xún)前,t1.comumn的值是不確定的,所以我們也不能通過(guò)index dive的方式直接訪問(wèn)B+樹(shù)索引去統(tǒng)計(jì)每個(gè)單點(diǎn)區(qū)間對(duì)應(yīng)的記錄的數(shù)量,所以也只能依賴(lài)統(tǒng)計(jì)數(shù)據(jù)中的平均一個(gè)值重復(fù)多少次來(lái)計(jì)算單點(diǎn)區(qū)間對(duì)應(yīng)的記錄數(shù)量。
在統(tǒng)計(jì)索引列不重復(fù)的值的數(shù)量時(shí),有一個(gè)比較煩的問(wèn)題就是索引列中出現(xiàn)NULL值怎么辦,比如說(shuō)某個(gè)索引列的內(nèi)容是這樣:
+------+ | col | +------+ | 1 | | 2 | | NULL | | NULL | +------+
此時(shí)計(jì)算這個(gè)col列中不重復(fù)的值的數(shù)量就有下邊的分歧:
- 有的人認(rèn)為NULL值代表一個(gè)未確定的值,所以MySQL認(rèn)為任何和NULL值做比較的表達(dá)式的值都為NULL,就是這樣:
mysql> select 1 = null; +----------+ | 1 = null | +----------+ | null | +----------+ 1 row inset (0.00 sec)
mysql> select 1 != null; +-----------+ | 1 != null | +-----------+ | null | +-----------+ 1 row inset (0.00 sec)
mysql> select null = null; +-------------+ | null = null | +-------------+ | null | +-------------+ 1 row inset (0.00 sec)
mysql> select null != null; +--------------+ | null != null | +--------------+ | null | +--------------+ 1 row inset (0.00 sec)
所以每一個(gè)NULL值都是獨(dú)一無(wú)二的,也就是說(shuō)統(tǒng)計(jì)索引列不重復(fù)的值的數(shù)量時(shí),應(yīng)該把NULL值當(dāng)作一個(gè)獨(dú)立的值,所以col列的不重復(fù)的值的數(shù)量就是:4(分別是1、2、NULL、NULL這四個(gè)值)。
- 有的人認(rèn)為其實(shí)NULL值在業(yè)務(wù)上就是代表沒(méi)有,所有的NULL值代表的意義是一樣的,所以col列不重復(fù)的值的數(shù)量就是:3(分別是1、2、NULL這三個(gè)值)。
- 有的人認(rèn)為這NULL完全沒(méi)有意義嘛,所以在統(tǒng)計(jì)索引列不重復(fù)的值的數(shù)量時(shí)壓根不能把它們算進(jìn)來(lái),所以col列不重復(fù)的值的數(shù)量就是:2(分別是1、2這兩個(gè)值)。
MySQL提供了一個(gè)名為innodb_stats_method的系統(tǒng)變量,相當(dāng)于在計(jì)算某個(gè)索引列不重復(fù)值的數(shù)量時(shí)如何對(duì)待NULL值這個(gè)鍋甩給了用戶(hù),這個(gè)系統(tǒng)變量有三個(gè)候選值:
- nulls_equal:認(rèn)為所有NULL值都是相等的。這個(gè)值也是innodb_stats_method的默認(rèn)值。
- 如果某個(gè)索引列中NULL值特別多的話,這種統(tǒng)計(jì)?式會(huì)讓優(yōu)化器認(rèn)為某個(gè)列中平均一個(gè)值重復(fù)次數(shù)特別多,所以?xún)A向于不使用索引進(jìn)行訪問(wèn)。
- nulls_unequal:認(rèn)為所有NULL值都是不相等的。
- 如果某個(gè)索引列中NULL值特別多的話,這種統(tǒng)計(jì)?式會(huì)讓優(yōu)化器認(rèn)為某個(gè)列中平均一個(gè)值重復(fù)次數(shù)特別少,所以?xún)A向于使用索引進(jìn)行訪問(wèn)。
- nulls_ignored:直接把NULL值忽略掉。
反正這個(gè)鍋是甩給用戶(hù)了,當(dāng)你選定了innodb_stats_method值之后,優(yōu)化器即使選擇了不是最優(yōu)的執(zhí)行計(jì)劃,那也跟MySQL沒(méi)關(guān)系了哈~ 當(dāng)然對(duì)于用戶(hù)的我們來(lái)說(shuō),最好不在索引列中存放NULL值才是正解。
總結(jié)
InnoDB以表為單位來(lái)收集統(tǒng)計(jì)數(shù)據(jù),這些統(tǒng)計(jì)數(shù)據(jù)可以是基于磁盤(pán)的永久性統(tǒng)計(jì)數(shù)據(jù),也可以是基于內(nèi)存的非永久性統(tǒng)計(jì)數(shù)據(jù)。
innodb_stats_persistent控制著使用永久性統(tǒng)計(jì)數(shù)據(jù)還是非永久性統(tǒng)計(jì)數(shù)據(jù);innodb_stats_persistent_sample_pages控制著永久性統(tǒng)計(jì)數(shù)據(jù)的采樣
頁(yè)面數(shù)量;innodb_stats_transient_sample_pages控制著非永久性統(tǒng)計(jì)數(shù)據(jù)的采樣頁(yè)面數(shù)量;innodb_stats_auto_recalc控制著是否自動(dòng)重新計(jì)算統(tǒng)計(jì)數(shù)據(jù)。- 我們可以針對(duì)某個(gè)具體的表,在創(chuàng)建和修改表時(shí)通過(guò)指定stats_persistent、stats_auto_recalc、stats_sample_pages的值來(lái)控制相關(guān)統(tǒng)計(jì)數(shù)據(jù)屬性。
- innodb_stats_method決定著在統(tǒng)計(jì)某個(gè)索引列不重復(fù)值的數(shù)量時(shí)如何對(duì)待NULL值
以上就是一文帶你了解MySQL之InnoDB統(tǒng)計(jì)數(shù)據(jù)是如何收集的的詳細(xì)內(nèi)容,更多關(guān)于MySQL InnoDB統(tǒng)計(jì)數(shù)據(jù)收集的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
mysql累加計(jì)算實(shí)現(xiàn)方法詳解
這篇文章主要介紹了mysql累加計(jì)算實(shí)現(xiàn)方法,結(jié)合實(shí)例形勢(shì)分析了mysql累加計(jì)算原理、實(shí)現(xiàn)方法及操作注意事項(xiàng),需要的朋友可以參考下2020-05-05
怎么重置mysql的自增列AUTO_INCREMENT初時(shí)值
怎么重置mysql的自增列想必有很多的朋友都不會(huì)吧,下面與大家分享下常用的幾種方法,不懂的朋友可以了解下哈,希望對(duì)大家有所幫助2013-06-06
MySQL使用正則表達(dá)式進(jìn)行查詢(xún)操作經(jīng)典實(shí)例總結(jié)
這篇文章主要介紹了MySQL使用正則表達(dá)式進(jìn)行查詢(xún)操作,結(jié)合實(shí)例形式總結(jié)分析了各種常見(jiàn)匹配模式的查詢(xún)操作實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2018-06-06
MySQL開(kāi)啟遠(yuǎn)程訪問(wèn)權(quán)限的完整步驟記錄
MySQL是常用的關(guān)系型數(shù)據(jù)庫(kù),默認(rèn)僅支持本地訪問(wèn),若需更靈活管理,可通過(guò)遠(yuǎn)程連接突破限制,實(shí)現(xiàn)跨主機(jī)操作,這篇文章主要介紹了MySQL開(kāi)啟遠(yuǎn)程訪問(wèn)權(quán)限的完整步驟,需要的朋友可以參考下2025-06-06
mysql 8.0.18各版本安裝及安裝中出現(xiàn)的問(wèn)題(精華總結(jié))
這篇文章主要介紹了mysql 8.0.18各版本安裝及安裝中出現(xiàn)的問(wèn)題,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12
SQL處理時(shí)間戳?xí)r如何解決時(shí)區(qū)問(wèn)題實(shí)例詳解
時(shí)間戳?xí)r間不分東西南北、在地球的每一個(gè)角落都是相同的,下面這篇文章主要給大家介紹了關(guān)于SQL處理時(shí)間戳?xí)r如何解決時(shí)區(qū)問(wèn)題的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08
MySQL 修改數(shù)據(jù)庫(kù)名稱(chēng)的一個(gè)新奇方法
這篇文章主要介紹了MySQL 修改數(shù)據(jù)庫(kù)名稱(chēng)的一個(gè)新奇方法,MySQL 修改數(shù)據(jù)庫(kù)名的一個(gè)變通方法,需要的朋友可以參考下2014-07-07
mysql中關(guān)于between和in的區(qū)別
這篇文章主要介紹了mysql中關(guān)于between和in的區(qū)別說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07

