(三)
AntDB-T数据库常规锁设计
基础数据结构
1.LockMethodData表示锁方法表,是锁设计里面比较基础的一个结构。定义如下图2所示:

图2:LockMethodData数据结构
一个锁方法表的控制结构定义在 LockMethodData中,它存在于共享内存中。其中字段numLockModes表示在锁表上定义的锁模式数量,常规锁的锁模式在上图2中有宏定义,其中NoLock 的宏定义为0,其本身不是一种锁模式的标志,这里可以用来表示没有获得锁,numLockModes字段的数值必须小于或等于 MAX_LOCKMODES,MAX_LOCKMODES定义值为10。
字段lockModeNames 表示用于Debug时打印用锁模式名字用,具体锁模式名字见上图2所示,字段trace_flag 表示指本锁方法GUC traceflag 的指针。
字段conlictTab 显示锁模式冲突的位掩码数组,它用于指示持有或请求的锁模式的集合,锁模式的取值为1至numLockModes,所以conflictTab[0]未使用。通过不同位来表示的锁的模式不同, 每个位上都对应一个锁模式,取值1或者0,来代表有无锁,这就是bit格式的便利,例如加ShareLock锁,对应的锁模式为5,则将1左位移5位,通过查看该字段要判断的对应数组的第五位的值是否为1,来表示是否已经持有ShareLock。conflictTab字段还可用来判断锁冲突,例如:如果锁模式i和j冲突,那么conflictTab[i] 的第j位为1。下图3是锁模式冲突表(LockConflicts)的源码定义:

图3: LockConflicts 冲突表定义
其中LOCKBIT_ON 的定位为:#define LOCKBIT_ON(lockmode) (1 << (lockmode)),这个LockConflicts冲突表并没有直接设定某种类型锁的冲突bitmask ,而是采用位移运算加或运算,目的是让我们更直观的了解到,各种类型的锁之间相互冲突情况。例如:AccessExclusiveLock锁的冲突情况,代表这个锁的冲突情况的bitmask值为1111111110,即跟任何一种锁类型(no lock 除外,表示没有锁)的LOCKBIT ON(lock mode)的值取&,结果都会为真,表示都冲突。
2.LOCKTAG表示加锁对象标识,即锁对象,一个LOCKTAG的值唯一标识一个可加锁对象。定义如下图4所示:

图4:加锁对象标识数据结构
在LOCKTAG结构中,字段locktag_type表示锁对象的类型,具体枚举值前面图1有介绍,字段locktag_lockmethodid表示加锁的方法,即采用如种方式加锁的,采用数据库默认的方式加锁,还是采用用户命令的方式加锁(用户采用select for update, lock table等一些命令),字段locktag_field1~ locktag_field4没有具体指明这些变量的用途,因为针对不同的LockTagType,这些 locktag_field1~ locktag_field4 中保存的数据不一定相同。比如当LockTagType 为 LOCKTAG_RELATION时,要求的 locktag_field 包括 DB OID +REL OID,当 LockTagType为LOCKTAG_TRANSACTION,则只需要该事务的 XID。
3. LOCK表示加锁对象描述体,用于表示已经加锁的资源或是请求加锁的可锁资源,定义如下图5所示:
图5:加锁对象描述体数据结构
其中字段tag表示为加锁对象的描述符即锁对象,字段grantMask表示当前在该对象上分配的所有锁模式的掩码,字段waitMask表示当前在该对象上等待的所有锁模式的掩码,字段procLocks表示这个对象锁上的所有进程PROCLOCK对象队列,字段waitProcs表示等待该对象上锁的进程的等待队列,字段requested[MAX_LOCKMODES]表示记录每种模式的锁请求(持有+等待)该锁的次数,字段nRequested表示requested数组元素的个数,即所有的锁模式的锁请求的总的数量,字段granted[MAX_LOCKMODES]表示每一种锁模式上的已分配的锁的数量,字段nGranted表示granted数组中元素的个数。
在AntDB-T数据库中,有很多会话可能会同时访问一个对象,这些会话请求的锁模式可能相同,也可能不同,可能兼容,也可能不兼容。为了方便表示所有会话在这个对象上总共加了哪些模式的锁,总共还有哪些处于waiting中,所以采用了一个LOCKMASK(bitmask)来表示这两种情况,其中grantMask、waitMask这2个字段就是用于判断已持锁与请求锁是否冲突。
初始化RegularLock(常规锁)
RegularLock(常规锁)的初始化操作由函数InitLocks(void) 实现,该函数初始化锁管理器的数据结构,主要工作有初始化LockMethodLockHash、LockMethodProcLockHash 和 LockMethodLocalHash 这三个 Hash 表。
LockMethodLockHash:数据库级别的锁表,为Lock 数据结构创建的hash表,hash key 用 LOCKTAG 通过hash函数生成,整个hash表会存储到共享内存中。
LockMethodProcLockHash:进程级别的锁表,为 ProcLock 数据结构创建的hash 表,hash key用 PROCLOCKTAG 通过hash函数生成,同样会存储到共享内存中。
LockMethodLocalHash: 本地锁表,为 LocalLock 数据结构创建的hash表,LOCALLOCKTAG 通过hash 函数生成hash-key,存储到本地。
加RegularLock(常规锁)
RegularLock(常规锁)的加锁操作在函数LockAcquire(const LOCKTAG *locktag,LOCKMODE lockmode,bool sessionLock,bool dontWait)中定义。
其中参数含义如下:locktag 表示是被锁对象的唯一标识;lockmode指示要获得的锁模式;sessionLock表示加锁的模式,如果为TRUE,表示为会话加锁,如果为FALSE,则为当前事务申请锁;dontWait表示申请锁是否允许等待,如果为 TRUE,则在检查到无法获得锁之后不等待,如果为 FALSE,则可以等待。该函数的返回值LockAcquireResult,它是一个枚举值,表示加锁是否成功等结果信息。
申请加RegularLock(常规锁)的流程如下:
1)用locktag(锁对象)和lockmode(加锁模式)组成一个具体的加锁类型作为hash-key,然后在本地锁表对应的hash表(LockMethodLocalHash)中查找此加锁类型的信息。
2)如果没有找到此加锁类型的信息,则构造一条插入本地锁表中;否则找到此加锁类型的信息,分配空间以记录锁拥有者的信息。
3)如果当前事务已经持有过此类型的锁(locallock->nLocks >0),在本地表的计数器上加1并更新锁资源拥有者里面的计算器也加1,然后直接退出。
4)如果锁模式是AccessExclusiveLock且锁对象是 Relation(表),则会尝试分配一个 transactionid(事务id) 来在后续加锁成功之后写一条 WAL record。
5)满足快速锁模式时,加锁并返回加锁成功。加快速锁前提是当前会话之前没有添加过强锁且还有充足的保存弱锁的位置。
6)如果申请的是强锁,则会先将当前进程持有的强锁的计数自增,并将快速锁信息从会话本地转移到主锁表中(共享内存中)。
7)在全局的锁表(LockMethodLockHash)中查找这个锁,如果在“Lock Hash”中找不到,则在"Lock Hash”中插入一个新元素。然后在 ProcLock-Hash (LockMethodProcLockHash)表里也查找对应的 ProcLock,如果在ProcLock Hash表找不到,则插入该ProcLock。
8)检查新增的锁模式会不会与已加的锁模式发生冲突,如果不会,则加锁;否则,根据函数参数决定等待还是退出。如果退出,还需清除锁表中相应的元素以保持一致性。
9)如果是AccessExclusiveLock锁模式,则 需要记录一条WAL record。
释放RegularLock(常规锁)
与加RegularLock(常规锁)相对应的操作是解RegularLock(常规锁),RegularLock 的解锁操作定义在函数LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock)中定义。
其中参数含义如下:locktag 表示是被锁对象的唯一标识;lockmode指示要获得的锁模式;sessionLock表示加锁的模式,如果为TRUE,表示为会话加锁,如果为FALSE,则为当前事务申请锁。该函数在本地锁表(LockMethodLocalHash)中查找锁标记为LockTag的锁,并释放该锁;如果SessionLock为TRUE,则释放一个会话锁(SessionLock),否则,释放一个常规的事务锁。如果发现任何等待进程现在是可以被唤醒的,将请求的锁赋予它们并将其唤醒。
申请解RegularLock(常规锁)的流程如下:
1)用locktag(锁对象)和lockmode(加锁模式)组成一个具体的加锁类型作为hash-key,然后在本地锁表对应的hash表(LockMethodLocalHash)中查找此加锁类型的信息。
2)找到此类型锁的拥有者,在它持有锁的计数器上减 1。如果它已经不再持有此锁,则删除这个拥有者的信息。
3)如果这个类型的锁并没有真正释放,只是计数器减1,直接退出。
4)如果是快速锁则清除快速锁的加锁标记,如果解锁成功就返回。
5)如果不是快速锁,则在全局的“Lock Hash”(LockMethodLockHash)和"ProcLock Hash”(LockMethodProcLockHash)表里查找此锁对应的 Lock 和 ProcLock,调用UnGrantLock修改其信息。唤醒可以被唤醒的进程,并从“Local Hash"( LockMethodLocalHash)里移除该类型锁。
清理RegularLock(常规锁)
RegularLock(常规锁)的清理在函数 CleanUpLock 中,在释放锁之后执行,主要是清理LockMethodProcLockHash、LockMethodLockHash队列,以及唤醒可以被唤醒的进程。
关于亚信安慧AntDB数据库
亚信安慧AntDB数据库始于2008年,在运营商的核心系统上,为全国24个省份的10亿多用户提供在线服务,具备高性能、弹性扩展、高可靠等产品特性,峰值每秒可处理百万笔电信核心交易,保障系统持续稳定运行近十年,并在通信、金融、交通、能源、物联网等行业成功商用落地。




