第九章:事务与并发控制

最后更新: 2024-01-01 作者: PostgreSQL Team
页面目录

第九章:事务与并发控制

9.1 事务基础

事务是数据库操作的基本单元,具有 ACID 特性。

┌─────────────────────────────────────────────────────────────┐
│                      ACID 特性                              │
├─────────────────────────────────────────────────────────────┤
│  Atomicity (原子性)   │  事务是不可分割的工作单元            │
│  Consistency (一致性) │  事务执行后数据库状态保持一致         │
│  Isolation (隔离性)   │  并发事务相互隔离,互不干扰          │
│  Durability (持久性)   │  事务提交后永久保存                   │
└─────────────────────────────────────────────────────────────┘

9.2 事务控制

9.2.1 基本事务语句

-- 显式事务
BEGIN;
-- 或
START TRANSACTION;

-- 提交事务
COMMIT;

-- 回滚事务
ROLLBACK;

-- 保存点
SAVEPOINT savepoint_name;

-- 回滚到保存点
ROLLBACK TO SAVEPOINT savepoint_name;

-- 释放保存点
RELEASE SAVEPOINT savepoint_name;

9.2.2 事务示例

-- 转账示例
BEGIN;

-- 扣除转出账户
UPDATE accounts SET balance = balance - 1000 
WHERE user_id = 1;

-- 检查余额
SELECT balance FROM accounts WHERE user_id = 1;
-- 如果余额不足,回滚
-- IF balance < 0 THEN ROLLBACK;

-- 转入账户
UPDATE accounts SET balance = balance + 1000 
WHERE user_id = 2;

-- 记录交易日志
INSERT INTO transactions (from_user, to_user, amount)
VALUES (1, 2, 1000);

COMMIT;

9.2.3 自动提交模式

-- psql 中默认为自动提交
-- 开启手动提交
\echo :AUTOCOMMIT
\set AUTOCOMMIT off

-- 关闭自动提交(在 psql 中执行)
\encoding UTF8

-- 每个语句自动在事务中执行
-- 仍需显式 COMMIT 或 ROLLBACK

9.3 事务隔离级别

PostgreSQL 支持四种事务隔离级别:

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED 可能 可能 可能
READ COMMITTED 不可能 可能 可能
REPEATABLE READ 不可能 不可能 可能*
SERIALIZABLE 不可能 不可能 不可能

*PostgreSQL 的 REPEATABLE READ 可以防止幻读

-- 设置隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- 在 BEGIN 后设置
BEGIN ISOLATION LEVEL SERIALIZABLE;

9.3.1 READ COMMITTED(默认)

-- 会话 1
BEGIN;
UPDATE users SET email = 'new1@example.com' WHERE id = 1;
-- 未提交

-- 会话 2
SELECT email FROM users WHERE id = 1;
-- 结果: 旧 email(未提交)

-- 会话 1
COMMIT;

-- 会话 2
SELECT email FROM users WHERE id = 1;
-- 结果: new1@example.com(读取已提交数据)

9.3.2 REPEATABLE READ

-- 会话 1
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT email FROM users WHERE id = 1;
-- 结果: old@example.com

-- 会话 2(在 REPEATABLE READ 事务外)
UPDATE users SET email = 'new@example.com' WHERE id = 1;
COMMIT;

-- 会话 1
SELECT email FROM users WHERE id = 1;
-- 结果: 仍是 old@example.com(同一事务内一致)

COMMIT;
SELECT email FROM users WHERE id = 1;
-- 结果: new@example.com(新事务可见)

9.3.3 SERIALIZABLE

-- 会话 1
BEGIN ISOLATION LEVEL SERIALIZABLE;

-- 尝试插入重复记录
INSERT INTO unique_values (value)
SELECT 100 WHERE NOT EXISTS (
    SELECT 1 FROM unique_values WHERE value = 100
);

-- 会话 2 也同时执行相同操作
-- 只有一个会成功,另一个会报错
COMMIT;

-- ERROR: could not serialize access

9.4 MVCC 机制

Multi-Version Concurrency Control(多版本并发控制)实现读写不阻塞。

9.4.1 MVCC 工作原理

-- 事务可见性由以下条件决定:
-- 1. 事务开始时 xmin(创建版本)是否已提交
-- 2. 事务结束时 xmax(删除版本)是否已提交

-- 查看行版本信息
SELECT xmin, xmax, * FROM users WHERE id = 1;

9.4.2 事务ID

-- 获取当前事务ID
SELECT txid_current();

-- 获取当前后端的事务ID
SELECT txid_current_if_assigned();

-- 查看活动事务
SELECT 
    pid,
    txid_current_if_assigned() AS txid,
    state,
    query_start
FROM pg_stat_activity
WHERE state != 'idle';

9.4.3 事务快照

-- 获取当前快照
SELECT txid_current_snapshot();

-- 解析快照
SELECT * FROM pg_stat_get_snapshot_timestamp(txid_current());

9.5 锁机制

9.5.1 锁类型

锁类型 说明 范围
ACCESS SHARE SELECT 行级
ROW SHARE SELECT FOR SHARE 行级
ROW EXCLUSIVE INSERT, UPDATE, DELETE 行级
SHARE UPDATE EXCLUSIVE VACUUM, ANALYZE, CREATE INDEX CONCURRENTLY 表级
SHARE CREATE INDEX 表级
SHARE ROW EXCLUSIVE 表级
EXCLUSIVE REFRESH MATERIALIZED VIEW CONCURRENTLY 表级
ACCESS EXCLUSIVE ALTER TABLE, DROP TABLE, TRUNCATE 表级

9.5.2 显式锁

-- 表级锁
BEGIN;
LOCK TABLE users IN ACCESS EXCLUSIVE MODE;
-- 执行操作
COMMIT;

-- 行级锁
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 锁定行直到事务结束
UPDATE users SET email = 'new@example.com' WHERE id = 1;
COMMIT;

-- 多种行锁
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; -- 跳过已锁定的行

9.5.3 死锁处理

-- 会话 1
BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;  -- 锁定账户1
-- 等待...

-- 会话 2
BEGIN;
SELECT * FROM accounts WHERE id = 2 FOR UPDATE;  -- 锁定账户2
-- 等待...

-- 会话 1
SELECT * FROM accounts WHERE id = 2 FOR UPDATE;  -- 死锁!
-- ERROR: deadlock detected

-- 会话 2
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;  -- 成功(之前已释放)

9.6 并发问题

9.6.1 脏读 (Dirty Read)

读取未提交的数据(PostgreSQL 不允许)

-- 会话 1
BEGIN;
UPDATE users SET balance = balance - 1000 WHERE id = 1;
-- 未提交

-- 会话 2(READ COMMITTED)
SELECT balance FROM users WHERE id = 1;
-- 结果: 仍然是旧值(PostgreSQL 防止脏读)

9.6.2 不可重复读 (Non-repeatable Read)

同一事务中两次读取数据不同

-- 会话 1
BEGIN ISOLATION LEVEL READ COMMITTED;
SELECT balance FROM users WHERE id = 1;  -- 第一次读取: 1000

-- 会话 2
UPDATE users SET balance = 2000 WHERE id = 1;
COMMIT;

-- 会话 1
SELECT balance FROM users WHERE id = 1;  -- 第二次读取: 2000
-- READ COMMITTED 会出现不可重复读

9.6.3 幻读 (Phantom Read)

同一事务中查询结果行数不同

-- 会话 1
BEGIN ISOLATION LEVEL READ COMMITTED;
SELECT COUNT(*) FROM orders WHERE status = 'pending';  -- 10行

-- 会话 2
INSERT INTO orders (user_id, status) VALUES (1, 'pending');
COMMIT;

-- 会话 1
SELECT COUNT(*) FROM orders WHERE status = 'pending';  -- 11行
-- 出现幻读

9.7 乐观并发控制

使用版本号实现乐观锁:

-- 添加版本号字段
ALTER TABLE users ADD COLUMN version INTEGER DEFAULT 1;

-- 乐观锁更新
UPDATE users 
SET email = 'new@example.com', 
    version = version + 1
WHERE id = 1 AND version = 1;

-- 检查更新结果
IF NOT FOUND THEN
    -- 记录已被其他事务修改
    RAISE NOTICE '更新失败,数据已被修改';
END IF;

-- 应用层逻辑
-- 1. 读取记录及其版本号
-- 2. 执行更新,检查版本号
-- 3. 如果版本号不匹配,回滚重试

9.8 事务监控

-- 查看当前锁
SELECT 
    l.locktype,
    l.relation::regclass,
    l.mode,
    l.granted,
    l.pid,
    a.usename,
    a.query
FROM pg_locks l
JOIN pg_stat_activity a ON l.pid = a.pid
WHERE l.pid != pg_backend_pid();

-- 查看等待中的查询
SELECT 
    pid,
    usename,
    query,
    wait_event_type,
    wait_event
FROM pg_stat_activity
WHERE wait_event IS NOT NULL
    AND state != 'idle';

-- 查看长事务
SELECT 
    pid,
    usename,
    query,
    state,
    now() - query_start AS duration
FROM pg_stat_activity
WHERE state != 'idle'
    AND now() - query_start > interval '1 minute';

-- 终止阻塞查询
SELECT pg_cancel_backend(pid);   -- 优雅终止
SELECT pg_terminate_backend(pid); -- 强制终止

9.9 本章小结

事务与并发控制关键概念:

主题 关键点
ACID 原子性、一致性、隔离性、持久性
隔离级别 READ COMMITTED, REPEATABLE READ, SERIALIZABLE
MVCC 多版本并发控制,读写不阻塞
锁类型 ACCESS SHARE 到 ACCESS EXCLUSIVE
并发问题 脏读、不可重复读、幻读

📌 下一章预告

下一章将介绍 PostgreSQL 的存储过程、触发器和自动化任务。