锁资源
锁资源在系统中是共享的,即全局的,存放在static MDL_map mdl_locks;的hash链表中,对于数据库中的一个对象,其hashkey必然是唯一的,对应一个锁资源。多个事务同时对一张表操作时,申请的lock也是同一个内存对象。获取mdl_locks中的lock需要通过全局互斥量保护起来mysql_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进行上锁。
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
稍微解释下,首先是在mdl_request本身去查看有没有相等的或者stronger的ticket,如果存在,则直接使用。否则创建一个ticket,查找上锁对象对应的lock,没有则创建。检查lock是否可以被赋给本事务,如果可以直接返回,否则等待这个lock;
锁等待与唤醒
字典对象的锁等待是发生在两个事物对同一对象上不兼容的锁导致的。当然,由于lock的唯一性,先到先得,后到的只能等待。
如何判断一个lock是否可以grant给一个TX?这需要结合lock结构来看了,lock上有两个成员,grant和wait,grant代表此lock允许的事物都上了哪些锁,wait表示等待的事务需要上哪些锁。其判断一个事物是否可以grant的逻辑如下:
If (compatible(lock.wait, tx.locktype))
return can_grant;
End if
即首先判断grant中的锁类型和当前事务是否兼容,然后判断wait中的锁类型和当前事务是否兼容。细心的话,会想到,wait中的锁类型是不需要和当前事务进行兼容性比较的,这是不是说这个比较是多余的了?其实也不是,因为wait的兼容性矩阵和上面的矩阵是不一样的,wait的兼容性矩阵感觉是在DDL等待的情况下,防止DML继续进来(wait矩阵就不写出来了,大家可以去代码里看下)。
比如:
SELECT T1
DROP T1
SELECT T1
这时候TX2会阻塞,TX3也会阻塞,被TX2阻塞,也就是说被wait的事件阻塞了,这样可能就是为了保证在DDL等待时,禁止再做DML了,因为在DDL面前,DML显得确实不是那么重要了。
如何唤醒被等待的事务呢?比如唤醒TX2,当TX1结束时,会调用release_all_locks_for_name,对被锁住的事务进行唤醒,具体操作封装在reschedule_waiters函数中,重置等待时间的标记位进行唤醒,重点代码如下:
{
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);
}
今天把mdl系统总体上看了一下,对锁的请求、等待以及唤醒有了初步了解。并发性的问题是最难调试的,大家如果想做锁方面的实验,可以利用VS调试中的冻结线程的功能,这样就可以确保并发情况控制完全按照你设计思路去呈现。