MariaDB 10.1.1:整理InnoDB表空间中的未用空间
引言
当你例如删除行时,这些行只是被标记为已删除,而不是真正从索引中物理删除,并且产生的空闲空间也不会返回给操作系统以便后续重用。清理线程(Purge thread)会物理删除索引键和行,但产生的空闲空间仍然不会返回给操作系统,并且此操作可能在页面中留下空洞。如果你的行是变长的,这可能会导致无法将此空闲空间用于新行(如果这些新行比旧行更大)。用户可以使用 OPTIMIZE TABLE 或 ALTER TABLE <table> ENGINE=InnoDB 来重建表。
不幸的是,对存储在共享表空间文件 ibdata1 中的 InnoDB 表运行 OPTIMIZE TABLE 会做两件事
- 使表数据和索引在
ibdata1内部连续
- 使
ibdata1增长,因为连续的数据和索引页面被附加到ibdata1末尾
新的碎片整理功能
在 MariaDB 10.1 中,我们合并了 Facebook 的碎片整理代码,这些代码由 Matt 和 Kakao 的 Seong Uck Lee 为 MariaDB 准备。与 Facebook 的代码和 Matt 的补丁相比,主要的区别在于 MariaDB 决定不引入新的 SQL 关键字,也不改变服务器代码。相反,我们使用现有的 OPTIMIZE TABLE,所有代码更改都在 InnoDB/XtraDB 存储引擎内部。要启用此新功能,你需要向 my.cnf 添加以下内容(此要求是为了保留 OPTIMIZE TABLE 对需要它的用户原有的行为)。
[mysqld] innodb-defragment=1
这个新的碎片整理功能是就地执行的(inplace),因此不会创建新表,也无需将数据从旧表复制到新表。相反,此功能会加载 n 个页面,并尝试移动记录,使页面充满记录,并在操作后释放完全为空的页面。
新的配置变量
- innodb_defragment:启用/禁用 InnoDB 碎片整理。设置为 FALSE 时,所有正在进行的碎片整理将被暂停。新的碎片整理命令将失败。当此变量设置为 TRUE 时,暂停的碎片整理命令将恢复。默认值为 FALSE。
- innodb_defragment_n_pages:合并多个页面进行碎片整理时,一次考虑的页面数量。范围为 2-32,默认为 7。
- innodb_defragment_stats_accuracy:碎片整理统计信息在写入持久存储之前有多少变化。设置为 0 表示禁用碎片整理统计信息跟踪。默认为 0。
- innodb_defragment_fill_factor_n_recs:碎片整理应在页面上留出多少记录的空间。引入此变量以及 innodb_defragment_fill_factor 是为了避免碎片整理将页面填充得过满,从而导致下一次插入在每个页面上都引起页面分裂。哪个变量指示的碎片整理收益更大,就以哪个为准。范围为 1-100,默认为 20。
- innodb_defragment_fill_factor:一个介于 [0.7, 1] 之间的数字,告诉碎片整理应将页面填充到多满。默认为 0.9。低于 0.7 的数字意义不大。引入此变量以及 innodb_defragment_fill_factor_n_recs 是为了避免碎片整理将页面填充得过满,从而导致下一次插入在每个页面上都引起页面分裂。哪个变量指示的碎片整理收益更大,就以哪个为准。
- innodb_defragment_frequency: 每个索引每秒碎片整理的次数不超过此数。这控制了碎片整理线程请求索引 X_LOCK 的次数。碎片整理线程将检查自上次处理此索引以来是否已过去 1/defragment_frequency (秒),如果时间不足,则将索引放回队列。实际频率只能低于给定值。
新的状态变量
- Innodb_defragment_compression_failures:碎片整理重新压缩失败的次数
- Innodb_defragment_failures: 碎片整理失败的次数。
- Innodb_defragment_count: 碎片整理操作的次数。
示例
set @@global.innodb_file_per_table = 1;
set @@global.innodb_defragment_n_pages = 32;
set @@global.innodb_defragment_fill_factor = 0.95;
CREATE TABLE tb_defragment (
pk1 bigint(20) NOT NULL,
pk2 bigint(20) NOT NULL,
fd4 text,
fd5 varchar(50) DEFAULT NULL,
PRIMARY KEY (pk1),
KEY ix1 (pk2)
) ENGINE=InnoDB;
delimiter //;
create procedure innodb_insert_proc (repeat_count int)
begin
declare current_num int;
set current_num = 0;
while current_num < repeat_count do
INSERT INTO tb_defragment VALUES (current_num, 1, REPEAT('Abcdefg', 20), REPEAT('12345',5));
INSERT INTO tb_defragment VALUES (current_num+1, 2, REPEAT('HIJKLM', 20), REPEAT('67890',5));
INSERT INTO tb_defragment VALUES (current_num+2, 3, REPEAT('HIJKLM', 20), REPEAT('67890',5));
INSERT INTO tb_defragment VALUES (current_num+3, 4, REPEAT('HIJKLM', 20), REPEAT('67890',5));
set current_num = current_num + 4;
end while;
end//
delimiter ;//
commit;
set autocommit=0;
call innodb_insert_proc(50000);
commit;
set autocommit=1;
在 CREATE TABLE 和 INSERT 操作后,我们可以从 INFORMATION_SCHEMA 中看到以下内容
select count(*) as Value from information_schema.innodb_buffer_page where table_name like '%tb_defragment%' and index_name = 'PRIMARY';
Value
313
select count(*) as Value from information_schema.innodb_buffer_page where table_name like '%tb_defragment%' and index_name = 'ix1';
Value
72
select count(stat_value) from mysql.innodb_index_stats where table_name like '%tb_defragment%' and stat_name in ('n_pages_freed');
count(stat_value)
0
select count(stat_value) from mysql.innodb_index_stats where table_name like '%tb_defragment%' and stat_name in ('n_page_split');
count(stat_value)
0
select count(stat_value) from mysql.innodb_index_stats where table_name like '%tb_defragment%' and stat_name in ('n_leaf_pages_defrag');
count(stat_value)
0
SELECT table_name, data_free/1024/1024 AS data_free_MB, table_rows FROM information_schema.tables WHERE engine LIKE 'InnoDB' and table_name like '%tb_defragment%';
table_name data_free_MB table_rows
tb_defragment 4.00000000 50051
SELECT table_name, index_name, sum(number_records), sum(data_size) FROM information_schema.innodb_buffer_page where table_name like '%tb_defragment%' and index_name like 'PRIMARY';
table_name index_name sum(number_records) sum(data_size)
`test`.`tb_defragment` PRIMARY 25873 4739939
SELECT table_name, index_name, sum(number_records), sum(data_size) FROM information_schema.innodb_buffer_page where table_name like '%tb_defragment%' and index_name like 'ix1';
table_name index_name sum(number_records) sum(data_size)
`test`.`tb_defragment` ix1 50071 1051775
现在,如果我们删除 3/4 的记录,这将在页面中留下空洞,然后我们优化表以执行碎片整理
delete from tb_defragment where pk2 between 2 and 4; optimize table tb_defragment; Table Op Msg_type Msg_text test.tb_defragment optimize status OK show status like '%innodb_def%'; Variable_name Value Innodb_defragment_compression_failures 0 Innodb_defragment_failures 1 Innodb_defragment_count 4
在此之后,我们可以看到一些页面被释放,一些页面被合并
select count(*) as Value from information_schema.innodb_buffer_page where table_name like '%tb_defragment%' and index_name = 'PRIMARY';
Value
0
select count(*) as Value from information_schema.innodb_buffer_page where table_name like '%tb_defragment%' and index_name = 'ix1';
Value
0
select count(stat_value) from mysql.innodb_index_stats where table_name like '%tb_defragment%' and stat_name in ('n_pages_freed');
count(stat_value)
2
select count(stat_value) from mysql.innodb_index_stats where table_name like '%tb_defragment%' and stat_name in ('n_page_split');
count(stat_value)
2
select count(stat_value) from mysql.innodb_index_stats where table_name like '%tb_defragment%' and stat_name in ('n_leaf_pages_defrag');
count(stat_value)
2
SELECT table_name, data_free/1024/1024 AS data_free_MB, table_rows FROM information_schema.tables WHERE engine LIKE 'InnoDB';
table_name data_free_MB table_rows
innodb_index_stats 0.00000000 8
innodb_table_stats 0.00000000 0
tb_defragment 4.00000000 12431
SELECT table_name, index_name, sum(number_records), sum(data_size) FROM information_schema.innodb_buffer_page where table_name like '%tb_defragment%' and index_name like 'PRIMARY';
table_name index_name sum(number_records) sum(data_size)
`test`.`tb_defragment` PRIMARY 690 102145
SELECT table_name, index_name, sum(number_records), sum(data_size) FROM information_schema.innodb_buffer_page where table_name like '%tb_defragment%' and index_name like 'ix1';
table_name index_name sum(number_records) sum(data_size)
`test`.`tb_defragment` ix1 5295 111263
链接
WebScaleSQL Git 仓库 https://github.com/webscalesql/webscalesql-5.6
使用 innodb_file_per_table: optimize table 会否释放表空间,还是只会进行碎片整理,从而保留未使用的空间?请参阅 https://www.percona.com/blog/2013/09/25/how-to-reclaim-space-in-innodb-when-innodb_file_per_table-is-on/
只会进行碎片整理。
那么,如果启用了此设置,如何在磁盘上回收释放的空间呢,而不是通过 optimize table?
尊敬的开发者们
碎片整理何时会支持每张表?(支持 Innodb 文件每表模式的碎片整理)
我认为示例是错误的。变量名是 innodb_defragment,而不是 innodb-defragment。
错误
[mysqld]
innodb-defragment=1
正确
[mysqld]
innodb_defragment=1
请问有人能更新文档吗,以免其他人复制代码示例时出错?