暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

浅析MYSQL MDL锁

腾讯游戏存储与计算技术 2021-05-19
2717

MySQL为了保护数据字典元数据,使用了metadata lock,即MDL锁,保证在并发的情况下,结构变更的一致性。本文基于spider3.x(MySQL5.7)从代码实现角度分析了常用SQL语句的MDL加锁实现。

一. 基本概念

1. MDL的设计目标

字典锁在设计的时候是为了数据库对象的元数据。到达以下3个目的:

  • 提供对并发访问内存中字典对象缓存的保护。

  • 确保DML的并发性。如事务1对表T1查询,事务2同是对表T1插入。

  • 确保一些操作的互斥性,如 DML
    与大部分 DDL
    ( ALTER TABLE
    除外)的互斥性。如事务1对表 table1
    执行插入,事务2执行 DROP TABLE
    ,这两种操作是不允许并发的,故需要将表对象保护起来。

2.  MDL锁持有时间

  • MDL_STATEMENT
    : 语句范围的,语句结束自动释放

  • MDL_TRANSACTION
    :  事务范围的,事务结束时自动释放

  • MDL_EXPLICIT
    : 显式锁,由flush table with read lock这种获取,需要通过unlock tables释放

3. MDL锁粒度

MDL_lock
分为 MDL_scoped_lock
MDL_object_lock
其中 scope_lock
包括:

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

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

object_lock
包括:

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

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

  • USER_LOCK
    : 用于对特定字符串上锁。

4. MDL锁类型

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

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

  • MDL_SHARED(S)
    用在只访问元数据信息,不访问数据。例如 flush table with read lock
    第一阶段

  • MDL_SHARED_HIGH_PRIO(SH)
    用于访问 information_schema
    的表。例如:select * from information_schema.tables
    ;

  • MDL_SHARED_READ(SR)
    访问表结构并且读表数据,例如:SELECT * FROM t
    ;

  • MDL_SHARED_WRITE(SW)
    访问表结构且写表数据, 例如:LOCK TABLE t WRITE

  • MDL_SHARED_UPGRADABLE(SU)
    可升级锁,用在 alter table
    的第一阶段,使 alter table
    的时候不阻塞 DML
    ,防止其他 DDL
    ,减少锁等待时间。

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

  • MDL_SHARED_NO_READ_WRITE(SNRW)

  • MDL_SHARED_READ_ONLY(SRO)

  • MDL_SHARED_WRITE_LOW_PRIO(SWLP)

5. 兼容性

Scope
锁活跃锁和请求锁兼容性矩阵,grant_matrix

                 | Type of active   |
    Request | scoped lock |
    type | IS(*) IX S X |
    ---------+------------------+
    IS | + + + + |
    IX | + + - - |
    S | + - + - |
    X | + - - - |
    复制

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

                   |    Pending      |
      Request | scoped lock |
      type | IS(*) IX S X |
      ---------+-----------------+
      IS | + + + + |
      IX | + + - - |
      S | + + + - |
      X | + + + + |
      复制
        Here: "+" -- means that request can be satisfied
                "-" -- means that request can't be satisfied and should wait
      复制

      object
      上已持有锁和请求锁的兼容性矩阵,grant_matrix

             Request  |  Granted requests for lock            |
        type | S SH SR SW SU SRO SNW SNRW X |
        ----------+---------------------------------------+
        S | + + + + + + + + - |
        SH | + + + + + + + + - |
        SR | + + + + + + + - - |
        SW | + + + + + - - - - |
        SU | + + + + - + - - - |
        SRO | + + + - + + + - - |
        SNW | + + + - - + - - - |
        SNRW | + + - - - - - - - |
        X | - - - - - - - - - |
        SU -> X | - - - - 0 - 0 0 0 |
        SNW -> X | - - - 0 0 - 0 0 0 |
        SNRW -> X | - - 0 0 0 0 0 0 0 |
        复制

        object
         上等待锁和请求锁的优先级矩阵,wait_matrix

               Request  |  Pending requests for lock           |
          type | S SH SR SW SU SRO SNW SNRW X |
          ----------+--------------------------------------+
          S | + + + + + + + + - |
          SH | + + + + + + + + + |
          SR | + + + + + + + - - |
          SW | + + + + + + - - - |
          SU | + + + + + + + + - |
          SRO | + + + - + + + - - |
          SNW | + + + + + + + + - |
          SNRW | + + + + + + + + - |
          X | + + + + + + + + + |
          SU -> X | + + + + + + + + + |
          SNW -> X | + + + + + + + + + |
          SNRW -> X | + + + + + + + + + |
          复制
            Here: "+" -- means that request can be satisfied
                    "-" -- means that request can't be satisfied and should wait
          复制

          tips:

          1.如何判断一个事务是否能够得到锁呢?获取锁时需要先判断是否和已经授予的锁模式 (MDL_lock.m_granted)
          冲突,如果不冲突再判断和等待队列 (MDL_lock.m_waiting)
          中的请求是否冲突,都不冲突才能获取锁,否则进入 m_waiting
          队列等待。源码中的注释:

           New lock request can be satisfied if:
          - There are no incompatible types of satisfied requests
          in other contexts
          - There are no waiting requests which have higher priority
          than this request when priority was not ignored.
          复制

          2.wait_matrix
          有什么作用?  wait_matrix
          中,为什么所有锁都与自身兼容 ?相关解释见下一篇文章:<<flush table with write lock 实现MDL事务锁>>

          6. 数据结构

          MDL_lock
          :锁资源。一个对象全局唯一。可以允许多个可以并发的事物同时获得,存储在全局结构  mdl_locks (MDL_map)
          中。

          MDL_context
          :字典锁上下文。包含一个事物所有的字典锁请求。为 MDL
          上下文接口,表示一个资源竞争者, THD
          实现了这个接口, 即一个 Mysqld
          的线程可以是 MDL_lock
          的资源竞争者,每一个 MDL_context
          只能等待在一个锁上,用 MDL_context::m_waiting_for
          表示。

          MDL_request
          :字典锁请求。包含对某个对象的某种锁的请求。

          MDL_ticket
          :字典锁排队。MDL_request
          就是为了获取一个 ticket
          。表示 MDL_lock
          的许可或请求, 会同时挂在两处:

          1. 挂在当前线程的 MDL_Context
            中, 每个线程有3个 ticket
            链表,分别对应当前持有的 statement
            锁、transaction
            锁和 explicit
            锁,放在  MDL_context::m_tickets

          2. 挂在 MDL_lock
            的队列中, 通过 MDL_ticket.next_in_lock/prev_in_lock
            组织链表.  MDL_lock
            的队列分为两种, 一个 MDL_ticket
            可能会挂在其中之一

            • 挂在 MDL_lock
              的等待队列 (MDL_lock.m_waiting
              ) 中, 表示 MDL_ticket
              owner (MDL_context)
              正在等待该资源 (MDL_lock)

            • 挂在 MDL_lock
              的已许可队列 (MDL_lock.m_granted)
              中, 表示 MDL_ticket
              owner (MDL_context)
              已经获得该资源 (MDL_lock)

          一个 session
          连接在实现中对应一个 THD
          实体,一个 THD
          对应一个 MDL_CONTEXT
          ,表示需要的 MDL
          锁资源,一个 MDL_CONTEXT
          中包含多个 MDL_REQUEST
          ,一个 MDL_REQUEST
          即是对一个对象的某种类型的 lock
          请求。每个 mdl_request
          上有一个 ticket
          对象, ticket
          中包含 lock
          MDL_ticket
          MDL_lock
          的关系是多对一, 可以同时有多个资源许可在竞争一个资源, 或者多个资源许可可在共享一个资源。MDL_context
          MDL_ticket
          的关系是一对多, 一个竞争者可以同时申请/获得多个资源的许可;

          数据结构间关系见下图,涉及到的源码文件主要是 sql/mdl.cc


          二. 常见SQL加锁流程

          1. select语句操作MDL锁流程:

          1. open table
            阶段加事务级,表级,MDL_SHARED_READ

          2. commit
            阶段,释放锁


          2. alter语句操作MDL锁流程:

          1. Opening tables
            阶段,加共享锁

            • 加语句级,全局,MDL_INTENSION_EXCLUSIVE

            • 加事务级,schema
              级,MDL_INTENSION_EXCLUSIVE

            • MDL_SHARED_UPGRADABLE
              锁,升级到 MDL_SHARED_NO_WRITE

          2. 操作数据, copy data
            ,流程如下:

            • 创建临时表 tmp
              ,重定义 tmp
              为修改后的表结构

            • 从原表读取数据插入到 tmp

          3. MDL_SHARED_NO_WRITE
            读锁升级到 MDL_EXCLUSIVE

            • 删除原表,将 tmp
              重命名为原表名

            • alter
              结束,释放语句级,全局,MDL_INTENSION_EXCLUSIVE

          4. 事务提交阶段,释放 MDL

            • 释放事务级 MDL_INTENTION_EXCLUSIVE

            • 释放事务级 MDL_EXCLUSIVE


          3. DML语句操作MDL锁流程:

          1. Opening tables
            阶段,加共享锁

            • 加语句级,全局,MDL_INTENTION_EXCLUSIVE

            • 加事务级,表级,MDL_SHARED_WRITE

          2. 事务提交阶段,释放 MDL

            • 语句执行完,释放语句级,全局,MDL_INTENTION_EXCLUSIVE

            • commit
              后,释放事务级,表级,MDL_SHARED_WRITE


          4. flush table with read lock(ftwrl) 语句操作MDL锁流程:

          1. MDL_EXPLICIT GLOBAL
            (lock_global_read_lock)

          2. 清理表缓存 (close_cached_tables)

          3. MDL_EXPLICIT COMMIT
            (make_global_read_lock_block_commit)

          4. unlock
            后释放 GLOBAL
            锁和  COMMIT


          问题:

          从上述SQL加锁流程中,解释为什么在事务中执行DML语句后,可以执行flush  table with read lock?

          相关解释可以参考下一篇文章:<<flush table with write lock 实现MDL事务锁>>


          文章转载自腾讯游戏存储与计算技术,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

          评论