赞
踩
我们平时说的表锁、页锁、咨询锁等等(行锁除外),实际上都是常规锁根据不同锁定对象划分的子类。
有关常规锁的类型、等级、兼容性、查看方法等,属于DBA的日常运维知识,可以参考 PG锁和阻塞发现与处理_Hehuyi_In的博客-CSDN博客_pg查看阻塞 这里不重复列了。
然后介绍一个重要的结构体LockMethodData,它会在后面函数中反复出现
locks.h文件
- //该结构体定义了与"lock method"关联的锁的语义
- typedef struct LockMethodData
- {
- int numLockModes; // number of lock modes (READ,WRITE,etc) that
- are defined in this lock method. Must be less than MAX_LOCKMODES. 锁的模式数量
- const LOCKMASK *conflictTab; // 标志冲突的(二维)数组,mode i和mode j冲突则conflictTab [i]的第j位 = 1,conflictTab[0]未使用
- const char *const *lockModeNames; // ID strings for debug printouts.
- const bool *trace_flag; // pointer to GUC trace flag for this lock method.
- } LockMethodData;
-
- typedef const LockMethodData *LockMethod;
- //Lock methods的id,由于LOCKTAG对应字段为8位,所以最多指定256个
- typedef uint16 LOCKMETHODID;
-
- //两个预定义的lock methods
- #define DEFAULT_LOCKMETHOD 1
- #define USER_LOCKMETHOD 2
lockdefs.h文件
这里可以看到LOCKMODE是当整型用的,而LOCKMASK是当位图用的,所以前面的const LOCKMASK *conflictTab,这个冲突数组其实相当于是个二维数组。
- /*
- * LOCKMODE is an integer (1..N) indicating a lock type. LOCKMASK is a bit
- * mask indicating a set of held or requested lock types (the bit 1<<mode
- * corresponds to a particular lock mode).
- */
- typedef int LOCKMASK;
- typedef int LOCKMODE;
如果要对一个表进行操作,通常会通过heap_open打开这个表,并在打开时指定需要的锁模式。之后会有一系列函数将锁模式传递下去,最终通过LockRelationOid函数将表的Oid和lockmode联系在一起。
- # define heap_open(r,l)
- Relation table_open(Oid relationId, LOCKMODE lockmode)
- Relation relation_open(Oid relationId, LOCKMODE lockmode)
- void LockRelationOid(Oid relid, LOCKMODE lockmode)
当然,常规锁不仅可以对表加锁,也可以对各类对象加锁。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具体类型如下
- typedef enum LockTagType
- {
- LOCKTAG_RELATION, /* 表锁*/
- LOCKTAG_RELATION_EXTEND, /* 对表进行extend时加锁 */
- LOCKTAG_DATABASE_FROZEN_IDS, /* 更新 pg_database.datfrozenxid 时加锁 */
- LOCKTAG_PAGE, /* 页锁 */
- LOCKTAG_TUPLE, /* 行锁 */
- LOCKTAG_TRANSACTION, /* 事务锁,在分配事务id时对这个事务id加锁,由于行并发更新时做事务等待 */
- LOCKTAG_VIRTUALTRANSACTION, /* 虚拟事务id */
- LOCKTAG_SPECULATIVE_TOKEN, /* upsert语句中需要等待confirm的行加锁 */
- LOCKTAG_OBJECT, /* 对非表对象类型加锁 */
- LOCKTAG_USERLOCK, /* 用户自定义的锁 */
- LOCKTAG_ADVISORY /* 咨询锁,目前可以跨事务存在 */
- } LockTagType;
如果加锁对象是一个表,就可以通过SET_LOCKTAG_RELATION生成对应LOCKTAG(这部分代码就紧跟在LOCKTAG结构体定义后面):
- #define SET_LOCKTAG_RELATION(locktag,dboid,reloid) \
- ((locktag).locktag_field1 = (dboid), \
- (locktag).locktag_field2 = (reloid), \
- (locktag).locktag_field3 = 0, \
- (locktag).locktag_field4 = 0, \
- (locktag).locktag_type = LOCKTAG_RELATION, \
- (locktag).locktag_lockmethodid = DEFAULT_LOCKMETHOD)
在pg中,锁获取最重要的函数是LockAcquire(核心是调用LockAcquireExtended),这是非常高频的操作,性能相当重要。因此在LockAcquire中,对封锁的性能做了大量优化。
常规锁主要保存在以下4个位置:
本篇我们先只看第一项,本地锁表。
每个会话都保存了一个本地锁表 —— 当事务重复在同一个对象上申请同类型锁时,无须做冲突检测,只要将这个锁记录在本地即可,避免频繁访问主锁表和进程锁表。
- typedef struct LOCALLOCK
- {
- /* tag */
- LOCALLOCKTAG tag; /* 本地锁唯一id:LOCKTAG + LOCKMODE */
-
- /* data */
- uint32 hashcode; /* LOCKTAG的hash值 */
- LOCK *lock; /* 关联主锁表对象 */
- PROCLOCK *proclock; /*关联进程锁表对象*/
- int64 nLocks; /* 本地持有该锁的次数(类似计数器) */
- int numLockOwners; /* 相关的 ResourceOwners 数量,lockOwners数组的实际大小 */
- int maxLockOwners; /* ResourceOwners数组的最大长度 */
- LOCALLOCKOWNER *lockOwners; /* dynamically resizable array,长度可以动态变化的数组 */
- bool holdsStrongLockCount; /* bumped FastPathStrongRelationLocks,fast path需要的标记,是否持有了强锁 */
- bool lockCleared; /* we read all sinval msgs for lock,防止递归处理失效消息 */
- } LOCALLOCK;

- typedef struct LOCALLOCKTAG
- {
- LOCKTAG lock; /* identifies the lockable object */
- LOCKMODE mode; /* lock mode for this table entry */
- } LOCALLOCKTAG;
- typedef struct LOCALLOCKOWNER
- {
- /*
- * Note: if owner is NULL then the lock is held on behalf of the session;
- * otherwise it is held on behalf of my current transaction.
- *
- * Must use a forward struct reference to avoid circularity.
- */
- struct ResourceOwnerData *owner;
- int64 nLocks; /* # of times held by this owner */
- } LOCALLOCKOWNER;
本地锁表的名字是LockMethodLocalHash,这是个hash表,以下代码在lock.c
- /*
- * Pointers to hash tables containing lock state
- *
- * The LockMethodLockHash and LockMethodProcLockHash hash tables are in
- * shared memory; LockMethodLocalHash is local to each backend.
- */
- static HTAB *LockMethodLockHash;
- static HTAB *LockMethodProcLockHash;
- static HTAB *LockMethodLocalHash;
本地锁表的判断和获取在LockAcquireExtended函数,下面通过gdb调试跟踪一下执行情况。
模拟方法:LockAcquireExtended打断点,跟踪事务第一次select,和第二次select。
会话1
会话2
调用栈如下
- LockAcquireResult
- LockAcquireExtended(const LOCKTAG *locktag,
- LOCKMODE lockmode,
- bool sessionLock, // if true, acquire lock for session not current transaction。如果为true,为会话而不是当前事务获得锁
- bool dontWait, // if true, don't wait to acquire lock。如果为true,不需要等待获得锁
- bool reportMemoryError,// specifies whether a lock request that fills the lock table should generate an ERROR or not。填充lock table的锁请求是否需要生成一个错误信息
- LOCALLOCK **locallockp) //若locallockp非空,*locallockp为指向LOCALLOCK的指针,否则为空
- {
- LOCKMETHODID lockmethodid = locktag->locktag_lockmethodid;
- LockMethod lockMethodTable;
- LOCALLOCKTAG localtag;
- LOCALLOCK *locallock;
- LOCK *lock;
- PROCLOCK *proclock;
- bool found;
- ResourceOwner owner;
- uint32 hashcode;
- LWLock *partitionLock;
- bool found_conflict;
- bool log_lock = false;
- // lock method判断,必须>0且< LockMethods数组长度
- if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods))
- elog(ERROR, "unrecognized lock method: %d", lockmethodid);
- lockMethodTable = LockMethods[lockmethodid];
- // lock mode判断,必须>0且<8,因为一共就8级锁
- if (lockmode <= 0 || lockmode > lockMethodTable->numLockModes)
- elog(ERROR, "unrecognized lock mode: %d", lockmode);
- //是否为恢复模式,恢复模式不能获取RowExclusiveLock以上的锁
- if (RecoveryInProgress() && !InRecovery &&
- (locktag->locktag_type == LOCKTAG_OBJECT ||
- locktag->locktag_type == LOCKTAG_RELATION) &&
- lockmode > RowExclusiveLock)
- ereport(ERROR,
- (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
- errmsg("cannot acquire lock mode %s on database objects while recovery is in progress",
- lockMethodTable->lockModeNames[lockmode]),
- errhint("Only RowExclusiveLock or less can be acquired on database objects during recovery.")));

- #ifdef LOCK_DEBUG
- if (LOCK_DEBUG_ENABLED(locktag))
- elog(LOG, "LockAcquire: lock [%u,%u] %s",
- locktag->locktag_field1, locktag->locktag_field2,
- lockMethodTable->lockModeNames[lockmode]);
- #endif
-
- /* Identify owner for lock,函数入参之一 */
- if (sessionLock)
- owner = NULL;
- else
- owner = CurrentResourceOwner;
从下面开始正式是本地表锁的判断和获取。本地锁表的名字是LockMethodLocalHash,这是个hash表,其查询标签是 locktag+lockmode。
- /*
- * Find or create a LOCALLOCK entry for this lock and lockmode
- */
- MemSet(&localtag, 0, sizeof(localtag)); /* must clear padding */
- localtag.lock = *locktag;
- localtag.mode = lockmode;
-
- locallock = (LOCALLOCK *) hash_search(LockMethodLocalHash, //这就是前面提到的hash表
- (void *) &localtag,
- HASH_ENTER, &found);
下面是一个判断,如果没有从hash_search检查到锁,说明这是本事务第一次申请该锁。此时需要初始化locallock对象,例如在locallock->nLocks中填充值是0,证明还没有持有这个锁。
- /*
- * if it's a new locallock object, initialize it
- */
- if (!found)
- {
- locallock->lock = NULL;
- locallock->proclock = NULL;
- locallock->hashcode = LockTagHashCode(&(localtag.lock));
- locallock->nLocks = 0;
- locallock->holdsStrongLockCount = false;
- locallock->lockCleared = false;
- locallock->numLockOwners = 0;
- locallock->maxLockOwners = 8;
- locallock->lockOwners = NULL; /* in case next line fails */
- locallock->lockOwners = (LOCALLOCKOWNER *)
- MemoryContextAlloc(TopMemoryContext,
- locallock->maxLockOwners * sizeof(LOCALLOCKOWNER));
- }
- else
- {
- /* Make sure there will be room to remember the lock,如果检查到了锁,需要判断是否还有位置存放该锁。如果numLockOwners>= maxLockOwners了,则需要提高maxLockOwners上限值,并重新lockOwners数组调整大小 */
- if (locallock->numLockOwners >= locallock->maxLockOwners)
- {
- int newsize = locallock->maxLockOwners * 2;
-
- locallock->lockOwners = (LOCALLOCKOWNER *)
- repalloc(locallock->lockOwners,
- newsize * sizeof(LOCALLOCKOWNER));
- locallock->maxLockOwners = newsize;
- }
- }

- hashcode = locallock->hashcode;
-
- if (locallockp) //是否设置了locallockp参数
- *locallockp = locallock;
-
- if (locallock->nLocks > 0) //第一次因为没有检查到锁,locallock->nLocks=0,不会执行到这步
- {
- GrantLockLocal(locallock, owner);
- if (locallock->lockCleared)
- return LOCKACQUIRE_ALREADY_CLEAR;
- else
- return LOCKACQUIRE_ALREADY_HELD;
- }
- /*
- 再下面就是与fast path、进程锁相关,我们后面再看
- */
- if (EligibleForRelationFastPath(locktag, lockmode) &&
- FastPathLocalUseCount < FP_LOCK_SLOTS_PER_BACKEND)
- {
- …
- }
会话1
会话2
前面跟开始差不多
注意这次 if (!found) 为假,说明本地找到了对应锁,进到了else部分。
如果检查到了对应锁(locallock->nLocks一定大于0),说明这个锁对象和模式已经授予了本事务,给本地锁表对应锁增加引用计数即可,这个工作由GrantLockLocal函数完成。
- if (locallock->nLocks > 0) //并且nLocks=1,会进到GrantLockLocal函数
- {
- GrantLockLocal(locallock, owner);
- if (locallock->lockCleared)
- return LOCKACQUIRE_ALREADY_CLEAR;
- else
- return LOCKACQUIRE_ALREADY_HELD;
- }
负责给本地锁表对应锁增加引用计数,一个简单的小函数。GrantLockLocal函数首先增加locallock->nLocks计数,然后增加ResourceOwner中锁的计数,这次申请锁的任务就完成了。
再加一个gdb断点,重新update一下
函数调用栈如下
- /*
- * GrantLockLocal -- update the locallock data structures to show
- * the lock request has been granted.
- *
- * We expect that LockAcquire made sure there is room to add a new
- * ResourceOwner entry.
- */
- static void
- GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner)
- {
- LOCALLOCKOWNER *lockOwners = locallock->lockOwners;
- int i;
- Assert(locallock->numLockOwners < locallock->maxLockOwners);
- /* Count the total */
- locallock->nLocks++;
- /* Count the per-owner lock,增加对应owner持锁数 */
- for (i = 0; i < locallock->numLockOwners; i++)
- {
- if (lockOwners[i].owner == owner)
- {
- lockOwners[i].nLocks++;
- return; //如果匹配到,然后return,函数就结束了
- }
- }
- // 如果循环完还是没匹配到,例如第一次的时候(即第二次申请相同对象锁的时候),需要初始化lockOwners数组值
- lockOwners[i].owner = owner;
- lockOwners[i].nLocks = 1;
- locallock->numLockOwners++;
- if (owner != NULL)
- ResourceOwnerRememberLock(owner, locallock);
-
- /* Indicate that the lock is acquired for certain types of locks. */
- CheckAndSetLockHeld(locallock, true);
- }

gdb测试时由于已经是第三次申请该锁,if中的owner已经能匹配到,因此增加计数后就直接return了。
参考
《PostgreSQL技术内幕:事务处理深度探索》第2章
《PostgreSQL数据库内核分析》第7章
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。