MySQL drop/truncatre 大表分析及解决方案

课前知识

1.表空间组成
  表空间是由索引组成,每个索引2个segment(非叶子节点段,叶子节点段);
  segment有多个组组成,每个组256个extent,每个extent 1M;
  extent由页面组成,每个extent有64个页面,每个页面16k;
  页面由一行一行数据组成;
总结:表空间====>段====>组====>簇(区)====>页面====>行====>列。
2.drop表流程
  drop表需要先释放索引树然后再删除表空间

背景

mysql> drop table logdata;
Query OK, 0 rows affected (59.43 sec)
870G的一个冷表drop需要60秒左右,这60秒带来的问题:
  1.普通主从可能会造成磁盘io升高,影响集群响应,延迟增大...
  2.PXC这类集群直接会堵死整个集群

疑问

1.大表清理选择drop还是truncate?
2.drop大表慢的原因?
3.drop表会清理bufferpool中页面吗?
4.关闭AHI能够节省drop表的时间吗?
5.drop期间数据库可以执行DML吗?
6.bufferpool总大小影响drop表时间吗?
7.被删除的表在bufferpool中的大小影响drop表时间吗?

环境

机器硬件:raid10+24CPU+128G内存
MySQL:percona5.6.44
BufferPool 16G:无预热

查看drop表在哪个方法上耗时最长

image.png
image.png

drop表耗时最长的方法及功能如下

耗时1)que_eval_sql(用来释放索引树)
    fseg_free_extent       /*释放索引段时循环调用释放extent,每个extent都会调用一次,每个extent 1M,可以通过ibd文件计算有多少个extent*/
            /*删除自适应hash*/
            if (ahi) {        /*innodb_adaptive_hash_index如果为on会调用,否则跳过,注意有些版本是没有if(ahi)这个判断的,所以关闭AHI是否会生效取决于版本,percona5.6.44是没有的,oracle-mysql5.7.17是有的*/
                for (i = 0; i < FSP_EXTENT_SIZE; i++) {     /*FSP_EXTENT_SIZE为每个区的页面,为64*/
                    if (!xdes_mtr_get_bit(descr, XDES_FREE_BIT, i, mtr)) {
                        /* Drop search system page hash index
                        if the page is found in the pool and
                        is hashed */
                        btr_search_drop_page_hash_when_freed(page_id_t(space,first_page_in_extent + i),page_size);
                    }
                }
            }
            /*释放索引段时循环调用释放extent,每个extent都会调用一次*/
            flst_remove    /*从指定文件段列表(全满区列表,空闲区列表,半满区列表)里面移除该node区*/
                fut_get_ptr
                    buf_page_get_gen(xxx,mode,xxx)  /*mode=BUF_GET=10,bufferpool中不存在会去磁盘获取*/
                        buf_page_hash_get_low(buf_pool, page_id)      /*先从bufferpool中读取描述符页面*/
                        buf_read_page_low(如果在bufferpool中未找到描述符页面则调用这个方法到磁盘上获取)
#自适应hash删除逻辑
   btr_search_drop_page_hash_when_freed(当页面从bufferpool逐出或者释放索引段的时候需要删除bufferpool中对应的AHI)
     block= buf_page_get_gen(xxx,BUF_PEEK_IF_IN_POOL,xxxx)      /*mode=BUF_PEEK_IF_IN_POOL,bufferpool=12中不存在不去磁盘获取*/
        block= buf_page_hash_get_low(buf_pool, page_id)
        if (block)
            btr_search_drop_page_hash_index(block)

耗时2)fil_delete_tablespace(用来释放bufferpool页面、删除.ibd磁盘文件)
    buf_LRU_remove_pages(释放bufferpool页面)
        buf_flush_dirty_pages(移除脏页)
    os_file_delete_func(删除ibd文件)

--------------870G冷表删除慢原因--------------

1.需要释放AHI
2.需要释放extent时加载磁必要的盘页面到bufferpool
3.需要删除.ibd文件

继续深入第一问:删除.ibd文件慢原因

答案

saas盘,磁盘性能太差,删除.ibd需要占用大量时间,做硬链接删除表和手动删除磁盘文件用时:
[root@l-xxxxx.xx.xx /home/q/mysql/multi/3307/f_biddb4.ful.20191118121600/font_search_log]# ln logdata.ibd logdata.ibd_bak
mysql> drop table logdata;
Query OK, 0 rows affected (25.24 sec)
[root@l-xxxxx.xx.xx /home/q/mysql/multi/3307/f_biddb4.ful.20191118121600/font_search_log]# time rm -f logdata.ibd_bak
real    0m29.764s
user    0m0.000s
sys 0m28.475s

继续深入第二问:释放AHI慢原因

答案

870G表对应页面57016320个页面,每个页面都会调用一次,有些版本有if (ahi)这个判断,可以关闭AHI减少这部分时间,有些版本都没有if (ahi)这个判断,所以无论是否关闭AHI都会调用,释放AHI大概思路为,如果页面在bufferpool且被hash则释放该AHI,否则直接返回,源码为
if (ahi) {        /*innodb_adaptive_hash_index如果为on会调用,否则跳过,注意有些版本是没有if(ahi)这个判断的,所以关闭AHI是否会生效取决于版本,percona5.6.44是没有的,oracle-mysql5.7.17是有的*/
    for (i = 0; i < FSP_EXTENT_SIZE; i++) {
        if (!xdes_mtr_get_bit(descr, XDES_FREE_BIT, i, mtr)) {
            /* Drop search system page hash index
            if the page is found in the pool and
            is hashed */
            btr_search_drop_page_hash_when_freed(page_id_t(space,first_page_in_extent + i),page_size);
        }
    }
}

继续深入第三问:释放extent时加载哪些磁盘页面到bufferpool

探索drop表会将哪些页面加载到bufferpool

1)修改storage/innobase/buf/buf0rea.cc源码在buf_read_page_low方法处添加sql_print_information打印被加载到bufferpool中页面对应的space_id(表空间id),及page_no(页面号)
......
    buf_page_t* bpage;
    ulint       wake_later;
    ibool       ignore_nonexistent_pages;
    sql_print_information("===============================buf_read_page_low,table_id:%lu,page_no:%lu",space,offset);
    *err = DB_SUCCESS;
......
2)重启,做硬连接,drop 表
3)收集对应的页面号
[root@l-xxxxx.xx.xx /home/q/mysql/multi/3307/log]# grep 'buf_read_page_low,table_id' mysql-error.log|less
2020-02-05 16:23:53 11264 [Note] ===============================buf_read_page_low,table_id:78,page_no:0
2020-02-05 16:23:53 11264 [Note] ===============================buf_read_page_low,table_id:78,page_no:2
2020-02-05 16:23:53 11264 [Note] ===============================buf_read_page_low,table_id:78,page_no:18808832
2020-02-05 16:23:53 11264 [Note] ===============================buf_read_page_low,table_id:78,page_no:41009152
2020-02-05 16:23:53 11264 [Note] ===============================buf_read_page_low,table_id:78,page_no:35012608
2020-02-05 16:23:53 11264 [Note] ===============================buf_read_page_low,table_id:78,page_no:17989632
2020-02-05 16:23:53 11264 [Note] ===============================buf_read_page_low,table_id:78,page_no:55115776
4)分析这些页面
[root@l-xxxxx.xx.xx /home/q/mysql/multi/3307/log]#  innodb_space --system-space-file ibdata1 --table-name font_search_log/logstat -p  0 page-dump|grep 'type=>'|less
    :type=>:FSP_HDR,(表空间的第1个页面)
[root@l-xxxxx.xx.xx /home/q/mysql/multi/3307/log]#  innodb_space --system-space-file ibdata1 --table-name font_search_log/logstat -p  2 page-dump|grep 'type=>'|less
    :type=>:INODE,(表空间的第3个页面)
[root@l-xxxxx.xx.xx /home/q/mysql/multi/3307/log]#  innodb_space --system-space-file ibdata1 --table-name font_search_log/logstat -p  18808832 page-dump|grep 'type=>'|less
    :type=>:XDES,(表空间每256个区一个组,该组第一个区的第一个页面,用来登记该组256个区的属性)
.........(全部为XDES,共3448个)
表空间第一个组的XES在FSP_HDR里面,所有XES数量对应磁盘文件大小总共为3449个(INODE与FSP_HDR在一个区)共占用3449*256/1024=862.25G,与.ibd文件非常接近

答案

drop表时会将表空间第一个页面,第三个页面,以及每个组(256个extent一个组)的第一个页面加载到bufferpool,数据页和索引页不会加载到bufferpool

研究过程中新发现

1.做硬连接删除表的时候,如果手动先删除索引,然后在删除表,则非常快
2.删除索引的时候,删除第一个索引很慢,后续的索引删除都很快

做硬链接,先删除索引,在drop表测试结果如下

[root@l-xxxxxxx.xxxx.xxxx /home/q/mysql/multi/3307/f_biddb4.ful.20191118121600/font_search_log]# ln logdata.ibd logdata.ibd_bak
mysql> alter table logdata drop index idx_uid;
Query OK, 0 rows affected (20.44 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table logdata drop index idx_create_at;
Query OK, 0 rows affected (0.17 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table logdata drop index idx_rule;
Query OK, 0 rows affected (0.17 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table logdata drop index idx_page;
Query OK, 0 rows affected (0.18 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table logdata drop index idx_urlfrom;
Query OK, 0 rows affected (0.08 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table logdata drop index idx_global_created;
Query OK, 0 rows affected (0.14 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> drop table logdata;
Query OK, 0 rows affected (2.65 sec)

探索为什么drop第一个索引慢,drop后续索引快(索引大小对结果影响不是很大)

1.上面drop表已经知道会有3449个页面加载到bufferpool
2.依次drop索引,然后观察bufferpool数据页面,并统计加载到磁盘的页面号
mysql> alter table logdata drop index idx_uid;
Query OK, 0 rows affected (23.24 sec)
[root@l-xxxxx.xx.xx /home/q/mysql/multi/3307/log]# grep 'buf_read_page_low,table_id' mysql-error.log|wc -l
3418(删除第一个索引就已经和drop整张表载入bufferpool的页面3449数量相近了了)
mysql> alter table logdata drop index idx_create_at;
Query OK, 0 rows affected (0.23 sec)
[root@l-xxxxx.xx.xx /home/q/mysql/multi/3307/log]# grep 'buf_read_page_low,table_id' mysql-error.log|wc -l
3436
mysql> alter table logdata drop index idx_rule;
Query OK, 0 rows affected (0.12 sec)
[root@l-xxxxx.xx.xx /home/q/mysql/multi/3307/log]# grep 'buf_read_page_low,table_id' mysql-error.log|wc -l
3445
mysql> alter table logdata drop index idx_page;
Query OK, 0 rows affected (0.13 sec)
[root@l-xxxxx.xx.xx /home/q/mysql/multi/3307/log]# grep 'buf_read_page_low,table_id' mysql-error.log|wc -l
3448
mysql> alter table logdata drop index idx_urlfrom;
Query OK, 0 rows affected (0.07 sec)
[root@l-xxxxx.xx.xx /home/q/mysql/multi/3307/log]# grep 'buf_read_page_low,table_id' mysql-error.log|wc -l
3449
mysql> alter table logdata drop index idx_global_created;
Query OK, 0 rows affected (1.07 sec)
[root@l-xxxxx.xx.xx /home/q/mysql/multi/3307/log]# grep 'buf_read_page_low,table_id' mysql-error.log|wc -l
3450
[root@l-xxxxx.xx.xx/home/q/mysql/multi/3307/f_biddb4.ful.20191118121600/font_search_log]# ln logdata.ibd logdata.ibd_bak
mysql> drop table logdata;
Query OK, 0 rows affected (4.41 sec)
[root@l-xxxxx.xx.xx /home/q/mysql/multi/3307/log]# grep 'buf_read_page_low,table_id' mysql-error.log|wc -l
3453(新增加的3个是与改系统表有关的3个页面)

-----drop第一个索引慢,drop后续索引快的原因-----

在删除第一个索引的时候就已经将绝大多数的描述符页面(INODE,XDES)加载到bufferpool了,所以后续删除索引及表就变得非常快

最后,此次学习得出的几个结论

一、知道了删除大表耗时的几个位置在drop表时我们就可以对其今进行优化
1.优化删除.ibd慢:做硬连接删除,然后使用Linux truncate命令逐步缩小.ibd_bak文件进行删除
2.优化释放AHI慢:此处暂时没有想到好的方法,网上有些人说临时关闭AHI,这个关闭可以在线通过innodb_adaptive_hash_index=off立即清空AHI,这样就不会清理AHI了,但是所有页面是否要循环取决于版本(percona-5.6.44版本没有if (ahi)这个判断,所以无论是否关闭AHI都会循环调用,oracle-mysql5.7.17这个版本有if (ahi)这个判断,所以关闭AHI是可以生效的,其他版本是否有这个判断,请自行判断),这个参数在线关闭是否会影响线上环境,需要根据各自的业务和环境来进行选择
3.优化释放索引段加载描述符页面到bufferpool慢,还是用SSD来解决吧,普通磁盘性能实在是差差差...
4.drop表会清理bufferpool脏页,但是不会清理bufferpool数据页,所以对于热点表还是先采取rename方式,在进行删除操作
二、删除索引并不会释放表空间,这部分索引只是还给了表的free列表,并没有清理
三、drop表或者删除索引的时候只会将XDES描述符页面(每256个区加载一个XDES页面)加载到bufferpool,在释放extent过程中通过xdes_init方法来重新初始化该XDES描述符内对应区的属性,将其置为干净可用状态,在整个过程中数据和索引页面不会加载到bufferpool
四、drop表期间会持有row_mysql_lock_data_dictionary数据字典锁,这个锁是一个全局锁,对于后续操作数据字典的都会阻塞,例如create、show create、select等操作,被阻塞的SQL状态表现为Opening tables,drop表这个SQL的状态为checking permissions,这个锁会在删除表时获取直到将.ibd文件删除才会用row_mysql_unlock_data_dictionary释放,另外truncate table也会走这个持这个锁的流程
五、看了truncate流程后,对于允许drop或者truncate的表,优选drop,原因如下:
1.truncate table与mysql版本有很大关系,版本不同影响很大,5.6(本人percona-5.6.29) truncate是真正的删除.ibd文件然后重建,5.7(本人percona-5.7.26)是释放所有的索引树然后重用该ibd文件,也就是说5.6可以采用硬连接来消除删除.ibd这段时间,但是5.7不行,因为是重用该.ibd文件,硬连接空间会随着.ibd一同释放
2.truncate table不管5.6还是5.7都会立即清理bufferpool的数据页和脏页,而drop table只立即释放脏页面
3.truncate table 时会扫lru列表中所有的页面,期间会持有全局锁,持锁时间不仅与表大小有关,也与当前数据库lru中数据页面数量有关,lru中页面越多,持锁时间越长,即便是一个空表也会有这个过程导致夯死数据库,lru大小不要与bufferpool大小直接挂钩,bufferpool大未必lru列表大,lru列表是随着数据访问量逐步增加的,所以评估一张表truncate耗时可以依据innodb status中的LRU len长度以及表的.ibd文件来判断。
4.由上可知如果业务允许最好采用drop+create清理表,drop表可以通过硬连接+bufferpool数据页后台清理来降低持有数据字典这个全局锁的时间,进而降低对业务的影响

附一个saas盘、nvme盘drop表对比

image.png

nvme秒删870G文件也与这个磁盘剩余空间充足(5.8T盘),及当时无数据写入有关,实际生产环境可能没这么快,需要考虑SSD写放大的场景

mysql8.0优化:

https://mysqlserverteam.com/a-250x-improvement-to-tablespace-truncation-in-mysql-server-8-0-23/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容