一文帶你了解MySQL之InnoDB統(tǒng)計(jì)數(shù)據(jù)是如何收集的
一、兩種不同的統(tǒng)計(jì)數(shù)據(jù)存儲方式
InnoDB提供了兩種存儲統(tǒng)計(jì)數(shù)據(jù)的方式:
永久性的統(tǒng)計(jì)數(shù)據(jù)
:這種統(tǒng)計(jì)數(shù)據(jù)存儲在磁盤上,也就是服務(wù)器重啟之后這些統(tǒng)計(jì)數(shù)據(jù)還在非永久性的統(tǒng)計(jì)數(shù)據(jù)
:這種統(tǒng)計(jì)數(shù)據(jù)存儲在內(nèi)存中,當(dāng)服務(wù)器關(guān)閉時(shí)這些這些統(tǒng)計(jì)數(shù)據(jù)就都被清除掉了,等到服務(wù)器重啟之后,在某些適當(dāng)?shù)膱鼍跋虏艜?huì)重新收集這些統(tǒng)計(jì)數(shù)據(jù)
MySQL給我們提供了系統(tǒng)變量innodb_stats_persistent來控制到底采用哪種方式去存儲統(tǒng)計(jì)數(shù)據(jù)。在MySQL 5.6.6之前,innodb_stats_persistent的值默認(rèn)是OFF,也就是說InnoDB的統(tǒng)計(jì)數(shù)據(jù)默認(rèn)是存儲到內(nèi)存的,之后的版本中innodb_stats_persistent的值默認(rèn)是ON,也就是統(tǒng)計(jì)數(shù)據(jù)默認(rèn)被存儲到磁盤中。
不過InnoDB默認(rèn)是以表為單位來收集和存儲統(tǒng)計(jì)數(shù)據(jù)的,也就是說我們可以把某些表的統(tǒng)計(jì)數(shù)據(jù)(以及該表的索引統(tǒng)計(jì)數(shù)據(jù))存儲在磁盤上,把另一些表的統(tǒng)計(jì)數(shù)據(jù)存儲在內(nèi)存中。怎么做到的呢?我們可以在創(chuàng)建和修改表的時(shí)候通過指定stats_persistent屬性來指明該表的統(tǒng)計(jì)數(shù)據(jù)存儲方式:
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ù)永久的存儲到磁盤上
- 當(dāng)
stats_persistent=0
時(shí),表明我們想把該表的統(tǒng)計(jì)數(shù)據(jù)臨時(shí)的存儲到內(nèi)存中
需要注意的是如果我們在創(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)
二、基于磁盤的永久性統(tǒng)計(jì)數(shù)據(jù)
當(dāng)我們選擇把某個(gè)表以及該表索引的統(tǒng)計(jì)數(shù)據(jù)存放到磁盤上時(shí),實(shí)際上是把這些統(tǒng)計(jì)數(shù)據(jù)存儲到了兩個(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ù)庫下邊,其中:
innodb_table_stats
存儲了關(guān)于表的統(tǒng)計(jì)數(shù)據(jù),每一條記錄對應(yīng)著一個(gè)表的統(tǒng)計(jì)數(shù)據(jù)innodb_index_stats
存儲了關(guān)于索引的統(tǒng)計(jì)數(shù)據(jù),每一條記錄對應(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ù)庫名 |
table_name | 表名 |
last_update | 本條記錄最后更新時(shí)間 |
n_rows | 表中記錄的條數(shù) |
clustered_index_size | 表的聚簇索引占用的頁面數(shù)量 |
sum_of_other_index_sizes | 表的其他索引占用的頁面數(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ì)信息就對應(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è)頁面,這個(gè)值是也是一個(gè)估計(jì)值sum_of_other_index_sizes
的值是170
,表明demo8
表的其他索引一共占用170
個(gè)頁面,這個(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)在就來揭曉答案。InnoDB統(tǒng)計(jì)一個(gè)表中有多少條記錄是這樣的:
- 按照一定算法(并不是純粹隨機(jī)的)選取每個(gè)葉自節(jié)點(diǎn)頁面,計(jì)算每個(gè)頁面中主鍵值記錄數(shù)量,然后計(jì)算平均一個(gè)頁面中主鍵值的記錄數(shù)量乘以全部葉自節(jié)點(diǎn)的數(shù)量就算是該表的n_rows值
小提示
:真實(shí)的計(jì)算過程比這個(gè)稍微復(fù)雜一些,不過大致上就是這樣的意思
可以看出來這個(gè)n_rows
值精確與否取決于統(tǒng)計(jì)時(shí)采樣的頁面數(shù)量,MySQL為我們準(zhǔn)備了一個(gè)名為innodb_stats_persistent_sample_pages
的系統(tǒng)變量來控制使用永久性的統(tǒng)計(jì)數(shù)據(jù)時(shí),計(jì)算統(tǒng)計(jì)數(shù)據(jù)時(shí)采樣的頁面數(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)
我們前邊說過,不過InnoDB默認(rèn)是以表為單位來收集和存儲統(tǒng)計(jì)數(shù)據(jù)的
,我們也可以單獨(dú)設(shè)置某個(gè)表的采樣頁面的數(shù)量,設(shè)置方式就是在創(chuàng)建或修改表的時(shí)候通過指定stats_sample_pages
屬性來指明該表的統(tǒng)計(jì)數(shù)據(jù)存儲方式:
create table 表名 (...) engine=innodb, stats_sample_pages = 具體的采樣頁面數(shù)量; alter table 表名 engine=innodb, stats_sample_pages = 具體的采樣頁面數(shù)量;
如果我們在創(chuàng)建表的語句中并沒有指定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表空間的知識,如果大家壓根就沒有看那一章,那下邊的計(jì)算過程大家還是不要看了(看也看不懂);如果看過了,那大家就會(huì)發(fā)現(xiàn)InnoDB表空間的知識真是有用。
這兩個(gè)統(tǒng)計(jì)項(xiàng)的收集過程如下:
步驟一: 從數(shù)據(jù)字典中找到表的各個(gè)索引對應(yīng)的根頁面位置(系統(tǒng)表sys_indexes
里存儲了各個(gè)索引對應(yīng)的根頁面信息)
步驟二: 從根頁面的Page Header
里找到葉子節(jié)點(diǎn)段和非葉子節(jié)點(diǎn)段對應(yīng)的Segment Header
。在每個(gè)索引的根??的Page Heade
r部分都有兩個(gè)字段:
Page_btr_seg_leaf:
表示B+樹葉?段的Segment Header
信息Page_btr_seg_top
:表示B+樹?葉?段的Segment Header
信息
步驟三: 從葉子節(jié)點(diǎn)段和非葉子節(jié)點(diǎn)段的Segment Header
中找到這兩個(gè)段對應(yīng)的INODE Entry
結(jié)構(gòu)。
Segment Header
結(jié)構(gòu)如下:
從對應(yīng)的INODE Entry
結(jié)構(gòu)中可以找到該段對應(yīng)所有零散的頁面地址以及free
、not_full
、full
鏈表的基節(jié)點(diǎn)。
INODE Entry
結(jié)構(gòu)如下:
直接統(tǒng)計(jì)零散的頁面有多少個(gè),然后從那三個(gè)鏈表的List Length
字段中讀出該段占用的區(qū)的大小,每個(gè)區(qū)占用64個(gè)頁,所以就可以統(tǒng)計(jì)出整個(gè)段占用的頁面。
這個(gè)是鏈表基節(jié)點(diǎn)的示意圖:
步驟四: 分別計(jì)算聚簇索引的葉子結(jié)點(diǎn)段和非葉子節(jié)點(diǎn)段占用的頁面數(shù),它們的和就是clustered_index_size
的值,按照同樣的套路把其余索引占用的頁面數(shù)都算出來,加起來之后就是sum_of_other_index_sizes的值
這里需要大家注意一個(gè)問題,我們說一個(gè)段的數(shù)據(jù)在非常多時(shí)(超過32個(gè)頁面),會(huì)以區(qū)為單位來申請空間,這里的問題是以區(qū)為單位申請空間中有一些頁可能并沒有使用,但是在統(tǒng)計(jì)clustered_index_size和sum_of_other_index_sizes時(shí)都把它們算進(jìn)去了,所以說聚簇索引和其他的索引占用的頁面數(shù)可能比這兩個(gè)值要大一些。
2.2 innodb_index_stats
直接看一下這個(gè)innodb_index_stats
表中的各個(gè)列都是干嘛的:
字段名 | 描述 |
---|---|
database_name | 數(shù)據(jù)庫名 |
table_name | 表名 |
index_name | 索引名 |
last_update | 本條記錄最后更新時(shí)間 |
stat_name | 統(tǒng)計(jì)項(xiàng)的名稱 |
stat_value | 對應(yīng)的統(tǒng)計(jì)項(xiàng)的值 |
sample_size | 為生成統(tǒng)計(jì)數(shù)據(jù)而采樣的頁面數(shù)量 |
stat_description | 對應(yīng)的統(tǒng)計(jì)項(xiàng)的描述 |
注意這個(gè)表的主鍵是(database_name,table_name,index_name,stat_name),其中的stat_name是指統(tǒng)計(jì)項(xiàng)的名稱,也就是說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è)列說明該記錄是哪個(gè)索引的統(tǒng)計(jì)信息,從結(jié)果中我們可以看出來,PRIMARY索引(也就是主鍵)占了3條記錄,idx_key_part索引占了6條記錄。
- 針對index_name列相同的記錄,stat_name表示針對該索引的統(tǒng)計(jì)項(xiàng)名稱,stat_value展示的是該索引在該統(tǒng)計(jì)項(xiàng)上的值,stat_description指的是來描述該統(tǒng)計(jì)項(xiàng)的含義的。我們來具體看一下一個(gè)索引都有哪些統(tǒng)計(jì)項(xiàng):
n_leaf_pages
:表示該索引的葉子節(jié)點(diǎn)占用多少頁面size
:表示該索引共占用多少頁面-
n_diff_pfxNN
:表示對應(yīng)的索引列不重復(fù)的值有多少。其中的NN長得有點(diǎn)?怪呀,啥意思呢? 其實(shí)NN可以被替換為01、02、03… 這樣的數(shù)字。比如對于idx_key_part來說:- n_diff_pfx01表示的是統(tǒng)計(jì)key_part1這單單一個(gè)列不重復(fù)的值有多少
- n_diff_pfx02表示的是統(tǒng)計(jì)key_part1、key_part2這兩個(gè)列組合起來不重復(fù)的值有多少
- n_diff_pfx03表示的是統(tǒng)計(jì)key_part1、key_part2、key_part3這三個(gè)列組合起來不重復(fù)的值有多少
- n_diff_pfx04表示的是統(tǒng)計(jì)key_part1、key_part2、key_part3、id這四個(gè)列組合起來不重復(fù)的值有多少
小提示:
這里需要注意的是,對于普通的二級索引,并不能保證它的索引列值是唯一的,比如對于idx_key1來說,key1列就可能有很多值重復(fù)的記錄。此時(shí)只有在索引列上加上主鍵值才可以區(qū)分兩條索引列值都一樣的二級索引記錄。對于主鍵和唯一二級索引則沒有這個(gè)問題,它們本身就可以保證索引列值的不重復(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í),需要對這些葉子節(jié)點(diǎn)頁面進(jìn)行采樣,
size
列就表明了采樣的頁面數(shù)量是多少
小提示:
對于有多個(gè)列的聯(lián)合索引來說,采樣的頁面數(shù)量是:innodb_stats_persistent_sample_pages × 索引列的個(gè)數(shù)。當(dāng)需要采樣的頁面數(shù)量大于該索引的葉子節(jié)點(diǎn)數(shù)量的話,就直接采用全表掃描來統(tǒng)計(jì)索引列的不重復(fù)值數(shù)量了。所以大家可以在查詢結(jié)果中看到不同索引對應(yīng)的size列的值可能是不同的。
2.3 定期更新統(tǒng)計(jì)數(shù)據(jù)
隨著我們不斷的對表進(jìn)行增刪改操作,表中的數(shù)據(jù)也一直在變化,innodb_table_stats和innodb_index_stats表立的統(tǒng)計(jì)數(shù)據(jù)是不是也應(yīng)該跟著變一變了?當(dāng)然要變了,不變的話MySQL查詢優(yōu)化器計(jì)算的成本可就差老遠(yuǎn)了。MySQL的提供了如下兩種更新統(tǒng)計(jì)數(shù)據(jù)的方式:
- 開啟
innodb_stats_auto_recalc
系統(tǒng)變量innodb_stats_auto_recalc決定著服務(wù)器是自動(dòng)重新計(jì)算統(tǒng)計(jì)數(shù)據(jù),它的默認(rèn)值是ON,也就是該功能默認(rèn)是開啟的。每個(gè)表都維護(hù)了一個(gè)變量,該變量記錄著對該表進(jìn)行增刪改的記錄條數(shù),如果發(fā)生變動(dòng)的記錄數(shù)量超過了表大小的10%,并且自動(dòng)重新計(jì)算統(tǒng)計(jì)數(shù)據(jù)的功能是打開的,那么服務(wù)器會(huì)重新進(jìn)行一次統(tǒng)計(jì)數(shù)據(jù)的計(jì)算,并且更新innodb_table_stats和innodb_index_stats表。不過自動(dòng)重新計(jì)算統(tǒng)計(jì)數(shù)據(jù)的過程是異步發(fā)生的,也就是即使表中變動(dòng)的記錄數(shù)超過了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)是以表為單位來收集和存儲統(tǒng)計(jì)數(shù)據(jù)的,我們也可以單獨(dú)為某個(gè)表設(shè)置是否自動(dòng)重新計(jì)算統(tǒng)計(jì)數(shù)的屬性,設(shè)置方式就是在創(chuàng)建或修改表的時(shí)候通過指定stats_auto_recalc
屬性來指明該表的統(tǒng)計(jì)數(shù)據(jù)存儲方式:
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ù)
如果我們在創(chuàng)建表時(shí)未指定stats_auto_recalc
屬性,那默認(rèn)采用系統(tǒng)變量innodb_stats_auto_recalc
的值作為該屬性的值。
手動(dòng)調(diào)用
analyze table
語句來更新統(tǒng)計(jì)信息如果
innodb_stats_auto_recalc
系統(tǒng)變量的值為OFF的話,我們也可以手動(dòng)調(diào)用analyze table
語句來重新計(jì)算統(tǒng)計(jì)數(shù)據(jù),比如我們可以這樣更新關(guān)于demo8
表的統(tǒng)計(jì)數(shù)據(jù):
mysql> analyze table demo8;
需要注意的是,analyze table
語句會(huì)立即重新計(jì)算統(tǒng)計(jì)數(shù)據(jù),也就是這個(gè)過程是同步的,在表中索引多或者采樣頁面特別多時(shí)這個(gè)過程可能會(huì)特別慢,請不要沒事就運(yùn)行一下analyze table
語句,最好在業(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è)普通的表一樣,我們能對它們做增刪改查操作。這也就意味著我們可以手動(dòng)更新某個(gè)表或者索引的統(tǒng)計(jì)數(shù)據(jù)。比如說我們想把demo8表關(guān)于行數(shù)的統(tǒng)計(jì)數(shù)據(jù)更改一下可以這么做:
步驟一: 更新demo8表
update innodb_table_stats set n_rows = 1 where table_name = 'demo8';
步驟二: 讓MySQL查詢優(yōu)化器重新加載我們更改過的數(shù)據(jù)
更新完innodb_table_stats
只是單純的修改了一個(gè)表的數(shù)據(jù),需要讓MySQL查詢優(yōu)化器重新加載我們更改過的數(shù)據(jù),運(yùn)行下邊的命令就可以了:
flush table demo8;
之后我們使用show table status
語句查看表的統(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ù)采樣的頁面數(shù)量是由innodb_stats_transient_sample_pages控制的,這個(gè)系統(tǒng)變量的默認(rèn)值是8。另外,由于非永久性的統(tǒng)計(jì)數(shù)據(jù)經(jīng)常更新,所以導(dǎo)致MySQL查詢優(yōu)化器計(jì)算查詢成本的時(shí)候依賴的是經(jīng)常變化的統(tǒng)計(jì)數(shù)據(jù),也就會(huì)生成經(jīng)常變化的執(zhí)行計(jì)劃,這個(gè)可能讓大家有些懵逼。不過最近的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ù)對于MySQL查詢優(yōu)化器十分重要,因?yàn)橥ㄟ^它可以計(jì)算出在索引列中平均一個(gè)值重復(fù)多少行,它的應(yīng)用場景主要有兩個(gè):
- 單表查詢中單點(diǎn)區(qū)間太多,比如說這樣: 當(dāng)IN里的參數(shù)數(shù)量過多時(shí),采用index dive的方式直接訪問B+樹索引去統(tǒng)計(jì)每個(gè)單點(diǎn)區(qū)間對應(yīng)的記錄的數(shù)量就太耗費(fèi)性能了,所以直接依賴統(tǒng)計(jì)數(shù)據(jù)中的平均一個(gè)值重復(fù)多少行來計(jì)算單點(diǎn)區(qū)間對應(yīng)的記錄數(shù)量。
select * from tbl_name where key in ('xx1', 'xx2', ..., 'xxn');
- 連接查詢時(shí),如果有涉及兩個(gè)表的等值匹配連接條件,該連接條件對應(yīng)的被驅(qū)動(dòng)表中的列又擁有索引時(shí),則可以使用ref訪問方法來對被驅(qū)動(dòng)表進(jìn)行查詢,比如說這樣:
select * from t1 join t2 on t1.column = t2.key where ...;
在真正執(zhí)行對t2表的查詢前,t1.comumn的值是不確定的,所以我們也不能通過index dive的方式直接訪問B+樹索引去統(tǒng)計(jì)每個(gè)單點(diǎn)區(qū)間對應(yīng)的記錄的數(shù)量,所以也只能依賴統(tǒng)計(jì)數(shù)據(jù)中的平均一個(gè)值重復(fù)多少次來計(jì)算單點(diǎn)區(qū)間對應(yīng)的記錄數(shù)量。
在統(tǒng)計(jì)索引列不重復(fù)的值的數(shù)量時(shí),有一個(gè)比較煩的問題就是索引列中出現(xiàn)NULL值怎么辦,比如說某個(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ú)一無二的,也就是說統(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ù)上就是代表沒有,所有的NULL值代表的意義是一樣的,所以col列不重復(fù)的值的數(shù)量就是:3(分別是1、2、NULL這三個(gè)值)。
- 有的人認(rèn)為這NULL完全沒有意義嘛,所以在統(tǒng)計(jì)索引列不重復(fù)的值的數(shù)量時(shí)壓根不能把它們算進(jìn)來,所以col列不重復(fù)的值的數(shù)量就是:2(分別是1、2這兩個(gè)值)。
MySQL提供了一個(gè)名為innodb_stats_method
的系統(tǒng)變量,相當(dāng)于在計(jì)算某個(gè)索引列不重復(fù)值的數(shù)量時(shí)如何對待NULL值這個(gè)鍋甩給了用戶,這個(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ù)特別多,所以傾向于不使用索引進(jìn)行訪問。
- nulls_unequal:認(rèn)為所有NULL值都是不相等的。
- 如果某個(gè)索引列中NULL值特別多的話,這種統(tǒng)計(jì)?式會(huì)讓優(yōu)化器認(rèn)為某個(gè)列中平均一個(gè)值重復(fù)次數(shù)特別少,所以傾向于使用索引進(jìn)行訪問。
- nulls_ignored:直接把NULL值忽略掉。
反正這個(gè)鍋是甩給用戶了,當(dāng)你選定了innodb_stats_method
值之后,優(yōu)化器即使選擇了不是最優(yōu)的執(zhí)行計(jì)劃,那也跟MySQL沒關(guān)系了哈~ 當(dāng)然對于用戶的我們來說,最好不在索引列中存放NULL值才是正解。
總結(jié)
InnoDB以表為單位來收集統(tǒng)計(jì)數(shù)據(jù)
,這些統(tǒng)計(jì)數(shù)據(jù)可以是基于磁盤的永久性統(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ù)的采樣
頁面數(shù)量;innodb_stats_transient_sample_pages
控制著非永久性統(tǒng)計(jì)數(shù)據(jù)的采樣頁面數(shù)量;innodb_stats_auto_recalc
控制著是否自動(dòng)重新計(jì)算統(tǒng)計(jì)數(shù)據(jù)。- 我們可以針對某個(gè)具體的表,在創(chuàng)建和修改表時(shí)通過指定stats_persistent、stats_auto_recalc、stats_sample_pages的值來控制相關(guān)統(tǒng)計(jì)數(shù)據(jù)屬性。
- innodb_stats_method決定著在統(tǒng)計(jì)某個(gè)索引列不重復(fù)值的數(shù)量時(shí)如何對待NULL值
以上就是一文帶你了解MySQL之InnoDB統(tǒng)計(jì)數(shù)據(jù)是如何收集的的詳細(xì)內(nèi)容,更多關(guān)于MySQL InnoDB統(tǒng)計(jì)數(shù)據(jù)收集的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
MySql 字符串中提取數(shù)字的實(shí)現(xiàn)示例
在MySQL中,有時(shí)需要從字符串中提取數(shù)字,本文就來介紹一下MySql 字符串中提取數(shù)字的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09MySQL數(shù)字的取整、四舍五入、保留n位小數(shù)方式
這篇文章主要介紹了MySQL數(shù)字的取整、四舍五入、保留n位小數(shù)方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01CentOS 7 中以命令行方式安裝 MySQL 5.7.11 for Linux Generic 二進(jìn)制版本教程詳解
MySQL 目前的最新版本是 5.7.11,在 Linux 下提供特定發(fā)行版安裝包(如 .rpm)以及二進(jìn)制通用版安裝包(.tar.gz)。這篇文章主要介紹了CentOS 7 中以命令行方式安裝 MySQL 5.7.11 for Linux Generic 二進(jìn)制版本教程詳解的相關(guān)資料,需要的朋友可以參考下2016-10-10mysql使用報(bào)錯(cuò)1142(42000)的問題及解決
這篇文章主要介紹了mysql使用報(bào)錯(cuò)1142(42000)的問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08MySQL5.6 Replication主從復(fù)制(讀寫分離) 配置完整版
這篇文章主要介紹了MySQL5.6 Replication主從復(fù)制(讀寫分離) 配置完整版,需要的朋友可以參考下2016-04-04MySQL讀寫分離的項(xiàng)目時(shí)間實(shí)踐
本文主要介紹了MySQL數(shù)據(jù)庫的讀寫分離技術(shù),包括一主一從和雙主雙從兩種架構(gòu),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-03-03mysql如何分別按年/月/日/周分組統(tǒng)計(jì)數(shù)據(jù)詳解
我們在用Mysql抽取數(shù)據(jù)時(shí)候,經(jīng)常需要按照天、周、月等不同的粒度對數(shù)據(jù)進(jìn)行分組統(tǒng)計(jì),下面這篇文章主要給大家介紹了關(guān)于mysql如何分別按年/月/日/周分組統(tǒng)計(jì)數(shù)據(jù)的相關(guān)資料,需要的朋友可以參考下2022-12-12MySQL聯(lián)表查詢基本操作之left-join常見的坑
這篇文章主要給大家介紹了關(guān)于MySQL聯(lián)表查詢基本操作之left-join的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用MySQL具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05使用mysql的disctinct group by查詢不重復(fù)記錄
非常不錯(cuò)的方法,用mysql的group by解決不重復(fù)記錄的問題,看來我需要學(xué)習(xí)的地方太多了2008-08-08