分布式鎖不一定非要用 Redis/ZooKeeper。PostgreSQL 內建的諮詢鎖與表級鎖即可輕鬆實現,且天然支援故障自動釋放、HA 高可用。本文詳細演示兩種方案的 SQL 實現、適用場景與優缺點對比。
| 函數 | 說明 |
|---|---|
pg_advisory_lock(key bigint) | 阻塞獲取排他鎖,會話級 |
pg_try_advisory_lock(key bigint) | 非阻塞嘗試獲取,立即返回 true/false |
pg_advisory_unlock(key bigint) | 釋放鎖 |
pg_advisory_lock_shared(key bigint) | 共享鎖(讀鎖),多會話可同時持有 |
-- 獲取鎖(阻塞直到成功)
SELECT pg_advisory_lock(123456); -- key 可為業務 ID(訂單號、用戶 ID)
-- 業務邏輯執行...
-- 更新庫存、扣款等
-- 釋放鎖
SELECT pg_advisory_unlock(123456);
# PostgreSQL 服務端 postgresql.conf
tcp_keepalives_idle = 60 # 空閒 60s 發送探針
tcp_keepalives_interval = 10 # 每 10s 重試
tcp_keepalives_count = 6 # 重試 6 次失效判定斷開
# 客戶端連接池
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.connection-timeout=30000
CREATE TABLE dist_lock (
id INT PRIMARY KEY, -- 業務鍵
owner TEXT, -- 持有者標識
expired_time INTERVAL, -- 租約時長
ts TIMESTAMPTZ -- 最後續租時間
);
INSERT INTO dist_lock(id) VALUES (1); -- 預置鎖記錄
-- 參數:$1=鎖ID, $2=持有者, $3=租約間隔
UPDATE dist_lock
SET owner = $2, ts = now(), expired_time = $3
WHERE id = $1
AND (owner = $2 -- 自己已持有(續租)
OR owner IS NULL -- 無人持有
OR now() > ts + expired_time); -- 租約過期(搶佔)
-- 返回 UPDATE 1 表示獲取成功,0 表示失敗
UPDATE dist_lock
SET ts = now()
WHERE id = $1 AND owner = $2;
UPDATE dist_lock
SET owner = NULL, ts = now()
WHERE id = $1 AND owner = $2;
| 維度 | 會話諮詢鎖 | 表級期限鎖 |
|---|---|---|
| 實現複雜度 | 極低(2 個函數調用) | 中(建表 + 多條 SQL + 續租邏輯) |
| 性能 | 極高(純內存,微秒) | 中等(磁盤 I/O,毫秒) |
| 持有時長 | 短(< 秒級,受限連接數) | 長(分鐘~小時,無連接限制) |
| 故障恢復 | 連接斷開自動釋放 | 租約過期自動釋放 |
| 高可用 | 依賴連接池/驅動重連 | 依賴 PG 流複製/同步複製 |
| 適用場景 | 短關鍵區、單實例/低併發 | 長事務、跨服務協調、強一致性要求 |
pg_try_advisory_lock 非阻塞模式,失敗直接返回「系統繁忙」order:{id}、user:{uid}:withdraw),避免大顆粒度鎖(如 global_lock)pg_locks 視圖)PostgreSQL 同時提供了輕量級會話鎖與重量級持久鎖,覆蓋從毫秒級關鍵區到分鐘級協調流程的全谱系需求。選型原則:**能用諮詢鎖就不用表鎖,能用表鎖就不用 Redis/ZK**,在「足夠好」與「運維成本」間找到平衡點。