当前位置:   article > 正文

postgresql源码学习(八)—— 常规锁①-加锁步骤与本地锁表

postgresql源码学习(八)—— 常规锁①-加锁步骤与本地锁表

一、 简介

       我们平时说的表锁、页锁、咨询锁等等(行锁除外),实际上都是常规锁根据不同锁定对象划分的子类。

     有关常规锁的类型、等级、兼容性、查看方法等,属于DBA的日常运维知识,可以参考 PG锁和阻塞发现与处理_Hehuyi_In的博客-CSDN博客_pg查看阻塞  这里不重复列了。

然后介绍一个重要的结构体LockMethodData,它会在后面函数中反复出现

locks.h文件

  1. //该结构体定义了与"lock method"关联的锁的语义
  2. typedef struct LockMethodData
  3. {
  4. int numLockModes; // number of lock modes (READ,WRITE,etc) that
  5. are defined in this lock method. Must be less than MAX_LOCKMODES. 锁的模式数量
  6. const LOCKMASK *conflictTab; // 标志冲突的(二维)数组,mode i和mode j冲突则conflictTab [i]的第j位 = 1,conflictTab[0]未使用
  7. const char *const *lockModeNames; // ID strings for debug printouts.
  8. const bool *trace_flag; // pointer to GUC trace flag for this lock method.
  9. } LockMethodData;
  10. typedef const LockMethodData *LockMethod;
  1. //Lock methods的id,由于LOCKTAG对应字段为8位,所以最多指定256个
  2. typedef uint16 LOCKMETHODID;
  3. //两个预定义的lock methods
  4. #define DEFAULT_LOCKMETHOD 1
  5. #define USER_LOCKMETHOD 2

lockdefs.h文件

这里可以看到LOCKMODE是当整型用的,而LOCKMASK是当位图用的,所以前面的const LOCKMASK *conflictTab,这个冲突数组其实相当于是个二维数组。

  1. /*
  2. * LOCKMODE is an integer (1..N) indicating a lock type. LOCKMASK is a bit
  3. * mask indicating a set of held or requested lock types (the bit 1<<mode
  4. * corresponds to a particular lock mode).
  5. */
  6. typedef int LOCKMASK;
  7. typedef int LOCKMODE;

二、 常规锁的加锁对象

        如果要对一个表进行操作,通常会通过heap_open打开这个表,并在打开时指定需要的锁模式。之后会有一系列函数将锁模式传递下去,最终通过LockRelationOid函数将表的Oid和lockmode联系在一起。

  1. # define heap_open(r,l)
  2. Relation table_open(Oid relationId, LOCKMODE lockmode)
  3. Relation relation_open(Oid relationId, LOCKMODE lockmode)
  4. void LockRelationOid(Oid relid, LOCKMODE lockmode)

        当然,常规锁不仅可以对表加锁,也可以对各类对象加锁。LOCKTAG结构体的成员变量没有特定的含义,完全取决于具体类型。

  1. typedef struct LOCKTAG
  2. {
  3. uint32 locktag_field1; /* a 32-bit ID field */
  4. uint32 locktag_field2; /* a 32-bit ID field */
  5. uint32 locktag_field3; /* a 32-bit ID field */
  6. uint16 locktag_field4; /* a 16-bit ID field */
  7. uint8 locktag_type; /* see enum LockTagType */
  8. uint8 locktag_lockmethodid; /* lockmethod indicator */
  9. } LOCKTAG;

LOCKTAG具体类型如下

  1. typedef enum LockTagType
  2. {
  3. LOCKTAG_RELATION, /* 表锁*/
  4. LOCKTAG_RELATION_EXTEND, /* 对表进行extend时加锁 */
  5. LOCKTAG_DATABASE_FROZEN_IDS, /* 更新 pg_database.datfrozenxid 时加锁 */
  6. LOCKTAG_PAGE, /* 页锁 */
  7. LOCKTAG_TUPLE, /* 行锁 */
  8. LOCKTAG_TRANSACTION, /* 事务锁,在分配事务id时对这个事务id加锁,由于行并发更新时做事务等待 */
  9. LOCKTAG_VIRTUALTRANSACTION, /* 虚拟事务id */
  10. LOCKTAG_SPECULATIVE_TOKEN, /* upsert语句中需要等待confirm的行加锁 */
  11. LOCKTAG_OBJECT, /* 对非表对象类型加锁 */
  12. LOCKTAG_USERLOCK, /* 用户自定义的锁 */
  13. LOCKTAG_ADVISORY /* 咨询锁,目前可以跨事务存在 */
  14. } LockTagType;

       如果加锁对象是一个表,就可以通过SET_LOCKTAG_RELATION生成对应LOCKTAG(这部分代码就紧跟在LOCKTAG结构体定义后面):

  1. #define SET_LOCKTAG_RELATION(locktag,dboid,reloid) \
  2. ((locktag).locktag_field1 = (dboid), \
  3. (locktag).locktag_field2 = (reloid), \
  4. (locktag).locktag_field3 = 0, \
  5. (locktag).locktag_field4 = 0, \
  6. (locktag).locktag_type = LOCKTAG_RELATION, \
  7. (locktag).locktag_lockmethodid = DEFAULT_LOCKMETHOD)

三、 常规锁加锁步骤

       在pg中,锁获取最重要的函数是LockAcquire(核心是调用LockAcquireExtended),这是非常高频的操作,性能相当重要。因此在LockAcquire中,对封锁的性能做了大量优化。

常规锁主要保存在以下4个位置:

  • 本地锁表(LOCALLOCK结构体):对于重复申请的锁进行计数,避免频繁访问主锁表和进程锁表,相当于一层缓存
  • 快速路径(fast path):对弱锁的访问保存到本进程,避免频繁访问主锁表和进程锁表
  • 主锁表(LOCK结构体):保存一个锁对象所有相关信息
  • 进程锁表(PROCLOCK结构体):保存一个锁对象中与当前会话(进程)相关的信息

本篇我们先只看第一项,本地锁表。

四、 本地锁表

1. 结构体定义

       每个会话都保存了一个本地锁表 —— 当事务重复在同一个对象上申请同类型锁时,无须做冲突检测,只要将这个锁记录在本地即可,避免频繁访问主锁表和进程锁表。

  1. typedef struct LOCALLOCK
  2. {
  3. /* tag */
  4. LOCALLOCKTAG tag; /* 本地锁唯一id:LOCKTAG + LOCKMODE */
  5. /* data */
  6. uint32 hashcode; /* LOCKTAG的hash值 */
  7. LOCK *lock; /* 关联主锁表对象 */
  8. PROCLOCK *proclock; /*关联进程锁表对象*/
  9. int64 nLocks; /* 本地持有该锁的次数(类似计数器) */
  10. int numLockOwners; /* 相关的 ResourceOwners 数量,lockOwners数组的实际大小 */
  11. int maxLockOwners; /* ResourceOwners数组的最大长度 */
  12. LOCALLOCKOWNER *lockOwners; /* dynamically resizable array,长度可以动态变化的数组 */
  13. bool holdsStrongLockCount; /* bumped FastPathStrongRelationLocks,fast path需要的标记,是否持有了强锁 */
  14. bool lockCleared; /* we read all sinval msgs for lock,防止递归处理失效消息 */
  15. } LOCALLOCK;
  1. typedef struct LOCALLOCKTAG
  2. {
  3. LOCKTAG lock; /* identifies the lockable object */
  4. LOCKMODE mode; /* lock mode for this table entry */
  5. } LOCALLOCKTAG;
  1. typedef struct LOCALLOCKOWNER
  2. {
  3. /*
  4. * Note: if owner is NULL then the lock is held on behalf of the session;
  5. * otherwise it is held on behalf of my current transaction.
  6. *
  7. * Must use a forward struct reference to avoid circularity.
  8. */
  9. struct ResourceOwnerData *owner;
  10. int64 nLocks; /* # of times held by this owner */
  11. } LOCALLOCKOWNER;

本地锁表的名字是LockMethodLocalHash,这是个hash表,以下代码在lock.c

  1. /*
  2. * Pointers to hash tables containing lock state
  3. *
  4. * The LockMethodLockHash and LockMethodProcLockHash hash tables are in
  5. * shared memory; LockMethodLocalHash is local to each backend.
  6. */
  7. static HTAB *LockMethodLockHash;
  8. static HTAB *LockMethodProcLockHash;
  9. static HTAB *LockMethodLocalHash;

2. LockAcquireExtended函数

       本地锁表的判断和获取在LockAcquireExtended函数,下面通过gdb调试跟踪一下执行情况。

       模拟方法:LockAcquireExtended打断点,跟踪事务第一次select,和第二次select。

  • gdb测试

会话1

会话2

  • 具体代码与跟踪(第一次执行)

调用栈如下

  1. LockAcquireResult
  2. LockAcquireExtended(const LOCKTAG *locktag,
  3. LOCKMODE lockmode,
  4. bool sessionLock, // if true, acquire lock for session not current transaction。如果为true,为会话而不是当前事务获得锁
  5. bool dontWait, // if true, don't wait to acquire lock。如果为true,不需要等待获得锁
  6. bool reportMemoryError,// specifies whether a lock request that fills the lock table should generate an ERROR or not。填充lock table的锁请求是否需要生成一个错误信息
  7. LOCALLOCK **locallockp) //若locallockp非空,*locallockp为指向LOCALLOCK的指针,否则为空
  8. {
  9. LOCKMETHODID lockmethodid = locktag->locktag_lockmethodid;
  10. LockMethod lockMethodTable;
  11. LOCALLOCKTAG localtag;
  12. LOCALLOCK *locallock;
  13. LOCK *lock;
  14. PROCLOCK *proclock;
  15. bool found;
  16. ResourceOwner owner;
  17. uint32 hashcode;
  18. LWLock *partitionLock;
  19. bool found_conflict;
  20. bool log_lock = false;
  21. // lock method判断,必须>0且< LockMethods数组长度
  22. if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods))
  23. elog(ERROR, "unrecognized lock method: %d", lockmethodid);
  24. lockMethodTable = LockMethods[lockmethodid];
  25. // lock mode判断,必须>0且<8,因为一共就8级锁
  26. if (lockmode <= 0 || lockmode > lockMethodTable->numLockModes)
  27. elog(ERROR, "unrecognized lock mode: %d", lockmode);
  28. //是否为恢复模式,恢复模式不能获取RowExclusiveLock以上的锁
  29. if (RecoveryInProgress() && !InRecovery &&
  30. (locktag->locktag_type == LOCKTAG_OBJECT ||
  31. locktag->locktag_type == LOCKTAG_RELATION) &&
  32. lockmode > RowExclusiveLock)
  33. ereport(ERROR,
  34. (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
  35. errmsg("cannot acquire lock mode %s on database objects while recovery is in progress",
  36. lockMethodTable->lockModeNames[lockmode]),
  37. errhint("Only RowExclusiveLock or less can be acquired on database objects during recovery.")));

  1. #ifdef LOCK_DEBUG
  2. if (LOCK_DEBUG_ENABLED(locktag))
  3. elog(LOG, "LockAcquire: lock [%u,%u] %s",
  4. locktag->locktag_field1, locktag->locktag_field2,
  5. lockMethodTable->lockModeNames[lockmode]);
  6. #endif
  7. /* Identify owner for lock,函数入参之一 */
  8. if (sessionLock)
  9. owner = NULL;
  10. else
  11. owner = CurrentResourceOwner;

        从下面开始正式是本地表锁的判断和获取。本地锁表的名字是LockMethodLocalHash,这是个hash表,其查询标签是 locktag+lockmode。

  1. /*
  2. * Find or create a LOCALLOCK entry for this lock and lockmode
  3. */
  4. MemSet(&localtag, 0, sizeof(localtag)); /* must clear padding */
  5. localtag.lock = *locktag;
  6. localtag.mode = lockmode;
  7. locallock = (LOCALLOCK *) hash_search(LockMethodLocalHash, //这就是前面提到的hash表
  8. (void *) &localtag,
  9. HASH_ENTER, &found);

       下面是一个判断,如果没有从hash_search检查到锁,说明这是本事务第一次申请该锁。此时需要初始化locallock对象,例如在locallock->nLocks中填充值是0,证明还没有持有这个锁。

  1. /*
  2. * if it's a new locallock object, initialize it
  3. */
  4. if (!found)
  5. {
  6. locallock->lock = NULL;
  7. locallock->proclock = NULL;
  8. locallock->hashcode = LockTagHashCode(&(localtag.lock));
  9. locallock->nLocks = 0;
  10. locallock->holdsStrongLockCount = false;
  11. locallock->lockCleared = false;
  12. locallock->numLockOwners = 0;
  13. locallock->maxLockOwners = 8;
  14. locallock->lockOwners = NULL; /* in case next line fails */
  15. locallock->lockOwners = (LOCALLOCKOWNER *)
  16. MemoryContextAlloc(TopMemoryContext,
  17. locallock->maxLockOwners * sizeof(LOCALLOCKOWNER));
  18. }
  19. else
  20. {
  21. /* Make sure there will be room to remember the lock,如果检查到了锁,需要判断是否还有位置存放该锁。如果numLockOwners>= maxLockOwners了,则需要提高maxLockOwners上限值,并重新lockOwners数组调整大小 */
  22. if (locallock->numLockOwners >= locallock->maxLockOwners)
  23. {
  24. int newsize = locallock->maxLockOwners * 2;
  25. locallock->lockOwners = (LOCALLOCKOWNER *)
  26. repalloc(locallock->lockOwners,
  27. newsize * sizeof(LOCALLOCKOWNER));
  28. locallock->maxLockOwners = newsize;
  29. }
  30. }

  1. hashcode = locallock->hashcode;
  2. if (locallockp) //是否设置了locallockp参数
  3. *locallockp = locallock;
  4. if (locallock->nLocks > 0) //第一次因为没有检查到锁,locallock->nLocks=0,不会执行到这步
  5. {
  6. GrantLockLocal(locallock, owner);
  7. if (locallock->lockCleared)
  8. return LOCKACQUIRE_ALREADY_CLEAR;
  9. else
  10. return LOCKACQUIRE_ALREADY_HELD;
  11. }

  1. /*
  2. 再下面就是与fast path、进程锁相关,我们后面再看
  3. */
  4. if (EligibleForRelationFastPath(locktag, lockmode) &&
  5. FastPathLocalUseCount < FP_LOCK_SLOTS_PER_BACKEND)
  6. {
  7. }

  • 具体代码与跟踪(第二次执行)

会话1

会话2

前面跟开始差不多

注意这次 if (!found) 为假,说明本地找到了对应锁,进到了else部分。

       如果检查到了对应锁(locallock->nLocks一定大于0),说明这个锁对象和模式已经授予了本事务,给本地锁表对应锁增加引用计数即可,这个工作由GrantLockLocal函数完成。

  1. if (locallock->nLocks > 0) //并且nLocks=1,会进到GrantLockLocal函数
  2. {
  3. GrantLockLocal(locallock, owner);
  4. if (locallock->lockCleared)
  5. return LOCKACQUIRE_ALREADY_CLEAR;
  6. else
  7. return LOCKACQUIRE_ALREADY_HELD;
  8. }

3. GrantLockLocal函数

      负责给本地锁表对应锁增加引用计数,一个简单的小函数。GrantLockLocal函数首先增加locallock->nLocks计数,然后增加ResourceOwner中锁的计数,这次申请锁的任务就完成了。

再加一个gdb断点,重新update一下

函数调用栈如下

  1. /*
  2. * GrantLockLocal -- update the locallock data structures to show
  3. * the lock request has been granted.
  4. *
  5. * We expect that LockAcquire made sure there is room to add a new
  6. * ResourceOwner entry.
  7. */
  8. static void
  9. GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner)
  10. {
  11. LOCALLOCKOWNER *lockOwners = locallock->lockOwners;
  12. int i;
  13. Assert(locallock->numLockOwners < locallock->maxLockOwners);
  14. /* Count the total */
  15. locallock->nLocks++;

  

  1. /* Count the per-owner lock,增加对应owner持锁数 */
  2. for (i = 0; i < locallock->numLockOwners; i++)
  3. {
  4. if (lockOwners[i].owner == owner)
  5. {
  6. lockOwners[i].nLocks++;
  7. return; //如果匹配到,然后return,函数就结束了
  8. }
  9. }
  10. // 如果循环完还是没匹配到,例如第一次的时候(即第二次申请相同对象锁的时候),需要初始化lockOwners数组值
  11. lockOwners[i].owner = owner;
  12. lockOwners[i].nLocks = 1;
  13. locallock->numLockOwners++;
  14. if (owner != NULL)
  15. ResourceOwnerRememberLock(owner, locallock);
  16. /* Indicate that the lock is acquired for certain types of locks. */
  17. CheckAndSetLockHeld(locallock, true);
  18. }

      gdb测试时由于已经是第三次申请该锁,if中的owner已经能匹配到,因此增加计数后就直接return了。

参考

《PostgreSQL技术内幕:事务处理深度探索》第2章

《PostgreSQL数据库内核分析》第7章

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/236731
推荐阅读
相关标签
  

闽ICP备14008679号