MySQL 統(tǒng)計(jì)信息不準(zhǔn)確導(dǎo)致文件大小與系統(tǒng)表大小差異大
引言
MySQL 的統(tǒng)計(jì)信息中包括多個(gè)統(tǒng)計(jì)項(xiàng),由于基于采樣計(jì)算,因此存在誤差,最常見的是統(tǒng)計(jì)項(xiàng)【表的行數(shù)】不準(zhǔn)確,可能導(dǎo)致執(zhí)行計(jì)劃選擇錯(cuò)誤。
本文通過分析系統(tǒng)表中表大小與物理文件大小差異較大的現(xiàn)象,定位到原因是統(tǒng)計(jì)項(xiàng)【索引數(shù)據(jù)頁的數(shù)量】不準(zhǔn)確導(dǎo)致,文中對統(tǒng)計(jì)信息相關(guān)的知識點(diǎn)進(jìn)行了介紹。
現(xiàn)象
時(shí)間:20231026
現(xiàn)象:監(jiān)控顯示數(shù)據(jù) 2T,系統(tǒng)表顯示不到 1T,有分區(qū)表,并且有刪除操作
分析
系統(tǒng)表
執(zhí)行以下 SQL 獲取實(shí)例上每個(gè)庫的大小。
select
table_schema as '數(shù)據(jù)庫',
sum(table_rows) as '行數(shù)',
sum(
truncate(data_length / 1024 / 1024 / 1024, 2)
) as '數(shù)據(jù)容量(GB)',
sum(
truncate(index_length / 1024 / 1024 / 1024, 2)
) as '索引容量(GB)',
sum(
truncate(data_free / 1024 / 1024 / 1024, 2)
) as '碎片空間(GB)',
sum(
truncate(
(
data_length + index_length + data_free
)/ 1024 / 1024 / 1024,
2
)
) as '總?cè)萘?GB)'
from
information_schema.tables
group by
table_schema;
主庫,執(zhí)行結(jié)果如下所示。
+--------------------+------------+------------------+------------------+------------------+---------------+
| 數(shù)據(jù)庫 | 行數(shù) | 數(shù)據(jù)容量(GB) | 索引容量(GB) | 碎片空間(GB) | 總?cè)萘?GB) |
+--------------------+------------+------------------+------------------+------------------+---------------+
| information_schema | NULL | 0.00 | 0.00 | 98.60 | 98.60 |
| mysql | 254074 | 0.03 | 0.00 | 0.00 | 0.04 |
| performance_schema | 13328253 | 0.00 | 0.00 | 0.00 | 0.00 |
| sys | 6 | 0.00 | 0.00 | 0.00 | 0.00 |
| tracking_46 | 1903822252 | 185.59 | 88.38 | 4.47 | 280.90 |
| tracking_47 | 1958199360 | 190.14 | 90.70 | 4.56 | 287.67 |
| tracking_detail_46 | 677699672 | 58.23 | 25.56 | 0.59 | 84.68 |
| tracking_detail_47 | 667959727 | 57.23 | 24.87 | 0.60 | 83.00 |
+--------------------+------------+------------------+------------------+------------------+---------------+
8 rows in set (0.26 sec)
從庫,執(zhí)行結(jié)果如下所示。
+--------------------+------------+------------------+------------------+------------------+---------------+
| 數(shù)據(jù)庫 | 行數(shù) | 數(shù)據(jù)容量(GB) | 索引容量(GB) | 碎片空間(GB) | 總?cè)萘?GB) |
+--------------------+------------+------------------+------------------+------------------+---------------+
| information_schema | NULL | 0.00 | 0.00 | 0.00 | 0.00 |
| mysql | 146406 | 0.06 | 0.00 | 0.00 | 0.07 |
| performance_schema | 1519229 | 0.00 | 0.00 | 0.00 | 0.00 |
| sys | 6 | 0.00 | 0.00 | 0.00 | 0.00 |
| tracking_46 | 3006864458 | 309.54 | 144.75 | 3.46 | 460.18 |
| tracking_47 | 3006350150 | 310.27 | 144.79 | 3.53 | 460.63 |
| tracking_detail_46 | 1511754256 | 112.39 | 56.80 | 0.62 | 170.12 |
| tracking_detail_47 | 1515881664 | 112.67 | 56.89 | 0.57 | 170.43 |
+--------------------+------------+------------------+------------------+------------------+---------------+
8 rows in set (0.31 sec)
其中:
- 主從差異大,從庫大小是主庫的 2 倍左右。
主庫,查看具體表的大小,顯示單表 3G 左右,碎片 20M 左右。
圖片
監(jiān)控顯示用戶數(shù)據(jù) 2T。
圖片
由于監(jiān)控?cái)?shù)據(jù)采集自物理文件的大小,因此表現(xiàn)為物理文件大小和系統(tǒng)表大小不一致。
物理文件
庫級別
# du --max-depth=1 -h .
259G ./tracking_detail_46
100M ./mysql
680K ./sys
16K ./tracking_details_92
259G ./tracking_detail_47
775G ./tracking_46
774G ./tracking_47
16K ./tracking_details_94
16K ./tracking_details_93
16K ./tracking_details_95
4.0K ./tracking
1.1M ./performance_schema
2.2T .
表級別對比結(jié)果,輸出結(jié)果按照差異倒排,其中單位是 byte。
{
"tracking_detail_46.tracking_info_1497":{
"table_size_file":9127723008,
"table_size_sys":2268192768,
"table_size_gap":6859530240
},
"tracking_detail_47.tracking_info_1519":{
"table_size_file":9127723008,
"table_size_sys":2286411776,
"table_size_gap":6841311232
},
"tracking_detail_46.tracking_info_1490":{
"table_size_file":9127723008,
"table_size_sys":2368405504,
"table_size_gap":6759317504
},
...
}
差異最大的表 tracking_detail_46.tracking_info_1497。
查看表結(jié)構(gòu)
mysql> show create table tracking_detail_46.tracking_info_1497 \G
*************************** 1. row ***************************
Table: tracking_info_1497
Create Table: CREATE TABLE `tracking_info_1497` (
`id` bigint(20) NOT NULL COMMENT '主鍵',
`tenant_id` varchar(32) DEFAULT NULL COMMENT '租戶',
`source_ele_id` bigint(20) NOT NULL COMMENT '原始要素表主鍵',
`template_id` int(6) NOT NULL COMMENT '模板編號',
`business_id` varchar(100) NOT NULL COMMENT '業(yè)務(wù)操作單號',
`related_id` varchar(100) DEFAULT NULL COMMENT '關(guān)聯(lián)業(yè)務(wù)單號,例如:包裹號',
`remark` varchar(1024) DEFAULT NULL COMMENT '物流跟蹤話術(shù)',
`is_delete` tinyint(2) NOT NULL DEFAULT '0' COMMENT '刪除標(biāo)識,0有效,1刪除',
`create_time` datetime DEFAULT NULL COMMENT '創(chuàng)建時(shí)間',
`update_time` datetime DEFAULT NULL COMMENT '更新時(shí)間',
`partition_time` datetime NOT NULL COMMENT '分區(qū)時(shí)間',
`ts` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '時(shí)間戳',
PRIMARY KEY (`id`,`partition_time`),
KEY `idx_business_id` (`business_id`,`template_id`,`source_ele_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8 COMMENT='物流跟蹤明細(xì)'
/*!50500 PARTITION BY RANGE COLUMNS(partition_time)
(PARTITION p202305 VALUES LESS THAN ('2023-06-01 00:00:00') ENGINE = InnoDB,
PARTITION p202306 VALUES LESS THAN ('2023-07-01 00:00:00') ENGINE = InnoDB,
PARTITION p202307 VALUES LESS THAN ('2023-08-01 00:00:00') ENGINE = InnoDB,
PARTITION p202308 VALUES LESS THAN ('2023-09-01 00:00:00') ENGINE = InnoDB,
PARTITION p202309 VALUES LESS THAN ('2023-10-01 00:00:00') ENGINE = InnoDB,
PARTITION p202310 VALUES LESS THAN ('2023-11-01 00:00:00') ENGINE = InnoDB,
PARTITION p202311 VALUES LESS THAN ('2023-12-01 00:00:00') ENGINE = InnoDB,
PARTITION p202312 VALUES LESS THAN ('2024-01-01 00:00:00') ENGINE = InnoDB,
PARTITION p202401 VALUES LESS THAN ('2024-02-01 00:00:00') ENGINE = InnoDB,
PARTITION p202402 VALUES LESS THAN ('2024-03-01 00:00:00') ENGINE = InnoDB,
PARTITION p202403 VALUES LESS THAN ('2024-04-01 00:00:00') ENGINE = InnoDB,
PARTITION p202404 VALUES LESS THAN ('2024-05-01 00:00:00') ENGINE = InnoDB,
PARTITION p202405 VALUES LESS THAN ('2024-06-01 00:00:00') ENGINE = InnoDB,
PARTITION p202406 VALUES LESS THAN ('2024-07-01 00:00:00') ENGINE = InnoDB,
PARTITION p202407 VALUES LESS THAN ('2024-08-01 00:00:00') ENGINE = InnoDB,
PARTITION p202408 VALUES LESS THAN ('2024-09-01 00:00:00') ENGINE = InnoDB,
PARTITION p202409 VALUES LESS THAN ('2024-10-01 00:00:00') ENGINE = InnoDB,
PARTITION p202410 VALUES LESS THAN ('2024-11-01 00:00:00') ENGINE = InnoDB,
PARTITION p202411 VALUES LESS THAN ('2024-12-01 00:00:00') ENGINE = InnoDB,
PARTITION p202412 VALUES LESS THAN ('2025-01-01 00:00:00') ENGINE = InnoDB) */
1 row in set (0.00 sec)
其中:
- 分區(qū)表
- 壓縮表
經(jīng)確認(rèn),該實(shí)例上的表都是分區(qū)表,相當(dāng)于分表?分區(qū)。
查看該表對應(yīng)的文件大小,由于是分區(qū)表,因此對應(yīng)多個(gè)文件。
-rw-r----- 1 mysql mysql 1.6G Oct 25 17:45 ./tracking_detail_46/tracking_info_1497#P#p202305.ibd
-rw-r----- 1 mysql mysql 1.7G Oct 26 15:55 ./tracking_detail_46/tracking_info_1497#P#p202306.ibd
-rw-r----- 1 mysql mysql 1.2G Oct 26 11:37 ./tracking_detail_46/tracking_info_1497#P#p202307.ibd
-rw-r----- 1 mysql mysql 1.3G Oct 26 10:48 ./tracking_detail_46/tracking_info_1497#P#p202308.ibd
-rw-r----- 1 mysql mysql 1.5G Oct 26 17:25 ./tracking_detail_46/tracking_info_1497#P#p202309.ibd
-rw-r----- 1 mysql mysql 1.2G Oct 26 18:03 ./tracking_detail_46/tracking_info_1497#P#p202310.ibd
-rw-r----- 1 mysql mysql 64K Jun 20 17:18 ./tracking_detail_46/tracking_info_1497#P#p202311.ibd
-rw-r----- 1 mysql mysql 64K Jun 20 17:18 ./tracking_detail_46/tracking_info_1497#P#p202312.ibd
-rw-r----- 1 mysql mysql 64K Jun 20 17:18 ./tracking_detail_46/tracking_info_1497#P#p202401.ibd
-rw-r----- 1 mysql mysql 64K Jun 20 17:18 ./tracking_detail_46/tracking_info_1497#P#p202402.ibd
-rw-r----- 1 mysql mysql 64K Jun 20 17:18 ./tracking_detail_46/tracking_info_1497#P#p202403.ibd
-rw-r----- 1 mysql mysql 64K Jun 20 17:18 ./tracking_detail_46/tracking_info_1497#P#p202404.ibd
-rw-r----- 1 mysql mysql 64K Jun 20 17:18 ./tracking_detail_46/tracking_info_1497#P#p202405.ibd
-rw-r----- 1 mysql mysql 64K Jun 20 17:18 ./tracking_detail_46/tracking_info_1497#P#p202406.ibd
-rw-r----- 1 mysql mysql 64K Jun 20 17:18 ./tracking_detail_46/tracking_info_1497#P#p202407.ibd
-rw-r----- 1 mysql mysql 64K Jun 20 17:18 ./tracking_detail_46/tracking_info_1497#P#p202408.ibd
-rw-r----- 1 mysql mysql 64K Jun 20 17:18 ./tracking_detail_46/tracking_info_1497#P#p202409.ibd
-rw-r----- 1 mysql mysql 64K Jun 20 17:18 ./tracking_detail_46/tracking_info_1497#P#p202410.ibd
-rw-r----- 1 mysql mysql 64K Jun 20 17:19 ./tracking_detail_46/tracking_info_1497#P#p202411.ibd
-rw-r----- 1 mysql mysql 64K Jun 20 17:21 ./tracking_detail_46/tracking_info_1497#P#p202412.ibd
差異原因分析
下面是 chatgpt 回答的差異原因分析。
MySQL 物理文件大小和系統(tǒng)表大小不一致的原因可能有以下幾點(diǎn):
1、碎片化:當(dāng)我們頻繁地更新或刪除表中的內(nèi)容,會產(chǎn)生碎片,導(dǎo)致物理文件大小大于表的實(shí)際數(shù)據(jù)大小。
2、事務(wù)未提交:在 InnoDB 中,如果存在未提交的事務(wù),那么這部分?jǐn)?shù)據(jù)會被鎖定,導(dǎo)致物理文件的大小不變,而系統(tǒng)表的大小則可能已經(jīng)改變。
3、日志文件:InnoDB 的物理文件大小包括數(shù)據(jù)文件和日志文件兩部分,而系統(tǒng)表的大小只計(jì)算數(shù)據(jù)文件。
4、多版本并發(fā)控制(MVCC):在 InnoDB 中因?yàn)?MVCC 的存在,使得實(shí)際數(shù)據(jù)行存在多個(gè)版本,使得物理文件大小大于系統(tǒng)表大小。
5、索引:索引也會占用一部分的空間,如果表含有索引,那么物理文件的大小就會大于表的數(shù)據(jù)大小。
6、預(yù)留空間:MySQL 在創(chuàng)建表或者擴(kuò)容表的時(shí)候,會預(yù)留一部分空間,這也可能導(dǎo)致物理文件大小和表的大小不一致。
總結(jié)就是數(shù)據(jù)頁未充分利用、刪除數(shù)據(jù)后產(chǎn)生的碎片、索引等占用的空間等原因,都能導(dǎo)致物理文件大小和系統(tǒng)表大小不一致。
由于之前有刪除操作,因此懷疑與數(shù)據(jù)刪除有關(guān)系,分別分析刪除行與刪除表是否會導(dǎo)致該現(xiàn)象。
刪除行有兩種場景:
- 已刪除已提交的行數(shù)據(jù)不會釋放空間,原因是邏輯刪除用于復(fù)用,具體包括行記錄復(fù)用與數(shù)據(jù)頁復(fù)用。這些可以復(fù)用,但是沒有使用的空間稱為碎片,表現(xiàn)為 data_length 減小,data_free 增加,碎片空間可以通過重建表回收。而文中查詢系統(tǒng)表時(shí)包括了碎片;
- 已刪除未提交的行數(shù)據(jù)不會釋放空間,原因是數(shù)據(jù)保存在 undo log 中用于回滾和 MVCC。如果有大量更新操作,將導(dǎo)致 undo log 無法清理,表現(xiàn)為 undo log 文件過大,如果有非常多的長事務(wù),還會表現(xiàn)為 history list length 過大。而該實(shí)例中未發(fā)現(xiàn)大量未提交事務(wù)。
刪除表有一種場景:
- 文件未刪除,原因是系統(tǒng)占用。
因此查看 mysqld 進(jìn)程打開的 deleted 文件。
[root@MSS-pz564g9cew ~]# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Jun20 ? 00:00:03 /usr/sbin/init
root 705 1 0 Jun20 ? 00:00:13 /usr/sbin/sshd -D
root 736 1 0 Jun20 ? 00:00:00 /usr/sbin/rsyslogd -n
root 739 1 0 Jun20 ? 00:00:12 /usr/sbin/crond -n
root 743 1 0 Jun20 ? 00:39:41 /usr/bin/docker-api --config-file=/etc/docker-api.toml
root 797 1 0 Jun20 ? 00:00:00 /export/data/zabbix/sbin/zabbix_agentd
root 799 797 0 Jun20 ? 01:32:42 /export/data/zabbix/sbin/zabbix_agentd: collector [idle 1 sec]
root 800 797 0 Jun20 ? 00:00:21 /export/data/zabbix/sbin/zabbix_agentd: listener #1 [waiting for connection]
root 801 797 0 Jun20 ? 00:00:21 /export/data/zabbix/sbin/zabbix_agentd: listener #2 [waiting for connection]
root 802 797 0 Jun20 ? 00:00:21 /export/data/zabbix/sbin/zabbix_agentd: listener #3 [waiting for connection]
root 803 797 0 Jun20 ? 00:00:21 /export/data/zabbix/sbin/zabbix_agentd: listener #4 [waiting for connection]
root 804 797 0 Jun20 ? 00:00:21 /export/data/zabbix/sbin/zabbix_agentd: listener #5 [waiting for connection]
root 805 797 0 Jun20 ? 00:10:06 /export/data/zabbix/sbin/zabbix_agentd: active checks #1 [idle 1 sec]
mysql 3371 1 0 Jun20 ? 00:00:00 /bin/sh /export/servers/mysql/bin/mysqld_safe --defaults-file=/export/servers/mysql/etc/my.cnf
mysql 4628 3371 99 Jun20 ? 165-22:28:24 /export/servers/mysql/bin/mysqld --defaults-file=/export/servers/mysql/etc/my.cnf --basedir=/export/servers/mysql --datadir=/export/data/mysql/data --plugin-dir=/export/servers/mysql/l
root 45907 705 1 16:04 ? 00:00:00 sshd: root@pts/0
root 45909 45907 0 16:04 pts/0 00:00:00 -bash
root 45924 45909 0 16:04 pts/0 00:00:00 ps -ef
root 129505 1 11 Oct20 ? 2-20:31:08 /usr/bin/python /usr/lib/python2.7/site-packages/trove/cmd/trove-guestagent --config-dir=/export/etc/trove/conf.d/
root 129567 1 2 Oct20 ? 16:15:01 /usr/bin/python /usr/lib/python2.7/site-packages/trove/cmd/opentsdb-agent --config-file=/etc/opentsdb_agent/opentsdb_agent.conf
td-agent 129697 1 0 Oct20 ? 00:05:07 /opt/td-agent/embedded/bin/ruby /opt/td-agent/embedded/bin/fluentd --log /export/data/td-agent/td-agent.log --daemon /var/run/td-agent/td-agent.pid --log-rotate-age 2 --log-rotate-size 104
td-agent 129702 129697 0 Oct20 ? 02:28:18 /opt/td-agent/embedded/bin/ruby -Eascii-8bit:ascii-8bit /opt/td-agent/embedded/bin/fluentd --log /export/data/td-agent/td-agent.log --daemon /var/run/td-agent/td-agent.pid --log-rotate-age
[root@MSS-pz564g9cew ~]#
[root@MSS-pz564g9cew ~]#
[root@MSS-pz564g9cew ~]# lsof -p 4628 | grep deleted
mysqld 4628 mysql 5u REG 253,3 225269 2147483908 /export/data/mysql/tmp/ibKCmSCB (deleted)
mysqld 4628 mysql 6u REG 253,3 0 2147483909 /export/data/mysql/tmp/ibb414ul (deleted)
mysqld 4628 mysql 7u REG 253,3 0 2147483910 /export/data/mysql/tmp/ibS2Rhn5 (deleted)
mysqld 4628 mysql 8u REG 253,3 4207 2147483911 /export/data/mysql/tmp/ibSHfCOD (deleted)
mysqld 4628 mysql 13u REG 253,3 0 2147483912 /export/data/mysql/tmp/ibN8igSs (deleted)
其中:
- 未刪除的文件都是 mysqld 進(jìn)程占用的臨時(shí)文件,重啟后可以釋放,但是文件都很小,最大 225269 bytes,因此和已刪除未釋放的文件無關(guān)。
統(tǒng)計(jì)信息
由于該表是分區(qū)表,因此進(jìn)一步查看系統(tǒng)表判斷具體哪些分區(qū)的差異大。
mysql> SELECT PARTITION_NAME,DATA_LENGTH,INDEX_LENGTH,DATA_FREE,TABLE_ROWS
FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'tracking_info_1497';
+----------------+-------------+--------------+-----------+------------+
| PARTITION_NAME | DATA_LENGTH | INDEX_LENGTH | DATA_FREE | TABLE_ROWS |
+----------------+-------------+--------------+-----------+------------+
| p202305 | 553631744 | 193921024 | 0 | 5219137 |
| p202306 | 908558336 | 387973120 | 0 | 8723301 |
| p202307 | 26001408 | 16064512 | 0 | 402100 |
| p202308 | 26001408 | 15007744 | 0 | 376422 |
| p202309 | 34398208 | 20283392 | 0 | 517278 |
| p202310 | 28098560 | 16572416 | 0 | 403775 |
| p202311 | 9199616 | 6053888 | 0 | 143255 |
| p202312 | 8192 | 8192 | 0 | 0 |
| p202401 | 8192 | 8192 | 0 | 0 |
| p202402 | 8192 | 8192 | 0 | 0 |
| p202403 | 8192 | 8192 | 0 | 0 |
| p202404 | 8192 | 8192 | 0 | 0 |
| p202405 | 8192 | 8192 | 0 | 0 |
| p202406 | 8192 | 8192 | 0 | 0 |
| p202407 | 8192 | 8192 | 0 | 0 |
| p202408 | 8192 | 8192 | 0 | 0 |
| p202409 | 8192 | 8192 | 0 | 0 |
| p202410 | 8192 | 8192 | 0 | 0 |
| p202411 | 8192 | 8192 | 0 | 0 |
| p202412 | 8192 | 8192 | 0 | 0 |
+----------------+-------------+--------------+-----------+------------+
20 rows in set (0.00 sec)
其中:
- p202305、p202306 的 DATA_LENGTH 是 p202307、p202308 的 10-20 倍左右;
- p202305、p202306 的 TABLE_ROWS 是 p202307、p202308 的 10-20 倍左右。
查看分區(qū)準(zhǔn)確行數(shù)
mysql> select date_format(partition_time,'%Y-%m') mont ,count(*)
from tracking_detail_46.tracking_info_1497
group by date_format(partition_time,'%Y-%m');
+---------+----------+
| mont | count(*) |
+---------+----------+
| 2023-05 | 10571445 |
| 2023-06 | 13659671 |
| 2023-07 | 10874195 |
| 2023-08 | 12275399 |
| 2023-09 | 13722214 |
| 2023-10 | 13669851 |
| 2023-11 | 10710033 |
+---------+----------+
7 rows in set (2 min 2.82 sec)
其中:
- p202305 與 p202306 的行數(shù)與 p202307、p202308 接近。
表明行數(shù)的統(tǒng)計(jì)信息誤差較大,因此懷疑表大小與行數(shù)類似,也是統(tǒng)計(jì)信息不準(zhǔn)確導(dǎo)致差異大。
首先需要確認(rèn) DATA_LENGTH 的計(jì)算邏輯。
根據(jù)官方文檔,DATA_LENGTH 表示聚簇索引的大小,具體等于數(shù)據(jù)頁的數(shù)量??頁大小。
For InnoDB, DATA_LENGTH is the approximate amount of space allocated for the clustered index, in bytes. Specifically, it is the clustered index size, in pages, multiplied by the InnoDB page size.
因此懷疑數(shù)據(jù)頁的數(shù)量不準(zhǔn)確。
mysql.innodb_index_stats數(shù)據(jù)表中 stat_name 列與 stat_value 分別表示各種類型統(tǒng)計(jì)信息與對應(yīng)的值:
- 如果 stat_name = size,則 stat_value 列顯示索引中的總大?。▎挝?page);
- 如果 stat_name = n_leaf_pages,則 stat_value 列顯示索引中的葉子頁數(shù);
- 如果 stat_name = n_diff_pfx01,則 stat_value 列顯示索引第一列中的不同值的數(shù)量。當(dāng) stat_name = n_diff_pfx02,stat_value 列顯示索引前兩列中的不同值的數(shù)量,依此類推。此外,在stat_name = n_diff_pfxNN 的情況下,stat_description 列顯示了計(jì)算的索引列。
查看mysql.innodb_index_stats表,其中僅查詢主鍵索引的索引數(shù)據(jù)頁的數(shù)量。
mysql> select * from mysql.innodb_index_stats
where database_name='tracking_detail_46' and table_name like 'tracking_info_1497%'
and index_name='PRIMARY' and stat_name = 'size';
+--------------------+------------------------------+------------+---------------------+-----------+------------+-------------+------------------------------+
| database_name | table_name | index_name | last_update | stat_name | stat_value | sample_size | stat_description |
+--------------------+------------------------------+------------+---------------------+-----------+------------+-------------+------------------------------+
| tracking_detail_46 | tracking_info_1497#P#p202305 | PRIMARY | 2023-05-16 11:37:14 | size | 67582 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202306 | PRIMARY | 2023-06-19 19:54:23 | size | 110908 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202307 | PRIMARY | 2023-07-02 07:53:22 | size | 3174 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202308 | PRIMARY | 2023-08-01 23:45:17 | size | 3174 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202309 | PRIMARY | 2023-09-02 03:58:29 | size | 4199 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202310 | PRIMARY | 2023-10-02 05:41:08 | size | 3430 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202311 | PRIMARY | 2023-11-01 04:24:55 | size | 1123 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202312 | PRIMARY | 2023-03-27 20:27:05 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202401 | PRIMARY | 2023-03-27 20:27:05 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202402 | PRIMARY | 2023-03-27 20:27:05 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202403 | PRIMARY | 2023-03-27 20:27:05 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202404 | PRIMARY | 2023-03-27 20:27:05 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202405 | PRIMARY | 2023-03-27 20:27:05 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202406 | PRIMARY | 2023-03-27 20:27:05 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202407 | PRIMARY | 2023-03-27 20:27:05 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202408 | PRIMARY | 2023-03-27 20:27:05 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202409 | PRIMARY | 2023-03-27 20:27:05 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202410 | PRIMARY | 2023-03-27 20:27:05 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202411 | PRIMARY | 2023-03-27 20:27:05 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202412 | PRIMARY | 2023-03-27 20:27:05 | size | 1 | NULL | Number of pages in the index |
+--------------------+------------------------------+------------+---------------------+-----------+------------+-------------+------------------------------+
20 rows in set (0.01 sec)
其中不同分區(qū)對應(yīng)主鍵索引的數(shù)據(jù)頁數(shù)量差異較大。
partition | stat_value |
p202305 | 67582 |
p202306 | 110908 |
p202307 | 3174 |
p202308 | 3174 |
根據(jù)數(shù)據(jù)頁的數(shù)量計(jì)算索引大小,其中由于是壓縮表,因此頁大小等于 8 KB。
mysql> SELECT SUM(stat_value) AS pages, index_name
, SUM(stat_value) * 8 * 1024 AS size
FROM mysql.innodb_index_stats
WHERE table_name LIKE 'tracking_info_1497%'
AND database_name = 'tracking_detail_46'
AND stat_name = 'size'
GROUP BY index_name;
+--------+-----------------+------------+
| pages | index_name | size |
+--------+-----------------+------------+
| 193603 | PRIMARY | 1585995776 |
| 80076 | idx_business_id | 655982592 |
+--------+-----------------+------------+
2 rows in set (0.00 sec)
對比 information_schema.tables 表中記錄的 DATA_LENGTH 與 INDEX_LENGTH,顯示兩者相等,表明索引大小計(jì)算正確。
mysql> select * from information_schema.tables
where table_name = 'tracking_info_1497' AND table_schema = 'tracking_detail_46' \G
*************************** 1. row ***************************
TABLE_CATALOG: def
TABLE_SCHEMA: tracking_detail_46
TABLE_NAME: tracking_info_1497
TABLE_TYPE: BASE TABLE
ENGINE: InnoDB
VERSION: 10
ROW_FORMAT: Compressed
TABLE_ROWS: 15785619
AVG_ROW_LENGTH: 100
DATA_LENGTH: 1585995776
MAX_DATA_LENGTH: 0
INDEX_LENGTH: 655982592
DATA_FREE: 26214400
AUTO_INCREMENT: NULL
CREATE_TIME: 2023-10-11 20:10:00
UPDATE_TIME: 2023-11-16 11:18:15
CHECK_TIME: NULL
TABLE_COLLATION: utf8mb4_general_ci
CHECKSUM: NULL
CREATE_OPTIONS: row_format=COMPRESSED KEY_BLOCK_SIZE=8 partitioned
TABLE_COMMENT: 物流跟蹤明細(xì)
1 row in set (0.01 sec)
更新統(tǒng)計(jì)信息,驗(yàn)證是否是統(tǒng)計(jì)信息導(dǎo)致的差異。
mysql> analyze table tracking_detail_46.tracking_info_1497;
+---------------------------------------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------------------------------------+---------+----------+----------+
| tracking_detail_46.tracking_info_1497 | analyze | status | OK |
+---------------------------------------+---------+----------+----------+
1 row in set (0.31 sec)
重新查看索引的統(tǒng)計(jì)信息
mysql> SELECT PARTITION_NAME,DATA_LENGTH,INDEX_LENGTH,DATA_FREE,TABLE_ROWS
FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 'tracking_info_1497';
+----------------+-------------+--------------+-----------+------------+
| PARTITION_NAME | DATA_LENGTH | INDEX_LENGTH | DATA_FREE | TABLE_ROWS |
+----------------+-------------+--------------+-----------+------------+
| p202305 | 1185398784 | 419889152 | 0 | 11515577 |
| p202306 | 1168072704 | 542638080 | 0 | 12883994 |
| p202307 | 730333184 | 432537600 | 0 | 10481848 |
| p202308 | 847249408 | 488636416 | 0 | 12156027 |
| p202309 | 952107008 | 546308096 | 0 | 14038632 |
| p202310 | 942145536 | 543670272 | 0 | 13551039 |
| p202311 | 728236032 | 425197568 | 0 | 11022861 |
| p202312 | 8192 | 8192 | 0 | 0 |
| p202401 | 8192 | 8192 | 0 | 0 |
| p202402 | 8192 | 8192 | 0 | 0 |
| p202403 | 8192 | 8192 | 0 | 0 |
| p202404 | 8192 | 8192 | 0 | 0 |
| p202405 | 8192 | 8192 | 0 | 0 |
| p202406 | 8192 | 8192 | 0 | 0 |
| p202407 | 8192 | 8192 | 0 | 0 |
| p202408 | 8192 | 8192 | 0 | 0 |
| p202409 | 8192 | 8192 | 0 | 0 |
| p202410 | 8192 | 8192 | 0 | 0 |
| p202411 | 8192 | 8192 | 0 | 0 |
| p202412 | 8192 | 8192 | 0 | 0 |
+----------------+-------------+--------------+-----------+------------+
20 rows in set (0.00 sec)
mysql> select * from mysql.innodb_index_stats
where database_name='tracking_detail_46' and table_name like 'tracking_info_1497%'
and index_name='PRIMARY' and stat_name = 'size';
+--------------------+------------------------------+------------+---------------------+-----------+------------+-------------+------------------------------+
| database_name | table_name | index_name | last_update | stat_name | stat_value | sample_size | stat_description |
+--------------------+------------------------------+------------+---------------------+-----------+------------+-------------+------------------------------+
| tracking_detail_46 | tracking_info_1497#P#p202305 | PRIMARY | 2023-11-16 11:20:12 | size | 144702 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202306 | PRIMARY | 2023-11-16 11:20:12 | size | 142587 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202307 | PRIMARY | 2023-11-16 11:20:12 | size | 89152 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202308 | PRIMARY | 2023-11-16 11:20:12 | size | 103424 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202309 | PRIMARY | 2023-11-16 11:20:12 | size | 116224 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202310 | PRIMARY | 2023-11-16 11:20:12 | size | 115008 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202311 | PRIMARY | 2023-11-16 11:20:12 | size | 88896 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202312 | PRIMARY | 2023-11-16 11:20:12 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202401 | PRIMARY | 2023-11-16 11:20:12 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202402 | PRIMARY | 2023-11-16 11:20:12 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202403 | PRIMARY | 2023-11-16 11:20:12 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202404 | PRIMARY | 2023-11-16 11:20:12 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202405 | PRIMARY | 2023-11-16 11:20:12 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202406 | PRIMARY | 2023-11-16 11:20:12 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202407 | PRIMARY | 2023-11-16 11:20:12 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202408 | PRIMARY | 2023-11-16 11:20:12 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202409 | PRIMARY | 2023-11-16 11:20:12 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202410 | PRIMARY | 2023-11-16 11:20:12 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202411 | PRIMARY | 2023-11-16 11:20:12 | size | 1 | NULL | Number of pages in the index |
| tracking_detail_46 | tracking_info_1497#P#p202412 | PRIMARY | 2023-11-16 11:20:12 | size | 1 | NULL | Number of pages in the index |
+--------------------+------------------------------+------------+---------------------+-----------+------------+-------------+------------------------------+
20 rows in set (0.00 sec)
不同分區(qū)對應(yīng)主鍵索引的數(shù)據(jù)頁數(shù)量接近
partition | stat_value |
p202305 | 144702 |
p202306 | 142587 |
p202307 | 89152 |
p202308 | 103424 |
重新查看索引大小
mysql> SELECT SUM(stat_value) AS pages, index_name
, SUM(stat_value) * 8 * 1024 AS size
FROM mysql.innodb_index_stats
WHERE table_name LIKE 'tracking_info_1497%'
AND database_name = 'tracking_detail_46'
AND stat_name = 'size'
GROUP BY index_name;
+--------+-----------------+------------+
| pages | index_name | size |
+--------+-----------------+------------+
| 800006 | PRIMARY | 6553649152 |
| 414915 | idx_business_id | 3398983680 |
+--------+-----------------+------------+
2 rows in set (0.01 sec)
mysql> select * from information_schema.tables
where table_name = 'tracking_info_1497' AND table_schema = 'tracking_detail_46' \G
*************************** 1. row ***************************
TABLE_CATALOG: def
TABLE_SCHEMA: tracking_detail_46
TABLE_NAME: tracking_info_1497
TABLE_TYPE: BASE TABLE
ENGINE: InnoDB
VERSION: 10
ROW_FORMAT: Compressed
TABLE_ROWS: 85650217
AVG_ROW_LENGTH: 76
DATA_LENGTH: 6553649152
MAX_DATA_LENGTH: 0
INDEX_LENGTH: 3398983680
DATA_FREE: 26214400
AUTO_INCREMENT: NULL
CREATE_TIME: 2023-10-11 20:10:00
UPDATE_TIME: 2023-11-16 11:21:15
CHECK_TIME: NULL
TABLE_COLLATION: utf8mb4_general_ci
CHECKSUM: NULL
CREATE_OPTIONS: row_format=COMPRESSED KEY_BLOCK_SIZE=8 partitioned
TABLE_COMMENT: 物流跟蹤明細(xì)
1 row in set (0.00 sec)
對比大小,顯示該表的差異從 4 倍縮小為 1.09 倍。
mysql> select (6553649152+3398983680)/9127723008;
+------------------------------------+
| (6553649152+3398983680)/9127723008 |
+------------------------------------+
| 1.0904 |
+------------------------------------+
1 row in set (0.00 sec)
mysql> select 9127723008/2268192768;
+-----------------------+
| 9127723008/2268192768 |
+-----------------------+
| 4.0242 |
+-----------------------+
1 row in set (0.00 sec)
表明物理文件的大小準(zhǔn)確,統(tǒng)計(jì)信息不準(zhǔn)確。
知識點(diǎn)
統(tǒng)計(jì)信息
關(guān)于統(tǒng)計(jì)信息,主要存在以下三個(gè)問題:
- 數(shù)據(jù)如何計(jì)算
- 數(shù)據(jù)如何存儲
- 數(shù)據(jù)如何更新
下面分別進(jìn)行簡單介紹。
存儲方式
InnoDB 提供了兩種存儲統(tǒng)計(jì)數(shù)據(jù)的方式:
- 永久性存儲統(tǒng)計(jì)數(shù)據(jù),保存在磁盤上,服務(wù)器重啟后依然存在;
- 非永久性存儲統(tǒng)計(jì)數(shù)據(jù),保存在內(nèi)存中,服務(wù)器關(guān)閉時(shí)清除數(shù)據(jù),重啟后重新收集。
系統(tǒng)參數(shù)用于控制是否永久性存儲統(tǒng)計(jì)數(shù)據(jù),MySQL 5.6 版本之前默認(rèn) OFF,表示保存在內(nèi)存中,自 MySQL 5.6 版本起默認(rèn) ON,表示保存在磁盤中。
mysql> select @@innodb_stats_persistent;
+---------------------------+
| @@innodb_stats_persistent |
+---------------------------+
| 1 |
+---------------------------+
1 row in set (0.00 sec)
其中,永久性存儲具體是保存在以下兩張系統(tǒng)表中:
- innodb_table_stats,保存表的統(tǒng)計(jì)數(shù)據(jù),每一條記錄對應(yīng)一個(gè)表的統(tǒng)計(jì)數(shù)據(jù);
- innodb_index_stats,保存索引的統(tǒng)計(jì)數(shù)據(jù),每一條記錄對應(yīng)一個(gè)索引的第一個(gè)統(tǒng)計(jì)項(xiàng)的統(tǒng)計(jì)數(shù)據(jù)。
兩張表每個(gè)列的用途見下表。
innodb_table_stats 表
字段名 | 描述 |
database_name | 數(shù)據(jù)庫名 |
table_name | 表名 |
last_update | 本條記錄最后更新的時(shí)間 |
n_rows | 表中記錄的條數(shù) |
clustered_index_size | 表的聚簇索引占用的頁面數(shù)量 |
sum_of_other_index_sizes | 表的其他索引占用的頁面數(shù)量 |
其中有兩個(gè)統(tǒng)計(jì)項(xiàng):
- n_rows,表中記錄的條數(shù)
- clustered_index_size & sum_of_other_index_sizes,索引的頁面數(shù)量
innodb_index_stats 表
字段名 | 描述 |
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è)統(tǒng)計(jì)項(xiàng):
- n_leaf_pages,索引的葉子節(jié)點(diǎn)實(shí)際占用多少頁面;
- size,索引總共占用多少頁面(包括已經(jīng)分配給葉子節(jié)點(diǎn)或非葉子節(jié)點(diǎn)段但尚未使用的頁面);
- n_diff_pfxNN,表示對應(yīng)的索引列不重復(fù)的值有多少,其中 NN 可以被替換為 01、02 等。
查詢一張表的統(tǒng)計(jì)數(shù)據(jù)進(jìn)行舉例說明。
mysql> select * from mysql.innodb_table_stats
where database_name='test_zk' and table_name='t3_bak';
+---------------+------------+---------------------+---------+----------------------+--------------------------+
| database_name | table_name | last_update | n_rows | clustered_index_size | sum_of_other_index_sizes |
+---------------+------------+---------------------+---------+----------------------+--------------------------+
| test_zk | t3_bak | 2023-08-08 12:35:47 | 9976096 | 27448 | 13747 |
+---------------+------------+---------------------+---------+----------------------+--------------------------+
1 row in set (0.00 sec)
mysql> select * from mysql.innodb_index_stats
where database_name='test_zk' and table_name='t3_bak';
+---------------+------------+------------+---------------------+--------------+------------+-------------+-----------------------------------+
| database_name | table_name | index_name | last_update | stat_name | stat_value | sample_size | stat_description |
+---------------+------------+------------+---------------------+--------------+------------+-------------+-----------------------------------+
| test_zk | t3_bak | PRIMARY | 2023-08-08 12:35:47 | n_diff_pfx01 | 9976096 | 20 | id |
| test_zk | t3_bak | PRIMARY | 2023-08-08 12:35:47 | n_leaf_pages | 23981 | NULL | Number of leaf pages in the index |
| test_zk | t3_bak | PRIMARY | 2023-08-08 12:35:47 | size | 27448 | NULL | Number of pages in the index |
| test_zk | t3_bak | idx_name_a | 2023-08-08 12:35:47 | n_diff_pfx01 | 1 | 2 | name |
| test_zk | t3_bak | idx_name_a | 2023-08-08 12:35:47 | n_diff_pfx02 | 9988160 | 20 | name,a |
| test_zk | t3_bak | idx_name_a | 2023-08-08 12:35:47 | n_diff_pfx03 | 9988160 | 20 | name,a,id |
| test_zk | t3_bak | idx_name_a | 2023-08-08 12:35:47 | n_leaf_pages | 12005 | NULL | Number of leaf pages in the index |
| test_zk | t3_bak | idx_name_a | 2023-08-08 12:35:47 | size | 13747 | NULL | Number of pages in the index |
+---------------+------------+------------+---------------------+--------------+------------+-------------+-----------------------------------+
8 rows in set (0.00 sec)
去重后共有以下三個(gè)統(tǒng)計(jì)項(xiàng):
- 表中記錄的條數(shù)
- 索引的頁面數(shù)量
- 索引列不重復(fù)的值有多少
下面分別介紹每個(gè)統(tǒng)計(jì)項(xiàng)的計(jì)算方式。
統(tǒng)計(jì)項(xiàng)計(jì)算方式
1)表中記錄的條數(shù)
計(jì)算過程可以簡化為:
- 按照一定的算法從聚簇索引中選取幾個(gè)葉子節(jié)點(diǎn)頁面;
- 統(tǒng)計(jì)每個(gè)頁面中包括的記錄數(shù)量,然后計(jì)算一個(gè)頁面中平均包含的記錄數(shù)量;
- 每個(gè)頁面平均包含的記錄數(shù)量??全部葉子節(jié)點(diǎn)的數(shù)量,結(jié)果就是該表的 n_rows 值。
因此,n_rows 值的精確與否取決于統(tǒng)計(jì)時(shí)采樣的頁面數(shù)量,具體由 innodb_stats_persistent_sample_pages 系統(tǒng)變量控制,默認(rèn) 20。
mysql> select @@innodb_stats_persistent_sample_pages;
+----------------------------------------+
| @@innodb_stats_persistent_sample_pages |
+----------------------------------------+
| 20 |
+----------------------------------------+
1 row in set (0.00 sec)
2)索引的頁面數(shù)量
每個(gè)索引占用兩個(gè)段(segment),一個(gè)葉子節(jié)點(diǎn)段,一個(gè)非葉子節(jié)點(diǎn)段。
因此索引的頁面數(shù)量等于對應(yīng)的葉子節(jié)點(diǎn)段與非葉子節(jié)點(diǎn)段分別占用的頁面數(shù)量之和。
數(shù)據(jù)字典中存在每個(gè)表中各個(gè)索引對應(yīng)的根頁面位置,而根頁面的Page Header中保存葉子節(jié)點(diǎn)段與非葉子節(jié)點(diǎn)段對應(yīng)的Segment header。
圖片
其中:
- root page 的 FSEG Header 中有兩組指針,分別由 number 和 offset 組成,指向了該索引結(jié)構(gòu)(B+ 樹)的 segment;
- FSEG 是區(qū)的一種狀態(tài),表示附屬于某個(gè)段的區(qū)。
段是以區(qū)(extent)為單位申請存儲空間的,對于 16KB 的頁來說,連續(xù)的 64 個(gè)頁就是一個(gè)區(qū),也就是說一個(gè)區(qū)默認(rèn)占用 1MB 空間大小。
區(qū)通過鏈表進(jìn)行管理,鏈表基節(jié)點(diǎn)List Base Node中保存鏈表的頭節(jié)點(diǎn)和尾節(jié)點(diǎn)的指針以及這個(gè)鏈表中包含了多少個(gè)節(jié)點(diǎn)即List Length。
圖片
其中:
- XDES Entry的全稱是Extent Descriptor Entry,每一個(gè)區(qū)都對應(yīng)著一個(gè)XDES Entry結(jié)構(gòu),這個(gè)結(jié)構(gòu)記錄了對應(yīng)的區(qū)的一些屬性。
通過鏈表的List Length字段讀出該段占用的數(shù)量,每個(gè)區(qū)占用 64 個(gè)頁,就可以統(tǒng)計(jì)出整個(gè)段占用的空間,進(jìn)而得到索引的頁面數(shù)量。
3)索引列不重復(fù)的值有多少
計(jì)算過程可以簡化為:
- 按照一定的算法從聚簇索引中選取幾個(gè)葉子節(jié)點(diǎn)頁面;
- 統(tǒng)計(jì)所有頁面中包括的不重復(fù)值的數(shù)量。
其中對于有多個(gè)列的聯(lián)合索引將從前往后依次統(tǒng)計(jì)列的組合的不重復(fù)的值有多少。
mysql> select stat_name,stat_value,sample_size,stat_description
from mysql.innodb_index_stats
where database_name='test_zk' and table_name='t3_bak'
and index_name='idx_name_a' and stat_name like 'n_diff_pfx%';
+--------------+------------+-------------+------------------+
| stat_name | stat_value | sample_size | stat_description |
+--------------+------------+-------------+------------------+
| n_diff_pfx01 | 1 | 2 | name |
| n_diff_pfx02 | 9988160 | 20 | name,a |
| n_diff_pfx03 | 9988160 | 20 | name,a,id |
+--------------+------------+-------------+------------------+
3 rows in set (0.00 sec)
其中:
- 對于普通二級索引,并不能保證索引列值唯一,因此還會統(tǒng)計(jì)二級索引列?主鍵的不重復(fù)的值的數(shù)量;
- 對于主鍵和唯一二級索引,本身保證索引列值唯一,因此不需要統(tǒng)計(jì)索引列?主鍵的不重復(fù)的值的數(shù)量;
- 對于有多個(gè)列的聯(lián)合索引來說,采樣的頁面數(shù)量是:innodb_stats_persistent_sample_pages??索引列的個(gè)數(shù)。
更新方式
更新統(tǒng)計(jì)數(shù)據(jù)的方式分為以下兩種:
- 自動更新,系統(tǒng)參數(shù)innodb_stats_auto_recalc用于控制服務(wù)器是否自動重新計(jì)算統(tǒng)計(jì)數(shù)據(jù),默認(rèn) ON。每個(gè)表都維護(hù)一個(gè)變量,用于記錄對該表進(jìn)行增刪改的記錄條數(shù),當(dāng)發(fā)生變動的記錄數(shù)量超過表大小的 10% 以后,如果開啟自動更新,將自動重新計(jì)算統(tǒng)計(jì)數(shù)據(jù),這個(gè)過程是異步的,因此存在延遲;
- 手動更新,手動調(diào)用analyze table語句時(shí)立即重新計(jì)算統(tǒng)計(jì)數(shù)據(jù),這個(gè)過程是同步的。注意當(dāng)表中索引較多或采樣頁面非常多時(shí),這個(gè)過程可能會比較慢。
實(shí)際上,手動更新還有一種方式,即直接修改 innodb_table_stats 與 innodb_index_stats 數(shù)據(jù)表,然后執(zhí)行flush table命令重新加載更改過的數(shù)據(jù)。但是正常情況下不需要使用這種方式,因此不詳細(xì)介紹。
下面進(jìn)行 ANALYZE TABLE 復(fù)雜度分析,便于理解什么場景下手動調(diào)用analyze table語句會慢。
ANALYZE TABLE 復(fù)雜度分析
執(zhí)行 ANALYZE TABLE 命令的復(fù)雜度取決于以下三個(gè)條件:
- 采樣的頁面數(shù)量,由innodb_stats_persistent_sample_pages系統(tǒng)參數(shù)控制;
- 表中索引列的數(shù)量;
- 分區(qū)的數(shù)量,默認(rèn) 1,表示沒有分區(qū)。
ANALYZE TABLE 復(fù)雜性的近似公式為:
innodb_stats_persistent_sample_pages * 表中索引列的數(shù)量 * 分區(qū)數(shù)
ANALYZE TABLE 復(fù)雜度可以描述為:
O(n_sample
* (n_cols_in_uniq_i
+ n_cols_in_non_uniq_i
+ n_cols_in_pk * (1 + n_non_uniq_i))
* n_part)
其中:
- n_sample 是采樣的頁數(shù)
- n_cols_in_uniq_i 是所有唯一索引中所有列的總數(shù)(不包括主鍵列)
- n_cols_in_non_uniq_i 是所有非唯一索引中所有列的總數(shù)
- n_cols_in_pk 是主鍵中的列數(shù)(如果未定義主鍵,InnoDB 在內(nèi)部創(chuàng)建單列主鍵)
- n_non_uniq_i 是表中非唯一索引的數(shù)量
- n_part 是分區(qū)數(shù)。如果未定義分區(qū),則該表被視為單個(gè)分區(qū)
根據(jù)復(fù)雜度公式可以預(yù)估 ANALYZE TABLE 命令的執(zhí)行用時(shí)。
比如前文中更新統(tǒng)計(jì)信息的表結(jié)構(gòu)。
PRIMARY KEY (`id`,`partition_time`),
KEY `idx_business_id` (`business_id`,`template_id`,`source_ele_id`)
可以確定以下值:
- n_cols_in_uniq_i 是所有唯一索引中所有列的總數(shù)(不包括主鍵列),0
- n_cols_in_non_uniq_i 是所有非唯一索引中所有列的總數(shù),3
- n_cols_in_pk 是主鍵中的列數(shù)(如果未定義主鍵,InnoDB 在內(nèi)部創(chuàng)建單列主鍵),2
- n_non_uniq_i 是表中非唯一索引的數(shù)量,1
- n_part 是分區(qū)數(shù),20
計(jì)算掃描的頁數(shù)等于 2800
mysql> select 20*(0+3+2*(1+1))*20;
+---------------------+
| 20*(0+3+2*(1+1))*20 |
+---------------------+
| 2800 |
+---------------------+
1 row in set (0.00 sec)
執(zhí)行用時(shí) 0.31s
mysql> analyze table tracking_detail_46.tracking_info_1497;
+---------------------------------------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------------------------------------+---------+----------+----------+
| tracking_detail_46.tracking_info_1497 | analyze | status | OK |
+---------------------------------------+---------+----------+----------+
1 row in set (0.31 sec)
結(jié)論
數(shù)據(jù)庫實(shí)例的物理文件的大小是根據(jù)系統(tǒng)表計(jì)算的表大小的兩倍以上,原因是統(tǒng)計(jì)數(shù)據(jù)不準(zhǔn)確,具體是索引數(shù)據(jù)頁的數(shù)量不準(zhǔn)確。
在定位到差異最大的表以后,通過更新統(tǒng)計(jì)信息,將差異從 4 倍縮小為 1.09 倍。
索引的大小等于數(shù)據(jù)頁的數(shù)量??頁大小,其中數(shù)據(jù)頁的數(shù)量依賴統(tǒng)計(jì)數(shù)據(jù),顯然在除了數(shù)據(jù)行以外,數(shù)據(jù)頁的數(shù)量也是一個(gè)重要的統(tǒng)計(jì)項(xiàng)。
統(tǒng)計(jì)數(shù)據(jù)的永久性存儲具體保存在 innodb_table_stats 與 innodb_index_stats 數(shù)據(jù)表中。
其中主要有以下三個(gè)統(tǒng)計(jì)項(xiàng):
- 表中記錄的條數(shù),等于采樣的每個(gè)頁面平均包含的記錄數(shù)量??全部葉子節(jié)點(diǎn)的數(shù)量;
- 索引的頁面數(shù)量,索引的兩個(gè)段分別查看List Length字段讀出該段占用的數(shù)量,每個(gè)區(qū)占用 64 個(gè)頁,就可以統(tǒng)計(jì)出整個(gè)段占用的空間,進(jìn)而得到索引的頁面數(shù)量;
- 索引列不重復(fù)的值有多少,等于采樣的所有頁面中包括的不重復(fù)值的數(shù)量。
參考教程
- MySQL InnoDB配置統(tǒng)計(jì)信息
https://www.cnblogs.com/wanbin/p/9554091.html
- MySQL Document: Estimating ANALYZE TABLE Complexity for InnoDB Tables
https://dev.mysql.com/doc/refman/5.7/en/innodb-analyze-table-complexity.html
- 《MySQL 是怎樣運(yùn)行的》
- MySQL的最深處-磁盤文件結(jié)構(gòu)
https://cloud.tencent.com/developer/article/2043729
- InnoDB : Tablespace Space Management
https://dev.mysql.com/blog-archive/innodb-tablespace-space-management/