MySQL 案例:表空间碎片
原创背景
经常使用 MySQL 的话,会发现 MySQL 数据文件的磁盘空间一般会不停的增长,而且有时候删了数据或者插入一批数据的时候,磁盘空间有时候还会毫无变化。引发这个其妙现象的就是 MySQL 的表空间碎片。
什么是表空间碎片?
表空间碎片指的是表空间中存在碎片,形象一点来比喻的话,就像是一张 A4 纸,“表空间碎片”就像是把这张 A4 纸撕碎,再重新拼起来,各个碎片之间都会有一些缝隙存在,这些缝隙就是“表空间碎片”。重新拼起来的碎片实际上会比完整的 A4 纸大上一圈,这也代表着 表空间容易引发的问题:空间浪费 。
对于背景中描述的现象,可以用一张图来进行解释:
图中的数字代表真实的数据行,圆角矩形代表一个表的表空间。从左往右,第一次操作是删除数据,由于 MySQL 在设计上是不会主动释放空间的,因此当表中的数据行被删除时,虽然数据被“删除”了,但是实际上这部分空间是没有释放的,依旧会被 Table A 占用,因此也就出现了这样子的情景: 删除了日志表的很多数据,但是 MySQL 的磁盘空间并没有降低 。
PS:这种不释放空间的设计多半和惰性删除有关,早期设计数据库时,使用的 IO 设备一般是机械盘,读写性能比 SSD 差很多,所以删除操作一般不会直接触发磁盘上的数据删除。
可以看到数据删除之后,原本连续的空间中出现了两个空白的区域,这种一般就叫做表空间空洞, 空洞太多了就叫做表空间碎片化(对应的是表空间连续) 。这部分的空间虽然不会释放,但是会被标记为可重复利用,参考最右边的表空间示意图(第三个圆角矩形),当新插入数据的时候新数据会重新写入到表空间空洞中,这也代表着: 在大规模删除过数据的表上,写入数据时,表空间可能不会明显增长或者不会增长 。
实际上产生表空间空洞的操作并不只有 delete,update 也会引起这个问题,比如在 varchar 这种变长的字符型列中修改数据,改短一些的时候就会出现非常小的空洞,改长的话就有可能会因为空间不足导致把数据行的一些数据迁移到其他地方去。
怎么查看表空间碎片
MySQL 的系统表记录了表空间的使用情况,可以用如下查询检查:
SELECT CONCAT(table_schema,'.',table_name) AS 'table_name',
table_rows AS 'Number of Rows',
CONCAT(ROUND(data_length/(1024*1024),2),' M') AS 'data_size',
CONCAT(ROUND(index_length/(1024*1024),2),' M') AS 'index_size' ,
CONCAT(ROUND(data_free/(1024*1024),2),' M') AS'data_free',
CONCAT(ROUND(data_free/data_length,2),' %') AS 'data_free_pct',
ENGINE as 'engine'
FROM information_schema.TABLES
WHERE table_schema = 'tablename'
ORDER by data_free desc;
data_free 指表空间碎片的总空间大小,data_free_pct 指这个表的碎片百分比,效果如下:
mysql> SELECT CONCAT(table_schema,'.',table_name) AS 'table_name',
-> table_rows AS 'Number of Rows',
-> CONCAT(ROUND(data_length/(1024*1024),2),' M') AS 'data_size',
-> CONCAT(ROUND(index_length/(1024*1024),2),' M') AS 'index_size' ,
-> CONCAT(ROUND(data_free/(1024*1024),2),' M') AS'data_free',
-> CONCAT(ROUND(data_free/data_length,2),' %') AS 'data_free_pct',
-> ENGINE as 'engine'
-> FROM information_schema.TABLES
-> WHERE table_schema = 'sbtest'
-> ORDER by data_free desc;
+----------------+----------------+-----------+------------+-----------+---------------+--------+
| table_name | Number of Rows | data_size | index_size | data_free | data_free_pct | engine |
+----------------+----------------+-----------+------------+-----------+---------------+--------+
| sbtest.sbtest5 | 0 | 0.02 M | 0.00 M | 44.00 M | 2816.00 % | InnoDB |