作z者:高鹏文章末尾有他著作的《深入理解MySQL主从原理 32讲》,深入透彻理解MySQL主从,GTID相关技术知识。本案例由徐晨亮提供,并且一起探讨。本文中,FTWRL = “flush table with read lock” 关于常用操作加 MDL LOCK 锁类型参考文章: http://blog.itpub.net/7728585/viewspace-2143093/测试版本 : MySQL 5.7.22
想阅读更多内容请点击订阅专栏
mysql> select Id,State,Info from information_schema.processlist where command<>'sleep';
+----+------------------------------+------------------------------------------------------------------------------------+
| Id | State | Info |
+----+------------------------------+------------------------------------------------------------------------------------+
| 1 | Waiting on empty queue | NULL |
| 18 | Waiting for global read lock | flush table with read lock |
| 3 | User sleep | select sleep(1000) from baguait1 for update |
| 6 | executing | select Id,State,Info from information_schema.processlist where command<>'sleep' |
+----+------------------------------+------------------------------------------------------------------------------------+
mysql> select Id,State,Info from information_schema.processlist where command<>'sleep';
+----+-------------------------+------------------------------------------------------------------------------------+
| Id | State | Info |
+----+-------------------------+------------------------------------------------------------------------------------+
| 1 | Waiting on empty queue | NULL |
| 26 | User sleep | select sleep(1000) from baguait1 |
| 23 | Waiting for table flush | flush table with read lock |
| 6 | executing | select Id,State,Info from information_schema.processlist where command<>'sleep' |
+----+-------------------------+------------------------------------------------------------------------------------+
mysql> select Id,State,Info from information_schema.processlist where command<>'sleep';
+----+-------------------------+------------------------------------------------------------------------------------+
| Id | State | Info |
+----+-------------------------+------------------------------------------------------------------------------------+
| 1 | Waiting on empty queue | NULL |
| 26 | User sleep | select sleep(1000) from baguait1 |
| 27 | executing | select Id,State,Info from information_schema.processlist where command<>'sleep' |
| 6 | Waiting for table flush | select * from testmts.baguait1 limit 1 |
+----+-------------------------+------------------------------------------------------------------------------------+
T@3: | | | | | | | | >evaluate_join_record
T@3: | | | | | | | | | enter: join: 0x7ffee0007350 join_tab index: 0 table: tii cond: 0x0
T@3: | | | | | | | | | counts: evaluate_join_record join->examined_rows++: 1
T@3: | | | | | | | | | >end_send
T@3: | | | | | | | | | | >Query_result_send::send_data
T@3: | | | | | | | | | | | >send_result_set_row
T@3: | | | | | | | | | | | | >THD::enter_cond
T@3: | | | | | | | | | | | | | THD::enter_stage: 'User sleep' /mysqldata/percona-server-locks-detail-5.7.22/sql/item_func.cc:6057
T@3: | | | | | | | | | | | | | >PROFILING::status_change
T@3: | | | | | | | | | | | | | <PROFILING::status_change 384
T@3: | | | | | | | | | | | | <THD::enter_cond 3405
if (thd->global_read_lock.lock_global_read_lock(thd))//加 MDL GLOBAL 级别S锁
return 1; // Killed
if (close_cached_tables(thd, tables, //关闭表操作释放 share 和 cache
((options & REFRESH_FAST) ? FALSE : TRUE),
thd->variables.lock_wait_timeout)) //等待时间受lock_wait_timeout影响
{
/*
NOTE: my_error() has been already called by reopen_tables() within
close_cached_tables().
*/
result= 1;
}
if (thd->global_read_lock.make_global_read_lock_block_commit(thd)) // MDL COMMIT 锁
{
/* Don't leave things in a half-locked state */
thd->global_read_lock.unlock_global_read_lock(thd);
return 1;
}
table cache define:每一个表第一次打开的时候都会建立一个静态的表定义结构内存,当多个会话同时访问同一个表的时候,从这里拷贝成相应的 instance 供会话自己使用。由参数 table_definition_cache 定义大小,由状态值 Open_table_definitions查看当前使用的个数。对应函数 get_table_share。
table cache instance:同上所述,这是会话实际使用的表定义结构是一个instance。由参数 table_open_cache 定义大小,由状态值 Open_tables 查看当前使用的个数。对应函数 open_table_from_share。
| Type of active |
Request | scoped lock |
type | IS(*) IX S X |
---------+------------------+
IS | + + + + |
IX | + + - - |
S | + - + - |
X | + - - - |
for (uint idx=0 ; idx < table_def_cache.records ; idx++)
{
share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx); //寻找整个 table cache shared hash结构
if (share->has_old_version()) //如果版本 和 当前 的 refresh_version 版本不一致
{
found= TRUE;
break; //跳出第一层查找 是否有老版本 存在
}
}
...
if (found)//如果找到老版本,需要等待
{
/*
The method below temporarily unlocks LOCK_open and frees
share's memory.
*/
if (share->wait_for_old_version(thd, &abstime,
MDL_wait_for_subgraph::DEADLOCK_WEIGHT_DDL))
{
mysql_mutex_unlock(&LOCK_open);
result= TRUE;
goto err_with_reopen;
}
}
if (table->s->has_old_version() || table->needs_reopen() ||
table_def_shutdown_in_progress)
{
tc->remove_table(table);//关闭 table cache instance
mysql_mutex_lock(&LOCK_open);
intern_close_table(table);//去掉 table cache define
mysql_mutex_unlock(&LOCK_open);
}
bool MDL_wait::set_status(enum_wait_status status_arg) open_table
{
bool was_occupied= TRUE;
mysql_mutex_lock(&m_LOCK_wait_status);
if (m_wait_status == EMPTY)
{
was_occupied= FALSE;
m_wait_status= status_arg;
mysql_cond_signal(&m_COND_wait_status);//唤醒
}
mysql_mutex_unlock(&m_LOCK_wait_status);//解锁
return was_occupied;
}
if (share->has_old_version())
{
/*
We already have an MDL lock. But we have encountered an old
version of table in the table definition cache which is possible
when someone changes the table version directly in the cache
without acquiring a metadata lock (e.g. this can happen during
"rolling" FLUSH TABLE(S)).
Release our reference to share, wait until old version of
share goes away and then try to get new version of table share.
*/
release_table_share(share);
...
wait_result= tdc_wait_for_old_version(thd, table_list->db,
table_list->table_name,
ot_ctx->get_timeout(),
deadlock_weight);
长时间的 DDL\DML\FOR UPDATE 堵塞 FTWRL ,因为 FTWRL 需要获取 GLOBAL 的 S 锁,而这些语句都会对 GLOBAL 持有 IX(MDL_INTENTION_EXCLUSIVE) 锁,根据兼容矩阵不兼容。等待为:Waiting for global read lock 。本文的案例 1 就是这种情况。
长时间的 select 堵塞 FTWRL , 因为 FTWRL 会释放所有空闲的 table 缓存,如果有占用者占用某些 table 缓存,则会等待占用者自己释放这些 table 缓存。等待为:Waiting for table flush 。本文的案例 2 就是这种情况,会堵塞随后关于本表的任何语句,即便 KILL FTWRL 会话也不行,除非 KILL 掉长时间的 select 操作才行。实际上 flush table 也会存在这种堵塞情况。
长时间的 commit(如大事务提交)也会堵塞 FTWRL ,因为 FTWRL 需要获取 COMMIT 的 S 锁,而 commit 语句会对 commit 持有 IX(MDL_INTENTION_EXCLUSIVE)锁,根据兼容矩阵不兼容。等待为 Waiting for commit lock 。
FTWRL 会堵塞 DDL\DML\FOR UPDATE 操作,堵塞点为 GLOBAL 级别的 S 锁,等待为:Waiting for global read lock 。
FTWRL 会堵塞 commit 操作,堵塞点为 COMMIT 的 S 锁,等待为 Waiting for commit lock 。
FTWRL 不会堵塞 select 操作,因为 select 不会在 GLOBAL 级别上锁。
(1)使用的断点
MDL_context::acquire_lock 获取 DML LOCK
open_table_from_share 获取 table cache instance
alloc_table_share 分配 table define(share)
get_table_share 获取 table define(share)
close_cached_tables flush table 关闭全部 table cache instance 和 table define
reload_acl_and_cache flush with read lock 进行 MDL LOCK 加锁为 GLOBAL TYPE:S ,同时调用 close_cached_tables 同时获取 COMMIT 级别 TYPE S
MDL_wait::set_status 唤醒操作
close_thread_table 占用者判断释放
-
my_hash_delete hash 删除操作,从 table cache instance 和 table define 中释放 table 缓存都是需要调用这个删除操作的。
(gdb) bt
#0 0x00007ffff7bd3a5e in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1 0x000000000192027b in native_cond_timedwait (cond=0x7ffedc007c78, mutex=0x7ffedc007c30, abstime=0x7fffec5bbb90)
at /mysqldata/percona-server-locks-detail-5.7.22/include/thr_cond.h:129
#2 0x00000000019205ea in safe_cond_timedwait (cond=0x7ffedc007c78, mp=0x7ffedc007c08, abstime=0x7fffec5bbb90,
file=0x204cdd0 "/mysqldata/percona-server-locks-detail-5.7.22/sql/mdl.cc", line=1899) at /mysqldata/percona-server-locks-detail-5.7.22/mysys/thr_cond.c:88
#3 0x00000000014b9f21 in my_cond_timedwait (cond=0x7ffedc007c78, mp=0x7ffedc007c08, abstime=0x7fffec5bbb90,
file=0x204cdd0 "/mysqldata/percona-server-locks-detail-5.7.22/sql/mdl.cc", line=1899) at /mysqldata/percona-server-locks-detail-5.7.22/include/thr_cond.h:180
#4 0x00000000014ba484 in inline_mysql_cond_timedwait (that=0x7ffedc007c78, mutex=0x7ffedc007c08, abstime=0x7fffec5bbb90,
src_file=0x204cdd0 "/mysqldata/percona-server-locks-detail-5.7.22/sql/mdl.cc", src_line=1899)
at /mysqldata/percona-server-locks-detail-5.7.22/include/mysql/psi/mysql_thread.h:1229
#5 0x00000000014bb702 in MDL_wait::timed_wait (this=0x7ffedc007c08, owner=0x7ffedc007b70, abs_timeout=0x7fffec5bbb90, set_status_on_timeout=true,
wait_state_name=0x2d897b0) at /mysqldata/percona-server-locks-detail-5.7.22/sql/mdl.cc:1899
#6 0x00000000016cdb30 in TABLE_SHARE::wait_for_old_version (this=0x7ffee0a4fc30, thd=0x7ffedc007b70, abstime=0x7fffec5bbb90, deadlock_weight=100)
at /mysqldata/percona-server-locks-detail-5.7.22/sql/table.cc:4717
#7 0x000000000153829b in close_cached_tables (thd=0x7ffedc007b70, tables=0x0, wait_for_refresh=true, timeout=31536000)
at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:1291
#8 0x00000000016123ec in reload_acl_and_cache (thd=0x7ffedc007b70, options=16388, tables=0x0, write_to_binlog=0x7fffec5bc9dc)
at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_reload.cc:224
#9 0x00000000015cee9c in mysql_execute_command (thd=0x7ffedc007b70, first_level=true) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:4433
#10 0x00000000015d2fde in mysql_parse (thd=0x7ffedc007b70, parser_state=0x7fffec5bd600) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:5901
#11 0x00000000015c6b72 in dispatch_command (thd=0x7ffedc007b70, com_data=0x7fffec5bdd70, command=COM_QUERY)
at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:1490
(3)杀点 FTWRL 会话后其他 select 操作等待栈帧:
#0 MDL_wait::timed_wait (this=0x7ffee8008298, owner=0x7ffee8008200, abs_timeout=0x7fffec58a600, set_status_on_timeout=true, wait_state_name=0x2d897b0)
at /mysqldata/percona-server-locks-detail-5.7.22/sql/mdl.cc:1888
#1 0x00000000016cdb30 in TABLE_SHARE::wait_for_old_version (this=0x7ffee0011620, thd=0x7ffee8008200, abstime=0x7fffec58a600, deadlock_weight=0)
at /mysqldata/percona-server-locks-detail-5.7.22/sql/table.cc:4717
#2 0x000000000153b6ba in tdc_wait_for_old_version (thd=0x7ffee8008200, db=0x7ffee80014a0 "testmts", table_name=0x7ffee80014a8 "tii", wait_timeout=31536000,
deadlock_weight=0) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:2957
#3 0x000000000153ca97 in open_table (thd=0x7ffee8008200, table_list=0x7ffee8001708, ot_ctx=0x7fffec58aab0)
at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:3548
#4 0x000000000153f904 in open_and_process_table (thd=0x7ffee8008200, lex=0x7ffee800a830, tables=0x7ffee8001708, counter=0x7ffee800a8f0, flags=0,
prelocking_strategy=0x7fffec58abe0, has_prelocking_list=false, ot_ctx=0x7fffec58aab0) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:5213
#5 0x0000000001540a58 in open_tables (thd=0x7ffee8008200, start=0x7fffec58aba0, counter=0x7ffee800a8f0, flags=0, prelocking_strategy=0x7fffec58abe0)
at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:5831
#6 0x0000000001541e93 in open_tables_for_query (thd=0x7ffee8008200, tables=0x7ffee8001708, flags=0)
at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:6606
#7 0x00000000015d1dca in execute_sqlcom_select (thd=0x7ffee8008200, all_tables=0x7ffee8001708) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:5416
#8 0x00000000015ca380 in mysql_execute_command (thd=0x7ffee8008200, first_level=true) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:2939
#9 0x00000000015d2fde in mysql_parse (thd=0x7ffee8008200, parser_state=0x7fffec58c600) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:5901
#10 0x00000000015c6b72 in dispatch_command (thd=0x7ffee8008200, com_data=0x7fffec58cd70, command=COM_QUERY)
at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:1490
Breakpoint 3, MDL_wait::set_status (this=0x7ffedc000c78, status_arg=MDL_wait::GRANTED) at /mysqldata/percona-server-locks-detail-5.7.22/sql/mdl.cc:1832
1832 bool was_occupied= TRUE;
(gdb) bt
#0 MDL_wait::set_status (this=0x7ffedc000c78, status_arg=MDL_wait::GRANTED) at /mysqldata/percona-server-locks-detail-5.7.22/sql/mdl.cc:1832
#1 0x00000000016c2483 in free_table_share (share=0x7ffee0011620) at /mysqldata/percona-server-locks-detail-5.7.22/sql/table.cc:607
#2 0x0000000001536a22 in table_def_free_entry (share=0x7ffee0011620) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:524
#3 0x00000000018fd7aa in my_hash_delete (hash=0x2e4cfe0, record=0x7ffee0011620 "\002") at /mysqldata/percona-server-locks-detail-5.7.22/mysys/hash.c:625
#4 0x0000000001537673 in release_table_share (share=0x7ffee0011620) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:949
#5 0x00000000016cad10 in closefrm (table=0x7ffee000f280, free_share=true) at /mysqldata/percona-server-locks-detail-5.7.22/sql/table.cc:3597
#6 0x0000000001537d0e in intern_close_table (table=0x7ffee000f280) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:1109
#7 0x0000000001539054 in close_thread_table (thd=0x7ffee0000c00, table_ptr=0x7ffee0000c68) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:1780
#8 0x00000000015385fe in close_open_tables (thd=0x7ffee0000c00) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:1443
#9 0x0000000001538d4a in close_thread_tables (thd=0x7ffee0000c00) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_base.cc:1722
#10 0x00000000015d19bc in mysql_execute_command (thd=0x7ffee0000c00, first_level=true) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:5307
#11 0x00000000015d2fde in mysql_parse (thd=0x7ffee0000c00, parser_state=0x7fffec5ee600) at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:5901
#12 0x00000000015c6b72 in dispatch_command (thd=0x7ffee0000c00, com_data=0x7fffec5eed70, command=COM_QUERY)
at /mysqldata/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:1490
最后推荐高鹏的专栏《深入理解MySQL主从原理 32讲》,想要透彻了解学习MySQL 主从原理的朋友不容错过。
作者微信:gp_22389860
点击下图小程序订阅
《深入理解MySQL主从原理32讲》专栏
可了解更多八怪技术文章
扫码加入MySQL技术Q群
(群号:793818397)