77

浅析MYSQL MDL锁

 4 years ago
source link: https://www.tuicool.com/articles/bYjUnyI
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

6Z367fV.png!web

二. 常见SQL加锁流程

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

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

  2. commit 阶段,释放锁

qUJ7NbJ.png!web

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

  1. Opening tables 阶段,加共享锁

  • 加语句级,全局, MDL_INTENSION_EXCLUSIVE

  • 加事务级, schema 级, MDL_INTENSION_EXCLUSIVE

  • MDL_SHARED_UPGRADABLE 锁,升级到 MDL_SHARED_NO_WRITE

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

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

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

MDL_SHARED_NO_WRITE 读锁升级到 MDL_EXCLUSIVE

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

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

事务提交阶段,释放 MDL

  • 释放事务级 MDL_INTENTION_EXCLUSIVE

  • 释放事务级 MDL_EXCLUSIVE

7FbyuaR.jpg!web

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

  1. Opening tables 阶段,加共享锁

  • 加语句级,全局, MDL_INTENTION_EXCLUSIVE

  • 加事务级,表级, MDL_SHARED_WRITE

事务提交阶段,释放 MDL

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

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

uaQJjmE.png!web

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

umqARbf.png!web

问题:

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK