PostgreSQL 事务
内容涵盖:事务基础、隔离级别、锁、常见问题、最佳实践、性能建议、异常处理。
事务(Transaction)是数据库中的 原子操作单元,具有:
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
简称 ACID。
事务确保一组 SQL 要么全部成功,要么全部失败,不会出现部分成功的情况。
每条 SQL 都是一个独立事务:
INSERT INTO ... ; -- 自动开始 + 自动提交
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT; -- 成功
失败回滚:
ROLLBACK;
等价命令:
BEGIN 或 START TRANSACTION
COMMIT
ROLLBACK
PostgreSQL 支持 4 种隔离级别:
| 隔离级别 | 说明 | PostgreSQL 默认 |
|---|---|---|
| READ UNCOMMITTED | 允许读未提交(脏读)。PostgreSQL 实际等同于 READ COMMITTED,不支持真正的脏读 | ❌ |
| READ COMMITTED | 每条语句看到提交后的最新数据 | ✅ 默认 |
| REPEATABLE READ | 整个事务期间看到一致的快照 | ✅ 不可重复读不存在 |
| SERIALIZABLE | 完全串行化 | ✅ 最强 |
设置隔离级别:
BEGIN ISOLATION LEVEL REPEATABLE READ;
或:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
PostgreSQL 使用 MVCC(多版本并发控制) 来保证事务隔离:
- 修改不会阻塞读
- 读也不会阻塞写
- 每个事务看到的是自己快照内的数据
这使得 PostgreSQL 非常适合集群 OLTP 高并发场景。
常见锁:
| 锁类型 | 触发场景 |
|---|---|
| Row Share | SELECT FOR SHARE |
| Row Exclusive | INSERT、UPDATE、DELETE |
| Share | CREATE INDEX |
| Exclusive | DROP TABLE |
| Access Exclusive | ALTER TABLE、VACUUM FULL |
行级锁(最常用)
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
ERROR: deadlock detected
常见原因:
- 两个事务互相等待对方的行锁
- 非全局一致的锁顺序
💡 解决办法:保持一致的锁顺序
长事务无法释放旧版本记录 -> 表膨胀 -> 性能下降。
解决:
- 避免长事务
- 避免长时间保持闲置事务
- 监控 pg_stat_activity
该状态会造成:
- 行无法删除(VACUUM 无法清理)
- 表膨胀、查询变慢
避免:
- 每个事务必须显式 COMMIT 或 ROLLBACK
- 应用一定要正确关闭连接
最简单,但连续操作不安全。
推荐用于一组逻辑性的更新。
用于捕获局部异常,而不回滚整个事务。
BEGIN;
SAVEPOINT sp1;
INSERT INTO table VALUES (...);
ROLLBACK TO sp1;
COMMIT;
锁行用于安全读取:
SELECT * FROM orders WHERE id = 10 FOR UPDATE;
PostgreSQL 细化了读写锁:
| 语法 | 锁级别 | 场景 |
|---|---|---|
| FOR UPDATE | 强写锁 | 准备修改该行 |
| FOR NO KEY UPDATE | 不修改主键时使用 | 更轻量 |
| FOR SHARE | 共享锁 | 不影响别人读 |
| FOR KEY SHARE | 最弱 | 外键检查 |
自动事务(2.0 API)
async with async_session() as session:
async with session.begin():
obj = MyTable(...)
session.add(obj)
手动 commit / rollback
session = Session()
try:
session.add(obj)
session.commit()
except:
session.rollback()
raise
保持事务尽可能短。
否则会导致 MVCC 膨胀。
减少锁冲突。
用于扣款、库存等业务场景。
READ COMMITTED 足够多数业务。
查看正在运行的事务:
SELECT * FROM pg_stat_activity WHERE state = 'active';
查看锁:
SELECT * FROM pg_locks l JOIN pg_stat_activity a
ON l.pid = a.pid;
查看死锁:
SELECT * FROM pg_stat_activity WHERE wait_event = 'Deadlock';
- 使用 statement_timeout
- 避免大量 UPDATE,改用 UPSERT 或 INSERT-only 模式
- 加索引避免锁冲突
- 高并发下使用 SKIP LOCKED
例如:
SELECT * FROM jobs
WHERE status = 'pending'
FOR UPDATE SKIP LOCKED
LIMIT 1;
PostgreSQL 事务体系非常强大(MVCC 超越大多数数据库),理解它可以解决:
- 并发写冲突
- 死锁
- 锁表问题
- 膨胀问题
- 一致性保证