1. 程式人生 > >MySQL · 原始碼分析 · 常用SQL語句的MDL加鎖原始碼分析

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;
          }