MySQL锁系列3 MDL锁

MySQL为了保护数据字典元数据,使用了metadata lock,即MDL锁,保证在并发的情况下,结构变更的一致性。

MDL锁的加锁模式和源码上的组织上和上一篇blog中MySQL表锁的实现方式一致,都采用了【mutex+condition+queue】来实现并发,阻塞,唤醒的控制。

下面就来看看MDL锁:

1. 重要的数据结构:

     

1. MDL_map

  mdl_map使用hash表,保存了MySQL所有的mdl_lock,全局共享,使用MDL_KEY作为key来表,key=【db_name+table_name】唯一定位一个表。

2. mdl_context

  mdl_context在MySQL为每一个connection创建thd时,初始化一个mdl上下文,保存了当前session请求的mdl信息。

3. MDL_lock

  mdl_lock表示系统的一个mdl锁,所有的mdl request都请求对应的mdl_lock,这个mdl_lock结构保存了两个queue,一个是grant_queue表示拿到lock的请求队列。

  一个是wait_queue表示请求这个mdl_lock的阻塞队列。

4. MDL_wait

  mdl_wait包装了一个mutex和一个condition,提供了所有的加锁,wait,notify操作。

5. MDL_request

  在open table的时候,会init一个request,包含了请求的enum_mdl_type,enum_mdl_duration,MDL_ticket,MDL_key。

下面再看看三个重要的枚举类型:

  enum enum_mdl_namespace { GLOBAL=0,
                            SCHEMA,
                            TABLE,
                            FUNCTION,
                            PROCEDURE,
                            TRIGGER,
                            EVENT,
                            COMMIT,
                            /* This should be the last ! */
                            NAMESPACE_END };
enum enum_mdl_duration {
  /**
    Locks with statement duration are automatically released at the end
    of statement or transaction.
  */
  MDL_STATEMENT= 0,
  /**
    Locks with transaction duration are automatically released at the end
    of transaction.
  */
  MDL_TRANSACTION,
  /**
    Locks with explicit duration survive the end of statement and transaction.
    They have to be released explicitly by calling MDL_context::release_lock().
  */
  MDL_EXPLICIT,
  /* This should be the last ! */
  MDL_DURATION_END };
enum enum_mdl_type {
  MDL_INTENTION_EXCLUSIVE= 0,
  MDL_SHARED,
  MDL_SHARED_HIGH_PRIO,
  MDL_SHARED_READ,
  MDL_SHARED_WRITE,
  MDL_SHARED_NO_WRITE,
  MDL_SHARED_NO_READ_WRITE,
  MDL_EXCLUSIVE,
  MDL_TYPE_END};

首先:enum_mdl_namespace 表示mdl_request的作用域,比如alter table操作,需要获取TABLE作用域。

然后:enum_mdl_duration 表示mdl_request的持久类型,比如alter table操作,类型是MDL_STATEMENT,即语句结束,就释放mdl锁。又比如autocommit=0;select 操作,类型是MDL_TRANSACTION,必须在显示的commit,才释放mdl锁。

最后:enum_mdl_type 表示mdl_request的lock类型,根据这个枚举类型,来判断是否兼容和互斥。

2. 测试

下面根据一个测试,看一下加锁,释放,阻塞的过程,已经主要的函数调用栈:

  session1:            session2:

    set autocommit=0;        alter table pp add name varchar(100):

    select * from pp;

2.1 创建connection过程中,初始化mdl_context.

  函数调用:

    handle_connections_sockets

      MDL_context::init: 每一个connection对应一个mdl_context

2.2 初始化mdl_request

  函数调用:

    parse_sql

      st_select_lex::add_table_to_list
        MDL_request::init

  说明: 在session1的过程中,创建的mdl_request:

      mdl_namespace=MDL_key::TABLE,

      db_arg=0x8c7047c8 "xpchild",

      name_arg=0x8c7047d0 "pp",
      mdl_type_arg=MDL_SHARED_READ,

      mdl_duration_arg=MDL_TRANSACTION

2.3 加锁

acquire_lock:

if (lock->can_grant_lock(mdl_request->type, this))
 {
    lock->m_granted.add_ticket(ticket);
    mysql_prlock_unlock(&lock->m_rwlock);
    m_tickets[mdl_request->duration].push_front(ticket);
    mdl_request->ticket= ticket;
  }

说明:首先进行兼容性判断,如果兼容,那么就把ticket加入到队列中,加锁成功。

  函数调用栈

   open_and_lock_tables

    open_table

1. 排他锁使用
  lock_table_names
  MDL_context::acquire_locks
2. 共享锁使用
  open_table_get_mdl_lock
  MDL_context::try_acquire_lock

2.4 阻塞

  下面进入session2. 因为session1拿到了pp表的share读锁,但session2的alter操作的mdl_request类型是:MDL_INTENTION_EXCLUSIVE,兼容性判断是互斥,所以ddl被阻塞。

while (!m_wait_status && !thd_killed(thd) &&
         wait_result != ETIMEDOUT && wait_result != ETIME)
  {
    wait_result= mysql_cond_timedwait(&m_COND_wait_status, &m_LOCK_wait_status,abs_timeout);
  }

说明:上面的这段代码,session2进入阻塞状态,等待超时或者mdl_wait中的条件变量。

2.5 唤醒

session1进行提交动作,commit。 然后session1 release mdl_lock,最后wake up session2.  session 2完成alte操作。

    MDL_context::release_lock();
        lock->remove_ticket();
            reschedule_waiters();
            while ((ticket= it++))
        {
          if (can_grant_lock(ticket->get_type(), ticket->get_ctx()))
           {
              if (! ticket->get_ctx()->m_wait.set_status(MDL_wait::GRANTED))

    MDL_wait::set_status();
        mysql_cond_signal(&m_COND_wait_status);

说明: commit操作,释放session 1持有的mdl事务锁,然后遍历wait队列,判断兼容性测试,最后wakeup session2.

总结: 根据上面的测试,我们看到,mdl的机制和表锁的机制基本一致性,但从上面的测试和源码的设计上,也看到MySQL表锁,mdl锁令人蛋疼的地方。

3. 蛋疼的锁

下面简单介绍下MySQL锁令人蛋疼的两个地方:

1. 事务开始begin transaction的位置

  •   MySQL的设计:在设置的autocommit=0;read_commited的时候,无论session的第一条语句是select还是dml,都开始一个事务,然后直到commit,所持有的MDL锁也一直维持到commit结束。
  •   Oracle的设计:在session的第一条更新语句发起时,才创建transaction,在读多的系统上,减少了阻塞的发生可能性。特别是在开发人员发起select语句时,认为没有更新,就不再commit。但在MySQL上,发起select语句,而忘记commit,是非常危险的。

2. ddl语句阻塞

  •   MySQL的设计:ddl语句发起时,如果无法获取排他锁,那么ddl将进入阻塞状态,但由于是queue的设计,就阻塞了后续所有的dml和selec操作,在高并发系统上,可能会引起雪崩。
  •       Oracle的设计:在oracle 11g之前,ddl语句是fast fail的,不进入阻塞状态,所以繁忙的表进行ddl操作时,经常遇到的错误:ORA-00054: resource busy。但在11g之后虽然可以进行阻塞,并提供了ddl_time_out这样的参数进行控制,但在高并发的系统上,运维的操作依然不采用,而是fast fail。

后话:

  这里可以参照oracle的设计进行改良,ddl语句阻塞相对改源码来说,比较简单。而事务开始的位置,牵涉到mvcc和事务隔离级别,改动会比较大。

下一篇blog介绍下innodb的锁。

MySQL锁系列3 MDL锁

时间: 2024-08-14 08:17:34

MySQL锁系列3 MDL锁的相关文章

MySQL锁系列2 表锁

上一篇介绍了MySQL源码中保护内存结构或变量的锁,这里开始介绍下MySQL事务中的表锁. 注1: 在表锁的实现上面,使用[mutex+condition+queue]的结构实现并发,阻塞,唤醒的表锁功能. 注2: 本文进行的一些实验,重要的配置项: 1. autocommit=0 2. tx_isolation=read-commited 3. engine=innodb 1. MySQL加锁的顺序: 这里包括了一个sql在执行的过程中,包括commit,所添加和释放跟事务相关的锁以及加不同锁

Innodb 锁系列2 事务锁

上一篇介绍了Innodb的同步机制锁:Innodb锁系列1 这一篇介绍一下Innodb的事务锁,只所以称为事务锁,是因为Innodb为实现事务的ACID特性,而添加的表锁或者行级锁. 这一部分分两篇来介绍,先来介绍下事务锁相关的数据结构 事务锁数据结构 1. 锁模式 /* Basic lock modes */ enum lock_mode { LOCK_IS = 0, /* intention shared */ LOCK_IX, /* intention exclusive */ LOCK_

MySQL锁系列1

MySQL的锁:MySQL内部有很多种类的锁,按照用途不同,可以分为两类:1. 保护内存结构的锁 server层对于线程共享的变量,基本上使用mutex,rwlock来做保护. innodb层会增加使用spinlock自旋锁 2. 提供或者保证事务性功能的锁 server提供了MDL锁,表锁两种锁 innodb实现行级锁 原子操作: 在单处理器系统(UniProcessor)结构中,在一个指令周期内完成的都是原子指令,因为硬中断的响应是在每一个cpu指令完成后check的. 而在多处理器结构(S

Mysql高手系列 - 第26篇:聊聊如何使用mysql实现分布式锁

Mysql系列的目标是:通过这个系列从入门到全面掌握一个高级开发所需要的全部技能. 欢迎大家加我微信itsoku一起交流java.算法.数据库相关技术. 这是Mysql系列第26篇. 本篇我们使用mysql实现一个分布式锁. 分布式锁的功能 分布式锁使用者位于不同的机器中,锁获取成功之后,才可以对共享资源进行操作 锁具有重入的功能:即一个使用者可以多次获取某个锁 获取锁有超时的功能:即在指定的时间内去尝试获取锁,超过了超时时间,如果还未获取成功,则返回获取失败 能够自动容错,比如:A机器获取锁l

mysql 开发进阶篇系列 13 锁问题(关于表锁,死锁示例,锁等待设置)

一. 什么时候使用表锁 对于INNODB表,在绝大部分情况下都应该使用行锁.在个别特殊事务中,可以考虑使用表锁(建议). 1. 事务需要更新大部份或全部数据,表又比较大,默认的行锁不仅使这个事务执行效率低,可能造成其他事务长时间锁等待和锁冲突,这种情况考虑使用表锁来提高事务的执行速度(具我在sql server中的经历,该大表有上100w,删除40w,表锁有时会造成长时间未执行完成. 还是使用分批来执行好). 2. 事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚.这种情况可以考虑一次

一步一步带你入门MySQL中的索引和锁 (转)

出处: 一步一步带你入门MySQL中的索引和锁 索引 索引常见的几种类型 索引常见的类型有哈希索引,有序数组索引,二叉树索引,跳表等等.本文主要探讨 MySQL 的默认存储引擎 InnoDB 的索引结构. InnoDB的索引结构 在InnoDB中是通过一种多路搜索树——B+树实现索引结构的.在B+树中是只有叶子结点会存储数据,而且所有叶子结点会形成一个链表.而在InnoDB中维护的是一个双向链表. 你可能会有一个疑问,为什么使用 B+树 而不使用二叉树或者B树? 首先,我们知道访问磁盘需要访问到

一步一步带你入门MySQL中的索引和锁

索引 索引常见的几种类型 索引常见的类型有哈希索引,有序数组索引,二叉树索引,跳表等等.本文主要探讨 MySQL 的默认存储引擎 InnoDB 的索引结构. InnoDB的索引结构 在InnoDB中是通过一种多路搜索树——B+树实现索引结构的.在B+树中是只有叶子结点会存储数据,而且所有叶子结点会形成一个链表.而在InnoDB中维护的是一个双向链表. 你可能会有一个疑问,为什么使用 B+树 而不使用二叉树或者B树? 首先,我们知道访问磁盘需要访问到指定块中,而访问指定块是需要 盘片旋转 和 磁臂

Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock

ReentrantLock介绍 ReentrantLock是一个可重入的互斥锁,又被称为"独占锁". 顾名思义,ReentrantLock锁在同一个时间点只能被一个线程锁持有:而可重入的意思是,ReentrantLock锁,可以被单个线程多次获取.ReentrantLock分为"公平锁"和"非公平锁".它们的区别体现在获取锁的机制上是否公平."锁"是为了保护竞争资源,防止多个线程同时操作线程而出错,ReentrantLock在

【锁】Oracle锁系列

[锁]Oracle锁系列 1  BLOG文档结构图 2  前言部分 2.1  导读和注意事项 各位技术爱好者,看完本文后,你可以掌握如下的技能,也可以学到一些其它你所不知道的知识,~O(∩_∩)O~: ① 锁的概念.分类.及其模拟 ② 查询锁的视图及视图之间的关联 ③ 锁的参数(DML_LOCKS.DDL_LOCK_TIMEOUT) ④ FOR UPDATE及FOR UPDATE OF系列 ⑤ 带ONLINE和不带ONLINE创建索引的锁情况(是否阻塞DML操作) ⑥ 包或存过不能编译的解决方法