PostgreSQL中的RegularLock

PostgreSQL中的RegularLock

RegularLock又稱爲HeavyweightLock,在PostgreSQL中我們常說的表鎖,指的其實就是這類鎖。因爲,對於用戶來說,關心的是表,數據庫,page等數據庫的對象,而之前所介紹的SpinLock和LWLock保護對象的是數據庫內部實現的數據結構。相比與SpinLock和LWLock,RegularLock的加鎖開銷非常大,因爲加鎖時要額外記錄鎖的持有者,加鎖次數,請求次數等額外信息。

1. what is RegularLock?

這裏從幾個關鍵的數據結構來了解RegularLock。

LockMethod:指定了加鎖的方法,即可以加的鎖模式(排他、共享等)個數,不同鎖模式的間的衝突矩陣。當前PG中又兩種鎖方法:DEFAULT_LOCKMETHOD和USER_LOCKMETHOD。其中,DEFAULT_LOCKMETHOD是系統默認使用的鎖方法,如RelationLock,tupleLock等;USER_LOCKMETHOD是用戶自定義的鎖方法,如pg advisory lock。

typedef struct LockMethodData
{
	int			numLockModes;           //鎖模式數量,讀/寫等
	const LOCKMASK *conflictTab;        // 不同鎖模式間的衝突矩陣
	const char *const *lockModeNames;   // LockMethod名
	const bool *trace_flag;             // 用戶debug信息輸出
} LockMethodData;
鎖方法的核心是衝突矩陣的定義,目前PG中RegularLock的鎖模式共有8種,其具體的使用場景如註釋所示:

#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 */

各種鎖模式之間的衝突如下所示:

static const LOCKMASK LockConflicts[] = {
	0,

	/* AccessShareLock */
        // AccessShareLock僅與AccessExclusiveLock衝突
	LOCKBIT_ON(AccessExclusiveLock), 
	/* RowShareLock */
        // RowShareLock與ExclusiveLock和AccessExclusiveLock衝突
	LOCKBIT_ON(ExclusiveLock) | LOCKBIT_ON(AccessExclusiveLock),

	/* RowExclusiveLock */
        // RowExclusiveLock與ShareLock、ShareRowExclusiveLock、ExclusiveLock、AccessExclusiveLock衝突
	LOCKBIT_ON(ShareLock) | LOCKBIT_ON(ShareRowExclusiveLock) |
	LOCKBIT_ON(ExclusiveLock) | LOCKBIT_ON(AccessExclusiveLock),

	/* ShareUpdateExclusiveLock */
        // ShareUpdateExclusiveLock與ShareUpdateExclusiveLock、ShareLock、ShareRowExclusiveLock、
        // ExclusiveLock、AccessExclusiveLock衝突
	LOCKBIT_ON(ShareUpdateExclusiveLock) |
	LOCKBIT_ON(ShareLock) | LOCKBIT_ON(ShareRowExclusiveLock) |
	LOCKBIT_ON(ExclusiveLock) | LOCKBIT_ON(AccessExclusiveLock),

	/* ShareLock */
        // ShareLock與RowExclusiveLock、ShareUpdateExclusiveLock、ShareRowExclusiveLock
        // ExclusiveLock、AccessExclusiveLock衝突
	LOCKBIT_ON(RowExclusiveLock) | LOCKBIT_ON(ShareUpdateExclusiveLock) |
	LOCKBIT_ON(ShareRowExclusiveLock) |
	LOCKBIT_ON(ExclusiveLock) | LOCKBIT_ON(AccessExclusiveLock),

	/* ShareRowExclusiveLock */
        // ShareRowExclusiveLock與RowExclusiveLock、ShareUpdateExclusiveLock、
        // ShareLock、ShareRowExclusiveLock、ExclusiveLock、AccessExclusiveLock衝突
	LOCKBIT_ON(RowExclusiveLock) | LOCKBIT_ON(ShareUpdateExclusiveLock) |
	LOCKBIT_ON(ShareLock) | LOCKBIT_ON(ShareRowExclusiveLock) |
	LOCKBIT_ON(ExclusiveLock) | LOCKBIT_ON(AccessExclusiveLock),

	/* ExclusiveLock */
        // ExclusiveLock 與 RowShareLock、RowExclusiveLock、ShareUpdateExclusiveLock、
        // ShareLock、ShareRowExclusiveLock、ExclusiveLock、AccessExclusiveLock衝突
	LOCKBIT_ON(RowShareLock) |
	LOCKBIT_ON(RowExclusiveLock) | LOCKBIT_ON(ShareUpdateExclusiveLock) |
	LOCKBIT_ON(ShareLock) | LOCKBIT_ON(ShareRowExclusiveLock) |
	LOCKBIT_ON(ExclusiveLock) | LOCKBIT_ON(AccessExclusiveLock),

	/* AccessExclusiveLock */
        // 與所有其它鎖包括自身衝突
	LOCKBIT_ON(AccessShareLock) | LOCKBIT_ON(RowShareLock) |
	LOCKBIT_ON(RowExclusiveLock) | LOCKBIT_ON(ShareUpdateExclusiveLock) |
	LOCKBIT_ON(ShareLock) | LOCKBIT_ON(ShareRowExclusiveLock) |
	LOCKBIT_ON(ExclusiveLock) | LOCKBIT_ON(AccessExclusiveLock)

};

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是加鎖的類型,表明是relation鎖、page鎖、行鎖、事務鎖等中的何種鎖。對於relation鎖,會記錄relation對應的db oid + relation oid,對於page鎖則記錄:db oid + relation oid + block number,其它依此類推:

typedef enum LockTagType
{
	LOCKTAG_RELATION,			/* whole relation */
	/* ID info for a relation is DB OID + REL OID; DB OID = 0 if shared */
	LOCKTAG_RELATION_EXTEND,	/* the right to extend a relation */
	/* same ID info as RELATION */
	LOCKTAG_PAGE,				/* one page of a relation */
	/* ID info for a page is RELATION info + BlockNumber */
	LOCKTAG_TUPLE,				/* one physical tuple */
	/* ID info for a tuple is PAGE info + OffsetNumber */
	LOCKTAG_TRANSACTION,		/* transaction (for waiting for xact done) */
	/* ID info for a transaction is its TransactionId */
	LOCKTAG_VIRTUALTRANSACTION, /* virtual transaction (ditto) */
	/* ID info for a virtual transaction is its VirtualTransactionId */
	LOCKTAG_SPECULATIVE_TOKEN,	/* speculative insertion Xid and token */
	/* ID info for a transaction is its TransactionId */
	LOCKTAG_OBJECT,				/* non-relation database object */
	/* ID info for an object is DB OID + CLASS OID + OBJECT OID + SUBID */

	/*
	 * Note: object ID has same representation as in pg_depend and
	 * pg_description, but notice that we are constraining SUBID to 16 bits.
	 * Also, we use DB OID = 0 for shared objects such as tablespaces.
	 */
	LOCKTAG_USERLOCK,			/* reserved for old contrib/userlock code */
	LOCKTAG_ADVISORY			/* advisory user locks */
} LockTagType;

procLocks裏存儲的是持有該鎖的backend,關於backend的相關信息描述存儲在了PROLOCK結構體當中:

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 */
	SHM_QUEUE	lockLink;		/* list link in LOCK's list of proclocks */
	SHM_QUEUE	procLink;		/* list link in PGPROC's list of proclocks */
} PROCLOCK;

LOCALLOCK:每個backend也維護了其當前正持有的鎖和需要加鎖的相關信息。這樣,對於其正在持有的鎖,如果backend有新的加鎖請求,那麼就無需再訪問共享內存。同時也記錄了持有該鎖的ResourceOwner,這樣就可以指定某一類型的資源來釋放鎖。

ResourceOwner objects are a concept invented to simplify management of query-related resources, such as buffer pins and table locks. These resources need to be tracked in a reliable way to ensure that they will be released at query end, even if the query fails due to an error. Rather than expecting the entire executor to have bulletproof data structures, we localize the tracking of such resources into a single module. We create a ResourceOwner for each transaction or subtransaction as well as one for each Portal.

typedef struct LOCALLOCKTAG
{
	LOCKTAG		lock;			/* identifies the lockable object */
	LOCKMODE	mode;			/* lock mode for this table entry */
} LOCALLOCKTAG;

typedef struct LOCALLOCKOWNER
{
	/*
	 * Note: if owner is NULL then the lock is held on behalf of the session;
	 * otherwise it is held on behalf of my current transaction.
	 *
	 * Must use a forward struct reference to avoid circularity.
	 */
	struct ResourceOwnerData *owner;
	int64		nLocks;			/* # of times held by this owner */
} LOCALLOCKOWNER;

typedef struct LOCALLOCK
{
	/* tag */
	LOCALLOCKTAG tag;			/* unique identifier of locallock entry */

	/* data */
	LOCK	   *lock;			/* associated LOCK object, if any */
	PROCLOCK   *proclock;		/* associated PROCLOCK object, if any */
	uint32		hashcode;		/* copy of LOCKTAG's hash value */
	int64		nLocks;			/* total number of times lock is held */
	int			numLockOwners;	/* # of relevant ResourceOwners */
	int			maxLockOwners;	/* allocated size of array */
	bool		holdsStrongLockCount;	/* bumped FastPathStrongRelationLocks */
	LOCALLOCKOWNER *lockOwners; /* dynamically resizable array */
} LOCALLOCK;

2. 鎖操作

主要從加鎖和放鎖這兩方面來介紹RegularLock 的鎖操作

加鎖操作

主體邏輯在函數LockAcquire(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock, bool dontWait)中:

查找backend本地的LOCALLOCK,如果存在該lock,並且當前backend已經持有了該鎖,則直接賦予該鎖,granted的次數+1。
如果1中不符合授予條件,則通過fastpath來獲取鎖。通過fastpasth授予鎖需要滿足幾個條件:1)加鎖方法是系統默認的default方法;2)加鎖的對象是relation;3)加鎖的模式必須小於ShareUpdateExclusiveLock,因爲小於該模式的鎖互不排斥。4)每個backend,最多只能有16把鎖通過fastpath獲取得到。5)當前請求的鎖對象,沒有被加上大於ShareUpdateExclusiveLock模式的鎖。fastpath加鎖,首先會遍歷已經通過fastpath加鎖的數組,如果找到與當前匹配的鎖對象,則返回加鎖成功。否則,新分配一個slot,記錄加鎖信息,返回加鎖成功。
對於鎖模式高於ShareUpdateExclusiveLock的鎖,進行標記,下次在對其鎖對象加鎖時,禁止走fastpath。對與其它backend的fastpath,如果已經加了該對象的鎖,則需要把該鎖從fastpath中清除,然後將已經加鎖的對象放入到shared hash table中記錄。這是爲了保證加模式更強的鎖,能夠通過shared hash table檢測到其它backend在這個對象上加的模式更低的鎖。
在共享內存中進行加鎖。爲此,需要首先在共享內存中創建LOCK,PROLOCK。
判斷當前加鎖是否與正在等鎖的請求衝突,當前加鎖不成功需要等待,否則,檢查是否與當前已經持有(其它backend持有)的鎖衝突,如果衝突,同樣也需要等待。如果都不衝突,則加鎖成功。
如果加鎖的參數dontWait爲true,那麼當有衝突時,不會等待,清除掉當前請求的信息,返回LOCKACQUIRE_NOT_AVAIL。否則,會將加鎖請求放入等待隊列中,然後休眠,直到有信號量(有釋放鎖)喚醒,然後在此嘗試加鎖。當然,在等待鎖的時候,後臺會進行死鎖檢測,檢測到死鎖時,可能會終止此時加鎖。
放鎖操作

查找backend本地的LOCALLOCK,如果存在該lock,並且當前backend已經持有了該鎖,將對應ResourceOwner的鎖計數減1,如果減到了0,則ResourceOwner需要釋放該鎖。另外,將總的granted數量-1。
如果釋放的鎖的鎖模式小於ShareUpdateExclusiveLock,從fastpath中查找該鎖,如果找到,釋放對應模式的鎖。
如果locallock中的lock爲空,那麼從shared hash table中查找該鎖,因爲有可能該鎖被其它backend從fastpath放入到了shared hash table中(加鎖邏輯中)。
釋放該鎖,如果有等待的鎖請求,那麼喚醒其它等待的進程。

3. 總結

相比於SpinLock、LWLock。RegularLock加鎖的開銷更大。但是提供更加豐富的鎖模式,爲數據庫不同的操作場景提供了更細粒度的鎖衝突控制,儘可能地提供了數據庫的高併發訪問。豐富的加鎖信息,有助於DBA、應用開發人員、數據庫內核開發人員優化查詢、應用和系統的性能。另外,RegularLock還提供了死鎖檢測機制,可以系統發生死鎖時,終止加鎖,保證系統正常運行。

轉自

PostgreSQL中的RegularLock

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章