分布式事务 #

一、事务基础 #

1.1 ACID特性 #

text
事务 ACID 特性
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   Atomicity (原子性)                                        │
│   ├── 事务是不可分割的工作单位                             │
│   ├── 要么全部成功,要么全部失败                           │
│   └── TiDB: 通过两阶段提交保证                             │
│                                                             │
│   Consistency (一致性)                                      │
│   ├── 事务使数据库从一个一致状态变到另一个一致状态         │
│   └── TiDB: 通过约束检查保证                               │
│                                                             │
│   Isolation (隔离性)                                        │
│   ├── 多个事务并发执行时互不干扰                           │
│   └── TiDB: 支持多种隔离级别                               │
│                                                             │
│   Durability (持久性)                                       │
│   ├── 事务提交后永久保存                                   │
│   └── TiDB: 通过 Raft 日志持久化保证                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.2 基本事务操作 #

sql
-- 开始事务
BEGIN;
-- 或
START TRANSACTION;

-- 提交事务
COMMIT;

-- 回滚事务
ROLLBACK;

-- 事务示例
BEGIN;

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

COMMIT;

二、隔离级别 #

2.1 支持的隔离级别 #

text
TiDB 隔离级别
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   REPEATABLE READ (默认)                                    │
│   ├── 可重复读                                             │
│   ├── 同一事务内多次读取结果相同                           │
│   ├── 使用 MVCC 实现                                       │
│   └── 无幻读问题                                           │
│                                                             │
│   READ COMMITTED                                            │
│   ├── 读已提交                                             │
│   ├── 只能读取已提交的数据                                 │
│   └── 可能出现不可重复读                                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

2.2 设置隔离级别 #

sql
-- 查看当前隔离级别
SELECT @@transaction_isolation;

-- 设置全局隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 设置会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 设置下个事务隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 在事务开始时设置
START TRANSACTION WITH CONSISTENT SNAPSHOT;

2.3 隔离级别示例 #

sql
-- 可重复读示例
-- 会话1
BEGIN;
SELECT balance FROM accounts WHERE id = 1;  -- 结果: 1000

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

-- 会话1 (再次查询)
SELECT balance FROM accounts WHERE id = 1;  -- 结果: 1000 (不变)
COMMIT;

-- 会话1 (新事务)
BEGIN;
SELECT balance FROM accounts WHERE id = 1;  -- 结果: 2000 (新值)
COMMIT;

三、事务模型 #

3.1 乐观事务 #

text
乐观事务流程
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   1. 获取 start_ts                                         │
│   ┌─────────────────────────────────────────────────────┐   │
│   │                                                     │   │
│   │   BEGIN → 从 PD 获取 start_ts                      │   │
│   │                                                     │   │
│   └─────────────────────────────────────────────────────┘   │
│                          │                                  │
│                          ▼                                  │
│   2. 执行 SQL (本地缓存)                                    │
│   ┌─────────────────────────────────────────────────────┐   │
│   │                                                     │   │
│   │   INSERT/UPDATE/DELETE                              │   │
│   │   - 只在 TiDB Server 本地缓存                       │   │
│   │   - 不写入 TiKV                                     │   │
│   │                                                     │   │
│   └─────────────────────────────────────────────────────┘   │
│                          │                                  │
│                          ▼                                  │
│   3. COMMIT (两阶段提交)                                    │
│   ┌─────────────────────────────────────────────────────┐   │
│   │                                                     │   │
│   │   Prewrite:                                         │   │
│   │   - 检查冲突                                        │   │
│   │   - 写入锁和数据                                    │   │
│   │                                                     │   │
│   │   Commit:                                           │   │
│   │   - 获取 commit_ts                                  │   │
│   │   - 提交 Primary Key                                │   │
│   │   - 异步提交 Secondary Keys                         │   │
│   │                                                     │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
│   特点:                                                     │
│   - 提交时检测冲突                                         │
│   - 适合冲突少的场景                                       │
│   - 冲突时需要重试                                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

3.2 悲观事务 #

text
悲观事务流程
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   1. 获取 start_ts                                         │
│   ┌─────────────────────────────────────────────────────┐   │
│   │                                                     │   │
│   │   BEGIN → 从 PD 获取 start_ts                      │   │
│   │                                                     │   │
│   └─────────────────────────────────────────────────────┘   │
│                          │                                  │
│                          ▼                                  │
│   2. 执行 SQL (立即加锁)                                    │
│   ┌─────────────────────────────────────────────────────┐   │
│   │                                                     │   │
│   │   SELECT ... FOR UPDATE                             │   │
│   │   - 立即获取行锁                                    │   │
│   │   - 阻止其他事务修改                                │   │
│   │                                                     │   │
│   │   UPDATE/DELETE                                     │   │
│   │   - 执行时加锁                                      │   │
│   │   - 直接写入 TiKV                                   │   │
│   │                                                     │   │
│   └─────────────────────────────────────────────────────┘   │
│                          │                                  │
│                          ▼                                  │
│   3. COMMIT                                                 │
│   ┌─────────────────────────────────────────────────────┐   │
│   │                                                     │   │
│   │   - 释放锁                                          │   │
│   │   - 提交数据                                        │   │
│   │                                                     │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
│   特点:                                                     │
│   - 执行时加锁                                             │
│   - 适合冲突多的场景                                       │
│   - 避免提交失败                                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

3.3 选择事务模式 #

sql
-- 启用悲观事务
SET SESSION tidb_txn_mode = 'pessimistic';

-- 启用乐观事务
SET SESSION tidb_txn_mode = 'optimistic';

-- 全局设置
SET GLOBAL tidb_txn_mode = 'pessimistic';
text
事务模式选择建议
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   乐观事务适合:                                             │
│   ├── 读多写少                                             │
│   ├── 冲突较少                                             │
│   ├── 批量操作                                             │
│   └── 数据导入                                             │
│                                                             │
│   悲观事务适合:                                             │
│   ├── 写多冲突多                                           │
│   ├── 需要严格串行化                                       │
│   ├── 金融交易                                             │
│   └── 库存扣减                                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

四、两阶段提交 #

4.1 提交流程 #

text
两阶段提交流程
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   Phase 1: Prewrite                                         │
│   ┌─────────────────────────────────────────────────────┐   │
│   │                                                     │   │
│   │   对每个修改的 Key:                                 │   │
│   │                                                     │   │
│   │   1. 检查冲突                                       │   │
│   │      - 检查是否有其他事务已提交                    │   │
│   │      - 检查是否有锁                                 │   │
│   │                                                     │   │
│   │   2. 写入数据                                       │   │
│   │      - 写入 start_ts 版本的数据                    │   │
│   │                                                     │   │
│   │   3. 写入锁                                         │   │
│   │      - Primary Lock: 主锁                          │   │
│   │      - Secondary Lock: 从锁                        │   │
│   │                                                     │   │
│   └─────────────────────────────────────────────────────┘   │
│                          │                                  │
│                          ▼                                  │
│   Phase 2: Commit                                           │
│   ┌─────────────────────────────────────────────────────┐   │
│   │                                                     │   │
│   │   1. 获取 commit_ts                                 │   │
│   │      - 从 PD 获取全局时间戳                        │   │
│   │                                                     │   │
│   │   2. 提交 Primary Key                               │   │
│   │      - 写入 commit_ts                               │   │
│   │      - 清除 Primary Lock                            │   │
│   │      - 此时事务提交成功                             │   │
│   │                                                     │   │
│   │   3. 异步提交 Secondary Keys                        │   │
│   │      - 后台异步清除 Secondary Lock                  │   │
│   │      - 不影响事务成功                               │   │
│   │                                                     │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

4.2 异步提交 #

sql
-- 启用异步提交 (默认开启)
SET SESSION tidb_enable_async_commit = ON;

-- 异步提交优化
-- Primary Key 提交成功即返回
-- Secondary Keys 异步提交
-- 减少提交延迟

4.3 1PC优化 #

sql
-- 单阶段提交优化
-- 适用于只涉及一个 Region 的事务
SET SESSION tidb_enable_1pc = ON;

-- 1PC 跳过 Prewrite 阶段
-- 直接提交,减少延迟

五、事务最佳实践 #

5.1 事务大小控制 #

sql
-- 问题: 大事务
-- - 占用大量内存
-- - 长时间持有锁
-- - 可能超时

-- 解决: 分批提交
-- 批次1
BEGIN;
UPDATE orders SET status = 'processed' 
WHERE status = 'pending' LIMIT 1000;
COMMIT;

-- 批次2
BEGIN;
UPDATE orders SET status = 'processed' 
WHERE status = 'pending' LIMIT 1000;
COMMIT;

5.2 避免长事务 #

sql
-- 问题: 长事务
-- - 阻塞其他事务
-- - 占用资源
-- - 可能失败

-- 解决: 及时提交
BEGIN;
-- 只做必要的操作
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- 不要在事务中做耗时操作

5.3 死锁处理 #

sql
-- 死锁示例
-- 会话1
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;

-- 会话2
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 2;

-- 会话1 (等待)
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

-- 会话2 (死锁)
UPDATE accounts SET balance = balance + 100 WHERE id = 1;
-- Error: Deadlock found

-- 解决: 按相同顺序访问资源
-- 始终按 id 顺序更新

六、事务监控 #

6.1 查看运行中的事务 #

sql
-- 查看当前运行的事务
SELECT * FROM INFORMATION_SCHEMA.TIDB_TRX;

-- 查看锁等待
SELECT * FROM INFORMATION_SCHEMA.DATA_LOCK_WAITS;

-- 查看死锁信息
SELECT * FROM INFORMATION_SCHEMA.DEADLOCKS;

6.2 事务相关配置 #

sql
-- 事务超时时间
SET SESSION tidb_txn_wait_timeout = 60000;  -- 60秒

-- 语句超时时间
SET SESSION max_execution_time = 30000;  -- 30秒

-- GC Life Time (影响历史数据可见性)
SET GLOBAL tidb_gc_life_time = '10m';

七、总结 #

分布式事务要点:

模块 要点
ACID 原子性、一致性、隔离性、持久性
隔离级别 REPEATABLE READ、READ COMMITTED
事务模型 乐观事务、悲观事务
两阶段提交 Prewrite + Commit
最佳实践 控制大小、避免长事务、防止死锁

下一步,让我们学习数据复制和高可用!

最后更新:2026-03-27