Skip to content

事务处理

事务概述

PostgreSQL 是完全符合 ACID 的数据库,提供完善的事务支持。

ACID 特性

特性说明
原子性 (Atomicity)事务是不可分割的工作单位
一致性 (Consistency)事务前后数据保持一致状态
隔离性 (Isolation)多个事务互不干扰
持久性 (Durability)事务提交后永久保存

事务控制

基本语法

sql
BEGIN;
START TRANSACTION;

COMMIT;
ROLLBACK;

事务示例

sql
BEGIN;

UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

COMMIT;

保存点

sql
BEGIN;

INSERT INTO orders (user_id, amount) VALUES (1, 100);
SAVEPOINT sp1;

INSERT INTO order_items (order_id, product_id) VALUES (1, 1);
SAVEPOINT sp2;

ROLLBACK TO sp1;
COMMIT;

事务模式

sql
BEGIN READ ONLY;
BEGIN READ WRITE;
BEGIN ISOLATION LEVEL READ COMMITTED;
BEGIN ISOLATION LEVEL SERIALIZABLE;

隔离级别

四种隔离级别

级别脏读不可重复读幻读
READ UNCOMMITTED---
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

READ COMMITTED(默认)

sql
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED;

每次查询看到事务开始时已提交的数据。

REPEATABLE READ

sql
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ;

事务中所有查询看到同一快照。

SERIALIZABLE

sql
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;

最高隔离级别,完全串行化执行。

MVCC 多版本并发控制

实现原理

  • 每行数据包含 xmin、xmax 隐藏列
  • xmin: 创建该行的事务 ID
  • xmax: 删除/更新该行的事务 ID
  • 使用快照确定可见性

查看隐藏列

sql
SELECT xmin, xmax, * FROM users;

可见性判断

如果 xmin 已提交且对当前快照可见,且 xmax 为空或未提交或对当前快照不可见:
    该行可见
否则:
    该行不可见

锁机制

表级锁

锁模式说明
ACCESS SHARESELECT
ROW SHARESELECT FOR UPDATE/SHARE
ROW EXCLUSIVEINSERT/UPDATE/DELETE
SHARE UPDATE EXCLUSIVEVACUUM, ANALYZE
SHARE创建外键
SHARE ROW EXCLUSIVE排他共享
EXCLUSIVE阻止并发更新
ACCESS EXCLUSIVEALTER TABLE, DROP
sql
LOCK TABLE users IN SHARE MODE;
LOCK TABLE users IN EXCLUSIVE MODE;

行级锁

锁模式说明
FOR UPDATE排他行锁
FOR NO KEY UPDATE非键排他锁
FOR SHARE共享行锁
FOR KEY SHARE键共享锁
sql
SELECT * FROM users WHERE id = 1 FOR UPDATE;
SELECT * FROM users WHERE id = 1 FOR SHARE;
SELECT * FROM users WHERE id = 1 FOR UPDATE NOWAIT;
SELECT * FROM users WHERE id = 1 FOR UPDATE SKIP LOCKED;

查看锁

sql
SELECT * FROM pg_locks;
SELECT * FROM pg_locks WHERE relation = 'users'::regclass;

查看阻塞

sql
SELECT
    blocked_locks.pid AS blocked_pid,
    blocked_activity.usename AS blocked_user,
    blocking_locks.pid AS blocking_pid,
    blocking_activity.usename AS blocking_user,
    blocked_activity.query AS blocked_statement,
    blocking_activity.query AS blocking_statement
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.relation = blocked_locks.relation
    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;

死锁

死锁示例

事务A: UPDATE users SET age = 1 WHERE id = 1;
事务B: UPDATE users SET age = 2 WHERE id = 2;
事务A: UPDATE users SET age = 3 WHERE id = 2;  -- 等待
事务B: UPDATE users SET age = 4 WHERE id = 1;  -- 死锁

死锁检测

PostgreSQL 自动检测死锁并回滚其中一个事务。

避免死锁

  1. 按相同顺序访问表和行
  2. 避免长事务
  3. 使用较低的隔离级别
  4. 合理设计索引

事务日志

WAL(Write-Ahead Logging)

所有修改先写入 WAL,再修改数据文件。

sql
SHOW wal_level;
SHOW max_wal_size;
SHOW wal_keep_size;

WAL 配置

ini
wal_level = replica
max_wal_size = 2GB
min_wal_size = 1GB
wal_keep_size = 1GB
checkpoint_completion_target = 0.9

并发问题

脏读

PostgreSQL 不会发生脏读。

不可重复读

READ COMMITTED 级别可能发生:

事务A: SELECT age FROM users WHERE id = 1;  -- 25
事务B: UPDATE users SET age = 30 WHERE id = 1; COMMIT;
事务A: SELECT age FROM users WHERE id = 1;  -- 30

幻读

REPEATABLE READ 级别可能发生:

事务A: SELECT * FROM users WHERE age > 20;  -- 5条
事务B: INSERT INTO users (age) VALUES (25); COMMIT;
事务A: SELECT * FROM users WHERE age > 20;  -- 5条(快照)

序列化异常

SERIALIZABLE 级别可能检测到:

事务A: SELECT COUNT(*) FROM users WHERE age > 20;  -- 5
事务B: INSERT INTO users (age) VALUES (25); COMMIT;
事务A: INSERT INTO users (age) VALUES (26);  -- 可能失败

事务监控

查看活跃事务

sql
SELECT * FROM pg_stat_activity WHERE state = 'active';
SELECT * FROM pg_stat_activity WHERE query NOT LIKE '%pg_stat_activity%';

查看长事务

sql
SELECT
    pid,
    now() - pg_stat_activity.query_start AS duration,
    query,
    state
FROM pg_stat_activity
WHERE (now() - pg_stat_activity.query_start) > interval '5 minutes';

终止事务

sql
SELECT pg_cancel_backend(pid);
SELECT pg_terminate_backend(pid);

事务最佳实践

1. 保持事务简短

sql
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;

2. 避免长事务

sql
SET statement_timeout = '30s';
SET lock_timeout = '10s';
SET idle_in_transaction_session_timeout = '60s';

3. 合理选择隔离级别

sql
BEGIN ISOLATION LEVEL READ COMMITTED;
BEGIN ISOLATION LEVEL REPEATABLE READ;
BEGIN ISOLATION LEVEL SERIALIZABLE;

4. 使用适当的锁

sql
SELECT * FROM users WHERE id = 1 FOR UPDATE;
SELECT * FROM users WHERE id = 1 FOR UPDATE SKIP LOCKED;

下一步学习