PostgreSQL的四種程式間鎖
在PostgreSQL裡有四種型別的程式間鎖:
Spinlocks:自旋鎖,其保護的物件一般是資料庫內部的一些資料結構,是一種輕量級的鎖。
LWLocks:輕量鎖,也是主要用於保護資料庫內部的一些資料結構,支援獨佔和共享兩種模式。
Regular locks:又叫heavyweight locks,也就是我們常說的表鎖、行鎖這些。
SIReadLock predicate locks:謂詞鎖,主要是用來表示資料庫物件和事務間的一個特定關係。
一、Spinlocks-自旋鎖
自旋鎖顧名思義就是一直原地旋轉等待的鎖。一個程式如果想要訪問 臨界區,必須先獲得鎖資源,如果不能獲得,就會一直自旋,直到獲取到鎖資源,它是最輕量級的鎖,不需要做記憶體上下文轉換的。
所謂臨界區(也稱為臨界段)就是訪問和操作共享資料的程式碼段
這種自旋會造成CPU浪費,但是通常它保護的臨界區非常小,封鎖時間很短,因此通常 自旋比釋放CPU資源帶來的上下文切換消耗要小。 它是一種和硬體結合的互斥鎖,借用了硬體提供的原子操作的原語來對一些共享變數進行封鎖,通常適用於臨界區比較小的情況。
特點是:持有鎖時間很短、無死鎖檢測機制和等待佇列、事務結束時不會自動釋放SpinLock。
Spinlocks來自於Linux核心,自旋鎖簡單來說是一種低階的同步機制,表示了一個變數可能的兩個狀態: acquired 、acquired 。在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-輕量鎖
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);
三、Regular locks-普通鎖
就是通常說的對資料庫物件的鎖。按照鎖粒度,可以分為表鎖、頁鎖、行鎖等,這應該是我們最熟悉的了;按照等級,鎖一共有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);
四、SIReadLock predicate locks-謂詞鎖
謂詞鎖,主要是用來表示資料庫物件和事務間的一個特定關係 。
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結構體註釋的內容翻譯過來如下“它是謂詞鎖目標(這是一個可鎖定物件)和已獲得該目標上的鎖的可序列化事務的組合。”
這表明謂詞鎖是資料庫物件和事務間的一個特定關係,這樣的關係是用以表示讀寫衝突的。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69990629/viewspace-2897982/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- synchronized四種鎖狀態的升級synchronized
- PostgreSQL:鎖SQL
- PostgreSQL-PG程式之checkpoint(四)SQL
- 詳解 MySql InnoDB 中的三種行鎖(記錄鎖、間隙鎖與臨鍵鎖)MySql
- 程式碼共享的四種方法
- 關於 鎖的四種狀態與鎖升級過程 圖文詳解
- 程式間通訊如何加鎖
- iOS 中常見的幾種鎖-程式碼示例iOS
- 程式間的幾種通訊方式
- 按照名字殺死程式的四種方法
- Java 種15種鎖的介紹:公平鎖,可重入鎖,獨享鎖,互斥鎖等等...Java
- Java 種15種鎖的介紹:公平鎖,可重入鎖,獨享鎖,互斥鎖等等Java
- 縮短 Linux 命令節省時間的四種方法Linux
- PostgreSQL 死鎖異常SQL
- PostgreSQL死鎖相關SQL
- MySQL鎖(四)行鎖的加鎖規則和案例MySql
- MySQL 四 鎖MySql
- Linux 程式間通訊的六種機制Linux
- 程式間的五種通訊方式介紹
- C++程式間通訊的十一種方法C++
- tomcat 設定session過期時間(四種方式)TomcatSession
- 四、InnoDB儲存引擎如何利用鎖實現四種事務隔離級別儲存引擎
- 3 種使用 PostgreSQL 命令的方式SQL
- PostgreSQL 表空間SQL
- PostgreSQL:表空間SQL
- 間隙鎖
- PostgreSQL之鎖監控指令碼SQL指令碼
- 低程式碼開發需要 DevSecOps 的四種情況dev
- Java程式猿必會的四種執行緒池Java執行緒
- Java的四種引用Java
- Java 各種鎖的小結Java
- C++中的各種鎖C++
- 什麼是程式間通訊?Linux程式間通訊有幾種方式?Linux
- 程式間通訊是什麼?Linux程式間通訊有幾種方式?Linux
- 程式間的五種通訊方式介紹-詳解
- Linux命令列下進行時間管理,四種方式完成!Linux命令列
- PostgreSQL 跟蹤checkpointer出現死鎖SQL
- 執行緒鎖(四)執行緒