当前位置:   article > 正文

【PostgreSQL的四种进程间锁】_谓词锁

谓词锁

在PostgreSQL里有四种类型的进程间锁:

Spinlocks:自旋锁,其保护的对象一般是数据库内部的一些数据结构,是一种轻量级的锁。
LWLocks:轻量锁,也是主要用于保护数据库内部的一些数据结构,支持独占和共享两种模式。
Regular locks:又叫heavyweight locks,也就是我们常说的表锁、行锁这些。
SIReadLock predicate locks:谓词锁,主要是用来表示数据库对象和事务间的一个特定关系。

一、Spinlocks-自旋锁

自旋锁顾名思义就是一直原地旋转等待的锁。一个进程如果想要访问临界区,必须先获得锁资源,如果不能获得,就会一直自旋,直到获取到锁资源,它是最轻量级的锁,不需要做内存上下文转换的。

所谓临界区(也称为临界段)就是访问和操作共享数据的代码段

这种自旋会造成CPU浪费,但是通常它保护的临界区非常小,封锁时间很短,因此通常自旋比释放CPU资源带来的上下文切换消耗要小。 它是一种和硬件结合的互斥锁,借用了硬件提供的原子操作的原语来对一些共享变量进行封锁,通常适用于临界区比较小的情况。

特点是:持有锁时间很短、无死锁检测机制和等待队列、事务结束时不会自动释放SpinLock。

Spinlocks来自于Linux内核,自旋锁简单来说是一种低级的同步机制,表示了一个变量可能的两个状态: Acquired 、Released。在PostgreSQL的源码里其实经常可以看到这种spinlocks的用法,拿出PostgreSQL-15beta1的WalRcvForceReply()函数来简单举例的话,

  1. /*
  2. * Wake up the walreceiver main loop.
  3. *
  4. * This is called by the startup process whenever interesting xlog records
  5. * are applied, so that walreceiver can check if it needs to send an apply
  6. * notification back to the primary which may be waiting in a COMMIT with
  7. * synchronous_commit = remote_apply.
  8. */
  9. void
  10. WalRcvForceReply(void)
  11. {
  12. Latch *latch;
  13. WalRcv->force_reply = true;
  14. /* fetching the latch pointer might not be atomic, so use spinlock */
  15. SpinLockAcquire(&WalRcv->mutex);
  16. latch = WalRcv->latch;
  17. SpinLockRelease(&WalRcv->mutex);
  18. if (latch)
  19. SetLatch(latch);
  20. }

可以看到在latch = WalRcv->latch; 这里想用指针取出结构体中的数据 。  WalRcv->latch,其中WalRcv是指向结构体的指针,latch是这个结构体类型的一个成员。表达式 WalRcv->latch引用了指针WalRcv指向的结构体的成员latch。而我们找到latch对应的结构体,看它的定义(如果是VScode的话直接左键双击,摁F12),在结构体里的定义为  Latch      *latch; 表示latch是一个指针,*latch表示latch指针指向的相应的额外的结构体。这里的Latch是另外的一个结构体,如下图所示,但是我们只看上述内容以及Latch      *latch的话就可以看出这里想实现的功能是一个赋值的过程。

  1. typedef struct Latch
  2. {
  3. sig_atomic_t is_set;
  4. sig_atomic_t maybe_sleeping;
  5. bool is_shared;
  6. int owner_pid;
  7. #ifdef WIN32
  8. HANDLE event;
  9. #endif
  10. } Latch;

这个过程的上下两侧,有SpinLockAcquire()和SpinLockRelease()两个函数。因为每一个想要获取自旋锁的处理,必须为这个变量写入一个表示自旋锁获取 (spinlock acquire)状态的值,并且为这个变量写入锁释放 (spinlock released)状态。如果一个处理程序尝试执行受自旋锁保护的代码,那么代码将会被锁住,直到占有锁的处理程序释放掉。在这个例子里,为了避免同时访问临界区,阻止竞态条件状态,所以操作必须是原子的。 因此用到了Spinlocks,从注释里也可以看见—— “获取闩锁指针可能不是原子的,所以使用自旋锁”。

竞态条件是指在并发环境中,当有多个事件同时访问同一个临界资源时,由于多个事件的并发执行顺序的不确定,从而导致程序输出结果的不确定,这种情况我们称之为竞态条件 (Race Conditions)或者竞争冒险(race hazard)。

二、LWLocks-轻量锁

LWLocks 负责保护共享内存中的数据结构,通常有共享和排他两种模式。

在几乎所有具有并行处理的软件中,都会采用一种轻量级锁(比如Oracle 叫latch,Mysql叫rw-lock,PG叫LwLock)来做串行化控制 ,这种轻量级锁,我们一般也统称为闩锁 。oracle的latch和PG的LwLocks都是在系统的spinlock之上实现的轻量级锁。

还记得原来维护ORACLE数据库的时候,数据库里的“latch: cache buffers chains ” 等待事件引起了我的好奇,分析逻辑读产生CBC latch分析了好久。

LWLocks一般由Spinlocks来保护。LWLocks比Spinlocks多一种共享模式。因此比Spinlocks稍微重了点,但是和其他的锁相比,还是比较轻的。

特点是:持有锁时间较短、无死锁检测机制、有等待队列、事务结束时会自动释放

LWLocks通常用于对共享内存中数据结构的联锁访问。LWLocks支持独占和共享锁模式(用于读/写和只读)访问共享对象)。获取或释放LWLock的速度非常快。

看了下PG-15beta1版本src/backend/storage/lmgr/lwlocknames.h里轻量锁类型定义 ,发现和14.2的没有什么调整。

而对于LWLocks 的模式,我说有共享和排他两种模式时,加了个通常,因为我在看源码的时候发现LWLocks 的模式,除了原本LW_EXCLUSIVE和LW_SHARED之外,还有一个LW_WAIT_UNTIL_FREE,表示PGPROC->lwWaitMode中使用的一种特殊模式,是等待锁变空闲时的状态。它是不能用作LWLockAcquire()的参数来请求LWLocks 的,所以大家平时一般说两种模式。

  1. typedef enum LWLockMode
  2. {
  3. LW_EXCLUSIVE,
  4. LW_SHARED,
  5. LW_WAIT_UNTIL_FREE /* A special mode used in PGPROC->lwWaitMode,when waiting for lock to become free. Not to be used as LWLockAcquire argument */
  6. } LWLockMode;

如下,则是使用排他和共享模式LWLocks 的部分的相应举例:

  1. LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
  2. ControlFile->checkPointCopy.nextXid = checkPoint.nextXid;
  3. LWLockRelease(ControlFileLock);
  4. LWLockAcquire(XidGenLock, LW_SHARED);
  5. ctx->next_fxid = ShmemVariableCache->nextXid;
  6. ctx->oldest_xid = ShmemVariableCache->oldestXid;
  7. LWLockRelease(XidGenLock);

三、Regular locks-普通锁

就是通常说的对数据库对象的锁。按照锁粒度,可以分为表锁、页锁、行锁等,这应该是我们最熟悉的了;按照等级,锁一共有8个等级。

特点是:持有锁时间可以很长、有死锁检测机制和等待队列、事务结束时会自动释放。

如下,是这八个级别锁的定义,相应的,其特定场景使用锁的情况在注释部分已经列举出来了。

  1. /*
  2. * These are the valid values of type LOCKMODE for all the standard lock
  3. * methods (both DEFAULT and USER).
  4. */
  5. /* NoLock is not a lock mode, but a flag value meaning "don't get a lock" */
  6. #define NoLock 0
  7. #define AccessShareLock 1 /* SELECT */
  8. #define RowShareLock 2 /* SELECT FOR UPDATE/FOR SHARE */
  9. #define RowExclusiveLock 3 /* INSERT, UPDATE, DELETE */
  10. #define ShareUpdateExclusiveLock 4 /* VACUUM (non-FULL),ANALYZE, CREATE INDEX CONCURRENTLY */
  11. #define ShareLock 5 /* CREATE INDEX (WITHOUT CONCURRENTLY) */
  12. #define ShareRowExclusiveLock 6 /* like EXCLUSIVE MODE, but allows ROW SHARE */
  13. #define ExclusiveLock 7 /* blocks ROW SHARE/SELECT...FOR UPDATE */
  14. #define AccessExclusiveLock 8 /* ALTER TABLE, DROP TABLE, VACUUM FULL,and unqualified LOCK TABLE */
  15. #define MaxLockMode 8 /* highest standard lock mode */

如下LOCK的结构体,lock的获取与释放,都有队列来维护 ,

  1. typedef struct LOCK
  2. {
  3. /* hash key */
  4. LOCKTAG tag; /* unique identifier of lockable object */
  5. /* data */
  6. LOCKMASK grantMask; /* bitmask for lock types already granted */
  7. LOCKMASK waitMask; /* bitmask for lock types awaited */
  8. SHM_QUEUE procLocks; /* list of PROCLOCK objects assoc. with lock */
  9. PROC_QUEUE waitProcs; /* list of PGPROC objects waiting on lock */
  10. int requested[MAX_LOCKMODES]; /* counts of requested locks */
  11. int nRequested; /* total of requested[] array */
  12. int granted[MAX_LOCKMODES]; /* counts of granted locks */
  13. int nGranted; /* total of granted[] array */
  14. } LOCK;

tag——唯一标识被锁定的对象
grantMask——当前在该对象上授予的所有锁类型的位掩码。
waitMask——当前该对象上等待的所有锁类型的位掩码。
procLocks——该锁的PROCLOCK对象列表。
waitProcs——等待该锁的进程队列。
requested——该锁当前请求的每种锁类型的数量(包括已经被批准的请求)。
nRequested——所有类型请求的锁总数。
granted——当前在该锁上授予的每种锁类型的计数。
nGranted——所有类型被授予的锁总数。

上面结构体里的LOCKTAG,与PG中的数据库,relation等强相关也就是我们可以可以锁定的不同类型的对象,例如表锁、行锁等等。通过注释我们就可以看到可以锁的对象。

  1. typedef enum LockTagType
  2. {
  3. LOCKTAG_RELATION, /* whole relation */
  4. LOCKTAG_RELATION_EXTEND, /* the right to extend a relation */
  5. LOCKTAG_DATABASE_FROZEN_IDS, /* pg_database.datfrozenxid */
  6. LOCKTAG_PAGE, /* one page of a relation */
  7. LOCKTAG_TUPLE, /* one physical tuple */
  8. LOCKTAG_TRANSACTION, /* transaction (for waiting for xact done) */
  9. LOCKTAG_VIRTUALTRANSACTION, /* virtual transaction (ditto) */
  10. LOCKTAG_SPECULATIVE_TOKEN, /* speculative insertion Xid and token */
  11. LOCKTAG_OBJECT, /* non-relation database object */
  12. LOCKTAG_USERLOCK, /* reserved for old contrib/userlock code */
  13. LOCKTAG_ADVISORY /* advisory user locks */
  14. } LockTagType;

下边是一个申请AccessShareLock和释放锁的举例

  1. /* Get tuple descriptor from relation OID */
  2. rel = relation_open(relid, AccessShareLock);
  3. ...
  4. relation_close(rel, AccessShareLock);

四、SIReadLock predicate locks-谓词锁

谓词锁,主要是用来表示数据库对象和事务间的一个特定关系 。

PostgreSQL里用PREDICATELOCK结构体代表一个单独的锁。

  1. typedef struct PREDICATELOCK
  2. {
  3. /* hash key */
  4. PREDICATELOCKTAG tag; /* unique identifier of lock */
  5. /* data */
  6. SHM_QUEUE targetLink; /* list link in PREDICATELOCKTARGET's list of
  7. * predicate locks */
  8. SHM_QUEUE xactLink; /* list link in SERIALIZABLEXACT's list of
  9. * predicate locks */
  10. SerCommitSeqNo commitSeqNo; /* only used for summarized predicate locks */
  11. } PREDICATELOCK;

然后用一个对象和事务作为此谓词锁的标识( tag )来标识一个谓词锁,如下PREDICATELOCKTAG结构体用来标识一个单独的谓词锁。

  1. typedef struct PREDICATELOCKTAG
  2. {
  3. PREDICATELOCKTARGET *myTarget;
  4. SERIALIZABLEXACT *myXact;
  5. } PREDICATELOCKTAG;

PREDICATELOCK结构体注释的内容翻译过来如下 “当读取相关的数据库对象时,或者通过提升多个细粒度目标,可以在这里创建一个条目。当可序列化事务被清除时,与该可序列化事务相关的所有条目将被删除。当条目被组合成一个粗粒度的锁条目时,也可以删除它们。”

PREDICATELOCKTAG结构体注释的内容翻译过来如下“它是谓词锁目标(这是一个可锁定对象)和已获得该目标上的锁的可序列化事务的组合。”

这表明谓词锁是数据库对象和事务间的一个特定关系,这样的关系是用以表示读写冲突的。

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

闽ICP备14008679号