概述
前面已經介紹了PG鎖機制的一些概念性內容,這裡主要做一些相關實驗,幫助大家理解下PG的鎖機制。
以下基於PG10.9進行測試。
1、創建測試表
CREATE TABLE parent (
id serial NOT NULL PRIMARY KEY,
name text NOT NULL);
CREATE TABLE child (
id serial NOT NULL PRIMARY KEY,
parent_id int4 NOT NULL,
name text NOT NULL, CONSTRAINT child_parent_fk FOREIGN KEY (parent_id) REFERENCES parent(id)
);
2、簡單事務測試
嘗試一些簡單的事務,看看鎖是什麼樣的:
BEGIN;
SELECT * FROM active_locks; -- There are no active locks yet
INSERT INTO parent (name) VALUES ('Parent 1');
SELECT * FROM active_locks;
COMMIT;
SELECT * FROM active_locks;
可以看到在 parent 表里插入一行後,獲得了 parent 表上的行獨占鎖。parent_id_seq 是 parent 表的主鍵序列。由於這種關係被選中(如表),獲得了訪問共享鎖。
3、插入數據
咱們試著往 child 表里插點東西;
BEGIN;
INSERT INTO child (parent_id, name) VALUES (1, 'Child 1 Parent 1');
COMMIT;
可以看到 parent 表上增加的行共享鎖。我們看不到的是,這個插入同樣獲得了 parent 表上引用行的行級共享鎖。並行執行兩個事務我們就可以看到它了:
-- Transaction 1
BEGIN;
INSERT INTO child (parent_id, name) VALUES (1, 'Child 2 Parent 1');
-- Transaction 2
BEGIN;
DELETE FROM parent WHERE id = 1;
現在開始第三個會話,看看鎖怎麼樣了:
SELECT * FROM active_locks;
DELETE 查詢被阻塞了,等待事務 1 完成。我們可以看到它在元組 1 上獲得了一個鎖。但是如果我們看到所有的鎖都是准許的(granted=t),為什麼 DELETE 查詢被阻塞了?這兩個事務在任何關係上的鎖都沒有同步過。事實上,如果一個事務在某些行上持有一個鎖,第二個事務請求這個鎖,第二個事務會嘗試獲取持有此鎖的事務上的共享鎖。當第一個事務完成時,第二個事務將繼續。這是可能的,因為每個事務都持有它自身的排他鎖。我們可以看看 pg_locks 視圖,這是輸出(只有部分是重要的):
可以看到事務 563(pid 5690)和事務564(pid 5790)擁有它們事務標識上的排他鎖,事務564 獲得了事務563 上的共享鎖。
4、更新子表
可以玩玩更新子表但不實際改變任何父表與相關的東西(在這個案例中是parent_id列)。
BEGIN;
UPDATE child SET name = 'My new name' WHERE id = 1;
SELECT * FROM active_locks;
UPDATE child SET name = 'My new name' WHERE id = 1;
SELECT * FROM active_locks;
COMMIT;
可以看到正在執行的 UPDATE 查詢不會觸及任何與父表相關的東西。第一次執行後,我們可以看到,只有 child 表包含表級鎖。行級鎖也是如此。只有 child 表的行有更新鎖。這是 Postgres 中的優化。
如果鎖管理器可以從第一個查詢中發現外鍵沒有改變(沒有被更新查詢提及或被設置為相同的值),它不會鎖定父表。但在第二個查詢它會像文檔描述的那樣處理(它將鎖定 parent 表為行共享鎖定模式和涉及的行為分享模式)。這是非常危險的,因為它會導致最危險的和最難找到的死鎖。不過可以在事務開頭使用顯式鎖定以避免它。
防止死鎖的最好方式,是當我們意識到它們可能在兩個事務之間發生時,去按一定順序獲取行級鎖(例如主鍵排序)和首先獲取最嚴格的鎖。一旦在 Postgres 發生死鎖,可以通過中止一個參與死鎖的事務來消除。
總結
對 Postgres 中鎖的工作機制銘記於心非常重要。在高並發環境中死鎖可能無法避免,但重要的是要知道如何發現、監控並解決它們。即使所有事情都照書而行也不一定能解決所有潛在的鎖問題,但會減少它們並使其易於解決。表級鎖可以通過 pg_locks 系統視圖查看,但行級鎖不行,所以這讓調試鎖更為困難,不過Postgres 的未來版本中還是有可能實現的。後面會分享更多devops和DBA方面的內容,感興趣的朋友可以關注一下~