MySQL · 原始碼分析 · 常用SQL語句的MDL加鎖原始碼分析
前言
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的第一階段,用於支援Online DDL。使能夠在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鎖活躍鎖和請求鎖相容性矩陣如下。
| Type of active |
Request | scoped lock |
type | IS(*) IX S X |
---------+------------------+
IS | + + + + |
IX | + + - - |
S | + - + - |
X | + - - - |
+號表示請求的鎖可以滿足。
-號表示請求的鎖無法滿足需要等待。
Scope鎖等待鎖和請求鎖優先順序矩陣
| Pending |
Request | scoped lock |
type | IS(*) IX S X |
---------+-----------------+
IS | + + + + |
IX | + + - - |
S | + + + - |
X | + + + + |
+號表示請求的鎖可以滿足。
-號表示請求的鎖無法滿足需要等待。
object上已持有鎖和請求鎖的相容性矩陣如下。
Request | Granted requests for lock |
type | S SH SR SW SWLP SU SRO SNW SNRW X |
----------+---------------------------------------------+
S | + + + + + + + + + - |
SH | + + + + + + + + + - |
SR | + + + + + + + + - - |
SW | + + + + + + - - - - |
SWLP | + + + + + + - - - - |
SU | + + + + + - + - - - |
SRO | + + + - - + + + - - |
SNW | + + + - - - + - - - |
SNRW | + + - - - - - - - - |
X | - - - - - - - - - - |
object上等待鎖和請求鎖的優先順序矩陣如下。
Request | Pending requests for lock |
type | S SH SR SW SWLP SU SRO SNW SNRW X |
----------+--------------------------------------------+
S | + + + + + + + + + - |
SH | + + + + + + + + + + |
SR | + + + + + + + + - - |
SW | + + + + + + + - - - |
SWLP | + + + + + + - - - - |
SU | + + + + + + + + + - |
SRO | + + + - + + + + - - |
SNW | + + + + + + + + + - |
SNRW | + + + + + + + + + - |
X | + + + + + + + + + + |
常用語句MDL鎖加鎖分析
使用performance_schema可以輔助分析加鎖。利用下面語句開啟MDL鎖分析,可以看到在只有當前session訪問的時候,SELECT語句對metadata_locks表加了TRANSACTION週期的SHARED_READ鎖,即鎖粒度、時間範圍和鎖型別分別為:TABLE, TRANSACTION, SHARED_READ,在程式碼位置sql_parse.cc:5996初始化鎖。。後面的鎖分析也按照鎖粒度-時間範圍-鎖型別介紹。
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME ='global_instrumentation';
UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME ='wait/lock/metadata/sql/mdl';
select * from performance_schema.metadata_locks\G
*************************** 1. row ***************************
OBJECT_TYPE: TABLE
OBJECT_SCHEMA: performance_schema
OBJECT_NAME: metadata_locks
OBJECT_INSTANCE_BEGIN: 46995934864720
LOCK_TYPE: SHARED_READ
LOCK_DURATION: TRANSACTION
LOCK_STATUS: GRANTED
SOURCE: sql_parse.cc:5996
OWNER_THREAD_ID: 26
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。
select * from performance_schema.metadata_locks\G
*************************** 1. row ***************************
OBJECT_TYPE: GLOBAL
OBJECT_SCHEMA: NULL
OBJECT_NAME: NULL
OBJECT_INSTANCE_BEGIN: 46996001973424
LOCK_TYPE: SHARED
LOCK_DURATION: EXPLICIT
LOCK_STATUS: GRANTED
SOURCE: lock.cc:1110
OWNER_THREAD_ID: 27
OWNER_EVENT_ID: 92
*************************** 2. row ***************************
OBJECT_TYPE: COMMIT
OBJECT_SCHEMA: NULL
OBJECT_NAME: NULL
OBJECT_INSTANCE_BEGIN: 46996001973616
LOCK_TYPE: SHARED
LOCK_DURATION: EXPLICIT
LOCK_STATUS: GRANTED
SOURCE: lock.cc:1194
OWNER_THREAD_ID: 27
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鎖。
sql/sql_yacc.yy
flush_options:
table_or_tables
{
Lex->type|= REFRESH_TABLES;
/*
Set type of metadata and table locks for
FLUSH TABLES table_list [WITH READ LOCK].
*/
YYPS->m_lock_type= TL_READ_NO_INSERT;
YYPS->m_mdl_type= MDL_SHARED_HIGH_PRIO;
}
opt_table_list {}
opt_flush_lock {}
| flush_options_list
;
opt_flush_lock:
/* empty */ {}
| WITH READ_SYM LOCK_SYM
{
TABLE_LIST *tables= Lex->query_tables;
Lex->type|= REFRESH_READ_LOCK;
sql/sql_parse.cc
...
case SQLCOM_FLUSH:
if (first_table && lex->type & REFRESH_READ_LOCK)//當指定表的時候,對指定表加鎖。
{
if (flush_tables_with_read_lock(thd, all_tables))
}
...
if (!reload_acl_and_cache(thd, lex->type, first_table, &write_to_binlog))
sql/sql_reload.cc
reload_acl_and_cache
{
if (options & (REFRESH_TABLES | REFRESH_READ_LOCK))
{
if ((options & REFRESH_READ_LOCK) && thd)
{
...
if (thd->global_read_lock.lock_global_read_lock(thd))//當未指定表的時候,加全域性鎖
return 1;
...
if (thd->global_read_lock.make_global_read_lock_block_commit(thd))//當未指定表的時候,加COMMIT鎖
}
//對GLOBAL加EXPLICIT的S鎖。
sql/lock.cc
bool Global_read_lock::lock_global_read_lock(THD *thd)
{
...
MDL_REQUEST_INIT(&mdl_request,
MDL_key::GLOBAL, "", "", MDL_SHARED, MDL_EXPLICIT);
...
}
//對COMMIT加EXPLICIT的S鎖。
bool Global_read_lock::make_global_read_lock_block_commit(THD *thd)
{
...
MDL_REQUEST_INIT(&mdl_request,
MDL_key::COMMIT, "", "", MDL_SHARED, MDL_EXPLICIT);
...
}
sql/handler.cc
事務提交和外部XA事務的commit\rollback\prepare均需要加COMMIT的IX鎖.
int ha_commit_trans(THD *thd, bool all, bool ignore_global_read_lock)
{
...
if (rw_trans && !ignore_global_read_lock) //對於內部表slave status table的更新可以忽略global read lock
{
MDL_REQUEST_INIT(&mdl_request,
MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE,
MDL_EXPLICIT);
DBUG_PRINT("debug", ("Acquire MDL commit lock"));
if (thd->mdl_context.acquire_lock(&mdl_request,
thd->variables.lock_wait_timeout))
}
...
}
sql/xa.cc
bool Sql_cmd_xa_commit::trans_xa_commit(THD *thd)
{
...
MDL_request mdl_request;
MDL_REQUEST_INIT(&mdl_request,
MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE,
MDL_STATEMENT);
if (thd->mdl_context.acquire_lock(&mdl_request,
thd->variables.lock_wait_timeout))
...
}
bool Sql_cmd_xa_rollback::trans_xa_rollback(THD *thd)
{
...
MDL_request mdl_request;
MDL_REQUEST_INIT(&mdl_request,
MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE,
MDL_STATEMENT);
}
bool Sql_cmd_xa_prepare::trans_xa_prepare(THD *thd)
{
...
MDL_request mdl_request;
MDL_REQUEST_INIT(&mdl_request,
MDL_key::COMMIT, "", "", MDL_INTENTION_EXCLUSIVE,
MDL_STATEMENT);
}
//寫入語句的執行和DDL執行需要GLOBAL的IX鎖,這與S鎖不相容。
sql/sql_base.cc
bool open_table(THD *thd, TABLE_LIST *table_list, Open_table_context *ot_ctx)
{
if (table_list->mdl_request.is_write_lock_request() &&
{
MDL_request protection_request;
MDL_deadlock_handler mdl_deadlock_handler(ot_ctx);
if (thd->global_read_lock.can_acquire_protection())
DBUG_RETURN(TRUE);
MDL_REQUEST_INIT(&protection_request,
MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
MDL_STATEMENT);
}
}
bool
lock_table_names(THD *thd,
TABLE_LIST *tables_start, TABLE_LIST *tables_end,
ulong lock_wait_timeout, uint flags)
{
if (need_global_read_lock_protection)
{
/*
Protect this statement against concurrent global read lock
by acquiring global intention exclusive lock with statement
duration.
*/
if (thd->global_read_lock.can_acquire_protection())
return true;
MDL_REQUEST_INIT(&global_request,
MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
MDL_STATEMENT);
mdl_requests.push_front(&global_request);
}
}
LOCK TABLE t READ [LOCAL]
LOCK TABLE t READ LOCAL會加鎖TABLE-TRANSACTION-SHARED_READ。
select * from performance_schema.metadata_locks\G
*************************** 1. row ***************************
OBJECT_TYPE: TABLE
OBJECT_SCHEMA: test
OBJECT_NAME: t
LOCK_TYPE: SHARED_READ
LOCK_DURATION: TRANSACTION
LOCK_STATUS: GRANTED
SOURCE: sql_parse.cc:5996
LOCK TABLE t READ會加鎖TABLE-TRANSACTION-SHARED_READ_ONLY。
select * from performance_schema.metadata_locks\G
*************************** 1. row ***************************
OBJECT_TYPE: TABLE
OBJECT_SCHEMA: test
OBJECT_NAME: t
LOCK_TYPE: SHARED_READ_ONLY
LOCK_DURATION: TRANSACTION
LOCK_STATUS: GRANTED
SOURCE: sql_parse.cc:5996
這兩個的區別是對於MyISAM引擎,LOCAL方式的加鎖與insert寫入不衝突,而沒有LOCAL的時候SHARED_READ_ONLY會阻塞寫入。不過對於InnoDB引擎兩種方式是一樣的,帶有LOCAL的語句執行後面會升級為SHARED_READ_ONLY。
原始碼分析
table_lock:
table_ident opt_table_alias lock_option
{
thr_lock_type lock_type= (thr_lock_type) $3;
enum_mdl_type mdl_lock_type;
if (lock_type >= TL_WRITE_ALLOW_WRITE)
{
/* LOCK TABLE ... WRITE/LOW_PRIORITY WRITE */
mdl_lock_type= MDL_SHARED_NO_READ_WRITE;
}
else if (lock_type == TL_READ)
{
/* LOCK TABLE ... READ LOCAL */
mdl_lock_type= MDL_SHARED_READ;
}
else
{
/* LOCK TABLE ... READ */
mdl_lock_type= MDL_SHARED_READ_ONLY;
}
if (!Select->add_table_to_list(YYTHD, $1, $2, 0, lock_type,
mdl_lock_type))
MYSQL_YYABORT;
}
lock_option:
READ_SYM { $$= TL_READ_NO_INSERT; }
| WRITE_SYM { $$= TL_WRITE_DEFAULT; }
| LOW_PRIORITY WRITE_SYM
{
$$= TL_WRITE_LOW_PRIORITY;
push_deprecated_warn(YYTHD, "LOW_PRIORITY WRITE", "WRITE");
}
| READ_SYM LOCAL_SYM { $$= TL_READ; }
;
sql/sql_parse.cc
TABLE_LIST *st_select_lex::add_table_to_list(THD *thd,
Table_ident *table,
LEX_STRING *alias,
ulong table_options,
thr_lock_type lock_type,
enum_mdl_type mdl_type,
List<Index_hint> *index_hints_arg,
List<String> *partition_names,
LEX_STRING *option)
{
// Pure table aliases do not need to be locked:
if (!MY_TEST(table_options & TL_OPTION_ALIAS))
{
MDL_REQUEST_INIT(& ptr->mdl_request,
MDL_key::TABLE, ptr->db, ptr->table_name, mdl_type,
MDL_TRANSACTION);
}
}
//對於Innodb引擎
static bool lock_tables_open_and_lock_tables(THD *thd, TABLE_LIST *tables)
{
...
else if (table->lock_type == TL_READ &&
! table->prelocking_placeholder &&
table->table->file->ha_table_flags() & HA_NO_READ_LOCAL_LOCK)
{
/*
In case when LOCK TABLE ... READ LOCAL was issued for table with
storage engine which doesn't support READ LOCAL option and doesn't
use THR_LOCK locks we need to upgrade weak SR metadata lock acquired
in open_tables() to stronger SRO metadata lock.
This is not needed for tables used through stored routines or
triggers as we always acquire SRO (or even stronger SNRW) metadata
lock for them.
*/
bool result= thd->mdl_context.upgrade_shared_lock(
table->table->mdl_ticket,
MDL_SHARED_READ_ONLY,
thd->variables.lock_wait_timeout);
...
}
LOCK TABLE t WITH WRITE
LOCK TABLE t WITH WRITE會加鎖:GLOBAL-STATEMENT-INTENTION_EXCLUSIVE,SCHEMA-TRANSACTION-INTENTION_EXCLUSIVE,TABLE-TRANSACTION-SHARED_NO_READ_WRITE。
select OBJECT_TYPE,OBJECT_SCHEMA,OBJECT_NAME,LOCK_TYPE,LOCK_DURATION,SOURCE from performance_schema.metadata_locks\G
*************************** 1. row ***************************
OBJECT_TYPE: GLOBAL
OBJECT_SCHEMA: NULL
OBJECT_NAME: NULL
LOCK_TYPE: INTENTION_EXCLUSIVE
LOCK_DURATION: STATEMENT
SOURCE: sql_base.cc:5497
*************************** 2. row ***************************
OBJECT_TYPE: SCHEMA
OBJECT_SCHEMA: test
OBJECT_NAME: NULL
LOCK_TYPE: INTENTION_EXCLUSIVE
LOCK_DURATION: TRANSACTION
SOURCE: sql_base.cc:5482
*************************** 3. row ***************************
OBJECT_TYPE: TABLE
OBJECT_SCHEMA: test
OBJECT_NAME: ti
LOCK_TYPE: SHARED_NO_READ_WRITE
LOCK_DURATION: TRANSACTION
SOURCE: sql_parse.cc:5996
相關原始碼
bool
lock_table_names(THD *thd,
TABLE_LIST *tables_start, TABLE_LIST *tables_end,
ulong lock_wait_timeout, uint flags)
{
...
while ((table= it++))
{
MDL_request *schema_request= new (thd->mem_root) MDL_request;
if (schema_request == NULL)
return true;
MDL_REQUEST_INIT(schema_request,
MDL_key::SCHEMA, table->db, "",
MDL_INTENTION_EXCLUSIVE,
MDL_TRANSACTION);
mdl_requests.push_front(schema_request);
}
if (need_global_read_lock_protection)
{
/*
Protect this statement against concurrent global read lock
by acquiring global intention exclusive lock with statement
duration.
*/
if (thd->global_read_lock.can_acquire_protection())
return true;
MDL_REQUEST_INIT(&global_request,
MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
MDL_STATEMENT);
mdl_requests.push_front(&global_request);
}
...
// Phase 3: Acquire the locks which have been requested so far.
if (thd->mdl_context.acquire_locks(&mdl_requests, lock_wait_timeout))
return true;
}
在open_table中也會請求鎖。
SHARED_NO_READ_WRITE的加鎖原始碼參考LOCK TABLE WITH READ的原始碼分析。
- SELECT查詢語句的執行
SELECT語句的執行加鎖TABLE-TRANSACTION-SHARED_READ鎖。
select * from performance_schema.metadata_locks\G
*************************** 1. row ***************************
OBJECT_TYPE: TABLE
OBJECT_SCHEMA: test
OBJECT_NAME: t1
LOCK_TYPE: SHARED_READ
LOCK_DURATION: TRANSACTION
LOCK_STATUS: GRANTED
SOURCE: sql_parse.cc:5996
原始碼分析:
class Yacc_state
{
void reset()
{
yacc_yyss= NULL;
yacc_yyvs= NULL;
yacc_yyls= NULL;
m_lock_type= TL_READ_DEFAULT;
m_mdl_type= MDL_SHARED_READ;
m_ha_rkey_mode= HA_READ_KEY_EXACT;
}
}
呼叫add_table_to_list初始化鎖,呼叫open_table_get_mdl_lock獲取鎖。
static bool
open_table_get_mdl_lock(THD *thd, Open_table_context *ot_ctx,
TABLE_LIST *table_list, uint flags,
MDL_ticket **mdl_ticket)
{
bool result= thd->mdl_context.acquire_lock(mdl_request,
ot_ctx->get_timeout());
}
INSERT/UPDATE/DELETE語句
在open table階段會獲取GLOBAL-STATEMENT-INTENTION_EXCLUSIVE,TABLE-TRANSACTION-SHARED_WRITE。
在commit階段獲取COMMIT-MDL_EXPLICIT-INTENTION_EXCLUSIVE鎖。
select OBJECT_TYPE,OBJECT_SCHEMA,OBJECT_NAME,LOCK_TYPE,LOCK_DURATION,SOURCE from performance_schema.metadata_locks\G
OBJECT_TYPE: GLOBAL
OBJECT_SCHEMA: NULL
OBJECT_NAME: NULL
LOCK_TYPE: INTENTION_EXCLUSIVE
LOCK_DURATION: STATEMENT
SOURCE: sql_base.cc:3190
*************************** 2. row ***************************
OBJECT_TYPE: TABLE
OBJECT_SCHEMA: test
OBJECT_NAME: ti
LOCK_TYPE: SHARED_WRITE
LOCK_DURATION: TRANSACTION
SOURCE: sql_parse.cc:5996
*************************** 3. row ***************************
OBJECT_TYPE: COMMIT
OBJECT_SCHEMA: NULL
OBJECT_NAME: NULL
LOCK_TYPE: INTENTION_EXCLUSIVE
LOCK_DURATION: EXPLICIT
SOURCE: handler.cc:1758
sql/sql_yacc.yy
insert_stmt:
INSERT /* #1 */
insert_lock_option /* #2 */
insert_lock_option:
/* empty */ { $$= TL_WRITE_CONCURRENT_DEFAULT; }
| LOW_PRIORITY { $$= TL_WRITE_LOW_PRIORITY; }
| DELAYED_SYM
{
$$= TL_WRITE_CONCURRENT_DEFAULT;
push_warning_printf(YYTHD, Sql_condition::SL_WARNING,
ER_WARN_LEGACY_SYNTAX_CONVERTED,
ER(ER_WARN_LEGACY_SYNTAX_CONVERTED),
"INSERT DELAYED", "INSERT");
}
| HIGH_PRIORITY { $$= TL_WRITE; }
;
//DELETE語句
delete_stmt:
DELETE_SYM
opt_delete_options
//UPDATE
update_stmt:
UPDATE_SYM /* #1 */
opt_low_priority /* #2 */
opt_ignore /* #3 */
join_table_list /* #4 */
SET /* #5 */
update_list /* #6 */
opt_low_priority:
/* empty */ { $$= TL_WRITE_DEFAULT; }
| LOW_PRIORITY { $$= TL_WRITE_LOW_PRIORITY; }
;
opt_delete_options:
/* empty */ { $$= 0; }
| opt_delete_option opt_delete_options { $$= $1 | $2; }
;
opt_delete_option:
QUICK { $$= DELETE_QUICK; }
| LOW_PRIORITY { $$= DELETE_LOW_PRIORITY; }
| IGNORE_SYM { $$= DELETE_IGNORE; }
;
sql/parse_tree_nodes.cc
bool PT_delete::add_table(Parse_context *pc, Table_ident *table)
{
...
const enum_mdl_type mdl_type=
(opt_delete_options & DELETE_LOW_PRIORITY) ? MDL_SHARED_WRITE_LOW_PRIO
: MDL_SHARED_WRITE;
...
}
bool PT_insert::contextualize(Parse_context *pc)
{
if (!pc->select->add_table_to_list(pc->thd, table_ident, NULL,
TL_OPTION_UPDATING,
yyps->m_lock_type,
yyps->m_mdl_type,
NULL,
opt_use_partition))
pc->select->set_lock_for_tables(lock_option);
}
bool PT_update::contextualize(Parse_context *pc)
{
pc->select->set_lock_for_tables(opt_low_priority);
}
void st_select_lex::set_lock_for_tables(thr_lock_type lock_type)
{
bool for_update= lock_type >= TL_READ_NO_INSERT;
enum_mdl_type mdl_type= mdl_type_for_dml(lock_type);
...
tables->mdl_request.set_type(mdl_type);
...
}
inline enum enum_mdl_type mdl_type_for_dml(enum thr_lock_type lock_type)
{
return lock_type >= TL_WRITE_ALLOW_WRITE ?
(lock_type == TL_WRITE_LOW_PRIORITY ?
MDL_SHARED_WRITE_LOW_PRIO : MDL_SHARED_WRITE) :
MDL_SHARED_READ;
}
最終呼叫open\_table加鎖
bool open_table(THD *thd, TABLE_LIST *table_list, Open_table_context *ot_ctx)
{
if (table_list->mdl_request.is_write_lock_request() &&
...
{
MDL_REQUEST_INIT(&protection_request,
MDL_key::GLOBAL, "", "", MDL_INTENTION_EXCLUSIVE,
MDL_STATEMENT);
bool result= thd->mdl_context.acquire_lock(&protection_request,
ot_ctx->get_timeout());
}
...
if (open_table_get_mdl_lock(thd, ot_ctx, table_list, flags, &mdl_ticket) ||
...
}
在commit階段呼叫ha_commit_trans函式時加COMMIT的INTENTION_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鎖。
原始碼解析:
GLOBAL、SCHEMA鎖初始化位置和LOCK TABLE WRITE位置一致都是在lock_table_names函式中。在open_table中也會請求鎖。
sql/sql_yacc.yy
alter:
ALTER TABLE_SYM table_ident
{
THD *thd= YYTHD;
LEX *lex= thd->lex;
lex->name.str= 0;
lex->name.length= 0;
lex->sql_command= SQLCOM_ALTER_TABLE;
lex->duplicates= DUP_ERROR;
if (!lex->select_lex->add_table_to_list(thd, $3, NULL,
TL_OPTION_UPDATING,
TL_READ_NO_INSERT,
MDL_SHARED_UPGRADABLE))
bool mysql_alter_table(THD *thd, const char *new_db, const char *new_name,
HA_CREATE_INFO *create_info,
TABLE_LIST *table_list,
Alter_info *alter_info)
{
//升級鎖
if (thd->mdl_context.upgrade_shared_lock(mdl_ticket, MDL_SHARED_NO_WRITE,
thd->variables.lock_wait_timeout)
|| lock_tables(thd, table_list, alter_ctx.tables_opened, 0))
...
if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME))
}
bool wait_while_table_is_used(THD *thd, TABLE *table,
enum ha_extra_function function)
{
DBUG_ENTER("wait_while_table_is_used");
DBUG_PRINT("enter", ("table: '%s' share: 0x%lx db_stat: %u version: %lu",
table->s->table_name.str, (ulong) table->s,
table->db_stat, table->s->version));
if (thd->mdl_context.upgrade_shared_lock(
table->mdl_ticket, MDL_EXCLUSIVE,
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鎖。
sql/sql_table.cc
static bool mysql_inplace_alter_table(THD *thd,
TABLE_LIST *table_list,
TABLE *table,
TABLE *altered_table,
Alter_inplace_info *ha_alter_info,
enum_alter_inplace_result inplace_supported,
MDL_request *target_mdl_request,
Alter_table_ctx *alter_ctx)
{
...
else if (inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE ||
inplace_supported == HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE)
{
/*
Storage engine has requested exclusive lock only for prepare phase
and we are not under LOCK TABLES.
Don't mark TABLE_SHARE as old in this case, as this won't allow opening
of table by other threads during main phase of in-place ALTER TABLE.
*/
if (thd->mdl_context.upgrade_shared_lock(table->mdl_ticket, MDL_EXCLUSIVE,
thd->variables.lock_wait_timeout))
...
if (table->file->ha_prepare_inplace_alter_table(altered_table,
ha_alter_info))
...
if ((inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE ||
inplace_supported == HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE) &&
!(thd->locked_tables_mode == LTM_LOCK_TABLES ||
thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) &&
(alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE))
{
/* If storage engine or user requested shared lock downgrade to SNW. */
if (inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK_AFTER_PREPARE ||
alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_SHARED)
table->mdl_ticket->downgrade_lock(MDL_SHARED_NO_WRITE);
else
{
DBUG_ASSERT(inplace_supported == HA_ALTER_INPLACE_NO_LOCK_AFTER_PREPARE);
table->mdl_ticket->downgrade_lock(MDL_SHARED_UPGRADABLE);
}
}
...
// Upgrade to EXCLUSIVE before commit.
if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME))
...
if (table->file->ha_commit_inplace_alter_table(altered_table,
ha_alter_info,
true))
}
CREATE TABLE 加鎖
CREATE TABLE先加鎖GLOBAL-STATEMENT-INTENTION_EXCLUSIVE,SCHEMA-MDL_TRANSACTION-INTENTION_EXCLUSIVE,TABLE-TRANSACTION-SHARED。
表不存在則升級表上的SHARED鎖到EXCLUSIVE。
bool open_table(THD *thd, TABLE_LIST *table_list, Open_table_context *ot_ctx)
{
...
if (!exists)
{
...
bool wait_result= thd->mdl_context.upgrade_shared_lock(
table_list->mdl_request.ticket,
MDL_EXCLUSIVE,
thd->variables.lock_wait_timeout);
...
}
...
}
DROP TABLE 加鎖
drop table語句執行加鎖GLOBAL-STATEMENT-INTENTION_EXCLUSIVE,SCHEMA-MDL_TRANSACTION-INTENTION_EXCLUSIVE,TABLE-EXCLUSIVE。
drop:
DROP opt_temporary table_or_tables if_exists
{
LEX *lex=Lex;
lex->sql_command = SQLCOM_DROP_TABLE;
lex->drop_temporary= $2;
lex->drop_if_exists= $4;
YYPS->m_lock_type= TL_UNLOCK;
YYPS->m_mdl_type= MDL_EXCLUSIVE;
}