事务是数据库系统中保证数据一致性的核心机制。PostgreSQL 作为企业级开源数据库,其事务实现完全符合 ACID 特性:
原子性(Atomicity):事务内的操作要么全部成功,要么全部回滚。例如银行转账场景中,扣款和入账必须作为一个整体执行。
一致性(Consistency):事务执行前后数据库必须处于一致状态。通过约束、触发器等机制保证业务规则不被破坏。
隔离性(Isolation):并发事务相互隔离,PostgreSQL 提供多版本并发控制(MVCC)实现不同隔离级别。
持久性(Durability):已提交事务的修改永久保存,通过预写日志(WAL)机制保证。
sql复制-- 典型事务示例
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
注意:在事务中执行DDL语句(如CREATE TABLE)会导致隐式提交,这是PostgreSQL与MySQL的重要区别
PostgreSQL 16 支持四种标准隔离级别,通过SET TRANSACTION ISOLATION LEVEL设置:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 性能影响 |
|---|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 | 最低 |
| READ COMMITTED | 不可能 | 可能 | 可能 | 低 |
| REPEATABLE READ | 不可能 | 不可能 | 可能 | 中 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 | 高 |
PostgreSQL 默认隔离级别,通过快照隔离实现:
sql复制-- 会话1
BEGIN;
SELECT * FROM products WHERE id = 1; -- 看到版本A
-- 会话2
UPDATE products SET price = 99 WHERE id = 1;
COMMIT;
-- 会话1再次查询
SELECT * FROM products WHERE id = 1; -- 看到版本B
通过事务级快照解决不可重复读问题:
sql复制BEGIN ISOLATION LEVEL REPEATABLE READ;
-- 第一次查询建立快照
SELECT * FROM orders WHERE user_id = 100;
-- 其他会话修改并提交数据...
-- 同一事务内重复查询结果不变
SELECT * FROM orders WHERE user_id = 100;
COMMIT;
实战经验:使用REPEATABLE READ时,长时间事务可能导致"快照过旧"错误,需合理设置
old_snapshot_threshold参数
PostgreSQL 提供多粒度锁控制并发:
sql复制-- 行锁应用示例
BEGIN;
SELECT * FROM inventory
WHERE product_id = 'P123' AND quantity > 0
FOR UPDATE; -- 获取排他锁
-- 检查库存并下单
UPDATE inventory SET quantity = quantity - 1
WHERE product_id = 'P123';
COMMIT;
PostgreSQL 默认开启死锁检测(deadlock_timeout=1s),常见解决方案:
LOCK TABLE提前获取所需锁sql复制-- 死锁日志示例
ERROR: deadlock detected
DETAIL: Process 123 waits for ShareLock on transaction 456;
blocked by process 789.
Process 789 waits for ShareLock on transaction 123;
blocked by process 123.
实现事务内部部分回滚:
sql复制BEGIN;
INSERT INTO audit_log(action) VALUES ('start process');
SAVEPOINT step1;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 发现错误
ROLLBACK TO SAVEPOINT step1;
SAVEPOINT step2;
-- 尝试其他操作
COMMIT;
分布式事务关键实现:
sql复制-- 协调节点
BEGIN;
PREPARE TRANSACTION 'order_123';
-- 参与者节点
COMMIT PREPARED 'order_123';
-- 或
ROLLBACK PREPARED 'order_123';
性能提示:2PC会增加网络往返,建议单数据库事务优先
通过commit_delay和commit_siblings优化小事务:
sql复制-- postgresql.conf 优化配置
commit_delay = 10ms -- 延迟时间
commit_siblings = 5 -- 当有5个以上活跃事务时启用延迟
使用PgBouncer时的关键参数:
ini复制[databases]
mydb = host=127.0.0.1 pool_size=50
[pgbouncer]
pool_mode = transaction # 推荐事务级连接池
max_client_conn = 1000
default_pool_size = 20
sql复制SELECT pid, usename, query, xact_start
FROM pg_stat_activity
WHERE state = 'active' AND xact_start IS NOT NULL;
sql复制SELECT blocked_locks.pid AS blocked_pid,
blocking_locks.pid AS blocking_pid,
blocked_activity.query AS blocked_query,
blocking_activity.query AS blocking_query
FROM pg_catalog.pg_locks blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.pg_locks blocking_locks
ON blocking_locks.locktype = blocked_locks.locktype
AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks.DATABASE
AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
AND blocking_locks.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.GRANTED;
sql复制-- 监控示例
SELECT backend_type, object, reads, writes
FROM pg_stat_io
WHERE backend_type = 'client backend';
我在生产环境使用PostgreSQL事务时,最大的经验是合理控制事务粒度——太大会增加锁冲突风险,太小则影响性能。一个实用的平衡点是将事务持续时间控制在100ms以内,对于批处理操作可以使用SAVEPOINT分段提交。另外,定期检查pg_stat_user_tables中的死元组数量,及时执行VACUUM维护事务ID回卷空间。