当前位置: 首页 > news >正文

Postgresql源码(144)LockRelease常规锁释放流程分析

相关
《Postgresql源码(69)常规锁细节分析》

最新遇到一个共享内存损坏导致常规锁释放报错warning "you don’t own a lock of type"的问题。

本篇对常规锁的概念做一些回顾,顺便分析下释放锁的流程。

  • SpinLock:❎
  • LWLock:❎
  • RegularLock:✅

基础概念回顾

《Postgresql源码(69)常规锁细节分析》

  • LockMethodLockHash:共享内存中的全局哈希表,所有进程可见。
    • 事务申请强锁、检测锁冲突或需要与其他事务协调时访问主锁表。
  • FastPathStrongRelationLocks:强锁标记表,共享内存中的计数器数组,用于快速判断是否存在强锁。
    • 快速筛选弱锁是否需进入主锁表流程,减少共享内存竞争。
  • LockMethodLocalHash:本地锁表,进程本地内存,每个后台进程独立维护。
    • 高频弱锁操作(如DML)的快速加锁与释放。

关于fastpath

  1. 先检查是否符合条件,EligibleForRelationFastPath,例如锁级别小于四,是表锁等。
  2. 去共享内存,FastPathStrongRelationLocks,查tag是否已经加过强锁了,加过就不能fastpath了。
  3. 如果都满足了,执行下面的,FastPathGrantRelationLock,开始加fast锁。加锁信息记录到PGPROC中。

《Postgresql源码(69)常规锁细节分析》

FastPathGrantRelationLock函数:

  • 使用PGPROC的数组Oid fpRelId[16];来保存OID。
  • 使用PGPROC的变量uint64 fpLockBits当做位图来记录锁级别。
    在这里插入图片描述
    FastPathGrantRelationLock逻辑:
  1. 3个bit一组按顺序查位图是不是空的,是空的就记录下来位置,不是空的就看下oid里面记的是不是需要的,如果正好Oid也是需要的,把当前请求锁模式或进去就可以返回了。
  2. 如果查了一遍位图,所有Oid都不是需要的,那就找一个空的位置,把锁级别记录到位图,OID记录到数组,然后返回。
  3. 如果查了一遍位图,没有一个空余位置,就返回false了。

LockRelease详细流程分析

bool
LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock)
{LOCKMETHODID lockmethodid = locktag->locktag_lockmethodid;LockMethod	lockMethodTable;LOCALLOCKTAG localtag;LOCALLOCK  *locallock;LOCK	   *lock;PROCLOCK   *proclock;LWLock	   *partitionLock;bool		wakeupNeeded;...

第一步 查本地锁表。
先查本地锁表,申请过的任何锁,一定会在本地锁表中存放,如果没有就会报错you don't own a lock of type,这里一般不会报错,本地锁表LockMethodLocalHash不在共享内存中,损坏的概率比较低。

	MemSet(&localtag, 0, sizeof(localtag)); /* must clear padding */localtag.lock = *locktag;localtag.mode = lockmode;locallock = (LOCALLOCK *) hash_search(LockMethodLocalHash,&localtag,HASH_FIND, NULL);if (!locallock || locallock->nLocks <= 0){elog(WARNING, "you don't own a lock of type %s",lockMethodTable->lockModeNames[lockmode]);return false;}

第二步 本地锁引用-1

  1. 锁在一个事务中可能被锁多次,但只有第一次锁才会走fastpath、主锁表,后面再加锁只会走本地锁表。
  2. 所以放锁时,如果发现是本地多次加锁的,只需要本地锁表-1即可。
  3. 这里还增加了多resowner的机制,因为可能有savepoint造成事务内 有多层子事务,这种情况下锁会被记录在多个resowner下,所以这里加了循环,遍历resowner,找到申请时使用的resowner,然后再ResourceOwnerForgetLock。
  4. 删完了一个顺便把数组最后一个挪到空洞位置,使数组始终是紧凑的。
	{LOCALLOCKOWNER *lockOwners = locallock->lockOwners;ResourceOwner owner;int			i;/* Identify owner for lock */if (sessionLock)owner = NULL;elseowner = CurrentResourceOwner;for (i = locallock->numLockOwners - 1; i >= 0; i--){if (lockOwners[i].owner == owner){Assert(lockOwners[i].nLocks > 0);if (--lockOwners[i].nLocks == 0){if (owner != NULL)ResourceOwnerForgetLock(owner, locallock);/* compact out unused slot */locallock->numLockOwners--;if (i < locallock->numLockOwners)lockOwners[i] = lockOwners[locallock->numLockOwners];}break;}}if (i < 0){/* don't release a lock belonging to another owner */elog(WARNING, "you don't own a lock of type %s",lockMethodTable->lockModeNames[lockmode]);return false;}}locallock->nLocks--;if (locallock->nLocks > 0)return true;

第三步 最后一次释放锁,需要真正把锁释放了。

尝试fastpath release,因为fastpath都是所在本地的,所以如果能释放也不需要知会主锁表。

这里需要给MyProc->fpInfoLock加LWLock的原因是:其他进程加强锁时,会遍历所有进程的PGPROC中fastpath记录的弱锁,将冲突的迁移到主锁表中。所以这里有可能被其他进程并发更新,所以需要LWLock。

	...if (EligibleForRelationFastPath(locktag, lockmode) &&FastPathLocalUseCount > 0){bool		released;LWLockAcquire(&MyProc->fpInfoLock, LW_EXCLUSIVE);released = FastPathUnGrantRelationLock(locktag->locktag_field2,lockmode);LWLockRelease(&MyProc->fpInfoLock);if (released){RemoveLocalLock(locallock);return true;}}

第四步:主锁表操作。

fastpath没删成,说明锁在主锁表中。

【共享内存】主锁表:LockMethodLockHash(存储所有锁对象)
【共享内存】锁进程关系表:LockMethodProcLockHash(查询当前进程阻塞了哪些进程,死锁检测)

  1. 开始操作主锁表,为了增加并发,这里按hashcode做了分区。如果不在一个分区中的锁可以并发。
  2. 在主锁表中定位锁对象。
  3. 在LockMethodProcLockHash定位PROCLOCK,用于查询当前进程阻塞了哪些进程
	partitionLock = LockHashPartitionLock(locallock->hashcode);LWLockAcquire(partitionLock, LW_EXCLUSIVE);lock = locallock->lock;if (!lock){PROCLOCKTAG proclocktag;Assert(EligibleForRelationFastPath(locktag, lockmode));lock = (LOCK *) hash_search_with_hash_value(LockMethodLockHash,locktag,locallock->hashcode,HASH_FIND,NULL);if (!lock)elog(ERROR, "failed to re-find shared lock object");locallock->lock = lock;proclocktag.myLock = lock;proclocktag.myProc = MyProc;locallock->proclock = (PROCLOCK *) hash_search(LockMethodProcLockHash,&proclocktag,HASH_FIND,NULL);if (!locallock->proclock)elog(ERROR, "failed to re-find shared proclock object");}LOCK_PRINT("LockRelease: found", lock, lockmode);proclock = locallock->proclock;PROCLOCK_PRINT("LockRelease: found", proclock);

如果下面报错,说明在主锁表和LockMethodProcLockHash都查到了,但是proclock->holdMask记录的lockmode和当前要release的不对应,报一个告警,把本地锁释放了,但不释放主锁表的信息。

	/** Double-check that we are actually holding a lock of the type we want to* release.*/if (!(proclock->holdMask & LOCKBIT_ON(lockmode))){PROCLOCK_PRINT("LockRelease: WRONGTYPE", proclock);LWLockRelease(partitionLock);elog(WARNING, "you don't own a lock of type %s",lockMethodTable->lockModeNames[lockmode]);RemoveLocalLock(locallock);return false;}
  • 调用 UnGrantLock 清除锁的授予状态(grantMask)和等待队列(waitMask),并标记是否需要唤醒等待进程

  • 通过 CleanUpLock 处理锁状态,若有等待进程,触发 LWLockWakeup 唤醒。

	/** Do the releasing.  CleanUpLock will waken any now-wakable waiters.*/wakeupNeeded = UnGrantLock(lock, lockmode, proclock, lockMethodTable);CleanUpLock(lock, proclock,lockMethodTable, locallock->hashcode,wakeupNeeded);LWLockRelease(partitionLock);RemoveLocalLock(locallock);return true;
}

LockRelease实例

这里截取drop table的一个中间态,给一个lock释放、并能走到主锁表释放的案例。

某一时刻查询到

postgres=# select * from pg_locks where relation=1214;
-[ RECORD 1 ]------+-----------------
locktype           | relation
database           | 0
relation           | 1214
page               |
tuple              |
virtualxid         |
transactionid      |
classid            |
objid              |
objsubid           |
virtualtransaction | 3/7
pid                | 1131328
mode               | RowExclusiveLock
granted            | t
fastpath           | f
waitstart          |

释放三级锁,没有走fastpath。

LockRelease (locktag=0x7ffd581db190, lockmode=3, sessionLock=false)

为什么没走fastpath?因为locktag_field1=0,表示这个表不属于某一个库,一般是是共享系统表(1214指的是pg_shdepend确实是共享系统表)。

(gdb) p	*locktag
$17 = {
locktag_field1 = 0, 
locktag_field2 = 1214, 
locktag_field3 = 0, 
locktag_field4 = 0, 
locktag_type = 0 '\000', 
locktag_lockmethodid = 1 '\001'}

释放锁时,两个主要数据结构的值:

(gdb) p	*lock
$19 = {tag = {locktag_field1 = 0, locktag_field2 = 1214, locktag_field3 = 0, locktag_field4 = 0, locktag_type = 0 '\000', locktag_lockmethodid = 1 '\001'}, grantMask = 8,waitMask = 0, procLocks = {head = {prev = 0x7f487fd34340, next = 0x7f487fd34340}}, waitProcs = {dlist = {head = {prev = 0x7f487e715988, next = 0x7f487e715988}}, count = 0},requested = {0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, nRequested = 1, granted = {0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, nGranted = 1}
(gdb) p	*proclock
$20 = {tag = {myLock = 0x7f487e715960, myProc = 0x7f4884dc4590}, groupLeader = 0x7f4884dc4590, holdMask = 8, releaseMask = 0, lockLink = {prev = 0x7f487e715978, next = 0x7f487e715978}, procLink = {prev = 0x7f4884dc4668, next = 0x7f4884dc4668}}

Lock

  • grantMask = 8:8 对应二进制 1000 对应 RowExclusiveLock。
  • waitMask = 0:资源无冲突请求
  • procLocks :指向自己,说明只有一个PROCLOCK关联当前这个锁。
  • requested[3] = 1​​:当前锁对象上有一个 RowExclusiveLock 模式的请求。
  • nRequested = 1​​:总请求次数为 1,与 requested[3] 一致。
  • ​granted[3] = 1:该请求已被成功授予

PROCLOCK

  • tag:关联的LOCK对象
  • holdMask = 8:与LOCK的grantMask一致,表示该进程在锁对象上持有RowExclusiveLock
  • releaseMask = 0:进程未触发锁释放操作
  • ​​lockLink​​:指向LOCK的procLocks链表,表示该PROCLOCK是链表中唯一的节点。
  • procLink​​:指向PGPROC的本地锁链表,表示该锁属于进程的本地锁管理范围。
/** Per-locked-object lock information:** tag -- uniquely identifies the object being locked* grantMask -- bitmask for all lock types currently granted on this object.* waitMask -- bitmask for all lock types currently awaited on this object.* procLocks -- list of PROCLOCK objects for this lock.* waitProcs -- queue of processes waiting for this lock.* requested -- count of each lock type currently requested on the lock*		(includes requests already granted!!).* nRequested -- total requested locks of all types.* granted -- count of each lock type currently granted on the lock.* nGranted -- total granted locks of all types.** Note: these counts count 1 for each backend.  Internally to a backend,* there may be multiple grabs on a particular lock, but this is not reflected* into shared memory.*/
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;/** We may have several different backends holding or awaiting locks* on the same lockable object.  We need to store some per-holder/waiter* information for each such holder (or would-be holder).  This is kept in* a PROCLOCK struct.** PROCLOCKTAG is the key information needed to look up a PROCLOCK item in the* proclock hashtable.  A PROCLOCKTAG value uniquely identifies the combination* of a lockable object and a holder/waiter for that object.  (We can use* pointers here because the PROCLOCKTAG need only be unique for the lifespan* of the PROCLOCK, and it will never outlive the lock or the proc.)** Internally to a backend, it is possible for the same lock to be held* for different purposes: the backend tracks transaction locks separately* from session locks.  However, this is not reflected in the shared-memory* state: we only track which backend(s) hold the lock.  This is OK since a* backend can never block itself.** The holdMask field shows the already-granted locks represented by this* proclock.  Note that there will be a proclock object, possibly with* zero holdMask, for any lock that the process is currently waiting on.* Otherwise, proclock objects whose holdMasks are zero are recycled* as soon as convenient.** releaseMask is workspace for LockReleaseAll(): it shows the locks due* to be released during the current call.  This must only be examined or* set by the backend owning the PROCLOCK.** Each PROCLOCK object is linked into lists for both the associated LOCK* object and the owning PGPROC object.  Note that the PROCLOCK is entered* into these lists as soon as it is created, even if no lock has yet been* granted.  A PGPROC that is waiting for a lock to be granted will also be* linked into the lock's waitProcs queue.*/
typedef struct PROCLOCKTAG
{/* NB: we assume this struct contains no padding! */LOCK	   *myLock;			/* link to per-lockable-object information */PGPROC	   *myProc;			/* link to PGPROC of owning backend */
} PROCLOCKTAG;typedef struct PROCLOCK
{/* tag */PROCLOCKTAG tag;			/* unique identifier of proclock object *//* data */PGPROC	   *groupLeader;	/* proc's lock group leader, or proc itself */LOCKMASK	holdMask;		/* bitmask for lock types currently held */LOCKMASK	releaseMask;	/* bitmask for lock types to be released */dlist_node	lockLink;		/* list link in LOCK's list of proclocks */dlist_node	procLink;		/* list link in PGPROC's list of proclocks */
} PROCLOCK;

相关文章:

  • 完美解决 mobile-ffmpeg Not overwriting - exiting
  • .NET平台用C#在PDF中创建可交互的表单域(Form Field)
  • 垃圾收集GC的基本理解
  • K8S Secret 快速开始
  • k8s -hpa
  • uniapp打包apk详细教程
  • [特殊字符] Spring Cloud 微服务配置统一管理:基于 Nacos 的最佳实践详解
  • 基于站点观测的中国1km土壤湿度日尺度数据集(2000-2022)
  • 【MySQL】增删改查(CRUD)
  • w~大模型~合集14
  • 精益数据分析(34/126):深挖电商运营关键要点与指标
  • 【爬虫】deepseek谈爬虫工具
  • HTTP 快速解析
  • Windows11下本地化部署AI开发环境(Dify+Ollama)
  • 【Kubernets知识】Secret组件更新大全
  • Python 爬取微店商品列表接口(item_search)的实战指南
  • 【ArcGISPro学习笔记】布局输出时图例总是有省略号怎么办?
  • React Native 从零开始完整教程(环境配置 → 国内镜像加速 → 运行项目)
  • Spark SQL 之 DAG
  • vite入门教程
  • 中国金茂向滨江集团提供11.21亿元诚意金借款,拟合作开发3月获取的地块
  • 《探秘海昏侯国》数字沉浸特展亮相首届江西文化旅游产业博览交易会
  • 2025五一档新片电影总票房破亿
  • 证监会副主席王建军被查
  • 中央网信办部署开展“清朗·整治AI技术滥用”专项行动
  • 奈雪的茶叫停“能喝奶茶就不要喝水”宣传,当地市监称不要误导消费者