前言

MySQL5.5版本开始引入了MDL锁用来保护元数据信息,让MySQL能够在并发环境下多DDL、DML同时操作下保持元数据的一致性。本文用MySQL5.7源码分析了常用SQL语句的MDL加锁实现。

MDL锁粒度

MDL_key由namespace、db_name、name组成。

namespace包含:

  • GLOBAL。用于global read lock,例如FLUSH TABLES WITH READ LOCK。

  • TABLESPACE/SCHEMA。用于保护tablespace/schema。

  • FUNCTION/PROCEDURE/TRIGGER/EVENT。用于保护function/procedure/trigger/event。

  • COMMIT。主要用于global read lock后,阻塞事务提交。

  • USER_LEVEL_LOCK。用于user level lock函数的实现,GET_LOCK(str,timeout), RELEASE_LOCK(str)。

  • LOCKING_SERVICE。用于locking service的实现。

MDL锁类型

  • MDL_INTENTION_EXCLUSIVE(IX) 意向排他锁,锁定一个范围,用在GLOBAL/SCHEMA/COMMIT粒度。

  • MDL_SHARED(S) 用在只访问元数据信息,不访问数据。例如CREATE TABLE t LIKE t1;

  • MDL_SHARED_HIGH_PRIO(SH) 也是用于只访问元数据信息,但是优先级比排他锁高,用于访问information_schema的表。例如:select * from information_schema.tables;

  • MDL_SHARED_READ(SR) 访问表结构并且读表数据,例如:SELECT * FROM t1; LOCK TABLE t1 READ LOCAL;

  • MDL_SHARED_WRITE(SW) 访问表结构且写表数据, 例如:INSERT/DELETE/UPDATE t1 … ;SELECT * FROM t1 FOR UPDATE;LOCK TALE t1 WRITE

  • MDL_SHARED_WRITE_LOW_PRIO(SWLP) 优先级低于MDL_SHARED_READ_ONLY。语句INSER/DELETE/UPDATE LOW_PRIORITY t1 …; LOCK TABLE t1 WRITE LOW_PRIORITY。

  • MDL_SHARED_UPGRADABLE(SU) 可升级锁,允许并发update/read表数据。持有该锁可以同时读取表metadata和表数据,但不能修改数据。可以升级到SNW、SNR、X锁。用在alter table的第一阶段,使alter table的时候不阻塞DML,防止其他DDL。

  • MDL_SHARED_READ_ONLY(SRO) 持有该锁可读取表数据,同时阻塞所有表结构和表数据的修改操作,用于LOCK TABLE t1 READ。

  • MDL_SHARED_NO_WRITE(SNW) 持有该锁可以读取表metadata和表数据,同时阻塞所有的表数据修改操作,允许读。可以升级到X锁。用在ALTER TABLE第一阶段,拷贝原始表数据到新表,允许读但不允许更新。

  • MDL_SHARED_NO_READ_WRITE(SNRW) 可升级锁,允许其他连接读取表结构但不可以读取数据,阻塞所有表数据的读写操作,允许INFORMATION_SCHEMA访问和SHOW语句。持有该锁的的连接可以读取表结构,修改和读取表数据。可升级为X锁。使用在LOCK TABLE WRITE语句。

  • MDL_EXCLUSIVE(X) 排他锁,持有该锁连接可以修改表结构和表数据,使用在CREATE/DROP/RENAME/ALTER TABLE 语句。

MDL锁持有时间

  • MDL_STATEMENT 语句中持有,语句结束自动释放

  • MDL_TRANSACTION 事务中持有,事务结束时释放

  • MDL_EXPLICIT 需要显示释放

MDL锁兼容性

Scope锁活跃锁和请求锁兼容性矩阵如下。

  1. | Type of active |
  2. Request | scoped lock |
  3. type | IS(*) IX S X |
  4. ---------+------------------+
  5. IS | + + + + |
  6. IX | + + - - |
  7. S | + - + - |
  8. X | + - - - |
  9. +号表示请求的锁可以满足。
  10. -号表示请求的锁无法满足需要等待。

Scope锁等待锁和请求锁优先级矩阵

  1. | Pending |
  2. Request | scoped lock |
  3. type | IS(*) IX S X |
  4. ---------+-----------------+
  5. IS | + + + + |
  6. IX | + + - - |
  7. S | + + + - |
  8. X | + + + + |
  9. +号表示请求的锁可以满足。
  10. -号表示请求的锁无法满足需要等待。

object上已持有锁和请求锁的兼容性矩阵如下。

  1. Request | Granted requests for lock |
  2. type | S SH SR SW SWLP SU SRO SNW SNRW X |
  3. ----------+---------------------------------------------+
  4. S | + + + + + + + + + - |
  5. SH | + + + + + + + + + - |
  6. SR | + + + + + + + + - - |
  7. SW | + + + + + + - - - - |
  8. SWLP | + + + + + + - - - - |
  9. SU | + + + + + - + - - - |
  10. SRO | + + + - - + + + - - |
  11. SNW | + + + - - - + - - - |
  12. SNRW | + + - - - - - - - - |
  13. X | - - - - - - - - - - |

object上等待锁和请求锁的优先级矩阵如下。

  1. Request | Pending requests for lock |
  2. type | S SH SR SW SWLP SU SRO SNW SNRW X |
  3. ----------+--------------------------------------------+
  4. S | + + + + + + + + + - |
  5. SH | + + + + + + + + + + |
  6. SR | + + + + + + + + - - |
  7. SW | + + + + + + + - - - |
  8. SWLP | + + + + + + - - - - |
  9. SU | + + + + + + + + + - |
  10. SRO | + + + - + + + + - - |
  11. SNW | + + + + + + + + + - |
  12. SNRW | + + + + + + + + + - |
  13. X | + + + + + + + + + + |

常用语句MDL锁加锁分析

使用performance_schema可以辅助分析加锁。利用下面语句打开MDL锁分析,可以看到在只有当前session访问的时候,SELECT语句对metadata_locks表加了TRANSACTION周期的SHARED_READ锁,即锁粒度、时间范围和锁类型分别为:TABLE, TRANSACTION, SHARED_READ,在代码位置sql_parse.cc:5996初始化锁。。后面的锁分析也按照锁粒度-时间范围-锁类型介绍。

  1. UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME ='global_instrumentation';
  2. UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME ='wait/lock/metadata/sql/mdl';
  3. select * from performance_schema.metadata_locks\G
  4. *************************** 1. row ***************************
  5. OBJECT_TYPE: TABLE
  6. OBJECT_SCHEMA: performance_schema
  7. OBJECT_NAME: metadata_locks
  8. OBJECT_INSTANCE_BEGIN: 46995934864720
  9. LOCK_TYPE: SHARED_READ
  10. LOCK_DURATION: TRANSACTION
  11. LOCK_STATUS: GRANTED
  12. SOURCE: sql_parse.cc:5996
  13. OWNER_THREAD_ID: 26
  14. OWNER_EVENT_ID: 163

使用performance_schema很难完整分析语句执行中所有的加锁过程,可以借助gdb分析,在 MDL_context::acquire_lock设置断点。

下面会结合performance_schema和gdb分析常用语句的MDL加锁源码实现。

FLUSH TABLES WITH READ LOCK

语句执行会加锁GLOBAL-EXPLICIT-SHARED和COMMIT-EXPLICIT-SHARED。

  1. select * from performance_schema.metadata_locks\G
  2. *************************** 1. row ***************************
  3. OBJECT_TYPE: GLOBAL
  4. OBJECT_SCHEMA: NULL
  5. OBJECT_NAME: NULL
  6. OBJECT_INSTANCE_BEGIN: 46996001973424
  7. LOCK_TYPE: SHARED
  8. LOCK_DURATION: EXPLICIT
  9. LOCK_STATUS: GRANTED
  10. SOURCE: lock.cc:1110
  11. OWNER_THREAD_ID: 27
  12. OWNER_EVENT_ID: 92
  13. *************************** 2. row ***************************
  14. OBJECT_TYPE: COMMIT
  15. OBJECT_SCHEMA: NULL
  16. OBJECT_NAME: NULL
  17. OBJECT_INSTANCE_BEGIN: 46996001973616
  18. LOCK_TYPE: SHARED
  19. LOCK_DURATION: EXPLICIT
  20. LOCK_STATUS: GRANTED
  21. SOURCE: lock.cc:1194
  22. OWNER_THREAD_ID: 27
  23. OWNER_EVENT_ID: 375

相关源码实现剖析。当FLUSH语句是FLUSH TABLES WITH READ LOCK的时候,lex->type会添加REFRESH_TABLES和REFRESH_READ_LOCK标记,当没有指定表即进入reload_acl_and_cache函数,通过调用lock_global_read_lock和make_global_read_lock_block_commit加对应锁,通过对应的锁来阻止元数据修改和表数据更改。DDL语句执行时会请求GLOBAL的INTENTION_EXCLUSIVE锁,事务提交和外部XA需要记录binlog的语句执行会请求COMMIT的INTENTION_EXCLUSIVE锁。

  1. sql/sql_yacc.yy
  2. flush_options:
  3. table_or_tables
  4. {
  5. Lex->type|= REFRESH_TABLES;
  6. /*
  7. Set type of metadata and table locks for
  8. FLUSH TABLES table_list [WITH READ LOCK].
  9. */
  10. YYPS->m_lock_type= TL_READ_NO_INSERT;
  11. YYPS->m_mdl_type= MDL_SHARED_HIGH_PRIO;
  12. }
  13. opt_table_list {}
  14. opt_flush_lock {}
  15. | flush_options_list
  16. ;
  17. opt_flush_lock:
  18. /* empty */ {}
  19. | WITH READ_SYM LOCK_SYM
  20. {
  21. TABLE_LIST *tables= Lex->query_tables;
  22. Lex->type|= REFRESH_READ_LOCK;
  23. sql/sql_parse.cc
  24. ...
  25. case SQLCOM_FLUSH:
  26. if (first_table && lex->type & REFRESH_READ_LOCK)//当指定表的时候,对指定表加锁。
  27. {
  28. if (flush_tables_with_read_lock(thd, all_tables))
  29. }
  30. ...
  31. if (!reload_acl_and_cache(thd, lex->type, first_table, &write_to_binlog))
  32. sql/sql_reload.cc
  33. reload_acl_and_cache
  34. {
  35. if (options & (REFRESH_TABLES | REFRESH_READ_LOCK))
  36. {
  37. if ((options & REFRESH_READ_LOCK) && thd)
  38. {
  39. ...
  40. if (thd->global_read_lock.lock_global_read_lock(thd))//当未指定表的时候,加全局锁
  41. return 1;
  42. ...
  43. if (thd->global_read_lock.make_global_read_lock_block_commit(thd))//当未指定表的时候,加COMMIT锁
  44. }
  45. //对GLOBAL加EXPLICIT的S锁。
  46. sql/lock.cc
  47. bool Global_read_lock::lock_global_read_lock(THD *thd)
  48. {
  49. ...
  50. MDL_REQUEST_INIT(&mdl_request,
  51. MDL_key::GLOBAL, "", "", MDL_SHARED, MDL_EXPLICIT);
  52. ...
  53. }
  54. //对COMMIT加EXPLICIT的S锁。
  55. bool Global_read_lock::make_global_read_lock_block_commit(THD *thd)
  56. {
  57. ...
  58. MDL_REQUEST_INIT(&mdl_request,
  59. MDL_key::COMMIT, "", "", MDL_SHARED, MDL_EXPLICIT);
  60. ...
  61. }
  62. sql/handler.cc
  63. 事务提交和外部XA事务的commit\rollback\prepare均需要加COMMITIX锁.
  64. int ha_commit_trans(THD *thd, bool all, bool ignore_global_read_lock)
  65. {
  66. ...
  67. if (rw_trans && !ignore_global_read_lock) //对于内部表slave status table的更新可以忽略global read lock
  68. {
  69. MDL_REQUEST_INIT(&mdl_request,
  70. MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE,
  71. MDL_EXPLICIT);
  72. DBUG_PRINT("debug", ("Acquire MDL commit lock"));
  73. if (thd->mdl_context.acquire_lock(&mdl_request,
  74. thd->variables.lock_wait_timeout))
  75. }
  76. ...
  77. }
  78. sql/xa.cc
  79. bool Sql_cmd_xa_commit::trans_xa_commit(THD *thd)
  80. {
  81. ...
  82. MDL_request mdl_request;
  83. MDL_REQUEST_INIT(&mdl_request,
  84. MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE,
  85. MDL_STATEMENT);
  86. if (thd->mdl_context.acquire_lock(&mdl_request,
  87. thd->variables.lock_wait_timeout))
  88. ...
  89. }
  90. bool Sql_cmd_xa_rollback::trans_xa_rollback(THD *thd)
  91. {
  92. ...
  93. MDL_request mdl_request;
  94. MDL_REQUEST_INIT(&mdl_request,
  95. MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE,
  96. MDL_STATEMENT);
  97. }
  98. bool Sql_cmd_xa_prepare::trans_xa_prepare(THD *thd)
  99. {
  100. ...
  101. MDL_request mdl_request;
  102. MDL_REQUEST_INIT(&mdl_request,
  103. MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE,
  104. MDL_STATEMENT);
  105. }
  106. //写入语句的执行和DDL执行需要GLOBAL的IX锁,这与S锁不兼容。
  107. sql/sql_base.cc
  108. bool open_table(THD *thd, TABLE_LIST *table_list, Open_table_context *ot_ctx)
  109. {
  110. if (table_list->mdl_request.is_write_lock_request() &&
  111. {
  112. MDL_request protection_request;
  113. MDL_deadlock_handler mdl_deadlock_handler(ot_ctx);
  114. if (thd->global_read_lock.can_acquire_protection())
  115. DBUG_RETURN(TRUE);
  116. MDL_REQUEST_INIT(&protection_request,
  117. MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
  118. MDL_STATEMENT);
  119. }
  120. }
  121. bool
  122. lock_table_names(THD *thd,
  123. TABLE_LIST *tables_start, TABLE_LIST *tables_end,
  124. ulong lock_wait_timeout, uint flags)
  125. {
  126. if (need_global_read_lock_protection)
  127. {
  128. /*
  129. Protect this statement against concurrent global read lock
  130. by acquiring global intention exclusive lock with statement
  131. duration.
  132. */
  133. if (thd->global_read_lock.can_acquire_protection())
  134. return true;
  135. MDL_REQUEST_INIT(&global_request,
  136. MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
  137. MDL_STATEMENT);
  138. mdl_requests.push_front(&global_request);
  139. }
  140. }

LOCK TABLE t READ [LOCAL]

LOCK TABLE t READ LOCAL会加锁TABLE-TRANSACTION-SHARED_READ。

  1. select * from performance_schema.metadata_locks\G
  2. *************************** 1. row ***************************
  3. OBJECT_TYPE: TABLE
  4. OBJECT_SCHEMA: test
  5. OBJECT_NAME: t
  6. LOCK_TYPE: SHARED_READ
  7. LOCK_DURATION: TRANSACTION
  8. LOCK_STATUS: GRANTED
  9. SOURCE: sql_parse.cc:5996

LOCK TABLE t READ会加锁TABLE-TRANSACTION-SHARED_READ_ONLY。

  1. select * from performance_schema.metadata_locks\G
  2. *************************** 1. row ***************************
  3. OBJECT_TYPE: TABLE
  4. OBJECT_SCHEMA: test
  5. OBJECT_NAME: t
  6. LOCK_TYPE: SHARED_READ_ONLY
  7. LOCK_DURATION: TRANSACTION
  8. LOCK_STATUS: GRANTED
  9. SOURCE: sql_parse.cc:5996

这两个的区别是对于MyISAM引擎,LOCAL方式的加锁与insert写入不冲突,而没有LOCAL的时候SHARED_READ_ONLY会阻塞写入。不过对于InnoDB引擎两种方式是一样的,带有LOCAL的语句执行后面会升级为SHARED_READ_ONLY。

源码分析

  1. table_lock:
  2. table_ident opt_table_alias lock_option
  3. {
  4. thr_lock_type lock_type= (thr_lock_type) $3;
  5. enum_mdl_type mdl_lock_type;
  6. if (lock_type >= TL_WRITE_ALLOW_WRITE)
  7. {
  8. /* LOCK TABLE ... WRITE/LOW_PRIORITY WRITE */
  9. mdl_lock_type= MDL_SHARED_NO_READ_WRITE;
  10. }
  11. else if (lock_type == TL_READ)
  12. {
  13. /* LOCK TABLE ... READ LOCAL */
  14. mdl_lock_type= MDL_SHARED_READ;
  15. }
  16. else
  17. {
  18. /* LOCK TABLE ... READ */
  19. mdl_lock_type= MDL_SHARED_READ_ONLY;
  20. }
  21. if (!Select->add_table_to_list(YYTHD, $1, $2, 0, lock_type,
  22. mdl_lock_type))
  23. MYSQL_YYABORT;
  24. }
  25. lock_option:
  26. READ_SYM { $$= TL_READ_NO_INSERT; }
  27. | WRITE_SYM { $$= TL_WRITE_DEFAULT; }
  28. | LOW_PRIORITY WRITE_SYM
  29. {
  30. $$= TL_WRITE_LOW_PRIORITY;
  31. push_deprecated_warn(YYTHD, "LOW_PRIORITY WRITE", "WRITE");
  32. }
  33. | READ_SYM LOCAL_SYM { $$= TL_READ; }
  34. ;
  35. sql/sql_parse.cc
  36. TABLE_LIST *st_select_lex::add_table_to_list(THD *thd,
  37. Table_ident *table,
  38. LEX_STRING *alias,
  39. ulong table_options,
  40. thr_lock_type lock_type,
  41. enum_mdl_type mdl_type,
  42. List<Index_hint> *index_hints_arg,
  43. List<String> *partition_names,
  44. LEX_STRING *option)
  45. {
  46. // Pure table aliases do not need to be locked:
  47. if (!MY_TEST(table_options & TL_OPTION_ALIAS))
  48. {
  49. MDL_REQUEST_INIT(& ptr->mdl_request,
  50. MDL_key::TABLE, ptr->db, ptr->table_name, mdl_type,
  51. MDL_TRANSACTION);
  52. }
  53. }
  54. //对于Innodb引擎
  55. static bool lock_tables_open_and_lock_tables(THD *thd, TABLE_LIST *tables)
  56. {
  57. ...
  58. else if (table->lock_type == TL_READ &&
  59. ! table->prelocking_placeholder &&
  60. table->table->file->ha_table_flags() & HA_NO_READ_LOCAL_LOCK)
  61. {
  62. /*
  63. In case when LOCK TABLE ... READ LOCAL was issued for table with
  64. storage engine which doesn't support READ LOCAL option and doesn't
  65. use THR_LOCK locks we need to upgrade weak SR metadata lock acquired
  66. in open_tables() to stronger SRO metadata lock.
  67. This is not needed for tables used through stored routines or
  68. triggers as we always acquire SRO (or even stronger SNRW) metadata
  69. lock for them.
  70. */
  71. bool result= thd->mdl_context.upgrade_shared_lock(
  72. table->table->mdl_ticket,
  73. MDL_SHARED_READ_ONLY,
  74. thd->variables.lock_wait_timeout);
  75. ...
  76. }

LOCK TABLE t WITH WRITE

LOCK TABLE t WITH WRITE会加锁:GLOBAL-STATEMENT-INTENTION_EXCLUSIVE,SCHEMA-TRANSACTION-INTENTION_EXCLUSIVE,TABLE-TRANSACTION-SHARED_NO_READ_WRITE。

  1. select OBJECT_TYPE,OBJECT_SCHEMA,OBJECT_NAME,LOCK_TYPE,LOCK_DURATION,SOURCE from performance_schema.metadata_locks\G
  2. *************************** 1. row ***************************
  3. OBJECT_TYPE: GLOBAL
  4. OBJECT_SCHEMA: NULL
  5. OBJECT_NAME: NULL
  6. LOCK_TYPE: INTENTION_EXCLUSIVE
  7. LOCK_DURATION: STATEMENT
  8. SOURCE: sql_base.cc:5497
  9. *************************** 2. row ***************************
  10. OBJECT_TYPE: SCHEMA
  11. OBJECT_SCHEMA: test
  12. OBJECT_NAME: NULL
  13. LOCK_TYPE: INTENTION_EXCLUSIVE
  14. LOCK_DURATION: TRANSACTION
  15. SOURCE: sql_base.cc:5482
  16. *************************** 3. row ***************************
  17. OBJECT_TYPE: TABLE
  18. OBJECT_SCHEMA: test
  19. OBJECT_NAME: ti
  20. LOCK_TYPE: SHARED_NO_READ_WRITE
  21. LOCK_DURATION: TRANSACTION
  22. SOURCE: sql_parse.cc:5996

相关源码

  1. bool
  2. lock_table_names(THD *thd,
  3. TABLE_LIST *tables_start, TABLE_LIST *tables_end,
  4. ulong lock_wait_timeout, uint flags)
  5. {
  6. ...
  7. while ((table= it++))
  8. {
  9. MDL_request *schema_request= new (thd->mem_root) MDL_request;
  10. if (schema_request == NULL)
  11. return true;
  12. MDL_REQUEST_INIT(schema_request,
  13. MDL_key::SCHEMA, table->db, "",
  14. MDL_INTENTION_EXCLUSIVE,
  15. MDL_TRANSACTION);
  16. mdl_requests.push_front(schema_request);
  17. }
  18. if (need_global_read_lock_protection)
  19. {
  20. /*
  21. Protect this statement against concurrent global read lock
  22. by acquiring global intention exclusive lock with statement
  23. duration.
  24. */
  25. if (thd->global_read_lock.can_acquire_protection())
  26. return true;
  27. MDL_REQUEST_INIT(&global_request,
  28. MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
  29. MDL_STATEMENT);
  30. mdl_requests.push_front(&global_request);
  31. }
  32. ...
  33. // Phase 3: Acquire the locks which have been requested so far.
  34. if (thd->mdl_context.acquire_locks(&mdl_requests, lock_wait_timeout))
  35. return true;
  36. }
  37. open_table中也会请求锁。
  38. SHARED_NO_READ_WRITE的加锁源码参考LOCK TABLE WITH READ的源码分析。
  • SELECT查询语句的执行

SELECT语句的执行加锁TABLE-TRANSACTION-SHARED_READ锁。

  1. select * from performance_schema.metadata_locks\G
  2. *************************** 1. row ***************************
  3. OBJECT_TYPE: TABLE
  4. OBJECT_SCHEMA: test
  5. OBJECT_NAME: t1
  6. LOCK_TYPE: SHARED_READ
  7. LOCK_DURATION: TRANSACTION
  8. LOCK_STATUS: GRANTED
  9. SOURCE: sql_parse.cc:5996

源码分析:

  1. class Yacc_state
  2. {
  3. void reset()
  4. {
  5. yacc_yyss= NULL;
  6. yacc_yyvs= NULL;
  7. yacc_yyls= NULL;
  8. m_lock_type= TL_READ_DEFAULT;
  9. m_mdl_type= MDL_SHARED_READ;
  10. m_ha_rkey_mode= HA_READ_KEY_EXACT;
  11. }
  12. }
  13. 调用add_table_to_list初始化锁,调用open_table_get_mdl_lock获取锁。
  14. static bool
  15. open_table_get_mdl_lock(THD *thd, Open_table_context *ot_ctx,
  16. TABLE_LIST *table_list, uint flags,
  17. MDL_ticket **mdl_ticket)
  18. {
  19. bool result= thd->mdl_context.acquire_lock(mdl_request,
  20. ot_ctx->get_timeout());
  21. }

INSERT/UPDATE/DELETE语句

在open table阶段会获取GLOBAL-STATEMENT-INTENTION_EXCLUSIVE,TABLE-TRANSACTION-SHARED_WRITE。

在commit阶段获取COMMIT-MDL_EXPLICIT-INTENTION_EXCLUSIVE锁。

  1. select OBJECT_TYPE,OBJECT_SCHEMA,OBJECT_NAME,LOCK_TYPE,LOCK_DURATION,SOURCE from performance_schema.metadata_locks\G
  2. OBJECT_TYPE: GLOBAL
  3. OBJECT_SCHEMA: NULL
  4. OBJECT_NAME: NULL
  5. LOCK_TYPE: INTENTION_EXCLUSIVE
  6. LOCK_DURATION: STATEMENT
  7. SOURCE: sql_base.cc:3190
  8. *************************** 2. row ***************************
  9. OBJECT_TYPE: TABLE
  10. OBJECT_SCHEMA: test
  11. OBJECT_NAME: ti
  12. LOCK_TYPE: SHARED_WRITE
  13. LOCK_DURATION: TRANSACTION
  14. SOURCE: sql_parse.cc:5996
  15. *************************** 3. row ***************************
  16. OBJECT_TYPE: COMMIT
  17. OBJECT_SCHEMA: NULL
  18. OBJECT_NAME: NULL
  19. LOCK_TYPE: INTENTION_EXCLUSIVE
  20. LOCK_DURATION: EXPLICIT
  21. SOURCE: handler.cc:1758
  1. sql/sql_yacc.yy
  2. insert_stmt:
  3. INSERT /* #1 */
  4. insert_lock_option /* #2 */
  5. insert_lock_option:
  6. /* empty */ { $$= TL_WRITE_CONCURRENT_DEFAULT; }
  7. | LOW_PRIORITY { $$= TL_WRITE_LOW_PRIORITY; }
  8. | DELAYED_SYM
  9. {
  10. $$= TL_WRITE_CONCURRENT_DEFAULT;
  11. push_warning_printf(YYTHD, Sql_condition::SL_WARNING,
  12. ER_WARN_LEGACY_SYNTAX_CONVERTED,
  13. ER(ER_WARN_LEGACY_SYNTAX_CONVERTED),
  14. "INSERT DELAYED", "INSERT");
  15. }
  16. | HIGH_PRIORITY { $$= TL_WRITE; }
  17. ;
  18. //DELETE语句
  19. delete_stmt:
  20. DELETE_SYM
  21. opt_delete_options
  22. //UPDATE
  23. update_stmt:
  24. UPDATE_SYM /* #1 */
  25. opt_low_priority /* #2 */
  26. opt_ignore /* #3 */
  27. join_table_list /* #4 */
  28. SET /* #5 */
  29. update_list /* #6 */
  30. opt_low_priority:
  31. /* empty */ { $$= TL_WRITE_DEFAULT; }
  32. | LOW_PRIORITY { $$= TL_WRITE_LOW_PRIORITY; }
  33. ;
  34. opt_delete_options:
  35. /* empty */ { $$= 0; }
  36. | opt_delete_option opt_delete_options { $$= $1 | $2; }
  37. ;
  38. opt_delete_option:
  39. QUICK { $$= DELETE_QUICK; }
  40. | LOW_PRIORITY { $$= DELETE_LOW_PRIORITY; }
  41. | IGNORE_SYM { $$= DELETE_IGNORE; }
  42. ;
  43. sql/parse_tree_nodes.cc
  44. bool PT_delete::add_table(Parse_context *pc, Table_ident *table)
  45. {
  46. ...
  47. const enum_mdl_type mdl_type=
  48. (opt_delete_options & DELETE_LOW_PRIORITY) ? MDL_SHARED_WRITE_LOW_PRIO
  49. : MDL_SHARED_WRITE;
  50. ...
  51. }
  52. bool PT_insert::contextualize(Parse_context *pc)
  53. {
  54. if (!pc->select->add_table_to_list(pc->thd, table_ident, NULL,
  55. TL_OPTION_UPDATING,
  56. yyps->m_lock_type,
  57. yyps->m_mdl_type,
  58. NULL,
  59. opt_use_partition))
  60. pc->select->set_lock_for_tables(lock_option);
  61. }
  62. bool PT_update::contextualize(Parse_context *pc)
  63. {
  64. pc->select->set_lock_for_tables(opt_low_priority);
  65. }
  66. void st_select_lex::set_lock_for_tables(thr_lock_type lock_type)
  67. {
  68. bool for_update= lock_type >= TL_READ_NO_INSERT;
  69. enum_mdl_type mdl_type= mdl_type_for_dml(lock_type);
  70. ...
  71. tables->mdl_request.set_type(mdl_type);
  72. ...
  73. }
  74. inline enum enum_mdl_type mdl_type_for_dml(enum thr_lock_type lock_type)
  75. {
  76. return lock_type >= TL_WRITE_ALLOW_WRITE ?
  77. (lock_type == TL_WRITE_LOW_PRIORITY ?
  78. MDL_SHARED_WRITE_LOW_PRIO : MDL_SHARED_WRITE) :
  79. MDL_SHARED_READ;
  80. }
  81. 最终调用open\_table加锁
  82. bool open_table(THD *thd, TABLE_LIST *table_list, Open_table_context *ot_ctx)
  83. {
  84. if (table_list->mdl_request.is_write_lock_request() &&
  85. ...
  86. {
  87. MDL_REQUEST_INIT(&protection_request,
  88. MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
  89. MDL_STATEMENT);
  90. bool result= thd->mdl_context.acquire_lock(&protection_request,
  91. ot_ctx->get_timeout());
  92. }
  93. ...
  94. if (open_table_get_mdl_lock(thd, ot_ctx, table_list, flags, &mdl_ticket) ||
  95. ...
  96. }
  97. commit阶段调用ha_commit_trans函数时加COMMITINTENTION_EXCLUSIVE锁,源码如FLUSH TABLES WITH READ LOCK所述。

如果INSERT/UPDATE/DELETE LOW_PRIORITY语句TABLE上加MDL_SHARED_WRITE_LOW_PRIO锁。

ALTER TABLE ALGORITHM=COPY[INPLACE]

ALTER TABLE ALGORITHM=COPY

COPY方式ALTER TABLE在open_table阶段加GLOBAL-STATEMENT-INTENTION_EXCLUSIVE锁,SCHEMA-TRANSACTION-INTENTION_EXCLUSIVE锁,TABLE-TRANSACTION-SHARED_UPGRADABLE锁。

在拷贝数据前将TABLE-TRANSACTION-SHARED_UPGRADABLE锁升级到SHARED_NO_WRITE。

拷贝完在交换表阶段将SHARED_NO_WRITE锁升级到EXCLUSIVE锁。

源码解析:

  1. GLOBALSCHEMA锁初始化位置和LOCK TABLE WRITE位置一致都是在lock_table_names函数中。在open_table中也会请求锁。
  2. sql/sql_yacc.yy
  3. alter:
  4. ALTER TABLE_SYM table_ident
  5. {
  6. THD *thd= YYTHD;
  7. LEX *lex= thd->lex;
  8. lex->name.str= 0;
  9. lex->name.length= 0;
  10. lex->sql_command= SQLCOM_ALTER_TABLE;
  11. lex->duplicates= DUP_ERROR;
  12. if (!lex->select_lex->add_table_to_list(thd, $3, NULL,
  13. TL_OPTION_UPDATING,
  14. TL_READ_NO_INSERT,
  15. MDL_SHARED_UPGRADABLE))
  16. bool mysql_alter_table(THD *thd, const char *new_db, const char *new_name,
  17. HA_CREATE_INFO *create_info,
  18. TABLE_LIST *table_list,
  19. Alter_info *alter_info)
  20. {
  21. //升级锁
  22. if (thd->mdl_context.upgrade_shared_lock(mdl_ticket, MDL_SHARED_NO_WRITE,
  23. thd->variables.lock_wait_timeout)
  24. || lock_tables(thd, table_list, alter_ctx.tables_opened, 0))
  25. ...
  26. if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME))
  27. }
  28. bool wait_while_table_is_used(THD *thd, TABLE *table,
  29. enum ha_extra_function function)
  30. {
  31. DBUG_ENTER("wait_while_table_is_used");
  32. DBUG_PRINT("enter", ("table: '%s' share: 0x%lx db_stat: %u version: %lu",
  33. table->s->table_name.str, (ulong) table->s,
  34. table->db_stat, table->s->version));
  35. if (thd->mdl_context.upgrade_shared_lock(
  36. table->mdl_ticket, MDL_EXCLUSIVE,
  37. thd->variables.lock_wait_timeout))

ALTER TABLE INPLACE的加锁:

INPLACE方式在打开表的时候也是加GLOBAL-STATEMENT-INTENTION_EXCLUSIVE锁,SCHEMA-TRANSACTION-INTENTION_EXCLUSIVE锁,TABLE-TRANSACTION-SHARED_UPGRADABLE锁。

在prepare前将TABLE-TRANSACTION-SHARED_UPGRADABLE升级为TABLE-TRANSACTION-EXCLUSIVE锁。

在prepare后会再将EXCLUSIVE根据不同引擎支持情况降级为SHARED_NO_WRITE(不允许其他线程写入)或者SHARED_UPGRADABLE锁(其他线程可以读写,InnoDB引擎)。

在commit前,TABLE上的锁会再次升级到EXCLUSIVE锁。

  1. sql/sql_table.cc
  2. static bool mysql_inplace_alter_table(THD *thd,
  3. TABLE_LIST *table_list,
  4. TABLE *table,
  5. TABLE *altered_table,
  6. Alter_inplace_info *ha_alter_info,
  7. enum_alter_inplace_result inplace_supported,
  8. MDL_request *target_mdl_request,
  9. Alter_table_ctx *alter_ctx)
  10. {
  11. ...
  12. else if (inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE ||
  13. inplace_supported == HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE)
  14. {
  15. /*
  16. Storage engine has requested exclusive lock only for prepare phase
  17. and we are not under LOCK TABLES.
  18. Don't mark TABLE_SHARE as old in this case, as this won't allow opening
  19. of table by other threads during main phase of in-place ALTER TABLE.
  20. */
  21. if (thd->mdl_context.upgrade_shared_lock(table->mdl_ticket, MDL_EXCLUSIVE,
  22. thd->variables.lock_wait_timeout))
  23. ...
  24. if (table->file->ha_prepare_inplace_alter_table(altered_table,
  25. ha_alter_info))
  26. ...
  27. if ((inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE ||
  28. inplace_supported == HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE) &&
  29. !(thd->locked_tables_mode == LTM_LOCK_TABLES ||
  30. thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) &&
  31. (alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE))
  32. {
  33. /* If storage engine or user requested shared lock downgrade to SNW. */
  34. if (inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE ||
  35. alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_SHARED)
  36. table->mdl_ticket->downgrade_lock(MDL_SHARED_NO_WRITE);
  37. else
  38. {
  39. DBUG_ASSERT(inplace_supported == HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE);
  40. table->mdl_ticket->downgrade_lock(MDL_SHARED_UPGRADABLE);
  41. }
  42. }
  43. ...
  44. // Upgrade to EXCLUSIVE before commit.
  45. if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME))
  46. ...
  47. if (table->file->ha_commit_inplace_alter_table(altered_table,
  48. ha_alter_info,
  49. true))
  50. }

CREATE TABLE 加锁

CREATE TABLE先加锁GLOBAL-STATEMENT-INTENTION_EXCLUSIVE,SCHEMA-MDL_TRANSACTION-INTENTION_EXCLUSIVE,TABLE-TRANSACTION-SHARED。

表不存在则升级表上的SHARED锁到EXCLUSIVE。

  1. bool open_table(THD *thd, TABLE_LIST *table_list, Open_table_context *ot_ctx)
  2. {
  3. ...
  4. if (!exists)
  5. {
  6. ...
  7. bool wait_result= thd->mdl_context.upgrade_shared_lock(
  8. table_list->mdl_request.ticket,
  9. MDL_EXCLUSIVE,
  10. thd->variables.lock_wait_timeout);
  11. ...
  12. }
  13. ...
  14. }

DROP TABLE 加锁

drop table语句执行加锁GLOBAL-STATEMENT-INTENTION_EXCLUSIVE,SCHEMA-MDL_TRANSACTION-INTENTION_EXCLUSIVE,TABLE-EXCLUSIVE。

  1. drop:
  2. DROP opt_temporary table_or_tables if_exists
  3. {
  4. LEX *lex=Lex;
  5. lex->sql_command = SQLCOM_DROP_TABLE;
  6. lex->drop_temporary= $2;
  7. lex->drop_if_exists= $4;
  8. YYPS->m_lock_type= TL_UNLOCK;
  9. YYPS->m_mdl_type= MDL_EXCLUSIVE;
  10. }