
专栏内容:
postgresql内核源码分析
手写数据库toadb
并发编程
个人主页:我的主页
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.
==================================
介绍
常规锁,主要用于数据库对象的加锁,如表,根据用户请求来加锁 。
它有死锁检测,在事务结束时会自动释放。
regular lock原理
regular lock像lwlock一样会预先在共享内存中分配,对于每一类型需要加锁的数据库对象都会分配一个锁对象。
为了标识具体的数据库对象,所以locktag唯一标识了每个锁,它的内容与加锁对象关联起来。 这里就有个问题,数据库对象可以非常多,锁的数量也很庞大,如何确定锁的数量呢?
regular lock 结构
typedef struct LOCK
{
	/* hash key */
	LOCKTAG		tag;			/* unique identifier of lockable object */
	/* data */
	LOCKMASK	grantMask;		/* bitmask for lock types already granted */
	LOCKMASK	waitMask;		/* bitmask for lock types awaited */
	dlist_head	procLocks;		/* list of PROCLOCK objects assoc. with lock */
	dclist_head waitProcs;		/* list of PGPROC objects waiting on lock */
	int			requested[MAX_LOCKMODES];	/* counts of requested locks */
	int			nRequested;		/* total of requested[] array */
	int			granted[MAX_LOCKMODES]; /* counts of granted locks */
	int			nGranted;		/* total of granted[] array */
} LOCK;
 
 
锁标识 LOCKTAG
锁标识的定义如下
typedef struct LOCKTAG
{
	uint32		locktag_field1; /* a 32-bit ID field */
	uint32		locktag_field2; /* a 32-bit ID field */
	uint32		locktag_field3; /* a 32-bit ID field */
	uint16		locktag_field4; /* a 16-bit ID field */
	uint8		locktag_type;	/* see enum LockTagType */
	uint8		locktag_lockmethodid;	/* lockmethod indicator */
} LOCKTAG;
 
locktag 唯一标识一个常规锁,它由6个成员组成, 前四个成员,在不同类型的锁中对应不同的数据
最后一个lockmethodid是定义锁的操作及冲突矩阵,一般采用默认的定义
| LOCKTAG_RELATION | LOCKTAG | 
|---|---|
| LOCKTAG_RELATION | locktag_field1 = dboid 当relation 为共享表时 dboid = 0  locktag_field2 = reloid  | 
| LOCKTAG_RELATION_EXTEND | 同上 | 
| LOCKTAG_DATABASE_FROZEN_IDS | locktag_field1 = dboid | 
| LOCKTAG_PAGE | locktag_field1 = dboid  locktag_field2 = reloid locktag_field3 = blocknum  | 
| LOCKTAG_TUPLE | locktag_field1 = dboid  locktag_field2 = reloid locktag_field3 = blocknum locktag_field4 = offnum  | 
| LOCKTAG_TRANSACTION | locktag_field1 = xid  locktag_field2 = 0 locktag_field3 = 0 locktag_field4 = 0  | 
| LOCKTAG_VIRTUALTRANSACTION | locktag_field1 = (vxid).backendId  locktag_field2 = (vxid).localTransactionId locktag_field3 = 0 locktag_field4 = 0  | 
| LOCKTAG_SPECULATIVE_TOKEN | locktag_field1 = xid  locktag_field2 = token locktag_field3 = 0 locktag_field4 = 0  | 
| LOCKTAG_OBJECT | locktag_field1 = dboid  locktag_field2 = classoid locktag_field3 = objoid locktag_field4 = objsubid  | 
| LOCKTAG_USERLOCK | 用户自定义锁 | 
| LOCKTAG_ADVISORY | locktag_field1 = id1  locktag_field2 = id2 locktag_field3 = id3 locktag_field4 = id4  | 
| LOCKTAG_APPLY_TRANSACTION | locktag_field1 = dboid  locktag_field2 = suboid locktag_field3 = xid locktag_field4 = objid  | 
锁流程
初始化
初始化主要获取锁的共享内存区域指针,并且创建本地记录已持有锁的hash空间。
另外还有一个全局hash记录锁持有者,在共享内存中存储。锁的初始化接口如下
void InitLocks(void);
 
- 获取锁的数量
 
  #define NLOCKENTS() \
	mul_size(max_locks_per_xact, add_size(MaxBackends, max_prepared_xacts))
  max_table_size = NLOCKENTS();
  init_table_size = max_table_size / 2;
 
每个backend可以最大持有锁的数量由GUC参数决定,最小10,最大为int上限
- 在共享内存中分配锁的内存空间
 
  LockMethodLockHash = ShmemInitHash("LOCK hash",
									   init_table_size,
									   max_table_size,
									   &info,
									   HASH_ELEM | HASH_BLOBS | HASH_PARTITION);
 
常规锁在hash中存储,LockMethodLockHash的大小分配初始大小,不够时会再扩展,直到最大值。
- 锁持有者记录的内存空间
 
锁持有者,按每个锁平均有两个持有者来计算,所以空间大小为
	max_table_size *= 2;
	init_table_size *= 2;
 
也是用hash链表来记录,在共享内存中分配
	LockMethodProcLockHash = ShmemInitHash("PROCLOCK hash",
										   init_table_size,
										   max_table_size,
										   &info,
										   HASH_ELEM | HASH_FUNCTION | HASH_PARTITION);
 
- fastpath 信息记录的内存空间分配
 
	FastPathStrongRelationLocks =
		ShmemInitStruct("Fast Path Strong Relation Lock Data",
						sizeof(FastPathStrongRelationLockData), &found);
 
- 每个backend自己也会记录自己持有锁信息
 
	LockMethodLocalHash = hash_create("LOCALLOCK hash",
									  16,
									  &info,
									  HASH_ELEM | HASH_BLOBS);
 
这里也是使用hash,内存是在当前本地内存分配,大小指定为16,这里对于重复持有锁只累积计数。
锁种类
typedef enum LockTagType
{
	LOCKTAG_RELATION,			/* whole relation */
	LOCKTAG_RELATION_EXTEND,	/* the right to extend a relation */
	LOCKTAG_DATABASE_FROZEN_IDS,	/* pg_database.datfrozenxid */
	LOCKTAG_PAGE,				/* one page of a relation */
	LOCKTAG_TUPLE,				/* one physical tuple */
	LOCKTAG_TRANSACTION,		/* transaction (for waiting for xact done) */
	LOCKTAG_VIRTUALTRANSACTION, /* virtual transaction (ditto) */
	LOCKTAG_SPECULATIVE_TOKEN,	/* speculative insertion Xid and token */
	LOCKTAG_OBJECT,				/* non-relation database object */
	LOCKTAG_USERLOCK,			/* reserved for old contrib/userlock code */
	LOCKTAG_ADVISORY,			/* advisory user locks */
	LOCKTAG_APPLY_TRANSACTION	/* transaction being applied on a logical
								 * replication subscriber */
} LockTagType;
 
当前使用regular lock 加锁的数据库对象,由LockTagType定义,每一种对象都有对应的加锁、解锁接口
常规锁接口
表锁相关接口
表锁被分为8级,每级定义了使用范围,同时按等级递增规定了冲突矩阵。
在open表时,使用OID或relid来对表进行加锁,也可以尝试加锁,不进行等待
extern void LockRelationOid(Oid relid, LOCKMODE lockmode);
extern void LockRelationId(LockRelId *relid, LOCKMODE lockmode);
extern bool ConditionalLockRelationOid(Oid relid, LOCKMODE lockmode);
extern void UnlockRelationId(LockRelId *relid, LOCKMODE lockmode);
extern void UnlockRelationOid(Oid relid, LOCKMODE lockmode);
 
以下接口是open之后,进一步对表加锁时调用
此时relation结构可用,直接使用此结构就可以对表加锁
extern void LockRelation(Relation relation, LOCKMODE lockmode);
extern bool ConditionalLockRelation(Relation relation, LOCKMODE lockmode);
extern void UnlockRelation(Relation relation, LOCKMODE lockmode);
extern bool CheckRelationLockedByMe(Relation relation, LOCKMODE lockmode,
									bool orstronger);
extern bool LockHasWaitersRelation(Relation relation, LOCKMODE lockmode);
extern void LockRelationIdForSession(LockRelId *relid, LOCKMODE lockmode);
extern void UnlockRelationIdForSession(LockRelId *relid, LOCKMODE lockmode);
 
表扩展时相关锁接口
extern void LockRelationForExtension(Relation relation, LOCKMODE lockmode);
extern void UnlockRelationForExtension(Relation relation, LOCKMODE lockmode);
extern bool ConditionalLockRelationForExtension(Relation relation,
												LOCKMODE lockmode);
extern int	RelationExtensionLockWaiterCount(Relation relation);
 
计算database frozenxid时的锁接口
extern void LockDatabaseFrozenIds(LOCKMODE lockmode);
 
每个数据库会计算 frozenxid,然后计算所有库的最小值作为集群的frozenxid;
每个database的值在 pg_database.datfrozenxid 字段中存储
锁定page 时的接口
extern void LockPage(Relation relation, BlockNumber blkno, LOCKMODE lockmode);
extern bool ConditionalLockPage(Relation relation, BlockNumber blkno, LOCKMODE lockmode);
extern void UnlockPage(Relation relation, BlockNumber blkno, LOCKMODE lockmode);
 
当前只在索引中使用
行锁接口
extern void LockTuple(Relation relation, ItemPointer tid, LOCKMODE lockmode);
extern bool ConditionalLockTuple(Relation relation, ItemPointer tid,
								 LOCKMODE lockmode);
extern void UnlockTuple(Relation relation, ItemPointer tid, LOCKMODE lockmode);
 
事务等待锁接口
extern void XactLockTableInsert(TransactionId xid);
extern void XactLockTableDelete(TransactionId xid);
extern void XactLockTableWait(TransactionId xid, Relation rel,
							  ItemPointer ctid, XLTW_Oper oper);
extern bool ConditionalXactLockTableWait(TransactionId xid);
 
主要用于事务隔离冲突时,等待某个事务xid结束时使用
VXID锁接口
extern void WaitForLockers(LOCKTAG heaplocktag, LOCKMODE lockmode, bool progress);
extern void WaitForLockersMultiple(List *locktags, LOCKMODE lockmode, bool progress);
 
用于 VXID冲突时进行等待
行插入锁接口
extern uint32 SpeculativeInsertionLockAcquire(TransactionId xid);
extern void SpeculativeInsertionLockRelease(TransactionId xid);
extern void SpeculativeInsertionWait(TransactionId xid, uint32 token);
 
其它数据库对象(除表外)
extern void LockDatabaseObject(Oid classid, Oid objid, uint16 objsubid,
							   LOCKMODE lockmode);
extern void UnlockDatabaseObject(Oid classid, Oid objid, uint16 objsubid,
								 LOCKMODE lockmode);
 
跨库数据库对角
extern void LockSharedObject(Oid classid, Oid objid, uint16 objsubid,
							 LOCKMODE lockmode);
extern void UnlockSharedObject(Oid classid, Oid objid, uint16 objsubid,
							   LOCKMODE lockmode);
extern void LockSharedObjectForSession(Oid classid, Oid objid, uint16 objsubid,
									   LOCKMODE lockmode);
extern void UnlockSharedObjectForSession(Oid classid, Oid objid, uint16 objsubid,
										 LOCKMODE lockmode);
extern void LockApplyTransactionForSession(Oid suboid, TransactionId xid, uint16 objid,
										   LOCKMODE lockmode);
extern void UnlockApplyTransactionForSession(Oid suboid, TransactionId xid, uint16 objid,
											 LOCKMODE lockmode);										 
 
结尾
非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!
作者邮箱:study@senllang.onaliyun.com
 如有错误或者疏漏欢迎指出,互相学习。
注:未经同意,不得转载!



















