innodb做table truncate时候,要把属于这个table的表空间文件的所有的页刷盘并从buffer pool中去掉。这个过程是通过遍历每一个buffer pool instance的LRU
链表,然后检查链表中每个页是否属于目标表空间文件,属于的话就evict它,是脏页的话还要先flush它。清理完buffer pool中的页后,再truncate表空间文件。
这样,当buffer pool很大的时候,这个truncate操作会耗时很久,因为要遍历的页的列表太长了。曾经我们在TDSQL 分布式事务处理中,为了定期迅速地清理commit log,把commit log表巧妙地设计成每个小时写一个分区,这样,删掉几个小时之前的分区就简单地truncate那个partition即可,结果实际测试发现buffer pool较大时候,这个truncate非常慢,最终不得不使用了“笨”办法 --- 逐条删除commit log表中的过期行。
而最近,我们又在另外一个地方遇到了类似的问题:mariadb的gtid_slave_pos变量的值,目前是装在mysql.gtid_slave_pos表中的,这是一个innodb表。当用户执行 set global gtid_slave_pos='xxx'时候,mariadb要truncate mysql.gtid_slave_pos表,然后,再插入新值。所以,当用户使用了250GB的buffer pool后,这个过程就很慢了。最终我还是要改成用‘笨’办法,逐行删除mysql.gtid_slave_pos的数据。
至此,mariadb10.0/10.1的内核中,没有使用ha_truncate() 来truncate内部使用的数据表的做法了。 mysql-5.7 没有这么truncate 内部数据表,不过其innodb的truncate的这个问题也是有的。
Innodb的truncate table/partition应该怎么实现才能更快呢? 我想到了这个办法:使用buffer pool的hash table找页。具体操作如下,后面我会这样来改进tdsql的mariadb/mysql 内核:
buffer pool instance的LRU list里面的页,都是可以通过buffer pool instance的hash table 找到的,hash key 就是(table-space-id, page-no), page-no从0开始,直到文件末尾(lseek得到)。依次用目标表空间id与它的每个page-no 生成hash key,然后查找每个buffer pool instance,一定可以找到这个页,然后从所在的buffer pool instance 中删除
foreach page in target-tablespace tts:
hash-key= (tts.id, page.no)
foreach bufferpool-instance bpi:
pg-ptr= find-page-from-buffer-pool-instance-hash-table(bpi, hash-key);
if (pg-ptr)
evict-from-buffer-pool(bpi, pg-ptr);
break;
truncate 表空间必须同步清理buffer pool中的页,是因为这个表还可能重新增长,旧数据不能被使用到或者误当做新数据,因此无法通过异步化缩短时间。
领取专属 10元无门槛券
私享最新 技术干货