BEGIN;
-- 1. 排他锁(悲观锁)
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
-- 2. 允许读,但禁止修改
SELECT * FROM products WHERE id = 10 FOR SHARE;
-- 3. 允许弱更新
SELECT * FROM orders WHERE id = 100 FOR NO KEY UPDATE;
-- 4. 最弱锁
SELECT * FROM logs WHERE id = 1000 FOR KEY SHARE;
COMMIT;
三、表级锁(Table-level Locks)
锁模式
说明
语法
ACCESS SHARE
SELECT 自动获取
最弱
ROW SHARE
SELECT FOR SHARE 获取
ROW EXCLUSIVE
UPDATE/DELETE/INSERT 获取
SHARE
允许读,禁止结构变更
LOCK TABLE t IN SHARE MODE;
SHARE ROW EXCLUSIVE
更强共享
EXCLUSIVE
禁止读写
LOCK TABLE t IN EXCLUSIVE MODE;
ACCESS EXCLUSIVE
最强,禁止一切
ALTER TABLE, DROP TABLE 自动获取
显式加表锁
-- 允许读,禁止写
LOCK TABLE products IN SHARE MODE;
-- 禁止所有访问(用于维护)
LOCK TABLE big_table IN ACCESS EXCLUSIVE MODE;
注意:表锁影响大,生产慎用
四、加锁最佳实践:悲观锁 vs 乐观锁
1. 悲观锁(Pessimistic Locking)—— FOR UPDATE
BEGIN;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
-- 检查余额
IF balance < 100 THEN
RAISE EXCEPTION 'Insufficient funds';
END IF;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
2. 乐观锁(Optimistic Locking)—— 版本号
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT,
stock INT,
version INT DEFAULT 1
);
-- 更新时检查版本
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 1 AND version = 5;
-- 影响行数 = 0 → 冲突,重试
五、死锁(Deadlock)检测与处理
PostgreSQL 自动检测死锁,每隔 deadlock_timeout(默认 1s)检查。
ERROR: deadlock detected
DETAIL: Process 123 waits for ShareLock on transaction 456...
HINT: See server log for query details.
死锁示例
-- 会话1
BEGIN;
UPDATE accounts SET balance = 100 WHERE id = 1;
UPDATE accounts SET balance = 200 WHERE id = 2; -- 等待
-- 会话2
BEGIN;
UPDATE accounts SET balance = 300 WHERE id = 2;
UPDATE accounts SET balance = 400 WHERE id = 1; -- 死锁!
解决策略
统一加锁顺序
缩短事务时间
使用 FOR UPDATE SKIP LOCKED(跳过被锁行)
重试机制
-- 跳过被锁行(适合队列)
SELECT * FROM tasks
WHERE status = 'pending'
FOR UPDATE SKIP LOCKED
LIMIT 10;
六、锁监控与诊断
1. 查看当前锁
SELECT
pid,
datname,
usename,
relation::regclass AS table,
mode,
granted,
query
FROM pg_locks l
JOIN pg_stat_activity a ON l.pid = a.pid
WHERE relation IS NOT NULL;
2. 查看阻塞关系
SELECT
blocked.pid AS blocked_pid,
blocked.query AS blocked_query,
blocking.pid AS blocking_pid,
blocking.query AS blocking_query
FROM pg_stat_activity blocked
JOIN pg_locks blocked_locks ON blocked.pid = blocked_locks.pid
JOIN pg_locks blocking_locks ON blocked_locks.locktype = blocking_locks.locktype
AND blocked_locks.database = blocking_locks.database
AND blocked_locks.relation = blocking_locks.relation
JOIN pg_stat_activity blocking ON blocking_locks.pid = blocking.pid
WHERE NOT blocked_locks.granted;
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
八、性能优化技巧
技巧
说明
避免 SELECT * FOR UPDATE
只锁必要列
使用 NOWAIT
立即失败,避免等待
使用 SKIP LOCKED
高并发队列
缩短事务
减少锁持有时间
分区表
减小锁冲突
-- 立即失败
SELECT * FROM queue FOR UPDATE NOWAIT;
-- 跳过被锁行
SELECT * FROM queue FOR UPDATE SKIP LOCKED LIMIT 1;
九、常见陷阱与避坑
问题
原因
解决
死锁频繁
加锁顺序不一致
统一顺序
查询慢
锁等待
用 NOWAIT 或 SKIP LOCKED
表被锁住
显式 LOCK TABLE
避免表锁
FOR UPDATE 没生效
在子查询中
放在最外层
分布式死锁
多节点
用 SERIALIZABLE 或应用锁
十、最佳实践脚本
1. 高并发转账(悲观锁 + 重试)
CREATE OR REPLACE FUNCTION transfer_money(
from_id INT, to_id INT, amount NUMERIC, max_retries INT = 3
) RETURNS BOOLEAN AS $$
DECLARE
i INT := 0;
locked_from BOOLEAN;
locked_to BOOLEAN;
BEGIN
LOOP
i := i + 1;
BEGIN
-- 固定顺序:小 ID 先锁
IF from_id < to_id THEN
SELECT TRUE FROM accounts WHERE id = from_id FOR UPDATE NOWAIT INTO locked_from;
SELECT TRUE FROM accounts WHERE id = to_id FOR UPDATE NOWAIT INTO locked_to;
ELSE
SELECT TRUE FROM accounts WHERE id = to_id FOR UPDATE NOWAIT INTO locked_to;
SELECT TRUE FROM accounts WHERE id = from_id FOR UPDATE NOWAIT INTO locked_from;
END IF;
-- 执行转账
UPDATE accounts SET balance = balance - amount WHERE id = from_id;
UPDATE accounts SET balance = balance + amount WHERE id = to_id;
RETURN TRUE;
EXCEPTION
WHEN lock_not_available THEN
IF i >= max_retries THEN RAISE; END IF;
PERFORM pg_sleep(0.05 * i);
WHEN deadlock_detected THEN
IF i >= max_retries THEN RAISE; END IF;
PERFORM pg_sleep(0.1 * i);
END;
END LOOP;
END;
$$ LANGUAGE plpgsql;
2. 队列消费(SKIP LOCKED)
BEGIN;
SELECT * FROM job_queue
WHERE status = 'pending'
ORDER BY priority DESC, created_at
FOR UPDATE SKIP LOCKED
LIMIT 10;
-- 处理后更新状态
UPDATE job_queue SET status = 'processing', worker = current_user
WHERE id IN (...);
COMMIT;
十一、锁模式兼容性表
当前锁 \ 请求锁
ACCESS SHARE
ROW SHARE
ROW EXCLUSIVE
SHARE
SHARE ROW EXCLUSIVE
EXCLUSIVE
ACCESS EXCLUSIVE
ACCESS SHARE
Yes
Yes
Yes
Yes
Yes
Yes
No
ROW SHARE
Yes
Yes
Yes
Yes
Yes
No
No
ROW EXCLUSIVE
Yes
Yes
Yes
No
No
No
No
SHARE
Yes
Yes
No
Yes
No
No
No
EXCLUSIVE
Yes
No
No
No
No
No
No
ACCESS EXCLUSIVE
No
No
No
No
No
No
No
十二、速查表
命令
用途
FOR UPDATE
排他锁
FOR SHARE
共享锁
FOR UPDATE SKIP LOCKED
跳过被锁行
FOR UPDATE NOWAIT
立即失败
LOCK TABLE t IN EXCLUSIVE MODE;
表级排他锁
SELECT pg_terminate_backend(pid);
杀进程
deadlock_timeout = 1s
死锁检测间隔
十三、锁决策树
graph TD
A[需要防止并发修改?] -->|是| B{是否高并发?}
B -->|是| C{是否可接受冲突?}
C -->|是| D[用乐观锁 version]
C -->|否| E[用 FOR UPDATE SKIP LOCKED]
B -->|否| F[用 FOR UPDATE]
A -->|否| G[无需锁]