赞
踩
Spinlocks:自旋锁,其保护的对象一般是数据库内部的一些数据结构,是一种轻量级的锁。
LWLocks:轻量锁,也是主要用于保护数据库内部的一些数据结构,支持独占和共享两种模式。
Regular locks:又叫heavyweight locks,也就是我们常说的表锁、行锁这些。
SIReadLock predicate locks:谓词锁,主要是用来表示数据库对象和事务间的一个特定关系。
自旋锁顾名思义就是一直原地旋转等待的锁。一个进程如果想要访问临界区,必须先获得锁资源,如果不能获得,就会一直自旋,直到获取到锁资源,它是最轻量级的锁,不需要做内存上下文转换的。
所谓临界区(也称为临界段)就是访问和操作共享数据的代码段
这种自旋会造成CPU浪费,但是通常它保护的临界区非常小,封锁时间很短,因此通常自旋比释放CPU资源带来的上下文切换消耗要小。 它是一种和硬件结合的互斥锁,借用了硬件提供的原子操作的原语来对一些共享变量进行封锁,通常适用于临界区比较小的情况。
特点是:持有锁时间很短、无死锁检测机制和等待队列、事务结束时不会自动释放SpinLock。
Spinlocks来自于Linux内核,自旋锁简单来说是一种低级的同步机制,表示了一个变量可能的两个状态: Acquired 、Released。在PostgreSQL的源码里其实经常可以看到这种spinlocks的用法,拿出PostgreSQL-15beta1的WalRcvForceReply()函数来简单举例的话,
- /*
- * Wake up the walreceiver main loop.
- *
- * This is called by the startup process whenever interesting xlog records
- * are applied, so that walreceiver can check if it needs to send an apply
- * notification back to the primary which may be waiting in a COMMIT with
- * synchronous_commit = remote_apply.
- */
- void
- WalRcvForceReply(void)
- {
- Latch *latch;
-
- WalRcv->force_reply = true;
- /* fetching the latch pointer might not be atomic, so use spinlock */
- SpinLockAcquire(&WalRcv->mutex);
- latch = WalRcv->latch;
- SpinLockRelease(&WalRcv->mutex);
- if (latch)
- SetLatch(latch);
- }
可以看到在latch = WalRcv->latch; 这里想用指针取出结构体中的数据 。 WalRcv->latch,其中WalRcv是指向结构体的指针,latch是这个结构体类型的一个成员。表达式 WalRcv->latch引用了指针WalRcv指向的结构体的成员latch。而我们找到latch对应的结构体,看它的定义(如果是VScode的话直接左键双击,摁F12),在结构体里的定义为 Latch *latch; 表示latch是一个指针,*latch表示latch指针指向的相应的额外的结构体。这里的Latch是另外的一个结构体,如下图所示,但是我们只看上述内容以及Latch *latch的话就可以看出这里想实现的功能是一个赋值的过程。
- typedef struct Latch
- {
- sig_atomic_t is_set;
- sig_atomic_t maybe_sleeping;
- bool is_shared;
- int owner_pid;
- #ifdef WIN32
- HANDLE event;
- #endif
- } Latch;
这个过程的上下两侧,有SpinLockAcquire()和SpinLockRelease()两个函数。因为每一个想要获取自旋锁的处理,必须为这个变量写入一个表示自旋锁获取 (spinlock acquire)状态的值,并且为这个变量写入锁释放 (spinlock released)状态。如果一个处理程序尝试执行受自旋锁保护的代码,那么代码将会被锁住,直到占有锁的处理程序释放掉。在这个例子里,为了避免同时访问临界区,阻止竞态条件状态,所以操作必须是原子的。 因此用到了Spinlocks,从注释里也可以看见—— “获取闩锁指针可能不是原子的,所以使用自旋锁”。
竞态条件是指在并发环境中,当有多个事件同时访问同一个临界资源时,由于多个事件的并发执行顺序的不确定,从而导致程序输出结果的不确定,这种情况我们称之为竞态条件 (Race Conditions)或者竞争冒险(race hazard)。
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 的,所以大家平时一般说两种模式。
- typedef enum LWLockMode
- {
- LW_EXCLUSIVE,
- LW_SHARED,
- 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 */
- } LWLockMode;
如下,则是使用排他和共享模式LWLocks 的部分的相应举例:
- LWLockAcquire(ControlFileLock, LW_EXCLUSIVE);
- ControlFile->checkPointCopy.nextXid = checkPoint.nextXid;
- LWLockRelease(ControlFileLock);
-
- LWLockAcquire(XidGenLock, LW_SHARED);
- ctx->next_fxid = ShmemVariableCache->nextXid;
- ctx->oldest_xid = ShmemVariableCache->oldestXid;
- LWLockRelease(XidGenLock);
就是通常说的对数据库对象的锁。按照锁粒度,可以分为表锁、页锁、行锁等,这应该是我们最熟悉的了;按照等级,锁一共有8个等级。
特点是:持有锁时间可以很长、有死锁检测机制和等待队列、事务结束时会自动释放。
如下,是这八个级别锁的定义,相应的,其特定场景使用锁的情况在注释部分已经列举出来了。
- /*
- * These are the valid values of type LOCKMODE for all the standard lock
- * methods (both DEFAULT and USER).
- */
- /* NoLock is not a lock mode, but a flag value meaning "don't get a lock" */
- #define NoLock 0
-
- #define AccessShareLock 1 /* SELECT */
- #define RowShareLock 2 /* SELECT FOR UPDATE/FOR SHARE */
- #define RowExclusiveLock 3 /* INSERT, UPDATE, DELETE */
- #define ShareUpdateExclusiveLock 4 /* VACUUM (non-FULL),ANALYZE, CREATE INDEX CONCURRENTLY */
- #define ShareLock 5 /* CREATE INDEX (WITHOUT CONCURRENTLY) */
- #define ShareRowExclusiveLock 6 /* like EXCLUSIVE MODE, but allows ROW SHARE */
- #define ExclusiveLock 7 /* blocks ROW SHARE/SELECT...FOR UPDATE */
- #define AccessExclusiveLock 8 /* ALTER TABLE, DROP TABLE, VACUUM FULL,and unqualified LOCK TABLE */
- #define MaxLockMode 8 /* highest standard lock mode */
如下LOCK的结构体,lock的获取与释放,都有队列来维护 ,
- 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 */
- SHM_QUEUE procLocks; /* list of PROCLOCK objects assoc. with lock */
- PROC_QUEUE 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;
tag——唯一标识被锁定的对象
grantMask——当前在该对象上授予的所有锁类型的位掩码。
waitMask——当前该对象上等待的所有锁类型的位掩码。
procLocks——该锁的PROCLOCK对象列表。
waitProcs——等待该锁的进程队列。
requested——该锁当前请求的每种锁类型的数量(包括已经被批准的请求)。
nRequested——所有类型请求的锁总数。
granted——当前在该锁上授予的每种锁类型的计数。
nGranted——所有类型被授予的锁总数。
上面结构体里的LOCKTAG,与PG中的数据库,relation等强相关也就是我们可以可以锁定的不同类型的对象,例如表锁、行锁等等。通过注释我们就可以看到可以锁的对象。
- typedef enum LockTagType
- {
- LOCKTAG_RELATION, /* whole relation */
- LOCKTAG_RELATION_EXTEND, /* the right to extend a relation */
- LOCKTAG_DATABASE_FROZEN_IDS, /* pg_database.datfrozenxid */
- LOCKTAG_PAGE, /* one page of a relation */
- LOCKTAG_TUPLE, /* one physical tuple */
- LOCKTAG_TRANSACTION, /* transaction (for waiting for xact done) */
- LOCKTAG_VIRTUALTRANSACTION, /* virtual transaction (ditto) */
- LOCKTAG_SPECULATIVE_TOKEN, /* speculative insertion Xid and token */
- LOCKTAG_OBJECT, /* non-relation database object */
- LOCKTAG_USERLOCK, /* reserved for old contrib/userlock code */
- LOCKTAG_ADVISORY /* advisory user locks */
- } LockTagType;
下边是一个申请AccessShareLock和释放锁的举例
- /* Get tuple descriptor from relation OID */
- rel = relation_open(relid, AccessShareLock);
-
- ...
-
- relation_close(rel, AccessShareLock);
谓词锁,主要是用来表示数据库对象和事务间的一个特定关系 。
PostgreSQL里用PREDICATELOCK结构体代表一个单独的锁。
- typedef struct PREDICATELOCK
- {
- /* hash key */
- PREDICATELOCKTAG tag; /* unique identifier of lock */
-
- /* data */
- SHM_QUEUE targetLink; /* list link in PREDICATELOCKTARGET's list of
- * predicate locks */
- SHM_QUEUE xactLink; /* list link in SERIALIZABLEXACT's list of
- * predicate locks */
- SerCommitSeqNo commitSeqNo; /* only used for summarized predicate locks */
- } PREDICATELOCK;
然后用一个对象和事务作为此谓词锁的标识( tag )来标识一个谓词锁,如下PREDICATELOCKTAG结构体用来标识一个单独的谓词锁。
- typedef struct PREDICATELOCKTAG
- {
- PREDICATELOCKTARGET *myTarget;
- SERIALIZABLEXACT *myXact;
- } PREDICATELOCKTAG;
PREDICATELOCK结构体注释的内容翻译过来如下 “当读取相关的数据库对象时,或者通过提升多个细粒度目标,可以在这里创建一个条目。当可序列化事务被清除时,与该可序列化事务相关的所有条目将被删除。当条目被组合成一个粗粒度的锁条目时,也可以删除它们。”
PREDICATELOCKTAG结构体注释的内容翻译过来如下“它是谓词锁目标(这是一个可锁定对象)和已获得该目标上的锁的可序列化事务的组合。”
这表明谓词锁是数据库对象和事务间的一个特定关系,这样的关系是用以表示读写冲突的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。