課前知識(shí)
1.表空間組成
表空間是由索引組成,每個(gè)索引2個(gè)segment(非葉子節(jié)點(diǎn)段除盏,葉子節(jié)點(diǎn)段);
segment有多個(gè)組組成叉橱,每個(gè)組256個(gè)extent,每個(gè)extent 1M;
extent由頁(yè)面組成者蠕,每個(gè)extent有64個(gè)頁(yè)面窃祝,每個(gè)頁(yè)面16k;
頁(yè)面由一行一行數(shù)據(jù)組成;
總結(jié):表空間====>段====>組====>簇(區(qū))====>頁(yè)面====>行====>列。
2.drop表流程
drop表需要先釋放索引樹(shù)然后再刪除表空間
背景
mysql> drop table logdata;
Query OK, 0 rows affected (59.43 sec)
870G的一個(gè)冷表drop需要60秒左右踱侣,這60秒帶來(lái)的問(wèn)題:
1.普通主從可能會(huì)造成磁盤io升高粪小,影響集群響應(yīng),延遲增大...
2.PXC這類集群直接會(huì)堵死整個(gè)集群
疑問(wèn)
1.大表清理選擇drop還是truncate?
2.drop大表慢的原因?
3.drop表會(huì)清理bufferpool中頁(yè)面嗎?
4.關(guān)閉AHI能夠節(jié)省drop表的時(shí)間嗎?
5.drop期間數(shù)據(jù)庫(kù)可以執(zhí)行DML嗎抡句?
6.bufferpool總大小影響drop表時(shí)間嗎探膊?
7.被刪除的表在bufferpool中的大小影響drop表時(shí)間嗎?
環(huán)境
機(jī)器硬件:raid10+24CPU+128G內(nèi)存
MySQL:percona5.6.44
BufferPool 16G:無(wú)預(yù)熱
查看drop表在哪個(gè)方法上耗時(shí)最長(zhǎng)
drop表耗時(shí)最長(zhǎng)的方法及功能如下
耗時(shí)1)que_eval_sql(用來(lái)釋放索引樹(shù))
fseg_free_extent /*釋放索引段時(shí)循環(huán)調(diào)用釋放extent,每個(gè)extent都會(huì)調(diào)用一次,每個(gè)extent 1M待榔,可以通過(guò)ibd文件計(jì)算有多少個(gè)extent*/
/*刪除自適應(yīng)hash*/
if (ahi) { /*innodb_adaptive_hash_index如果為on會(huì)調(diào)用逞壁,否則跳過(guò),注意有些版本是沒(méi)有if(ahi)這個(gè)判斷的锐锣,所以關(guān)閉AHI是否會(huì)生效取決于版本盔几,percona5.6.44是沒(méi)有的,oracle-mysql5.7.17是有的*/
for (i = 0; i < FSP_EXTENT_SIZE; i++) { /*FSP_EXTENT_SIZE為每個(gè)區(qū)的頁(yè)面质欲,為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);
}
}
}
/*釋放索引段時(shí)循環(huán)調(diào)用釋放extent,每個(gè)extent都會(huì)調(diào)用一次*/
flst_remove /*從指定文件段列表(全滿區(qū)列表,空閑區(qū)列表,半滿區(qū)列表)里面移除該node區(qū)*/
fut_get_ptr
buf_page_get_gen(xxx,mode,xxx) /*mode=BUF_GET=10,bufferpool中不存在會(huì)去磁盤獲取*/
buf_page_hash_get_low(buf_pool, page_id) /*先從bufferpool中讀取描述符頁(yè)面*/
buf_read_page_low(如果在bufferpool中未找到描述符頁(yè)面則調(diào)用這個(gè)方法到磁盤上獲裙)
#自適應(yīng)hash刪除邏輯
btr_search_drop_page_hash_when_freed(當(dāng)頁(yè)面從bufferpool逐出或者釋放索引段的時(shí)候需要?jiǎng)h除bufferpool中對(duì)應(yīng)的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)
耗時(shí)2)fil_delete_tablespace(用來(lái)釋放bufferpool頁(yè)面、刪除.ibd磁盤文件)
buf_LRU_remove_pages(釋放bufferpool頁(yè)面)
buf_flush_dirty_pages(移除臟頁(yè))
os_file_delete_func(刪除ibd文件)
--------------870G冷表刪除慢原因--------------
1.需要釋放AHI
2.需要釋放extent時(shí)加載磁必要的盤頁(yè)面到bufferpool
3.需要?jiǎng)h除.ibd文件
繼續(xù)深入第一問(wèn):刪除.ibd文件慢原因
答案
saas盤橘茉,磁盤性能太差工腋,刪除.ibd需要占用大量時(shí)間,做硬鏈接刪除表和手動(dòng)刪除磁盤文件用時(shí):
[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
繼續(xù)深入第二問(wèn):釋放AHI慢原因
答案
870G表對(duì)應(yīng)頁(yè)面57016320個(gè)頁(yè)面畅卓,每個(gè)頁(yè)面都會(huì)調(diào)用一次擅腰,有些版本有if (ahi)這個(gè)判斷,可以關(guān)閉AHI減少這部分時(shí)間翁潘,有些版本都沒(méi)有if (ahi)這個(gè)判斷趁冈,所以無(wú)論是否關(guān)閉AHI都會(huì)調(diào)用,釋放AHI大概思路為拜马,如果頁(yè)面在bufferpool且被hash則釋放該AHI渗勘,否則直接返回,源碼為
if (ahi) { /*innodb_adaptive_hash_index如果為on會(huì)調(diào)用,否則跳過(guò)俩莽,注意有些版本是沒(méi)有if(ahi)這個(gè)判斷的旺坠,所以關(guān)閉AHI是否會(huì)生效取決于版本,percona5.6.44是沒(méi)有的扮超,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);
}
}
}
繼續(xù)深入第三問(wèn):釋放extent時(shí)加載哪些磁盤頁(yè)面到bufferpool
探索drop表會(huì)將哪些頁(yè)面加載到bufferpool
1)修改storage/innobase/buf/buf0rea.cc源碼在buf_read_page_low方法處添加sql_print_information打印被加載到bufferpool中頁(yè)面對(duì)應(yīng)的space_id(表空間id),及page_no(頁(yè)面號(hào))
......
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)收集對(duì)應(yīng)的頁(yè)面號(hào)
[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)分析這些頁(yè)面
[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個(gè)頁(yè)面)
[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個(gè)頁(yè)面)
[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個(gè)區(qū)一個(gè)組,該組第一個(gè)區(qū)的第一個(gè)頁(yè)面璧疗,用來(lái)登記該組256個(gè)區(qū)的屬性)
.........(全部為XDES,共3448個(gè))
表空間第一個(gè)組的XES在FSP_HDR里面坯辩,所有XES數(shù)量對(duì)應(yīng)磁盤文件大小總共為3449個(gè)(INODE與FSP_HDR在一個(gè)區(qū))共占用3449*256/1024=862.25G,與.ibd文件非常接近
答案
drop表時(shí)會(huì)將表空間第一個(gè)頁(yè)面崩侠,第三個(gè)頁(yè)面漆魔,以及每個(gè)組(256個(gè)extent一個(gè)組)的第一個(gè)頁(yè)面加載到bufferpool,數(shù)據(jù)頁(yè)和索引頁(yè)不會(huì)加載到bufferpool
研究過(guò)程中新發(fā)現(xiàn)
1.做硬連接刪除表的時(shí)候啦膜,如果手動(dòng)先刪除索引有送,然后在刪除表,則非成遥快
2.刪除索引的時(shí)候雀摘,刪除第一個(gè)索引很慢,后續(xù)的索引刪除都很快
做硬鏈接八拱,先刪除索引阵赠,在drop表測(cè)試結(jié)果如下
[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第一個(gè)索引慢,drop后續(xù)索引快(索引大小對(duì)結(jié)果影響不是很大)
1.上面drop表已經(jīng)知道會(huì)有3449個(gè)頁(yè)面加載到bufferpool
2.依次drop索引肌稻,然后觀察bufferpool數(shù)據(jù)頁(yè)面清蚀,并統(tǒng)計(jì)加載到磁盤的頁(yè)面號(hào)
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(刪除第一個(gè)索引就已經(jīng)和drop整張表載入bufferpool的頁(yè)面3449數(shù)量相近了了)
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個(gè)是與改系統(tǒng)表有關(guān)的3個(gè)頁(yè)面)
-----drop第一個(gè)索引慢,drop后續(xù)索引快的原因-----
在刪除第一個(gè)索引的時(shí)候就已經(jīng)將絕大多數(shù)的描述符頁(yè)面(INODE,XDES)加載到bufferpool了爹谭,所以后續(xù)刪除索引及表就變得非臣闲埃快
最后,此次學(xué)習(xí)得出的幾個(gè)結(jié)論
一诺凡、知道了刪除大表耗時(shí)的幾個(gè)位置在drop表時(shí)我們就可以對(duì)其今進(jìn)行優(yōu)化
1.優(yōu)化刪除.ibd慢:做硬連接刪除东揣,然后使用Linux truncate命令逐步縮小.ibd_bak文件進(jìn)行刪除
2.優(yōu)化釋放AHI慢:此處暫時(shí)沒(méi)有想到好的方法,網(wǎng)上有些人說(shuō)臨時(shí)關(guān)閉AHI腹泌,這個(gè)關(guān)閉可以在線通過(guò)innodb_adaptive_hash_index=off立即清空AHI嘶卧,這樣就不會(huì)清理AHI了,但是所有頁(yè)面是否要循環(huán)取決于版本(percona-5.6.44版本沒(méi)有if (ahi)這個(gè)判斷凉袱,所以無(wú)論是否關(guān)閉AHI都會(huì)循環(huán)調(diào)用,oracle-mysql5.7.17這個(gè)版本有if (ahi)這個(gè)判斷芥吟,所以關(guān)閉AHI是可以生效的,其他版本是否有這個(gè)判斷专甩,請(qǐng)自行判斷)钟鸵,這個(gè)參數(shù)在線關(guān)閉是否會(huì)影響線上環(huán)境,需要根據(jù)各自的業(yè)務(wù)和環(huán)境來(lái)進(jìn)行選擇
3.優(yōu)化釋放索引段加載描述符頁(yè)面到bufferpool慢涤躲,還是用SSD來(lái)解決吧棺耍,普通磁盤性能實(shí)在是差差差...
4.drop表會(huì)清理bufferpool臟頁(yè),但是不會(huì)清理bufferpool數(shù)據(jù)頁(yè)篓叶,所以對(duì)于熱點(diǎn)表還是先采取rename方式烈掠,在進(jìn)行刪除操作
二、刪除索引并不會(huì)釋放表空間缸托,這部分索引只是還給了表的free列表左敌,并沒(méi)有清理
三、drop表或者刪除索引的時(shí)候只會(huì)將XDES描述符頁(yè)面(每256個(gè)區(qū)加載一個(gè)XDES頁(yè)面)加載到bufferpool俐镐,在釋放extent過(guò)程中通過(guò)xdes_init方法來(lái)重新初始化該XDES描述符內(nèi)對(duì)應(yīng)區(qū)的屬性矫限,將其置為干凈可用狀態(tài),在整個(gè)過(guò)程中數(shù)據(jù)和索引頁(yè)面不會(huì)加載到bufferpool
四佩抹、drop表期間會(huì)持有row_mysql_lock_data_dictionary數(shù)據(jù)字典鎖叼风,這個(gè)鎖是一個(gè)全局鎖,對(duì)于后續(xù)操作數(shù)據(jù)字典的都會(huì)阻塞棍苹,例如create无宿、show create、select等操作枢里,被阻塞的SQL狀態(tài)表現(xiàn)為Opening tables孽鸡,drop表這個(gè)SQL的狀態(tài)為checking permissions,這個(gè)鎖會(huì)在刪除表時(shí)獲取直到將.ibd文件刪除才會(huì)用row_mysql_unlock_data_dictionary釋放,另外truncate table也會(huì)走這個(gè)持這個(gè)鎖的流程
五栏豺、看了truncate流程后彬碱,對(duì)于允許drop或者truncate的表,優(yōu)選drop,原因如下:
1.truncate table與mysql版本有很大關(guān)系奥洼,版本不同影響很大,5.6(本人percona-5.6.29) truncate是真正的刪除.ibd文件然后重建巷疼,5.7(本人percona-5.7.26)是釋放所有的索引樹(shù)然后重用該ibd文件,也就是說(shuō)5.6可以采用硬連接來(lái)消除刪除.ibd這段時(shí)間,但是5.7不行灵奖,因?yàn)槭侵赜迷?ibd文件嚼沿,硬連接空間會(huì)隨著.ibd一同釋放
2.truncate table不管5.6還是5.7都會(huì)立即清理bufferpool的數(shù)據(jù)頁(yè)和臟頁(yè),而drop table只立即釋放臟頁(yè)面
3.truncate table 時(shí)會(huì)掃lru列表中所有的頁(yè)面桑寨,期間會(huì)持有全局鎖伏尼,持鎖時(shí)間不僅與表大小有關(guān),也與當(dāng)前數(shù)據(jù)庫(kù)lru中數(shù)據(jù)頁(yè)面數(shù)量有關(guān)尉尾,lru中頁(yè)面越多爆阶,持鎖時(shí)間越長(zhǎng),即便是一個(gè)空表也會(huì)有這個(gè)過(guò)程導(dǎo)致夯死數(shù)據(jù)庫(kù)沙咏,lru大小不要與bufferpool大小直接掛鉤辨图,bufferpool大未必lru列表大,lru列表是隨著數(shù)據(jù)訪問(wèn)量逐步增加的肢藐,所以評(píng)估一張表truncate耗時(shí)可以依據(jù)innodb status中的LRU len長(zhǎng)度以及表的.ibd文件來(lái)判斷故河。
4.由上可知如果業(yè)務(wù)允許最好采用drop+create清理表,drop表可以通過(guò)硬連接+bufferpool數(shù)據(jù)頁(yè)后臺(tái)清理來(lái)降低持有數(shù)據(jù)字典這個(gè)全局鎖的時(shí)間吆豹,進(jìn)而降低對(duì)業(yè)務(wù)的影響
附一個(gè)saas盤鱼的、nvme盤drop表對(duì)比
nvme秒刪870G文件也與這個(gè)磁盤剩余空間充足(5.8T盤)理盆,及當(dāng)時(shí)無(wú)數(shù)據(jù)寫(xiě)入有關(guān),實(shí)際生產(chǎn)環(huán)境可能沒(méi)這么快凑阶,需要考慮SSD寫(xiě)放大的場(chǎng)景
mysql8.0優(yōu)化:
https://mysqlserverteam.com/a-250x-improvement-to-tablespace-truncation-in-mysql-server-8-0-23/