1. 前言

编写过多线程程序的人应该都知道锁的重要性,它可以保证在并发的情况下,对临界资源的正确访问。在MySQL数据库中,锁亦是如此,从事务的角度上来说,它保证了事务之间的隔离性(isolation),也就是事务ACID中的I。

2. MDL锁

MDL锁的全称为Meta data lock,是在MySQL中sql层实现的锁,从其名字可以看出来,它的作用主要是为了保护元数据的访问。而在MySQL中,元数据就是指如schema,table,function这样的对象的元数据信息(如表名,表的列,列的属性等等)。具体地,可以从下面的代码中,看出MDL锁主要保护的对象:

/**Object namespaces.Sic: when adding a new member to this enum make sure toupdate m_namespace_to_wait_state_name array in mdl.cc!Different types of objects exist in different namespaces- GLOBAL is used for the global read lock.- TABLESPACE is for tablespaces.- SCHEMA is for schemas (aka databases).- TABLE is for tables and views.- FUNCTION is for stored functions.- PROCEDURE is for stored procedures.- TRIGGER is for triggers.- EVENT is for event scheduler events.- COMMIT is for enabling the global read lock to block commits.- USER_LEVEL_LOCK is for user-level locks.- LOCKING_SERVICE is for the name plugin RW-lock serviceNote that although there isn't metadata locking on triggers,it's necessary to have a separate namespace for them sinceMDL_key is also used outside of the MDL subsystem.Also note that requests waiting for user-level locks get specialtreatment - waiting is aborted if connection to client is lost.*/enum enum_mdl_namespace { GLOBAL=0,
TABLESPACE,
SCHEMA,
TABLE,
FUNCTION,
PROCEDURE,
TRIGGER,
EVENT,
COMMIT,
USER_LEVEL_LOCK,
LOCKING_SERVICE,
/* This should be the last ! */
NAMESPACE_END };

MDL使用的场景是为了保护在DDL和MDL并发的情况下,保护对应的对象,比如有如下两个事务:

T1: select * from t1;

T2: drop table t1;

如果没有MDL的保护,T1在select t1的时候刚拿到t1的元数据,准备读数据的时候,恰好T2进来到存储引擎并马上将t1删除了,从而会在存储引擎出现错误。所以,有MDL锁的时候,如果T1先拿到锁,那么T2,就会被阻塞在sql层,等T1完成。

笔者在网上查阅了些资料,发现有很多文章提到表级锁,笔者开发经验主要是在MySQL 5.7上,而上面是没有表级锁的概念的,所以笔者认为,MDL引入后,所有其它引擎的表级锁,应该使用的都是这个MDL。

3. InnoDB锁

MySQL事务的隔离级别是跟底下所使用的存储引擎相关的。比如:对MyISAM存储引擎来说,它根本就不支持事务。而当使用InnoDB存储引擎时,就可以支持全部四种隔离级别:读未提交(read-uncommitted),不可重复读(read-committed),可重复读(repeatable-read),串行化(serializable)。

上面的几种隔离级别,依赖于InnoDB的行级锁实现。行级锁的锁粒度为行(row)。

行锁的有两种锁模式:

S锁(共享锁):不同事务之间的S锁互不排斥,也即是一个事务对某行加了S锁后,其它事务依然可以对该行加S锁进行访问。

X锁(排他锁):不同事务之间的X锁相互排斥,即一个事务对某行加了X锁后,其它事务不能对该行再加X锁。

在InnoDB行锁中,又分为几种不同类型行锁。

Record锁:锁定某行记录,在行的索引上加锁。

Gap锁:锁定某个区间,即某个索引的范围区间内加锁,不包含边界行。

Next-key锁:同时锁住某行记录和一个区间,上界为开区间,下界为闭区间。

死锁:一个事务不是在一开始就把所有的锁加上,而是在执行的过程中逐步加上锁,因此不同事务之间,因此两个事务并发执行时,加锁顺序有可能产生循环等待从而死锁。为此,无论MDL还是InnoDB中的行锁都有死锁检测机制,当检测的死锁发生时,选择一个victim事务回滚。