返回顶部
首页 > 资讯 > 数据库 >MySQL:理解MDL Lock
  • 937
分享到

MySQL:理解MDL Lock

2024-04-02 19:04:59 937人浏览 泡泡鱼
摘要

本文基于源码版本5.7.14 水


本文基于源码版本5.7.14
水平有限,有误请谅解
笔者已经将加好MDL 获取过程和释放过程的版本放到了GitHub如下:
https://github.com/gaopenGCarl/percona-server-locks-detail-5.7.22
供参考


作者深入理解主从原理专栏

Https://www.jianshu.com/nb/43148932

一、MDL Lock综述

Mysql中MDL一直是一个比较让人比较头疼的问题,我们谈起堵塞一般更加倾向于InnoDB层的row lock(gap lock/next key lock/key lock),因为它很好理解也很好观察。而对于MDL Lock考虑就少一些,因为它实在不好观察,只有出现问题查看show processlist的时候,可以看到简单的所谓的‘Waiting for table metadata lock’之类的状态,其实MDL Lock是mysql上层一个非常复杂的子系统,有自己的死锁检测机制。
大家一般说是不是锁表了很大一部分就和MDL Lock有关,可见的它的关键性和严重性,笔者也是根据自己的需求学习了一些,且没有能力阅读全部的代码,但是笔者通过增加一个TICKET的打印函数让语句的MDL Lock加锁流程全部打印出来方便学习,下面从一些基础概念说起然后告诉大家笔者是如何做的打印功能,最后对每种MDL TYPE可能出现的语句进行测试和分析。如果大家对基本概念和增加打印函数不感兴趣可直接参考第五部分语句加MDL Lock测试和分析,希望这些测试能够帮助到大家诊断问题。

刚好最近笔者遇到一次MDL Lock出现死锁的情况会在下篇文章中给出案例,本文只看理论。

  • 处于层次:Mysql层,实际上早在open_table函数中MDL LOCK就开始获取了。
  • 最早获取阶段: THD::enter_stage: ‘Opening tables’
  • 调用栈帧
    
    #0 open_table_get_mdl_lock(thd=0x7fffd0000df0,ot_ctx=0x7fffec06fb00,
    table_list=0x7fffd00067d8,flags=0,mdl_ticket=0x7fffec06f950)
    at/root/MySQL5.7.14/percona-server-5.7.14-7/sql/sql_base.cc:2789
    #1 0x0000000001516e17inopen_table(thd=0x7fffd0000df0,
    table_list=0x7fffd00067d8,ot_ctx=0x7fffec06fb00)
    at/root/MySQL5.7.14/percona-server-5.7.14-7/sql/sql_base.cc:3237
    
  • 死锁检测出错码
    
    { "ER_LOCK_DEADLOCK", 1213, "Deadlock found when trying to get lock; try restarting transaction" },
    ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
    
    可以发现MDL Lock的死锁抛错和Innodb死锁一模一样,不同的只是‘show engine innodb status’没有死锁信息。

二、重要数据结构和概念

1、MDL Lock类型

我们主要研究的类型如下:

MDL_INTENTION_EXCLUSIVE(IX)
MDL_SHARED(S)
MDL_SHARED_HIGH_PRIO(SH)
MDL_SHARED_READ(SR)
MDL_SHARED_WRITE(SW)
MDL_SHARED_WRITE_LOW_PRIO(SWL)
MDL_SHARED_UPGRADABLE(SU)
MDL_SHARED_READ_ONLY(SRO)
MDL_SHARED_NO_WRITE(SNW)
MDL_SHARED_NO_READ_WRITE(SNRW)
MDL_EXCLUSIVE(X)

第五部分会对每种类型进行详细的测试和解释。

2、MDL Lock namespace

在MDL中MDL_KEY按照namespace+DB+OBJECT_NAME的方式进行表示,所谓的namespace也比较重要下面是namespace的分类:

  • GLOBAL is used for the global read lock.
    • TABLESPACE is for tablespaces.
    • SCHEMA is for schemas (aka databases).
    • TABLE is for tables and views.
    • FUNCTION is for stored functions.
    • PROCEDURE is for stored procedures.
    • TRIGGER is for triggers.
    • EVENT is for event scheduler events.
    • COMMIT is for enabling the global read lock to block commits.
    • USER_LEVEL_LOCK is for user-level locks.
    • LOCKING_SERVICE is for the name plugin RW-lock service

本文我们主要对GLOBAL/SCHEMA/TABLE namespace进行描述,而对于COMMIT namespace是提交的时候会用到的如果遇到等待,状态为‘Waiting for commit lock’,一般为FTWRL堵塞COMMIT。可参考我的《深入理解MySQL主从原理》15节。其他namespace不做描述。

3、MDL Lock实现分类
  • scope lock:一般对应全局MDL Lock,如flush table with read lock 会获取namespace space:GLOBAL type:S和namespace space:COMMIT type:S的MDL Lock。它包含GLOBAL, COMMIT, TABLESPACE和SCHEMA
  • object lock:如其名字所示,对象级别的MDL Lock,比如TABLE级别的MDL Lock,这也是本文的讨论核心。 它包含其他的namespace。

下面是源码注释:


  
4、MDL Lock兼容矩阵

MySQL:理解MDL Lock

MySQL:理解MDL Lock

这里兼容矩阵是学习锁堵塞的重点,类型很多比Innodb row lock类型要多很多,不用记住,只需要遇到能知道。

5、MDL Lock duration(MDL Lock持续周期)

这个对应源码的enum_mdl_duration,通常我们需要关注MDL Lock是事务提交后释放还是语句结束后释放,实际上就是这个,这对MDL lock堵塞的范围很重要。我直接复制源码的解释。

  • MDL_STATEMENT:Locks with statement duration are automatically released at the end of statement or transaction.
  • MDL_TRANSACTION:Locks with transaction duration are automatically released at the end of transaction.
  • MDL_EXPLICIT:Locks with explicit duration survive the end of statement and transaction.They have to be released explicitly by calling MDL_context::release_lock().
6、MDL Lock的FAST PATH(unobtrusive)和SLOW PATH(obtrusive)

使用两种不同的方式目的在于优化MDL Lock的实现,下面是源码的注释,可做适当了解:

  • A) “unobtrusive” lock types
    1) Each type from this set should be compatible with all other types from the set (including itself).
    2) These types should be common for DML operations Our Goal is to optimize acquisition and release of locks of this type by avoiding complex checks and manipulations on m_waiting/m_granted bitmaps/lists. We replace them with a check of and increment/decrement of integer counters.We call the latter type of acquisition/release “fast path”.Use of “fast path” reduces the size of critical section associated with MDL_lock::m_rwlock lock in the common case and thus increases Scalability.The amount by which acquisition/release of specific type “unobtrusive” lock increases/decreases packed counter in MDL_lock::m_fast_path_state is returned by this function.
  • B) “obtrusive” lock types
    1) Granted or pending lock of those type is incompatible withsome other types of locks or with itself.
    2) Not common for DML operations These locks have to be always acquired involving manipulations on m_waiting/m_granted bitmaps/lists, i.e. we have to use “slow path” for them. Moreover in the presence of active/pending locks from “obtrusive” set we have to acquire using “slow path” even locks of”unobtrusive” type.
7、MDL_request结构部分属性

也就是通过语句解析后需要获得的MDL Lock的需求,然后通过这个类对象在MDL子系统中进行MDL Lock申请,大概包含如下一些属性:



  enum enum_mdl_type type; //需求的类型
  
  enum enum_mdl_duration duration; //持续周期
  
  MDL_request *next_in_list; //双向链表实现
  MDL_request **prev_in_list;
  
  MDL_ticket *ticket; //注意这里如果申请成功(没有等待),会指向一个实际的TICKET,否则为NULL
  
8、MDL_key结构部分属性

就是实际的namespace+DB+OBJECT_NAME,整个放到一个char数组里面,他会在MDL_LOCK和MDL_REQUEST中出现。


private:
uint16m_length;
uint16m_db_name_length;
charm_ptr[MAX_MDLKEY_LENGTH];//放到了这里

9、MDL_ticket结构部分属性

如同门票一样,如果获取了MDL Lock必然给MDL_request返回一张门票,如果等待则不会分配。源码MDL_context::acquire_lock可以观察到。部分属性如下:



  MDL_ticket *next_in_context;
  MDL_ticket **prev_in_context;
  
  MDL_ticket *next_in_lock;
  MDL_ticket **prev_in_lock;

  MDL_context *m_ctx; 
  
  MDL_lock *m_lock;
  
  bool m_is_fast_path;
  
10、MDL_lock结构部分属性

每一个MDL_key都会对应一个MDL_lock,其中包含了所谓的GRANTED链表和WAIT链表,考虑它的复杂性,可以直接参考源码注释也非常详细,这里给出我所描述的几个属性如下:



  MDL_key key;

  Ticket_list m_granted;

  Ticket_list m_waiting;
11、MDL_context结构部分属性

这是整个MySQL线程和MDL Lock子系统进行交互的一个所谓的上下文结构,其中包含了很多方法和属性,我比较关注的属性如下:



  MDL_wait m_wait;

Ticket_list m_tickets[MDL_DURATION_END];
//这是一个父类指针指向子类对象,虚函数重写的典型,实际他就指向了一个线程

MDL_context_owner *m_owner;
12、所有等待状态

源码给出了所有的等待标记如下:


PSI_stage_info MDL_key::m_namespace_to_wait_state_name[NAMESPACE_END]=
{
  {0, "Waiting for global read lock", 0},
  {0, "Waiting for tablespace metadata lock", 0},
  {0, "Waiting for schema metadata lock", 0},
  {0, "Waiting for table metadata lock", 0},
  {0, "Waiting for stored function metadata lock", 0},
  {0, "Waiting for stored procedure metadata lock", 0},
  {0, "Waiting for trigger metadata lock", 0},
  {0, "Waiting for event metadata lock", 0},
  {0, "Waiting for commit lock", 0},
  {0, "User lock", 0}, 
  {0, "Waiting for locking service lock", 0},
  {0, "Waiting for backup lock", 0},
  {0, "Waiting for binlog lock", 0}
};

我们常见的是:

  • “Waiting for table metadata lock”:通常就是namespace TABLE级别的MDL Lock,具体根据兼容矩阵判断参考第五节。
  • “Waiting for global read lock”:通常就是namespace GLOBAL级别的MDL Lock,通常和flush table with read lock有关,参考第五节。
  • “Waiting for commit lock”:通常就是namespace COMMIT级别的MDL Lock,通常和flush table with read lock有关,参考第五节。
三、增加打印函数my_print_ticket

学习MDL Lock最好的方式当然是获取一条语句锁加的所有MDL Lock,包含加锁、升级、降级和释放的流程。虽然5.7加入诊断MDL Lock的方法:


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

但是对于每个语句获取的所有MDL Lock的流程仍然不好观察,因此我加入了打印函数:



int my_print_ticket(const MDL_ticket* p_ticket)

并且在mdl_ticket类中增加了这个函数原型为友元函数:


friend int my_print_ticket(const MDL_ticket* p_ticket);

主要捕获MDL Lock的加锁信息打印到err日志中,包含的信息如下:

  • 线程id:通过p_ticket->m_ctx->get_thd(); 获取
  • mdl lock database name:通过p_ticket->m_lock->key.db_name()获取
  • mdl lock object name:通过p_ticket->m_lock->key.name()获取
  • mdl lock namespace:通过p_ticket->m_lock->key.mdl_namespace()获取
  • mdl lock fast path:通过p_ticket->m_is_fast_path获取判断是则输出否则不输出
  • mdl lock type:通过p_ticket->m_type获取
  • mdl lock duration:通过p_ticket->m_duration获取

上面这些信息都在前面进行过描述了。具体的输出信息如下:


2017-08-03T07:34:21.720583Z 3 [Note] (>MDL PRINT) Thread id is 3: 
2017-08-03T07:34:21.720601Z 3 [Note] (->MDL PRINT) DB_name is:test 
2017-08-03T07:34:21.720619Z 3 [Note] (-->MDL PRINT) OBJ_name is:test 
2017-08-03T07:34:21.720637Z 3 [Note] (--->MDL PRINT) Namespace is:TABLE 
2017-08-03T07:34:21.720655Z 3 [Note] (---->MDL PRINT) Fast path is:(Y)
2017-08-03T07:34:21.720673Z 3 [Note] (----->MDL PRINT) Mdl type is:MDL_SHARED_WRITE(SW) 
2017-08-03T07:34:21.720692Z 3 [Note] (------>MDL PRINT) Mdl  duration is:MDL_TRANSACTION

这实际上和metadata_locks中的信息差不多,如下:


MySQL>   select * from performance_schema.metadata_locks\G
*************************** 1. row ***************************
          OBJECT_TYPE: TABLE
        OBJECT_SCHEMA: test
          OBJECT_NAME: test
OBJECT_INSTANCE_BEGIN: 140734412907760
            LOCK_TYPE: SHARED_WRITE
        LOCK_DURATION: TRANSACTION
          LOCK_STATUS: GRANTED
               SOURCE: sql_parse.cc:6314
      OWNER_THREAD_ID: 39
       OWNER_EVENT_ID: 241

一旦有了这个函数我们只需要在加锁、升级、降级和释放的位置进行适当添加就可以了。

四、在合适的位置增加my_print_ticket打印函数

既然我们要研究MDL Lock的加锁?升级?降级,那么我们就必要找到他们的函数入口,然后在合适的位置增加打印函数my_print_ticket进行观察,下面标示出打印位置。

1、加锁:MDL_context::acquire_lock

bool
MDL_context::acquire_lock(MDL_request *mdl_request, ulong lock_wait_timeout)
{
  if (mdl_request->ticket) //获取成功获得ticket
  {
    
    //REQUESET获取TICKET成功 此处打印
    return FALSE;
  }
  
  //获取不成功加入MDL_lock 等待队列
  lock= ticket->m_lock;
  lock->m_waiting.add_ticket(ticket);
  will_wait_for(ticket); //死锁检测
  
  DEBUG_SYNC(get_thd(), "mdl_acquire_lock_wait");
  find_deadlock();
  //此处打印TICKET进入了等待流程
  if (lock->needs_notification(ticket) || lock->needs_connection_check())
  {
   }
  done_waiting_for();//等待完成对死锁检测等待图进行调整去掉本等待边edge(无向图)
  //当然到这里也是通过等待后获得成功了状态为GRANTED
  DBUG_ASSERT(wait_status == MDL_wait::GRANTED);
  m_tickets[mdl_request->duration].push_front(ticket);
  mdl_request->ticket= ticket;
  MySQL_mdl_set_status(ticket->m_psi, MDL_ticket::GRANTED);
  //此处打印通过等待REQUEST获得了TICKET
  return FALSE;
}
2、降级:void MDL_ticket::downgrade_lock(enum_mdl_type new_type)

void MDL_ticket::downgrade_lock(enum_mdl_type new_type)
{
  
  DBUG_ASSERT(m_type == MDL_EXCLUSIVE ||
              m_type == MDL_SHARED_NO_WRITE);
//此处打印出降级前的TICKET
  if (m_hton_notified)
  {
    MySQL_mdl_set_status(m_psi, MDL_ticket::POST_RELEASE_NOTIFY);
    m_ctx->get_owner()->notify_hton_post_release_exclusive(&m_lock->key);
    m_hton_notified= false;
    MySQL_mdl_set_status(m_psi, MDL_ticket::GRANTED);
  }
//函数结尾答应出降级后的TICKET
}
3、升级:MDL_context::upgrade_shared_lock(MDL_ticket *mdl_ticket,enum_mdl_type new_type, ulong lock_wait_timeout)

bool
MDL_context::upgrade_shared_lock(MDL_ticket *mdl_ticket,
                                 enum_mdl_type new_type,
                                 ulong lock_wait_timeout)
{
  MDL_REQUEST_INIT_BY_KEY(&mdl_new_lock_request,
                          &mdl_ticket->m_lock->key, new_type,
                          MDL_TRANSACTION);//构造一个request
 //此处打印出来的TICKET类型 
  if (acquire_lock(&mdl_new_lock_request, lock_wait_timeout)) //尝试使用新的LOCK_TYPE进行加锁
    DBUG_RETURN(TRUE);
  is_new_ticket= ! has_lock(mdl_svp, mdl_new_lock_request.ticket);
  lock= mdl_ticket->m_lock;
  //下面进行一系列对MDL_LOCK的维护并且对所谓的合并操作
  
  DBUG_ASSERT(lock->is_obtrusive_lock(new_type));
  
  MySQL_prlock_wrlock(&lock->m_rwlock);
  if (is_new_ticket)
  {
    m_tickets[MDL_TRANSACTION].remove(mdl_new_lock_request.ticket);
    MDL_ticket::destroy(mdl_new_lock_request.ticket);
  }
 //此处打印出来的升级后TICKET类型 
  DBUG_RETURN(FALSE);
}
4、释放:略

五、常见MDL Lock类型加锁测试

1、MDL_INTENTION_EXCLUSIVE(IX)

这个锁会在很多操作的时候都会出现,比如做任何一个DML/DDL操作都会触发,实际上DELTE/UPDATE/INSERT/FOR UPDATE等DML操作会在GLOBAL 上加IX锁,然后才会在本对象上加锁。而DDL 语句至少会在GLOBAL 上加IX锁,对象所属 SCHEMA上加IX锁,本对象加锁。

下面是 DELETE 触发的 GLOABL IX MDL LOCK:


2017-08-03T18:22:38.092205Z 3 [Note] (acquire_lock)THIS MDL LOCK acquire ok!
2017-08-03T18:22:38.092242Z 3 [Note] (>MDL PRINT) Thread id is 3: 
2017-08-03T18:22:38.092276Z 3 [Note] (--->MDL PRINT) Namespace is:GLOBAL 
2017-08-03T18:22:38.092310Z 3 [Note] (---->MDL PRINT) Fast path is:(Y)
2017-08-03T18:22:38.092344Z 3 [Note] (----->MDL PRINT) Mdl type is:MDL_INTENTION_EXCLUSIVE(IX) 
2017-08-03T18:22:38.092380Z 3 [Note] (------>MDL PRINT) Mdl  duration is:MDL_STATEMENT 
2017-08-03T18:22:38.092551Z 3 [Note] (------->MDL PRINT) Mdl  status is:EMPTY

我们注意一样它的持续周期为语句级别。

下面是 ALETER 语句触发的GLOABL IX MDL Lock:


2017-08-03T18:46:05.894871Z 3 [Note] (acquire_lock)THIS MDL LOCK acquire ok!
2017-08-03T18:46:05.894915Z 3 [Note] (>MDL PRINT) Thread id is 3: 
2017-08-03T18:46:05.894948Z 3 [Note] (--->MDL PRINT) Namespace is:GLOBAL 
2017-08-03T18:46:05.894980Z 3 [Note] (---->MDL PRINT) Fast path is:(Y)
2017-08-03T18:46:05.895012Z 3 [Note] (----->MDL PRINT) Mdl type is:MDL_INTENTION_EXCLUSIVE(IX) 
2017-08-03T18:46:05.895044Z 3 [Note] (------>MDL PRINT) Mdl  duration is:MDL_STATEMENT 
2017-08-03T18:46:05.895076Z 3 [Note] (------->MDL PRINT) Mdl  status is:EMPTY

所以这个MDL Lock无所不在,而只有是否兼容问题,如果不兼容则堵塞。scope lock的IX类型一般都是兼容的除非遇到S类型,下面讨论。

2、MDL_SHARED(S)

这把锁一般用在flush tables with read lock中,如下:


MySQL> flush tables with read lock;
Query OK, 0 rows affected (0.01 sec)
2017-08-03T18:19:11.603911Z 3 [Note] (acquire_lock)THIS MDL LOCK acquire ok!
2017-08-03T18:19:11.603947Z 3 [Note] (>MDL PRINT) Thread id is 3: 
2017-08-03T18:19:11.603971Z 3 [Note] (--->MDL PRINT) Namespace is:GLOBAL 
2017-08-03T18:19:11.603994Z 3 [Note] (----->MDL PRINT) Mdl type is:MDL_SHARED(S) 
2017-08-03T18:19:11.604045Z 3 [Note] (------>MDL PRINT) Mdl  duration is:MDL_EXPLICIT 
2017-08-03T18:19:11.604073Z 3 [Note] (------->MDL PRINT) Mdl  status is:EMPTY 
2017-08-03T18:19:11.604133Z 3 [Note] (acquire_lock)THIS MDL LOCK acquire ok!
2017-08-03T18:19:11.604156Z 3 [Note] (>MDL PRINT) Thread id is 3: 
2017-08-03T18:19:11.604194Z 3 [Note] (--->MDL PRINT) Namespace is:COMMIT 
2017-08-03T18:19:11.604217Z 3 [Note] (----->MDL PRINT) Mdl type is:MDL_SHARED(S) 
2017-08-03T18:19:11.604240Z 3 [Note] (------>MDL PRINT) Mdl  duration is:MDL_EXPLICIT 
2017-08-03T18:19:11.604310Z 3 [Note] (------->MDL PRINT) Mdl  status is:EMPTY

我们注意到其namspace为GLOBAL和COMMIT显然他们是scope lock ,他们的TYPE为S,那么很显然根据兼容性原则scope lock的MDL IX和MDL S 不兼容, flush tables with read lock 就会堵塞所有DELTE/UPDATE/INSERT/FOR UPDATE等DML和DDL操作,并且也会堵塞commit操作。

3、MDL_SHARED_HIGH_PRIO(SH)

这个锁基本上大家也是经常用到只是没感觉到而已,比如我们一般desc操作,兼容矩阵如下:
MySQL:理解MDL Lock

操作记录如下:


MySQL> desc test.testsort10;
2017-08-03T19:06:05.843277Z 4 [Note] (acquire_lock)THIS MDL LOCK acquire ok!
2017-08-03T19:06:05.843324Z 4 [Note] (>MDL PRINT) Thread id is 4: 
2017-08-03T19:06:05.843359Z 4 [Note] (->MDL PRINT) DB_name is:test 
2017-08-03T19:06:05.843392Z 4 [Note] (-->MDL PRINT) OBJ_name is:testsort10 
2017-08-03T19:06:05.843425Z 4 [Note] (--->MDL PRINT) Namespace is:TABLE 
2017-08-03T19:06:05.843456Z 4 [Note] (---->MDL PRINT) Fast path is:(Y)
2017-08-03T19:06:05.843506Z 4 [Note] (----->MDL PRINT) Mdl type is:MDL_SHARED_HIGH_PRIO(SH) 
2017-08-03T19:06:05.843538Z 4 [Note] (------>MDL PRINT) Mdl  duration is:MDL_TRANSACTION 
2017-08-03T19:06:05.843570Z 4 [Note] (------->MDL PRINT) Mdl  status is:EMPTY

这中类型的优先级比较高,但是其和X不兼容。注意持续时间为MDL_TRANSACTION 。

4、MDL_SHARED_READ(SR)

这把锁一般用在非当前读取的select中,兼容性如下:
MySQL:理解MDL Lock

操作记录如下:


MySQL> select * from test.testsort10 limit 1;
2017-08-03T19:13:52.338764Z 4 [Note] (acquire_lock)THIS MDL LOCK acquire ok!
2017-08-03T19:13:52.338813Z 4 [Note] (>MDL PRINT) Thread id is 4: 
2017-08-03T19:13:52.338847Z 4 [Note] (->MDL PRINT) DB_name is:test 
2017-08-03T19:13:52.338883Z 4 [Note] (-->MDL PRINT) OBJ_name is:testsort10 
2017-08-03T19:13:52.338917Z 4 [Note] (--->MDL PRINT) Namespace is:TABLE 
2017-08-03T19:13:52.338950Z 4 [Note] (---->MDL PRINT) Fast path is:(Y)
2017-08-03T19:13:52.339025Z 4 [Note] (----->MDL PRINT) Mdl type is:MDL_SHARED_READ(SR) 
2017-08-03T19:13:52.339062Z 4 [Note] (------>MDL PRINT) Mdl  duration is:MDL_TRANSACTION 
2017-08-03T19:13:52.339097Z 4 [Note] (------->MDL PRINT) Mdl  status is:EMPTY

这里还是要提及一下平时我们偶尔会出现select也堵住的情况(比如DDL的某个阶段需要对象MDL X锁)。我们不得不抱怨MySQL居然会堵塞select其实这里也就是object mdl lock X 和SR 不兼容的问题(参考前面的兼容矩阵)。注意持续时间为MDL_TRANSACTION 。

5、MDL_SHARED_WRITE(SW)

这把锁一般用于DELTE/UPDATE/INSERT/FOR UPDATE等操作对table的加锁(当前读),不包含DDL操作,但是要注意DML操作实际上还会有一个GLOBAL的IX的锁,前面已经提及过了,这把锁只是对象上的,兼容性如下:

MySQL:理解MDL Lock

操作记录如下:


MySQL> select * from test.testsort10 limit 1 for update;
2017-08-03T19:25:41.218428Z 4 [Note] (acquire_lock)THIS MDL LOCK acquire ok!
2017-08-03T19:25:41.218461Z 4 [Note] (>MDL PRINT) Thread id is 4: 
2017-08-03T19:25:41.218493Z 4 [Note] (->MDL PRINT) DB_name is:test 
2017-08-03T19:25:41.218525Z 4 [Note] (-->MDL PRINT) OBJ_name is:testsort10 
2017-08-03T19:25:41.218557Z 4 [Note] (--->MDL PRINT) Namespace is:TABLE 
2017-08-03T19:25:41.218588Z 4 [Note] (---->MDL PRINT) Fast path is:(Y)
2017-08-03T19:25:41.218620Z 4 [Note] (----->MDL PRINT) Mdl type is:MDL_SHARED_WRITE(SW) 
2017-08-03T19:25:41.218677Z 4 [Note] (------>MDL PRINT) Mdl  duration is:MDL_TRANSACTION 
2017-08-03T19:25:41.218874Z 4 [Note] (------->MDL PRINT) Mdl  status is:EMPTY

注意持续时间为MDL_TRANSACTION 。

6、MDL_SHARED_WRITE_LOW_PRIO(SWL)

这把锁很少用到源码注释只有如下:


Used by DML statements modifying tables and using the LOW_PRIORITY clause

不做解释了。

7、MDL_SHARED_UPGRADABLE(SU)

这把锁一般在ALTER TABLE语句中会用到,他可以升级为SNW,、SNRW、X,同时至少X锁也可以降级为SU实际上在Innodb ONLINE DDL中非常依赖它,由于它的存在那么DML(SW)和SELECT(SR)都不会堵塞,兼容性如下:
MySQL:理解MDL Lock

我们有必要研究一下他的兼容性,可以看到 OBJECT LOCK中(SELECT)SR和(DML)SW都是允许的,而在SCOPED LOCK中虽然DML DDL都会在GLOBAL 上锁,但是其类型都是IX。所以这个SU锁不堵塞DML/SELECT 读写操作进入Innodb引擎层,它是ONLINE DDL的基础。如果不兼容你都进入不了Innodb引擎层,更谈不上什么ONLINE DDL,注意我这里说的ALGORITHM=INPLACE的ONLINE DDL。

操作日志记录:


MySQL>  alter table testsort12 add column it int not null;
Query OK, 0 rows affected (6.27 sec)
Records: 0  Duplicates: 0  Warnings: 0
2017-08-03T19:46:54.781453Z 3 [Note] (acquire_lock)THIS MDL LOCK acquire ok!
2017-08-03T19:46:54.781487Z 3 [Note] (>MDL PRINT) Thread id is 3: 
2017-08-03T19:46:54.781948Z 3 [Note] (->MDL PRINT) DB_name is:test 
2017-08-03T19:46:54.781990Z 3 [Note] (-->MDL PRINT) OBJ_name is:testsort12 
2017-08-03T19:46:54.782026Z 3 [Note] (--->MDL PRINT) Namespace is:TABLE 
2017-08-03T19:46:54.782060Z 3 [Note] (----->MDL PRINT) Mdl type is:MDL_SHARED_UPGRADABLE(SU) 
2017-08-03T19:46:54.782096Z 3 [Note] (------>MDL PRINT) Mdl  duration is:MDL_TRANSACTION 
2017-08-03T19:46:54.782175Z 3 [Note] (------->MDL PRINT) Mdl  status is:EMPTY 
2017-08-03T19:46:54.803898Z 3 [Note] (upgrade_shared_lock)THIS MDL LOCK will upgrade
2017-08-03T19:46:54.804201Z 3 [Note] (upgrade_shared_lock)THIS MDL LOCK  upgrade TO
2017-08-03T19:46:54.804240Z 3 [Note] (>MDL PRINT) Thread id is 3: 
2017-08-03T19:46:54.804254Z 3 [Note] (->MDL PRINT) DB_name is:test 
2017-08-03T19:46:54.804267Z 3 [Note] (-->MDL PRINT) OBJ_name is:testsort12 
2017-08-03T19:46:54.804280Z 3 [Note] (--->MDL PRINT) Namespace is:TABLE 
2017-08-03T19:46:54.804293Z 3 [Note] (----->MDL PRINT) Mdl type :MDL_EXCLUSIVE(X) 
2017-08-03T19:46:54.804306Z 3 [Note] (------>MDL PRINT) Mdl  duration is:MDL_TRANSACTION 
2017-08-03T19:46:54.804319Z 3 [Note] (------->MDL PRINT) Mdl  status is:EMPTY 
2017-08-03T19:46:54.855563Z 3 [Note] (downgrade_lock)THIS MDL LOCK will downgrade
2017-08-03T19:46:54.855693Z 3 [Note] (downgrade_lock) to this MDL lock
2017-08-03T19:46:54.855706Z 3 [Note] (>MDL PRINT) Thread id is 3: 
2017-08-03T19:46:54.855717Z 3 [Note] (->MDL PRINT) DB_name is:test 
2017-08-03T19:46:54.856053Z 3 [Note] (-->MDL PRINT) OBJ_name is:testsort12 
2017-08-03T19:46:54.856069Z 3 [Note] (--->MDL PRINT) Namespace is:TABLE 
2017-08-03T19:46:54.856082Z 3 [Note] (----->MDL PRINT) Mdl type is:MDL_SHARED_UPGRADABLE(SU) 
2017-08-03T19:46:54.856094Z 3 [Note] (------>MDL PRINT) Mdl  duration is:MDL_TRANSACTION 
2017-08-03T19:46:54.856214Z 3 [Note] (------->MDL PRINT) Mdl  status is:EMPTY 
2017-08-03T19:47:00.260166Z 3 [Note] (upgrade_shared_lock)THIS MDL LOCK will upgrade
2017-08-03T19:47:00.304057Z 3 [Note] (upgrade_shared_lock)THIS MDL LOCK  upgrade TO
2017-08-03T19:47:00.304090Z 3 [Note] (>MDL PRINT) Thread id is 3: 
2017-08-03T19:47:00.304105Z 3 [Note] (->MDL PRINT) DB_name is:test 
2017-08-03T19:47:00.304119Z 3 [Note] (-->MDL PRINT) OBJ_name is:testsort12 
2017-08-03T19:47:00.304132Z 3 [Note] (--->MDL PRINT) Namespace is:TABLE 
2017-08-03T19:47:00.304181Z 3 [Note] (----->MDL PRINT) Mdl type is:MDL_EXCLUSIVE(X) 
2017-08-03T19:47:00.304196Z 3 [Note] (------>MDL PRINT) Mdl  duration is:MDL_TRANSACTION 
2017-08-03T19:47:00.304211Z 3 [Note] (------->MDL PRINT) Mdl  status is:EMPTY 
2017-08-03T19:47:01.032329Z 3 [Note] (acquire_lock)THIS MDL LOCK acquire ok!

我们需要简单分析一下,获得testsort12表上的MDL Lock大概流程如下:


2017-08-03T19:46:54.781487 获得 MDL_SHARED_UPGRADABLE(SU)
2017-08-03T19:46:54.804293 升级 MDL_EXCLUSIVE(X)  准备阶段
2017-08-03T19:46:54.855563 降级 MDL_SHARED_UPGRADABLE(SU) 执行阶段
2017-08-03T19:47:00.304057 升级 MDL_EXCLUSIVE(X)  提交阶段

不管如何这个ALTER操作还是比较费时的,从时间我们看到2017-08-03T19:46:54降级完成(SU)到2017-08-03T19:47:00这段时间,实际上是最耗时的实际上这里就是实际的Inplace重建,但是这个过程实际在MDL SU模式下所以不会堵塞DML/SELECT操作。这里再给大家提个醒,所谓的ONLINE DDL只是在Inplace重建阶段不堵塞DML/SELECT操作,还是尽量在数据库压力小的时候操作,如果有DML没有提交或者SELECT没有做完这个时候SW或者SR必然堵塞X,而X为高优先级能够堵塞所有操作。这样导致的现象就是由于DML未提交会堵塞DDL操作,而DDL操作会堵塞所有操作,基本对于这个TABLE的表全部操作堵塞(SW堵塞X,X堵塞所有操作)。
而对于ALGORITHM=COPY 在COPY阶段用的是SNW锁,接下来我就先来看看SNW锁。

8、MDL_SHARED_NO_WRITE(SNW)

SU可以升级为SNW而SNW可以升级为X,如前面所提及的用于ALGORITHM=COPY 中,保护数据的一致性。先看看它的兼容性如下:
MySQL:理解MDL Lock

从兼容矩阵可以看到,本锁不会堵塞SR,但是堵塞SW,当然也就堵塞了DML(SW)而SELECT(SR)不会堵塞,下面是部分操作记录日志:


MySQL>  alter table testsort12 add column ik int not null, ALGORITHM=COPY  ;
2017-08-03T20:07:58.413215Z 3 [Note] (upgrade_shared_lock)THIS MDL LOCK  upgrade TO
2017-08-03T20:07:58.413241Z 3 [Note] (>MDL PRINT) Thread id is 3: 
2017-08-03T20:07:58.413257Z 3 [Note] (->MDL PRINT) DB_name is:test 
2017-08-03T20:07:58.413273Z 3 [Note] (-->MDL PRINT) OBJ_name is:testsort12 
2017-08-03T20:07:58.413292Z 3 [Note] (--->MDL PRINT) Namespace is:TABLE 
2017-08-03T20:07:58.413308Z 3 [Note] (----->MDL PRINT) Mdl type is:MDL_SHARED_NO_WRITE(SNW) 
2017-08-03T20:07:58.413325Z 3 [Note] (------>MDL PRINT) Mdl  duration is:MDL_TRANSACTION 
2017-08-03T20:07:58.413341Z 3 [Note] (------->MDL PRINT) Mdl  status is:EMPTY 
2017-08-03T20:08:25.392006Z 3 [Note] (upgrade_shared_lock)THIS MDL LOCK  upgrade TO
2017-08-03T20:08:25.392024Z 3 [Note] (>MDL PRINT) Thread id is 3: 
2017-08-03T20:08:25.392086Z 3 [Note] (->MDL PRINT) DB_name is:test 
2017-08-03T20:08:25.392159Z 3 [Note] (-->MDL PRINT) OBJ_name is:testsort12 
2017-08-03T20:08:25.392199Z 3 [Note] (--->MDL PRINT) Namespace is:TABLE 
2017-08-03T20:08:25.392214Z 3 [Note] (----->MDL PRINT) Mdl type is:MDL_EXCLUSIVE(X) 
2017-08-03T20:08:25.392228Z 3 [Note] (------>MDL PRINT) Mdl  duration is:MDL_TRANSACTION 
2017-08-03T20:08:25.392242Z 3 [Note] (------->MDL PRINT) Mdl  status is:EMPTY

我们可以发现如下:


2017-08-03T20:07:58.413308 获得了MDL_SHARED_NO_WRITE(SNW) 
2017-08-03T20:08:25.392006 升级为MDL_EXCLUSIVE(X)

2017-08-03T20:07:58.413308到2017-08-03T20:08:25.392006就是实际COPY的时间,可见整个COPY期间只能SELECT,而不能DML。也是ALGORITHM=COPY和ALGORITHM=INPLACE的一个关键区别。

9、MDL_SHARED_READ_ONLY(SRO)

用于LOCK TABLES READ 语句,兼容性如下:
MySQL:理解MDL Lock

根据兼容性可以发现,堵塞DML(SW)但是SELECT(SR)还是可以的。下面是操作日志:


MySQL> lock table testsort12 read;
Query OK, 0 rows affected (0.01 sec)
2017-08-03T21:08:27.267947Z 3 [Note] (acquire_lock)THIS MDL LOCK acquire ok!
2017-08-03T21:08:27.267979Z 3 [Note] (>MDL PRINT) Thread id is 3: 
2017-08-03T21:08:27.268009Z 3 [Note] (->MDL PRINT) DB_name is:test 
2017-08-03T21:08:27.268040Z 3 [Note] (-->MDL PRINT) OBJ_name is:testsort12 
2017-08-03T21:08:27.268070Z 3 [Note] (--->MDL PRINT) Namespace is:TABLE 
2017-08-03T21:08:27.268113Z 3 [Note] (----->MDL PRINT) Mdl type is:MDL_SHARED_READ_ONLY(SRO) 
2017-08-03T21:08:27.268145Z 3 [Note] (------>MDL PRINT) Mdl  duration is:MDL_TRANSACTION 
2017-08-03T21:08:27.268175Z 3 [Note] (------->MDL PRINT) Mdl  status is:EMPTY
10、MDL_SHARED_NO_READ_WRITE(SNRW)

用于LOCK TABLES WRITE语句,兼容性如下:
MySQL:理解MDL Lock

可以看到DML(SW)和SELECT(SR)都被它堵塞,但是还可以DESC(SH)。
操作日志记录如下:


MySQL> lock table testsort12 write;
Query OK, 0 rows affected (0.00 sec)
2017-08-03T21:13:07.113347Z 3 [Note] (acquire_lock)THIS MDL LOCK acquire ok!
2017-08-03T21:13:07.113407Z 3 [Note] (>MDL PRINT) Thread id is 3: 
2017-08-03T21:13:07.113435Z 3 [Note] (--->MDL PRINT) Namespace is:GLOBAL 
2017-08-03T21:13:07.113458Z 3 [Note] (---->MDL PRINT) Fast path is:(Y)
2017-08-03T21:13:07.113482Z 3 [Note] (----->MDL PRINT) Mdl type is:MDL_INTENTION_EXCLUSIVE(IX) 
2017-08-03T21:13:07.113505Z 3 [Note] (------>MDL PRINT) Mdl  duration is:MDL_STATEMENT 
2017-08-03T21:13:07.113604Z 3 [Note] (------->MDL PRINT) Mdl  status is:EMPTY 
2017-08-03T21:13:07.113637Z 3 [Note] (acquire_lock)THIS MDL LOCK acquire ok!
2017-08-03T21:13:07.113660Z 3 [Note] (>MDL PRINT) Thread id is 3: 
2017-08-03T21:13:07.113681Z 3 [Note] (->MDL PRINT) DB_name is:test 
2017-08-03T21:13:07.113703Z 3 [Note] (-->MDL PRINT) OBJ_name is: 
2017-08-03T21:13:07.113725Z 3 [Note] (--->MDL PRINT) Namespace is:SCHEMA 
2017-08-03T21:13:07.113746Z 3 [Note] (---->MDL PRINT) Fast path is:(Y)
2017-08-03T21:13:07.113768Z 3 [Note] (----->MDL PRINT) Mdl type is:MDL_INTENTION_EXCLUSIVE(IX) 
2017-08-03T21:13:07.113791Z 3 [Note] (------>MDL PRINT) Mdl  duration is:MDL_TRANSACTION 
2017-08-03T21:13:07.113813Z 3 [Note] (------->MDL PRINT) Mdl  status is:EMPTY 
2017-08-03T21:13:07.113842Z 3 [Note] (acquire_lock)THIS MDL LOCK acquire ok!
2017-08-03T21:13:07.113865Z 3 [Note] (>MDL PRINT) Thread id is 3: 
2017-08-03T21:13:07.113887Z 3 [Note] (->MDL PRINT) DB_name is:test 
2017-08-03T21:13:07.113922Z 3 [Note] (-->MDL PRINT) OBJ_name is:testsort12 
2017-08-03T21:13:07.113945Z 3 [Note] (--->MDL PRINT) Namespace is:TABLE 
2017-08-03T21:13:07.113975Z 3 [Note] (----->MDL PRINT) Mdl type is:MDL_SHARED_NO_READ_WRITE(SNRW) 
2017-08-03T21:13:07.113998Z 3 [Note] (------>MDL PRINT) Mdl  duration is:MDL_TRANSACTION 
2017-08-03T21:13:07.114021Z 3 [Note] (------->MDL PRINT) Mdl  status is:EMPTY

除此之外可以发现语句还需要GLOBAL和SCHEMA上的IX锁,换句话说flush tables with read lock; 会堵塞‘lock table testsort12 write’,但是‘lock table testsort12 read’却不会堵塞。

11、MDL_EXCLUSIVE(X)

用于各种DDL操作,实际上基本全部的DDL都会涉及到这个锁,即便是ONLINE DDL也会在准备和提交阶段获取本锁,因此ONLINE DDL不是完全不堵塞的,只是堵塞时间很短很短,兼容性如下:

MySQL:理解MDL Lock

我们在验证SU和SNW MDL Lock类型的时候已经看到了操作记录,不做补充了。

作者微信:gp_22389860

您可能感兴趣的文档:

--结束END--

本文标题: MySQL:理解MDL Lock

本文链接: https://lsjlt.com/news/47336.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • MySQL:理解MDL Lock
    本文基于源码版本5.7.14 水...
    99+
    2024-04-02
  • MYSQL METADATA LOCK(MDL LOCK)MDL锁问题的示例分析
    小编给大家分享一下MYSQL METADATA LOCK(MDL LOCK)MDL锁问题的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一...
    99+
    2024-04-02
  • MYSQL METADATA LOCK(MDL LOCK) 理论及加锁类型测试
    目录MYSQL METADATA LOCK(MDL LOCK)学习 理论知识和加锁类型测试 一、初步了解二、基础重要的数据结构(类)和概念1、MDL TYPE2、MDL N...
    99+
    2024-04-02
  • MySQL Innodb怎么让MDL LOCK和ROW LOCK记录到errlog
    本篇内容主要讲解“MySQL Innodb怎么让MDL LOCK和ROW LOCK记录到errlog”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“MySQL I...
    99+
    2024-04-02
  • MYSQL中一个特殊的MDL LOCK死锁的示例分析
    本篇文章为大家展示了MYSQL中一个特殊的MDL LOCK死锁的示例分析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。一、问题由来前段开发反馈时间线上数据库老是出现...
    99+
    2024-04-02
  • 如何理解mysql innodb lock锁中的record lock
    本篇文章给大家分享的是有关如何理解mysql innodb lock锁中的record lock,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。...
    99+
    2024-04-02
  • 如何理解mysql Meta Lock 机制
    如何理解mysql Meta Lock 机制,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。  &nb...
    99+
    2024-04-02
  • 怎么理解MySQL中的Waiting for commit lock
    这篇文章主要介绍“怎么理解MySQL中的Waiting for commit lock”,在日常操作中,相信很多人在怎么理解MySQL中的Waiting for commit lock问题上存在疑惑,小编查...
    99+
    2024-04-02
  • mysql innodb lock机制原理
    一.lock 和 latch latch 一般称为闩锁,目的是用来保证并发线程操作临界资源的正确性,无死锁检测机制。分为:mutex(互斥量),rwlock(读写锁) lock 的对象是事务,用来锁定的数据...
    99+
    2024-04-02
  • 如何使用mysql观测MDL锁
    如何使用mysql观测MDL锁,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。在给一个小表加字段的时候,一直拿不到锁,报错:ERROR ...
    99+
    2024-04-02
  • MySQL MDL类型和兼容矩阵
    MySQL的MDL(Metadata Locking)是一种用于管理和控制数据库对象的锁机制。MDL锁定允许同时对同一对象进行读取和...
    99+
    2024-04-09
    MySQL
  • 详细分析mysql MDL元数据锁
    前言: 当你在MySQL中执行一条SQL时,语句并没有在你预期的时间内执行完成,这时候我们通常会登陆到MySQL数据库上查看是不是出了什么问题,通常会使用的一个命令就是 show processlist,看看有哪些s...
    99+
    2022-05-23
    mysql mdl锁 mysql 元数据锁 mysql mdl元数据锁
  • 一文搞懂MySQL元数据锁(MDL)
    目录一、什么是metadata lock二、MDL和行锁有什么区别三、MDL为什么会造成系统崩溃四、MDL的生命周期有多长五、如何快速找到阻塞源头六、本文开始的案例最终如何解决小结某日,路上收到用户咨询,为了清除空间,想...
    99+
    2024-04-02
  • MySQL 由于MDL读锁select被阻塞
    thread 1、begin; 更新表;没有提交,也没有回滚操作 thread2、create index 在这个表上 这时候客户端超时中断 再次连接会话查询此表被阻塞,无法查询 thread3、查询...
    99+
    2024-04-02
  • MySQL中如何使用MDL字典锁
    这篇文章将为大家详细讲解有关MySQL中如何使用MDL字典锁,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。 什么是MDL   ...
    99+
    2024-04-02
  • 【Mysql】metadata lock锁
    原文地址:http://blog.itpub.net/26250550/viewspace-1071987/ metadata lock的超时时间是lock_wait_timeout,并不是innodb_l...
    99+
    2024-04-02
  • 怎么理解mysql中FLUSH TABLES和FLUSH TABLES WITH READ LOCK
    这篇文章主要讲解了“怎么理解mysql中FLUSH TABLES和FLUSH TABLES WITH READ LOCK”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研...
    99+
    2024-04-02
  • 怎么理解Oracle的lock和latch
    这篇文章主要介绍“怎么理解Oracle的lock和latch”,在日常操作中,相信很多人在怎么理解Oracle的lock和latch问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解...
    99+
    2024-04-02
  • 【MySQL】MySQL gap lock产生时机
    1)提出问题:我们通常说在Repeate read下面,会有next-key lock(LOCK_ORDINARY)对应值0,而READ COMMITTED隔离级别下只会有记录锁LOCK_REC_NOT_G...
    99+
    2024-04-02
  • mysql 间隙锁 Gap Lock
      MySQL InnoDB支持三种行锁定方式: 行锁(Record Lock):锁直接加在索引记录上面。 间隙锁(Gap Lock):锁加在不存在的空闲空间,可以是两个索引记录之间,也可能是第一...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作