最近做drop table操作,24G的sas机器,ibd文件17G,大约需要14S,在此期间mysql(5.1.48)基本hang住。详细了解了一下mysql drop table过程,发现是mysql drop table的逻辑引起,主要有2方面的原因:drop table过程会持有buffer pool mutex,做2次遍历--对于大内存的mysql服务器,会导致mysql hang住;rm ibd文件的过程,会持有LOCK_open mutex,对于大表,也会导致mysql hang住。对于第二个问题,bug#41158提到一个解决办法:对磁盘做碎片整理;drop前对ibd文件做一个硬链接,这样drop的时候,ibd文件就不会被rm掉,LOCK_OPEN持有的时间就会很短,drop完之后再单独rm。
下面详细理一下drop table的过程:
drop table的调用路径如下(5.1.58):
do_command(sql_parse.cc)
->dispatch_command(sql_parse.cc)
->mysql_parse(sql_parse.cc)
->mysql_execute_command(sql_parse.cc)
->mysql_rm_table(sql_table.cc)
->mysql_rm_table_part2(sql_table.cc:2072)(LOCK_open发生在这里)
->ha_delete_table(handler.cc)
->handler::ha_delete_table(handler.cc)
->ha_innodb::delete_table(handler/ha_innodb.cc)(这里开始就是innodb层的实现)
->row_drop_table_for_mysql(row/row0mysql.c)
->fil_delete_tablespace(fil/fil0fil.c)
->buf_LRU_invalidate_tablespace(buf/buf0lru.c)
->buf_LRU_drop_page_hash_for_tablespace(buf/buf0lru.c)(尝试批量删除被drop的space id的hash index entries,这里开始了对buffer pool的遍历)
->os_file_delete(os/os0file.c)(unlink()发生在这里)
unlink是在innodb的os_file_delete函数内调用的,而mysql_rm_table_part2(sql_table.cc:1923)开始持有LOCK_open,直到调用innodb层drop table完成 & mysql层表定义文件(frm和这个表上的trigger)被删除后才释放,这个就是本文一开头说的drop 大表时引起mysql hang住。
接下来看一下2次遍历的问题。fil_delete_tablespace()在unlink ibd文件前清理buffer pool中对应表的block,具体函数是buf_LRU_invalidate_tablespace()。这个函数一开始就调用buf_LRU_drop_page_hash_for_tablespace(),尝试清理哈希索引中的entries(不保证完全清理),这里开始了对buffer pool的第一次遍历(从后往前),遍历的开始就执行buf_pool_mutex_enter(),拿到buf_pool_mutex。处理完hash index之后,buf_LRU_invalidate_tablespace()就开始了对buffer pool的第二次遍历(从后往前),同样的在遍历的整个过程中持有buf_pool_mutex。如果你的buffer pool比较大,2次 buffer pool的遍历自然会花费很久,mysql也会hang的更久。
mysql5.5引入metadata lock,unlimit的时候持有LOCK_open的问题得到解决。同时对2次遍历的问题做了一些改进:每扫描1024个pages后(满足某些条件,bug#64284),会释放buffer pool mutex;将对lru list的第一遍扫描改为对flush list的扫描。
percona也对这个问题做了一些改进。percona引入buf_LRU_mark_space_was_deleted函数来解决这个问题。扫描lru list,对lru list中要被删除的page做个flag;对AHI加一把共享锁,扫描buffer pool,清理要被删除的page;释放AHI上的共享锁。
其实drop table的这个问题完全是设计产生的问题,按照正常的处理逻辑,drop table 完全不需要处理buffer pool,buffer pool中要被删除的block完全可以通过LRU淘汰掉。这个改动会涉及到比较多的地方,看起来mysql还没有这么做的打算。
另外没有外键的情况下,mysql中的truncate是通过drop table + create table 的方式完成的(mysql_truncate->mysql_truncate_by_delete->mysql_delete->ha_delete_all_rows,sql_delete.cc->ha_innodb::ha_delete_all_rows,handler/ha_innodb.cc->row_truncate_table_for_mysql,row/row0mysql.c->fil_discard_tablespace,fil/fil0fil.c->fil_delete_tablespace,fil/fil0fil.c->os_file_delete,os/os0file.c),因此truncate table也会有drop table所带来的问题,所以drop和truncate大表最好都通过硬链接的方式进行。
参考链接:
http://www.mysqlperformanceblog.com/2011/02/03/performance-problem-with-innodb-and-drop-table/
http://bugs.mysql.com/bug.php?id=56332
http://bugs.mysql.com/bug.php?id=51325
http://bugs.mysql.com/bug.php?id=39939
http://bugs.mysql.com/bug.php?id=41158(LOCK_OPEN is hold while unlink())
http://bugs.mysql.com/bug.php?id=56655
http://bugs.mysql.com/bug.php?id=56696(truncate相关)