AntDB数据库始于2008年,在运营商的核心系统上,为全国24个省份的10亿多用户提供在线服务,具备高性能、弹性扩展、高可靠等产品特性,峰值每秒可处理百万笔通信核心交易,保障系统持续稳定运行近十年,并在通信、金融、交通、能源、物联网等行业成功商用落地。锁是OLTP数据库中保证事务一致性的一种重要手段,本文主要阐述AntDB-M(AntDB内存引擎)的锁相关设计。
AntDB-M的锁的设计分为两层,1)元数据锁;2)数据锁。 在获取数据锁之前,必须先获取元数据锁,从而保护元数据与底层数据的一致性。
元数据锁
元数据锁代表了一个连接对于表元数据的访问能力。 AntDB-M根据操作语句对元数据、数据的不同要求,设计了多种元数据锁类型。以满足对元数据、数据的不同读、写限制、以及并发能力。 由于排他锁具有较高优先级,并且较低的并发度。因此为了避免阻塞大量其他类型的锁请求,在排他锁获取一定数据量后,会优先授权其他锁。
数据锁
数据锁代表对一个数据对象(表、记录)的访问能力。能力分为两种:1)读;2)写(读、删除、更新)。根据数据对象访问特点,通过锁类型、锁粒度、锁范围等对数据对象赋予了不同的访问限制。
后文主要为数据锁相关设计。
锁类型
锁的兼容性如下。
纵轴:已有锁; 横轴:待申请锁。
"Y":兼容; "-":不兼容;
表级
表1:表级锁
记录级
表2:记录级锁
锁与闩锁
锁的对象是表的数据对象(表、记录)。对于记录上的锁分为两种:行锁、闩锁。其中行锁用于非MVCC模式下对行的读写控制,闩锁用于MVCC模式下对记录的读写控制。
每个锁都有其所归属的事务,或者说锁的申请者是事务。每个事务都有其拥有的、待申请的表锁、行锁链表。
表锁
每个表都有一个表锁信息,包括:表锁链表、已经加锁个数、锁个数(含待加锁)。 每个事务在开始对表访问前,都需要先对表申请相应的表锁。所有申请都按先后顺序排队。
加锁校验
如果一个事务在申请表锁时,表已经被删除、或者表被重命名,则不允许对表进行加锁。
表锁申请
在对一个表访问前,都需要先对表申请合适的表锁。
表锁升级
一个事务,对于一个表只应当持有一种类型的表锁。 这里需要从几个方面来说,首先,从锁的兼容性,对于不兼容的锁,不允许同时持有,这类情况必须选择出一种出来。其次,从锁的约束力来看,排他锁约束力要高于共享锁(TX>TS、IX>IS),表锁高于表意向锁。再次,相同类型的锁重复申请没有必要。 结合之前的锁的兼容性来看,如表3:锁兼容性:‘-’表示不兼容的,‘X’表示同级不需重复申请的。剩下的只有A,B,C,D四种情况,这四种情况通过锁的约束力来看,都有不同的约束力,最终以较高约束力的锁为准。
表3:锁兼容性
综上,一个事务只需要一种类型的表锁。对于已经持有某类表锁时,再次申请表锁采取锁升级来处理。锁升级包括两类:1)对已申请锁升级,即改变已经持有锁,改变成功才算加锁成功;2)对新申请锁升级,不改变已经持有锁,加锁立即成功。
表4:表锁升级规则
横轴:已持有锁;纵轴:待申请锁
-:不需要升级;其他:升级目标
(1)加锁等待
不管是首次申请的锁,还是对已有的锁进行升级,都要判断其他事务已经申请到的表锁与当前申请的表锁是否兼容。
注:加锁等待过程中,如果表被删除、或者被重命名,则仍然认为加锁失败。
(2)等待超时
每种表锁类型都会有相应的加锁计数,阻塞等待必须等到所有不兼容的锁都解锁。等待超时时间可配置,默认为50ms,最大600ms,超时则加锁失败。 对于DDL操作,超时时间要长一些,默认为1800ms,可配置,最大7200ms。
(3)不同事务间影响
不同事务间存在并发,从表锁的角度来看事务之间相互影响有两个因素:1)锁不兼容,等待持有锁的释放锁;2)并发处理,表锁临界资源等待;其中因素2为次要原因,影响很小,可以忽略。造成锁等待的主要因素为锁不兼容。 若与表上已有锁不兼容,则事务必须等待其他事务对其持有的不兼容锁释放。若兼容,则无需等待。事务的阻塞与否,只与锁不兼容相关,与事务创建先后顺序、锁申请先后顺序无关。
(4)锁的分配顺序
同一时刻,表上可能存在多个阻塞的表锁申请,这些被阻塞的表锁类型可能相同,可能不同。 一个表上的所有表锁,不管是否持有锁,都按申请先后顺序排队。但是该顺序并不影响锁的阻塞与否。加锁成功与否,只与已持有锁是否兼容相关,与申请先后顺序无关。
行锁
行锁只有两种1)共享锁RS;2)互斥锁RX。 一行记录上可以加多个共享锁,只能有一个互斥锁。
(1)事务与行锁
每个事务都有自己的锁链表,记录了该事务持有的行锁。行锁的拥有者是事务。
(2)行锁链表
表记录的每行都有自己的锁链表,记录了已经持有或者正在等待的锁对象。 链表按照加锁申请顺序排队。
(3)授予条件
对于一个锁的授予,总体原则:
基于以上原则,锁授予条件如下,不满足条件的必须等待。
(4)新建行锁
一个事务对一行记录首次加锁、或者解锁后再次申请锁时,需要为该事务新建一个行锁对象,添加到行锁的锁链表尾部。同时会被添加到事务的行锁列表中。 对于新建的行锁对象立即判断是否可以授予锁,不满足条件的必须等待条件满足。
(5)行锁升级
由于RX锁具有排他性,因此认为RX锁比RS锁等级更高,即约束力更强。一个事务对于一行记录,只允许持有一种锁类型,避免过多的锁数量,也没必要,因为RX锁也具有读属性。 当一个事务已经持有某行的共享锁时,根据持有锁与申请锁等级判断是否需要对锁进行升级。有三种情况:1)同级不升级;2)对申请锁升级;3)对已有所升级;
1. 申请锁对象
为给该行申请一个RX锁对象;
2. 判断是否可以立即升级
立即升级条件:1)已持有锁处于行锁链表头部;2)没有其他事务持有RS锁。 如果满足条件,就直接将已持有锁的锁类型调整为RX锁。同时释放刚申请的RX锁对象。
3. 加入行锁链表
如果不能立即升级,则将RX锁对象加入到行锁链表中。加入方式与新建行锁添加到行锁链表尾部不同。升级锁是添加到行锁链表中已经持有锁的最后一个锁的后边。这样做的目的是对锁升级时,不必等待其他未授予锁的锁对象。尤其如果这些未授予的锁中有RX时,会违反顺序原则。 行锁同时会被添加到事务的行锁链表中。
4. 等待授予
待升级锁加入链表后,便进入等待前边所有的锁的释放。一旦等到通知,此时意味着前边的锁都已经释放。此时需要将已经持有的锁从行锁链表移除,将锁授予新添加的RX锁对象。同时会将事务的该行的行锁更新为该行锁。
(6)临界资源
行锁的临界资源为保证行锁独占、行锁链表管理的必需资源。每行记录必须要有对应的临界资源。AntDB-M在为一个表创建出一块新的数据块空间时,就会为每行记录分配好临界资源,尽管数据空间中的记录行还没实际使用。
为了提升MVCC对数据的并发读写性能,对于记录的读写并不加共享锁RS、或排他锁RX。而是通过闩锁来控制。闩锁本质上是一种读写锁。
行锁与闩锁区别
本文主要讲述了AntDB-M的数据锁的类型、粒度、兼容性。锁的具体设计上又分为表锁、行锁、闩锁。以及不同锁的申请、释放、升级过程,以及不同锁间的区别。限于篇幅,与锁相关的死锁检测、幻读、脏读相关内容没有涉及。
关于AntDB数据库
AntDB数据库始于2008年,在运营商的核心系统上,为全国24个省份的10亿多用户提供在线服务,具备高性能、弹性扩展、高可靠等产品特性,峰值每秒可处理百万笔电信核心交易,保障系统持续稳定运行近十年,并在通信、金融、交通、能源、物联网等行业成功商用落地。