共计 3269 个字符,预计需要花费 9 分钟才能阅读完成。
这篇文章将为大家详细讲解有关 MySQL 中如何使用 MDL 字典锁,文章内容质量较高,因此丸趣 TV 小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。
什么是 MDL
MDL,Meta Data lock,元数据锁,一般称为字典锁。字典锁与数据锁相对应。字典锁是为了保护数据对象被改变,一般是一些 DDL 会对字典对象改变,如两个 TX,TX1 先查询表,然后 TX2 试图 DROP,字典锁就会 lock 住 TX2,知道 TX1 结束(提交或回滚)。数据锁是保护表中的数据,如两个 TX 同时更新一行时,先得到 row lock 的 TX 会先执行,后者只能等待。
MDL 的设计目标
字典锁在设计的时候是为了数据库对象的元数据。到达以下 3 个目的。
1. 提供对并发访问内存中字典对象缓存 (table definatin cache,TDC) 的保护。这是系统的内部要求。
2. 确保 DML 的并发性。如 TX1 对表 T1 查询,TX2 同是对表 T1 插入。
3. 确保一些操作的互斥性,如 DML 与大部分 DDL(ALTER TABLE 除外)的互斥性。如 TX1 对表 T1 执行插入,TX2 执行 DROP TABLE,这两种操作是不允许并发的,故需要将表对象保护起来,这样可以保证 binlog 逻辑的正确性。(貌似之前的版本存在字典锁是语句级的,导致 binlog 不合逻辑的 bug。)
支持的锁类型
数据库理论中的基本锁类型是 S、X,意向锁 IS、IX 是为了层次上锁而引入的。比如要修改表中的数据,可能先对表上一个表级 IX 锁,然后再对修改的数据上一个行级 X 锁,这样就可以保证其他试图修改表定义的事物因为获取不到表级的 X 锁而等待。
MySQL 中将字典锁的类型根据不同语句的功能,进一步细分,细分的依据是对字典的操作和对数据的操作。细分的好处是能在一定程度上提高并发效率,因为如果只定义 X 和 S 两种锁,必然导致兼容性矩阵的局限性。MySQL 不遗余力的定义了如下的锁类型。
可以看到 MySQL 在 ALTER TABLE 的时候还是允许其他事务进行读表操作的。需要注意的是读操作的事物需要在 ALTER TABLE 获取 MDL_SHARED_NO_WRITE 锁之后,否则无法并发。这种应用场景应该是对一个较大的表进行 ALTER 时,其他事物仍然可以读,并发性得到了提高。
锁的兼容性
锁的兼容性就是我们经常看到的那些兼容性矩阵,X 和 S 必然互斥,S 和 S 兼容。MySQL 根据锁的类型我们也可以知道其兼容矩阵如下:
1 代表兼容,0 代表不兼容。你可能发现 X 和 IX 竟然兼容,没错,其实这里的 IX 已经不是传统意义上的 IX,这个 IX 是用在范围锁上,所以和 X 锁不互斥。
数据结构
涉及到的和锁相关的数据结构主要是如下几个:
MDL_context:字典锁上下文。包含一个事物所有的字典锁请求。
MDL_request:字典锁请求。包含对某个对象的某种锁的请求。
MDL_ticket:字典锁排队。MDL_request 就是为了获取一个 ticket。
MDL_lock:锁资源。一个对象全局唯一。可以允许多个可以并发的事物同时获得。
涉及到的文件主要是 sql/mdl.cc
锁资源
锁资源在系统中是共享的,即全局的,存放在 static MDL_map mdl_locks; 的 hash 链表中,对于中的一个对象,其 hashkey 必然是唯一的,对应一个锁资源。多个事务同时对一张表操作时,申请的 lock 也是同一个内存对象。获取 mdl_locks 中的 lock 需要通过全局互斥量保护起来_mutex_lock(m_mutex); m_mutex 是 MDL_map 的成员。
上锁流程
一个会话连接在实现中对应一个 THD 实体,一个 THD 对应一个 MDL_CONTEXT,表示需要的 mdl 锁资源,一个 MDL_CONTEXT 中包含多个 MDL_REQUEST,一个 MDL_REQUEST 即是对一个对象的某种类型的 lock 请求。每个 mdl_request 上有一个 ticket 对象,ticket 中包含 lock。
上锁的也就是根据 MDL_REQUEST 进行上锁。
Acquire_lock:
if (mdl_request contains the needed ticket)
return ticket;
End if;
Create a ticket;
If (!find lock in lock_sys)
Create a lock;
End if
If (lock can be granted to mdl_request)
Set lock to ticket;
Set ticket to mdl_request;
Else
Wait for lock
End if
稍微解释下,首先是在 mdl_request 本身去查看有没有相等的或者 stronger 的 ticket,如果存在,则直接使用。否则创建一个 ticket,查找上锁对象对应的 lock,没有则创建。检查 lock 是否可以被赋给本事务,如果可以直接返回,否则等待这个 lock;
锁等待与唤醒
字典对象的锁等待是发生在两个事物对同一对象上不兼容的锁导致的。当然,由于 lock 的唯一性,先到先得,后到的只能等待。
如何判断一个 lock 是否可以 grant 给一个 TX?这需要结合 lock 结构来看了,lock 上有两个成员,grant 和 wait,grant 代表此 lock 允许的事物都上了哪些锁,wait 表示等待的事务需要上哪些锁。其判断一个事物是否可以 grant 的逻辑如下:
If(compatible(lock.grant, tx.locktype))
If (compatible(lock.wait, tx.locktype))
return can_grant;
End if
End if
即首先判断 grant 中的锁类型和当前事务是否兼容,然后判断 wait 中的锁类型和当前事务是否兼容。细心的话,会想到,wait 中的锁类型是不需要和当前事务进行兼容性比较的,这是不是说这个比较是多余的了?其实也不是,因为 wait 的兼容性矩阵和上面的矩阵是不一样的,wait 的兼容性矩阵感觉是在 DDL 等待的情况下,防止 DML 继续进来(wait 矩阵就不写出来了,大家可以去代码里看下)。
比如:
TX1 TX2 TX3
SELECT T1
DROP T1
SELECT T1
这时候 TX2 会阻塞,TX3 也会阻塞,被 TX2 阻塞,也就是说被 wait 的事件阻塞了,这样可能就是为了保证在 DDL 等待时,禁止再做 DML 了,因为在 DDL 面前,DML 显得确实不是那么重要了。
如何唤醒被等待的事务呢?比如唤醒 TX2,当 TX1 结束时,会调用 release_all_locks_for_name,对被锁住的事务进行唤醒,具体操作封装在 reschedule_waiters 函数中,重置等待时间的标记位进行唤醒, 重点代码如下:
if (can_grant_lock(ticket- get_type(), ticket- get_ctx()))
{
if (! ticket- get_ctx()- m_wait.set_status(MDL_wait::GRANTED))
{
/*
Satisfy the found request by updating lock structures.
It is OK to do so even after waking up the waiter since any
session which tries to get any information about the state of
this lock has to acquire MDL_lock::m_rwlock first and thus,
when manages to do so, already sees an updated state of the
MDL_lock object.
*/
m_waiting.remove_ticket(ticket);
m_granted.add_ticket(ticket);
}
关于 MySQL 中如何使用 MDL 字典锁就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。