PostgreSQL 併發控制機制(2):表級鎖和行級鎖
併發控制是多個事務在併發執行時,資料庫保證事務一致性(Consistency)和隔離性(Isolation)的一種機制。主流商用關聯式資料庫使用的併發控制技術主要有三種:嚴格兩階段封鎖(S2PL)、多版本併發控制(MVCC)和樂觀併發控制(OCC)。
在工程實踐上,PostgreSQL使用了MVCC的一種變體快照隔離SI結合封鎖技術進行併發控制,本文介紹了鎖相關的部分理論以及PostgreSQL的工程實踐,最後結合案例對PostgreSQL的表級鎖和行級鎖進行了相關介紹。
一、相關理論
在開始之前,我們不妨考慮這麼一個問題:單純使用快照隔離是否可以實現併發控制?答案是肯定的。其中的一種解決方案,類似於ORM框架Hibernate樂觀鎖的機制,基於Tuple資料版本記錄實現。在PostgreSQL中,對於任一個tuple,xmin+xmax可以認為是該tuple的版本號,在不加鎖的情況下,事務Ti對本事務生成的tuple快照(與其他事務的tuple快照相互隔離)進行處理,在事務提交時判斷快照版本(xmin+xmax)與資料庫中該tuple的版本是否一致,如涉及的所有tuple版本均與本地版本一致則提交,否則整個事務回滾。不過這種方法在衝突很多的情況下,出現衝突的併發事務會頻繁的回滾,看起來機器資源利用率很高,但其實大多時間在做無用功(大量事務出現衝突而回滾),而且由於Tuple版本的判斷和事務真正完結之間有時間差,在這個時間差之間可能出現其他併發事務更新同一Tuple的現象(髒寫異象)。為了避免這種頻繁回滾的情況,PostgreSQL使用了相對“悲觀”的策略,通過封鎖技術對相同的資源(relation、page、tuple等)進行鎖定,在處理過程中判斷並等待而非不是延遲至事務提交時才進行判斷。
基於資料庫物件的多層次邏輯結構,PostgreSQL使用的是一種多粒度的封鎖機制,下面從理論層面簡單作一介紹。
1、資料庫物件邏輯結構
大凡資料庫的體系結構都會提到物件邏輯結構,就PostgreSQL而言,其邏輯物件體系結構如下圖所示:
從封鎖的角度來看,在層次“關係”以下,關係(relation)是最大的可封鎖資料元素(Data Item),每個關係由一個或多個頁(Page)組成,每個頁保護一個或多個元組(Tuple)。
2、多粒度鎖機制
按上一節介紹的物件邏輯層次結構,我們可以相應的指定意向鎖協議,該協議既包括“普通”鎖(即讀S鎖和寫X鎖)又包括“意向”鎖(以I開頭,IS表示意向共享鎖,IX表示意向排他鎖),規則如下:
1.在任何資料元素上加鎖(S或X鎖),必須從層次結構的根開始;
2.如已處於將要封鎖的元素位置,則不需要進一步查詢,直接在資料元素上加S或X鎖;
3.如將要封鎖的元素在當前層次之下,則在當前節點加意向鎖(如IS或者IX鎖),當前節點上的鎖被授予後才能繼續往下查詢子節點。重複2和3直至找到滿足條件的節點。
下面是S、X、IS和IX之間的相容性矩陣:
IS | IX | S | X | |
---|---|---|---|---|
IS | Y | Y | Y | N |
IX | Y | Y | N | N |
S | Y | N | Y | N |
X | N | N | N | N |
從相容性矩陣可以看出,IS除了X外,可與其他鎖相容;IX除了S和X外,可與其他意向鎖相容;S除了IS和S(自相容)外,與IX和X都不相容;X則與其他所有鎖均不相容。
上面介紹了意向鎖協議,我們不禁要問的一個問題是:為什麼要引入意向鎖?
考慮以下情況,事務Ti修改關係R中的某個元組y,無疑我們需要在y上加X鎖,但在R上需要加鎖嗎?如果不加鎖,這時候另外一個併發事務Tj需要在關係上建立索引(顯然,需要在關係上加S鎖),那麼Tj可以直接在關係加鎖或者需要在元組層次上判斷關係R的元組是否存在S鎖和X鎖,無異增加判斷的複雜度和代價。從效能和可維護性上考慮,希望直接在關係這個層次上判斷是否可以對整個關係加S鎖,我們因此引入意向鎖。就上例而言,事務Ti在關係R上加意向排他鎖(IX),在元組y上加X鎖,然後併發事務Tj期望獲取R上的S鎖,從相容性矩陣可得,S與IX不相容,Tj必須等待。可以看到,在同一個層次上執行封鎖判斷邏輯,顯得高效且十分簡潔。除此之外,引入意向鎖還有助於併發事務在更低層次(粒度)上解決衝突,從而有效的提高系統的併發,提升系統效能。
值得一提的是,除了IS和IX,還有一種意向鎖SIX,也就是共享意向寫鎖(Share + IX)。在事務需要訪問整個關係但只需要寫關係中的部分資料元素時在關係上加該鎖。
3、兩階段鎖(2PL)
兩階段鎖(2PL),簡單來說就是把鎖操作分為兩個階段:加鎖和解鎖,且要求在加鎖階段不允許解鎖,在解鎖階段不允許再加鎖。工程實踐中,實際使用的是強嚴格兩階段鎖(SS2PL,一般稱為S2PL),在2PL的基礎上要求在事務結束後才解鎖。
使用兩階段鎖協議的併發控制產生可序列的排程。下面是簡單的證明:
不失一般性,考察以下遵循兩階段鎖協議但不可序列化(形成環)的排程:T1->T2->…->Tn->T1。
T1->T2表示在排程中T1有操作與T2的操作相沖突,因為衝突,因此T1必須釋放鎖,T2獲得鎖才能繼續執行。以此類推,T2和T3類似,…,Tn-1和Tn類似,Tn和T1類似,由此可以得出結論:T1釋放鎖之後,又獲取了另外一個鎖,否則Tn->T1不應存在,這違反了兩階段鎖協議。因此,遵循兩階段鎖協議的排程不可能出現環,由此可證明遵循兩階段鎖協議是可序列化的。
在商用資料庫中,Informix是使用S2PL的代表,而PostgreSQL則在執行DDL(如drop table等)時使用S2PL,而DML時使用SI。
二、PostgreSQL中的表級鎖和行級鎖
基於上面介紹的理論基礎,理解PostgreSQL中的鎖相對容易一些(Oracle、MySQL同理)。
1、表級鎖
PostgreSQL表級鎖包括:Access Share(AS)、Row Share(RS)、Row Exclusive(RE)、Share Update Exclusive(SUE)、Share(S)、Share Row Exclusive(SRE)、Exclusive(E)、Access Exclusive(AE),共8種型別的表級鎖,初學者看到這麼鎖估計會發懵,但如果我們結合上一節的多粒度鎖機制來理解相對比較容易。
從兩個維度來看:粒度和操作。粒度分為Relation和Row,操作分為讀(Share)、寫(Exclusive)和讀寫(Share Exclusive),根據這兩個維度得到下面的矩陣:
Row | Relation | |
---|---|---|
讀 | Row Share | Access Share、Share |
寫 | Row Exclusive | Exclusive、Access Exclusive |
讀寫 | Share Update Exclusive、Share Row Exclusive |
這些鎖中,Row Share和Row Exclusive可視為意向鎖:真正需要鎖定的資料項是元組而非關係,如出現衝突則留待元組級解決。除此之外,其他均為普通鎖:鎖定的資料項是關係,且無需在行上加鎖。
上述八種鎖的相容性矩陣如下表所示:
模式 | Access Share | Row Share | Row Exclusive | Share Update Exclusive | Share | Share Row Exclusive | Exclusive | Access Exclusive | 樣例SQL |
---|---|---|---|---|---|---|---|---|---|
Access Share | Y | Y | Y | Y | Y | Y | Y | N | Select |
Row Share | Y | Y | Y | Y | Y | Y | N | N | Select for Update/Share |
Row Exclusive | Y | Y | Y | Y | N | N | N | N | Insert/Update/Delete |
Share Update Exclusive | Y | Y | Y | N | N | N | N | N | Vacuum,Alter Table,Create Index Concurrently |
Share | Y | Y | N | N | N | N | N | Create Index | |
Share Row Exclusive | Y | Y | N | N | N | N | N | N | Create Trigger,Alter Table |
Exclusive | Y | N | N | N | N | N | N | N | Refresh Material View Concurrently |
Access Exclusive | N | N | N | N | N | N | N | N | Drop/Truncate/… |
上一節提到,PostgreSQL在執行DDL時使用S2PL,在執行DML時使用SI,為了區分,PostgreSQL在執行DDL時關係上的鎖是Access Exclusive而不是Exclusive,在執行DML查詢語句時關係上的鎖是Access Share而不是Share,從相容性矩陣可以看出,這樣的區分可以實現在寫(Row Exclusive)的時候不會阻塞多版本讀(Access Share),多版本讀(Access Share)的時候不會阻塞寫(Row Exclusive)。
而傳統意義上的讀鎖(Share Lock),在PostgreSQL中用於Create Index,排斥所有含有Exclusive的寫鎖,但不排斥其他讀鎖(Share Lock),意味著建立索引時不可以修改資料,但允許查詢資料或者同時建立其他索引;常規意義上的寫鎖Exclusive,用於物化檢視的併發重新整理,會排斥除多版本讀外的其他所有鎖。
小結一下,在PostgreSQL中:
1.多版本讀鎖和寫鎖-2類:Access Share和Access Exclusive
2.意向鎖-2類:Row Share和Row Exclusive,它們之間的差異在於Row Share排斥僅表級的Exclusive&Access Exclusive,其他相容鎖如出現衝突則在行級解決
3.共享鎖-3類:細分為Share Update Exclusive、Share、Share Row Exclusive,目的是為了不同的SQL命令精細化控制鎖,提高系統併發
4.傳統寫鎖-1類:Exclusive,僅用於物化檢視重新整理
2、行級鎖
PostgreSQL的行級鎖有4個,從兩個維度來看:主(唯一)鍵相關和模式(排他和共享),見下面的矩陣:
主鍵相關 | 主鍵無關 | |
---|---|---|
排他 | FOR UPDATE | FOR NO KEY UPDATE |
共享 | FOR KEY SHARE | FOR SHARE |
排他模式
FOR UPDATE:對整行進行更新,包括刪除行
FOR NO KEY UPDATE:對除主(唯一)鍵外的欄位更新
共享模式
FOR SHARE:讀該行,不允許對行進行更新
FOR KEY SHARE:讀該行的鍵值,但允許對除鍵外的其他欄位更新。在外來鍵檢查時使用該鎖
值得一提的是,PostgreSQL的行級鎖並沒有在記憶體中儲存而是使用了元組Header的標記位儲存(相應的資料結構是HeapTupleHeaderData),因此理論上PostgreSQL可以支援無限多的元組鎖。
//t_infomask說明
1 #define HEAP_HASNULL 0x0001 /* has null attribute(s) */
10 #define HEAP_HASVARWIDTH 0x0002 /* has variable-width attribute(s) */
100 #define HEAP_HASEXTERNAL 0x0004 /* has external stored attribute(s) */
1000 #define HEAP_HASOID 0x0008 /* has an object-id field */
10000 #define HEAP_XMAX_KEYSHR_LOCK 0x0010 /* xmax is a key-shared locker */
100000 #define HEAP_COMBOCID 0x0020 /* t_cid is a combo cid */
1000000 #define HEAP_XMAX_EXCL_LOCK 0x0040 /* xmax is exclusive locker */
10000000 #define HEAP_XMAX_LOCK_ONLY 0x0080 /* xmax, if valid, is only a locker */
/* xmax is a shared locker */
#define HEAP_XMAX_SHR_LOCK (HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_KEYSHR_LOCK)
#define HEAP_LOCK_MASK (HEAP_XMAX_SHR_LOCK | HEAP_XMAX_EXCL_LOCK | \
HEAP_XMAX_KEYSHR_LOCK)
100000000 #define HEAP_XMIN_COMMITTED 0x0100 /* t_xmin committed */
1000000000 #define HEAP_XMIN_INVALID 0x0200 /* t_xmin invalid/aborted */
#define HEAP_XMIN_FROZEN (HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID)
10000000000 #define HEAP_XMAX_COMMITTED 0x0400 /* t_xmax committed */
100000000000 #define HEAP_XMAX_INVALID 0x0800 /* t_xmax invalid/aborted */
1000000000000 #define HEAP_XMAX_IS_MULTI 0x1000 /* t_xmax is a MultiXactId */
10000000000000 #define HEAP_UPDATED 0x2000 /* this is UPDATEd version of row */
100000000000000 #define HEAP_MOVED_OFF 0x4000 /* moved to another place by pre-9.0
* VACUUM FULL; kept for binary
* upgrade support */
1000000000000000 #define HEAP_MOVED_IN 0x8000 /* moved from another place by pre-9.0
* VACUUM FULL; kept for binary
* upgrade support */
#define HEAP_MOVED (HEAP_MOVED_OFF | HEAP_MOVED_IN)
1111111111110000 #define HEAP_XACT_MASK 0xFFF0 /* visibility-related bits */
//t_infomask2說明
11111111111 #define HEAP_NATTS_MASK 0x07FF
10000000000000 #define HEAP_KEYS_UPDATED 0x2000
100000000000000 #define HEAP_HOT_UPDATED 0x4000
1000000000000000 #define HEAP_ONLY_TUPLE 0x8000
1110000000000000 #define HEAP2_XACT_MASK 0xE000
1111111111111110 #define SpecTokenOffsetNumber 0xfffe
3、案例研究
下面通過一個案例來對錶級鎖和行級鎖作進一步的闡述,以便有直觀的感受。
該案例建立一張表,插入10,000行資料,然後啟動3個會話,同時對該表執行更新操作。
drop table lockdemo;
create table lockdemo(id int,c1 varchar);
insert into lockdemo(id,c1) select x,'c1'||x from generate_series(1,10000) as x;
根據資料結構HeapTupleHeaderData的描述,使用外掛pageinspect dump元組資訊查詢行鎖。
drop function get_tuple_locks;
create or replace function get_tuple_locks(pi_name in varchar)
returns setof record as $$
SELECT '(0,'||lp||')' AS ctid, -- tuple ctid
t_xmax as xmax, -- xmax
CASE WHEN (t_infomask & 128) > 0 THEN 't' END AS lock_only, -- 0x0080,HEAP_XMAX_LOCK_ONLY
CASE WHEN (t_infomask & 4096) > 0 THEN 't' END AS is_multi, -- 0x1000,HEAP_XMAX_IS_MULTI
CASE WHEN (t_infomask2 & 8192) > 0 THEN 't' END AS keys_upd, -- 0x2000,HEAP_KEYS_UPDATED
CASE WHEN (t_infomask & 16) > 0 THEN 't' END AS keyshr_lock, -- 0x0010,HEAP_XMAX_KEYSHR_LOCK
CASE WHEN (t_infomask & 16+64) = 16+64 THEN 't' END AS shr_lock -- 0x0010 & 0x0040,HEAP_XMAX_SHR_LOCK = HEAP_XMAX_KEYSHR_LOCK | HEAP_XMAX_EXCL_LOCK
FROM heap_page_items(get_raw_page(pi_name,0))
ORDER BY lp;
$$
language sql;
另外,PostgreSQL提供了pgrowlocks外掛用於查詢行級鎖。
create extension pgrowlocks;
我們先啟動兩個session,其中session 1先執行更新lockdemo的c1欄位,隨後session 2執行同樣的更新SQL
session 1
[local:/opt/data5012]:5012 pg12@testdb=# select pg_backend_pid();
pg_backend_pid
----------------
1714
(1 row)
Time: 2.994 ms
[local:/opt/data5012]:5012 pg12@testdb=# begin;
BEGIN
Time: 0.154 ms
[local:/opt/data5012]:5012 pg12@testdb=#* update lockdemo set c1 = 'x';
UPDATE 10000
Time: 15.786 ms
[local:/opt/data5012]:5012 pg12@testdb=#*
[local:/opt/data5012]:5012 pg12@testdb=#* select txid_current();
txid_current
--------------
529
(1 row)
Time: 2.916 ms
session 2
[local:/opt/data5012]:5012 pg12@testdb=# select pg_backend_pid();
pg_backend_pid
----------------
1712
(1 row)
Time: 0.616 ms
[local:/opt/data5012]:5012 pg12@testdb=# begin;
BEGIN
Time: 0.310 ms
[local:/opt/data5012]:5012 pg12@testdb=#* update lockdemo set c1 = 'y';
查詢session 1和2的鎖資訊
-- session 1
[local:/opt/data5012]:5012 pg12@testdb=# select pid,locktype,relation::regclass,page,tuple,transactionid,mode,granted,fastpath from pg_locks where pid = 1714;
pid | locktype | relation | page | tuple | transactionid | mode | granted | fastpath
------+---------------+----------+------+-------+---------------+------------------+---------+----------
1714 | relation | lockdemo | | | | RowExclusiveLock | t | t
1714 | virtualxid | | | | | ExclusiveLock | t | t
1714 | transactionid | | | | 529 | ExclusiveLock | t | f
(3 rows)
Time: 5.251 ms
-- session 2
[local:/opt/data5012]:5012 pg12@testdb=# select pid,locktype,relation::regclass,page,tuple,transactionid,mode,granted,fastpath from pg_locks where pid = 1712;
pid | locktype | relation | page | tuple | transactionid | mode | granted | fastpath
------+---------------+----------+------+-------+---------------+------------------+---------+----------
1712 | relation | lockdemo | | | | RowExclusiveLock | t | t
1712 | virtualxid | | | | | ExclusiveLock | t | t
1712 | transactionid | | | | 529 | ShareLock | f | f
1712 | tuple | lockdemo | 0 | 1 | | ExclusiveLock | t | f
1712 | transactionid | | | | 531 | ExclusiveLock | t | f
(5 rows)
Time: 0.797 ms
[local:/opt/data5012]:5012 pg12@testdb=#
可以看到,session 1持有lockdemo的RowExclusiveLock意向鎖,該鎖不會阻塞session 2持有同樣的RowExclusiveLock鎖,同時session 1持有事務ID 529的排他鎖。session 2持有lockdemo的RowExclusiveLock意向鎖,並且持有lockdemo上的”排他行級鎖”(page = 0,tuple = 1),同時期望獲取事務529的共享鎖,但由於session 1已持有529的排他鎖無法授予(granted = f),因此session 2需等待。
這時候我們啟動session 3,執行同樣的更新SQL
[local:/opt/data5012]:5012 pg12@testdb=# select pg_backend_pid();
pg_backend_pid
----------------
1837
(1 row)
Time: 0.644 ms
[local:/opt/data5012]:5012 pg12@testdb=# begin;
BEGIN
Time: 0.455 ms
[local:/opt/data5012]:5012 pg12@testdb=#* update lockdemo set c1='z';
查詢session 3的鎖資訊
[local:/opt/data5012]:5012 pg12@testdb=# select pid,locktype,relation::regclass,page,tuple,transactionid,mode,granted,fastpath from pg_locks where pid = 1837;
pid | locktype | relation | page | tuple | transactionid | mode | granted | fastpath
------+---------------+----------+------+-------+---------------+------------------+---------+----------
1837 | relation | lockdemo | | | | RowExclusiveLock | t | t
1837 | virtualxid | | | | | ExclusiveLock | t | t
1837 | tuple | lockdemo | 0 | 1 | | ExclusiveLock | f | f
1837 | transactionid | | | | 532 | ExclusiveLock | t | f
(4 rows)
Time: 0.705 ms
[local:/opt/data5012]:5012 pg12@testdb=#
可以看到,session 3的鎖資訊與session 2的鎖資訊略有不同:session 3持有lockdemo的RowExclusiveLock意向鎖,期望獲取lockdemo上的”排他行級鎖”(page = 0,tuple = 1),但由於session 2已持有無法授予(granted = f),因此需等待。
實際上,按照PostgreSQL原始碼的註釋說明[1],Locking tuples的處理方式如下:
When it is necessary to wait for a tuple-level lock to be released, the basic
delay is provided by XactLockTableWait or MultiXactIdWait on the contents of
the tuple’s XMAX. However, that mechanism will release all waiters
concurrently, so there would be a race condition as to which waiter gets the
tuple, potentially leading to indefinite starvation of some waiters. The
possibility of share-locking makes the problem much worse —- a steady stream
of share-lockers can easily block an exclusive locker forever. To provide
more reliable semantics about who gets a tuple-level lock first, we use the
standard lock manager, which implements the second level mentioned above. The
protocol for waiting for a tuple-level lock is really
LockTuple()
XactLockTableWait()
mark tuple as locked by me
UnlockTuple()
PostgreSQL使用了標準鎖管理器(在記憶體中儲存行級鎖),實現(元組)上的二級鎖。
按此邏輯,由於沒有衝突,session 1完整的執行了以上的處理邏輯,釋放了記憶體中的元組排他鎖;而session 2獲得了元組排他鎖,但無法獲取XID 529的共享鎖,因此等待;session 3無法獲取元組排他鎖,因此等待。
使用pageinspect/pgrowlocks檢視元組上的行級鎖:
[local:/opt/data5012]:5012 pg12@testdb=# select get_tuple_locks('lockdemo');
get_tuple_locks
----------------------
("(0,1)",529,,,,,)
("(0,2)",529,,,,,)
("(0,3)",529,,,,,)
("(0,4)",529,,,,,)
("(0,5)",529,,,,,)
("(0,6)",529,,,,,)
("(0,7)",529,,,,,)
("(0,8)",529,,,,,)
...
[local:/opt/data5012]:5012 pg12@testdb=# select * from pgrowlocks('lockdemo');
locked_row | locker | multi | xids | modes | pids
------------+--------+-------+-------+-------------------+--------
(0,1) | 529 | f | {529} | {"No Key Update"} | {1714}
...
由於更新SQL沒有帶條件,因此XID 529在lockdemo上的每個元組都加鎖。
三、參考資料
[1]
README.tuplock
[2]
PostgreSQL Manual
[3] Postgres Professional,erogov
Locks in PostgreSQL
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/6906/viewspace-2721167/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- PostgreSQL 併發控制機制(1):隔離級別SQL
- Mysql鎖之行級鎖和表級意向鎖MySql
- PostgreSQL 併發控制機制(4):RR隔離級別,MySQL vs PostgreSQLMySql
- MySQL複習筆記(05):MySQL表級鎖和行級鎖MySql筆記
- Mariadb之顯式使用表鎖和行級鎖
- 執行緒和鎖,鎖升級執行緒
- 併發控制——樂觀鎖和悲觀鎖
- java併發筆記之synchronized 偏向鎖 輕量級鎖 重量級鎖證明Java筆記synchronized
- 聊聊併發控制鎖
- Java併發程式設計(05):悲觀鎖和樂觀鎖機制Java程式設計
- 圖解Janusgraph系列-併發安全:鎖機制(本地鎖+分散式鎖)分析圖解分散式
- 《淺入淺出MySQL》表鎖 行鎖 併發插入MySql
- MySQL的事務機制和鎖(InnoDB引擎、MVCC多版本併發控制技術)MySqlMVC
- Java併發程式設計之鎖機制之(ReentrantLock)重入鎖Java程式設計ReentrantLock
- Mysql中的鎖機制——MyISAM表鎖MySql
- MySQL 行級鎖之 間隙鎖、臨鍵鎖MySql
- 【JavaSE】淺談偏向鎖、輕量級鎖和重量級鎖,如何獲取鎖,如何撤銷鎖。Java
- java 中的鎖 -- 偏向鎖、輕量級鎖、自旋鎖、重量級鎖Java
- PostgreSQL 併發控制機制(3):基於時間戳的併發控制SQL時間戳
- 你真的會用mysql行級鎖嗎?mysql 行級鎖全解析MySql
- Java併發程式設計之鎖機制之ReentrantReadWriteLock(讀寫鎖)Java程式設計
- Mysql事務隔離級別與鎖機制MySql
- MySQL如何加鎖控制併發MySql
- 自旋鎖、阻塞鎖、可重入鎖、悲觀鎖、樂觀鎖、讀寫鎖、偏向所、輕量級鎖、重量級鎖、鎖膨脹、物件鎖和類鎖物件
- 深入理解偏向鎖、輕量級鎖、重量級鎖
- mysql鎖之三種行級鎖介紹MySql
- JAVA物件分析之偏向鎖、輕量級鎖、重量級鎖升級過程Java物件
- Java併發程式設計之鎖機制之AQSJava程式設計AQS
- 全域性鎖、表鎖、行鎖
- 詳解Mysql事務隔離級別與鎖機制MySql
- 深入解析 PostgreSQL 系列之併發控制與事務機制SQL
- Java 併發機制底層實現 —— volatile 原理、synchronize 鎖優化機制Java優化
- Java併發程式設計之鎖機制之Condition介面Java程式設計
- Java併發程式設計之鎖機制之LockSupport工具Java程式設計
- Java併發程式設計之鎖機制之Lock介面Java程式設計
- Golang語言goroutine協程併發安全及鎖機制Golang
- Java併發程式設計中的鎖機制詳解Java程式設計
- Java開發技巧——併發控制中的樂觀鎖與悲觀鎖Java