当前位置:   article > 正文

postgresql源码学习(十)—— 常规锁③-主锁表与进程锁表的初始化与查询_pg查看锁表

pg查看锁表

一、 主锁表与进程锁表的结构

       在数据库启动阶段,pg通过InitLocks函数初始化保存锁对象的共享内存空间。在共享内存中,有两个“锁表”用于保存锁对象,分别是:

  • 主锁表(LockMethodLockHash)
  • 进程锁表(LockMethodProcLockHash)

       主锁表用于保存当前数据库中所有事务的锁对象,即LOCK结构体。

  1. typedef struct LOCK
  2. {
  3. /* hash key */
  4. LOCKTAG tag; /* 锁对象的唯一ID */
  5. /* data */
  6. LOCKMASK grantMask; /* 该对象已经持有的锁模式 */
  7. LOCKMASK waitMask; /* 等待队列中请求该对象的锁模式 */
  8. SHM_QUEUE procLocks; /* 这个锁上的所有 PROCLOCK */
  9. PROC_QUEUE waitProcs; /* 等在这个锁上的 PROCLOCK */
  10. int requested[MAX_LOCKMODES]; /* 请求(持有+等待)该锁的会话数 */
  11. int nRequested; /* requested数组的元素数 */
  12. int granted[MAX_LOCKMODES]; /* 持有该锁的会话数 */
  13. int nGranted; /* granted数组的元素数 */
  14. } LOCK;

       进程锁表保存当前进程(会话)的事务锁状态,即PROCLOCK结构体,这个结构体主要用于建立锁和会话的关系。

  1. typedef struct PROCLOCKTAG
  2. {
  3. /* NB: we assume this struct contains no padding! */
  4. LOCK *myLock; /* link to per-lockable-object information,指向每个锁会话信息 */
  5. PGPROC *myProc; /* link to PGPROC of owning backend,指向进程PGPROC */
  6. } PROCLOCKTAG;
  1. typedef struct PROCLOCK
  2. {
  3. /* tag */
  4. PROCLOCKTAG tag; /* proclock 的唯一ID */
  5. /* data */
  6. PGPROC *groupLeader; /* proc's lock group leader, or proc itself,并行执行时,并行会话的leader */
  7. LOCKMASK holdMask; /* 当前会话在这个对象上持有的锁模式 */
  8. LOCKMASK releaseMask; /* 记录需要释放的锁模式 */
  9. SHM_QUEUE lockLink; /* list link in LOCK's list of proclocks,LOCK结构体的proclocks字段 */
  10. SHM_QUEUE procLink; /* list link in PGPROC's list of proclocks,PGPROC结构体的proclocks字段 */
  11. } PROCLOCK;

二、 InitLocks函数

       来看看初始化函数InitLocks,它的用途就是初始化我们反复提到的4个锁对象——主锁表、进程锁表、fast-path对象、本地锁表。

  1. /*
  2. * InitLocks -- Initialize the lock manager's data structures.
  3. */
  4. void
  5. InitLocks(void)
  6. {
  7. HASHCTL info;
  8. long init_table_size,
  9. max_table_size;
  10. bool found;
  11. /*
  12. * Compute init/max size to request for lock hashtables. Note these
  13. * calculations must agree with LockShmemSize!
  14. * 计算锁hash表的初始和最大大小,最大为max_table_size,初始大小为最大大小的一半
  15. */
  16. max_table_size = NLOCKENTS();
  17. init_table_size = max_table_size / 2;

       初始化主锁表对象LOCK(LockMethodLockHash),主锁表用于保存每个锁对象信息

  1. /*
  2. * Allocate hash table for LOCK structs. This stores per-locked-object information.
  3. */
  4. info.keysize = sizeof(LOCKTAG);
  5. info.entrysize = sizeof(LOCK);
  6. info.num_partitions = NUM_LOCK_PARTITIONS;
  7. LockMethodLockHash = ShmemInitHash("LOCK hash",
  8. init_table_size,
  9. max_table_size,
  10. &info,
  11. HASH_ELEM | HASH_BLOBS | HASH_PARTITION);
  12. /* Assume an average of 2 holders per lock,假设平均每个锁有两个holders */
  13. max_table_size *= 2;
  14. init_table_size *= 2;

       初始化进程锁表对象PROCLOCK(LockMethodProcLockHash),进程锁表用于保存每个锁对象的每个holder(per-lock-per-holder)信息

  1. /*
  2. * Allocate hash table for PROCLOCK structs. This stores
  3. * per-lock-per-holder information.
  4. */
  5. info.keysize = sizeof(PROCLOCKTAG);
  6. info.entrysize = sizeof(PROCLOCK);
  7. info.hash = proclock_hash;
  8. info.num_partitions = NUM_LOCK_PARTITIONS;
  9. LockMethodProcLockHash = ShmemInitHash("PROCLOCK hash",
  10. init_table_size,
  11. max_table_size,
  12. &info,
  13. HASH_ELEM | HASH_FUNCTION | HASH_PARTITION);

       初始化fast-path变量 FastPathStrongRelationLocks(注意这不是个hash表,从它的初始化语句也能看出跟另外3个是不同的),记录是否有事务已获得这个对象的“强锁”。fast-path将对弱锁的访问保存到本进程,避免频繁访问主锁表和进程锁表。

  1. /*
  2. * Allocate fast-path structures.
  3. */
  4. FastPathStrongRelationLocks =
  5. ShmemInitStruct("Fast Path Strong Relation Lock Data",
  6. sizeof(FastPathStrongRelationLockData), &found);
  7. if (!found)
  8. SpinLockInit(&FastPathStrongRelationLocks->mutex);

       初始化本地锁表对象LOCALLOCK(LockMethodLocalHash),本地锁表用于保存本地锁计数和资源属主信息,这不是个共享表。

  1. /*
  2. * Allocate non-shared hash table for LOCALLOCK structs. This stores lock
  3. * counts and resource owner information.
  4. */
  5. if (LockMethodLocalHash)
  6. hash_destroy(LockMethodLocalHash);
  7. info.keysize = sizeof(LOCALLOCKTAG);
  8. info.entrysize = sizeof(LOCALLOCK);
  9. LockMethodLocalHash = hash_create("LOCALLOCK hash",
  10. 16,
  11. &info,
  12. HASH_ELEM | HASH_BLOBS);
  13. }

三、 SetupLockInTable函数

       实际上,本地锁表和fast path的主要目的都是提升性能,它们已经可以处理大多数情况,其他情况则需要对主锁表和进程锁表进行查询,根据锁的状态决定是获得锁还是进入等待。

       SetupLockInTable函数的主要作用就是在主锁表和进程锁表中查找对应的锁,如果该锁还不存在,则在主锁表和进程锁表中申请内存来保存锁并初始化锁的信息。

1. gdb测试

会话1

会话2

2. 具体代码与跟踪

函数调用栈如下:

  1. /*
  2. * Find or create LOCK and PROCLOCK objects as needed for a new lock
  3. * request.
  4. *
  5. * Returns the PROCLOCK object, or NULL if we failed to create the objects
  6. * for lack of shared memory.
  7. *
  8. * The appropriate partition lock must be held at entry, and will be
  9. * held at exit.
  10. */
  11. static PROCLOCK *
  12. SetupLockInTable(LockMethod lockMethodTable, PGPROC *proc,
  13. const LOCKTAG *locktag, uint32 hashcode, LOCKMODE lockmode)
  14. {
  15. LOCK *lock;
  16. PROCLOCK *proclock;
  17. PROCLOCKTAG proclocktag;
  18. uint32 proclock_hashcode;
  19. bool found;
  20. /*
  21. * Find or create a lock with this tag.从主锁表LockMethodLockHash中查找lock对象
  22. */
  23. lock = (LOCK *) hash_search_with_hash_value(LockMethodLockHash,
  24. (const void *) locktag,
  25. hashcode,
  26. HASH_ENTER_NULL,
  27. &found);
  28. if (!lock) //按照函数注释,return NULL表示由于共享内存不足报错了,lock为空(正常找不找得到都不应该是空)
  29. return NULL;

  1. /*
  2. * if it's a new lock object, initialize it,如果是新的锁,初始化锁对象,这个跟之前本地锁表类似
  3. */
  4. if (!found)
  5. {
  6. lock->grantMask = 0;
  7. lock->waitMask = 0;
  8. SHMQueueInit(&(lock->procLocks));
  9. ProcQueueInit(&(lock->waitProcs));
  10. lock->nRequested = 0;
  11. lock->nGranted = 0;
  12. MemSet(lock->requested, 0, sizeof(int) * MAX_LOCKMODES);
  13. MemSet(lock->granted, 0, sizeof(int) * MAX_LOCKMODES);
  14. LOCK_PRINT("LockAcquire: new", lock, lockmode);
  15. }
  16. else //如果不是,输出已找到该锁,并做一些判断
  17. {
  18. LOCK_PRINT("LockAcquire: found", lock, lockmode);
  19. Assert((lock->nRequested >= 0) && (lock->requested[lockmode] >= 0));
  20. Assert((lock->nGranted >= 0) && (lock->granted[lockmode] >= 0));
  21. Assert(lock->nGranted <= lock->nRequested);
  22. }

这里因为found为false,需要初始化锁对象

  1. /*
  2. * Create the hash key for the proclock table. 为进程锁表创建hash key */
  3. proclocktag.myLock = lock;
  4. proclocktag.myProc = proc;
  5. proclock_hashcode = ProcLockHashCode(&proclocktag, hashcode);
  6. /*
  7. * Find or create a proclock entry with this tag,根据lock和PGPROC信息在进程锁表LockMethodProcLockHash中查找
  8. */
  9. proclock = (PROCLOCK *) hash_search_with_hash_value(LockMethodProcLockHash,
  10. (void *) &proclocktag,
  11. proclock_hashcode,
  12. HASH_ENTER_NULL,
  13. &found);

  1. if (!proclock) // 这里一样,如果proclock为空表示由于共享内存不足报错了,需要把主锁表中的信息也删除,防止内存泄漏
  2. {
  3. /* Oops, not enough shmem for the proclock */
  4. if (lock->nRequested == 0)
  5. {
  6. /*
  7. * There are no other requestors of this lock, so garbage-collect
  8. * the lock object. We *must* do this to avoid a permanent leak
  9. * of shared memory, because there won't be anything to cause
  10. * anyone to release the lock object later.
  11. */
  12. Assert(SHMQueueEmpty(&(lock->procLocks)));
  13. if (!hash_search_with_hash_value(LockMethodLockHash,
  14. (void *) &(lock->tag),
  15. hashcode,
  16. HASH_REMOVE,
  17. NULL))
  18. elog(PANIC, "lock table corrupted");
  19. }
  20. return NULL;
  21. }
  22. /*
  23. * If new, initialize the new entry,如果没找到,初始化进程锁表
  24. */
  25. if (!found)
  26. {
  27. uint32 partition = LockHashPartition(hashcode);
  28. proclock->groupLeader = proc->lockGroupLeader != NULL ?
  29. proc->lockGroupLeader : proc;
  30. proclock->holdMask = 0;
  31. proclock->releaseMask = 0;
  32. /* Add proclock to appropriate lists */
  33. SHMQueueInsertBefore(&lock->procLocks, &proclock->lockLink);
  34. SHMQueueInsertBefore(&(proc->myProcLocks[partition]),
  35. &proclock->procLink);
  36. PROCLOCK_PRINT("LockAcquire: new", proclock);
  37. }

这里因为没找到proclock,需要初始化

  1. else //否则,输出找到了,并做一些判断
  2. {
  3. PROCLOCK_PRINT("LockAcquire: found", proclock);
  4. Assert((proclock->holdMask & ~lock->grantMask) == 0);
  5. #ifdef CHECK_DEADLOCK_RISK
  6. //死锁风险判断
  7. {
  8. int i;
  9. for (i = lockMethodTable->numLockModes; i > 0; i--) //从拥有的最高级模式锁开始检查
  10. {
  11. if (proclock->holdMask & LOCKBIT_ON(i))
  12. {
  13. if (i >= (int) lockmode)//如果持锁等级>=请求等级,不会有死锁风险
  14. break; /* safe: we have a lock >= req level */
  15. elog(LOG, "deadlock risk: raising lock level"
  16. " from %s to %s on object %u/%u/%u",
  17. lockMethodTable->lockModeNames[i],
  18. lockMethodTable->lockModeNames[lockmode],
  19. lock->tag.locktag_field1, lock->tag.locktag_field2,
  20. lock->tag.locktag_field3);
  21. break;
  22. }
  23. }
  24. }
  25. #endif /* CHECK_DEADLOCK_RISK */
  26. }
  1. /*
  2. * lock->nRequested and lock->requested[] count the total number of
  3. * requests, whether granted or waiting, so increment those immediately.
  4. * The other counts don't increment till we get the lock.
  5. */
  6. lock->nRequested++; //总的锁请求模式加1
  7. lock->requested[lockmode]++; // 对应的锁模式请求数加1
  8. Assert((lock->nRequested > 0) && (lock->requested[lockmode] > 0));
  9. /*
  10. * We shouldn't already hold the desired lock; else locallock table is
  11. * broken.
  12. */
  13. if (proclock->holdMask & LOCKBIT_ON(lockmode))
  14. elog(ERROR, "lock %s on object %u/%u/%u is already held",
  15. lockMethodTable->lockModeNames[lockmode],
  16. lock->tag.locktag_field1, lock->tag.locktag_field2,
  17. lock->tag.locktag_field3);
  18. return proclock;
  19. }

因为found为false,直接跳到了最后,执行完成

参考

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

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

闽ICP备14008679号