MySQL事务原理与底层实现:温故知新

109 阅读18分钟

MySQL事务原理与底层实现

版本说明:本文基于MySQL 8.x版本,内容涵盖InnoDB存储引擎的事务实现机制。

一、事务的基本概念与重要性

在Java后端开发中,我们习惯通过@Transactional注解或PlatformTransactionManager编程式开启事务,但真正理解事务,必须向下穿透到数据库内核层面。事务并非某个单一技术组件,而是一套协同工作的机制体系,它代表一个逻辑工作单元,由一系列相关的数据库操作组成。这些操作作为一个不可分割的整体,要么全部成功执行,要么全部失败回滚。事务机制是保证数据一致性、完整性和可靠性的关键技术基础。

经典事务示例:银行转账操作

-- 转账事务:从账户A向账户B转账100元
START TRANSACTION;

-- 检查账户A余额是否充足
SELECT balance INTO @current_balance FROM accounts WHERE account_id = 1;
IF @current_balance >= 100 THEN
    -- 扣减账户A余额
    UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
    
    -- 增加账户B余额
    UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;
    
    -- 记录交易日志
    INSERT INTO transaction_log(from_account, to_account, amount, timestamp) 
    VALUES (1, 2, 100, NOW());
    
    COMMIT;  -- 所有操作成功,提交事务
ELSE
    ROLLBACK;  -- 余额不足,回滚事务
END IF; 

二、ACID原则的底层实现机制

核心要点:

  • 原子性:由undo log保障,支持回滚与MVCC;
  • 持久性:由redo log + WAL + checkpoint保障,双1配置是生产底线;
  • 隔离性MVCC实现高效读,保障写安全;RR非完全串行化,需警惕幻读;
  • 一致性:是ACID的终点,需应用层(@Transactional)与数据库层协同设计。

2.1 原子性(Atomicity)

原子性确保事务中的操作要么全部成功,要么全部失败,其核心实现依赖于 undo log(回滚日志)

InnoDB在执行每条DML语句前,会先将该操作的逆向信息写入undo log,再修改内存中的数据页。当事务提交时,undo log不会立即删除,而是标记为“可清理”;当事务回滚时,系统则依据undo log中的记录逆序执行恢复动作。

undo log的详细工作机制:

undo log的详细工作机制.png

说明:该流程图揭示了原子性的实现根基——无论提交或回滚,系统均有明确路径:提交时仅标记undo待清理;失败时则逆序回放undo log,确保数据回归初始状态。

以一条UPDATE语句为例,其undo log记录的结构如下:

-- 以UPDATE操作为例的undo log记录
-- 原始数据: id=1, name='张三', balance=5000

-- 执行UPDATE语句:
UPDATE accounts SET balance = 4500 WHERE id = 1;

-- 生成的undo log包含:
-- 1. 事务ID: trx_id = 1001
-- 2. 回滚指针: roll_ptr = 0x123xxx
-- 3. 旧值记录: 
--     表空间: tablespace_id = 5
--     记录位置: page_no = 123, offset = 456
--     旧数据: balance = 5000
-- 4. 回滚SQL: 
--    UPDATE accounts SET balance = 5000 
--    WHERE id = 1 AND trx_id = 1001; 

注意:undo log中的"回滚SQL"并非真实存储SQL文本,而是以物理+逻辑混合形式记录页偏移、字段偏移与旧值,确保回滚高效可靠。

生产实践:实际undo log以二进制格式存储在undo tablespace中(MySQL 8.0+ 默认独立表空间),支持purge线程异步清理。生产环境中,监控innodb_undo_tablespaces参数,避免undo log膨胀导致空间不足。可通过SHOW ENGINE INNODB STATUS查看undo log使用情况。

2.2 持久性(Durability)

持久性确保一旦事务提交,其对数据的修改就是永久性的,即使系统崩溃也不会丢失。InnoDB通过 redo log(重做日志)WAL(Write-Ahead Logging) 机制实现此目标。

WAL原则要求:任何数据页修改必须先写日志,再改内存页。redo log采用循环写入的物理日志格式,记录的是“将某页某偏移处的N字节改为XXX”,而非SQL语义,因此重放速度极快。

redo log的完整工作流程:

sequenceDiagram
    participant Client as 客户端
    participant Server as MySQL Server
    participant LogBuffer as redo log buffer<br/>(内存)
    participant LogFile as redo log file<br/>(磁盘)
    participant BufferPool as Buffer Pool<br/>(内存)
    participant DataFile as 数据文件<br/>(磁盘)
    participant Checkpointer as 后台线程<br/>(Checkpointer)

    Client->>Server: 发起事务修改请求(DML)
    activate Server

    Server->>LogBuffer: 写入 redo log(mini-transaction)
    Note right of LogBuffer: 日志先入内存 buffer

    Server->>BufferPool: 应用修改(脏页生成)
    Note right of BufferPool: 数据页变“脏”,未落盘

    Server->>Client: 事务提交成功(2PC 后)
    deactivate Server

    Note over LogBuffer,LogFile: 触发刷盘条件:<br/>• log buffer ≥ innodb_log_buffer_size/2<br/>• 事务提交(sync_binlog=1等)<br/>• 后台每秒刷一次

    LogBuffer->>LogFile: 后台线程刷 redo 日志到磁盘
    Note right of LogFile: fsync() 保证持久性

    Note over BufferPool,DataFile: Checkpoint 机制:<br/>• LSN 推进<br/>• 脏页比例过高<br/>• 脏页存活过久

    Checkpointer->>BufferPool: 扫描脏页
    Checkpointer->>DataFile: 异步刷脏页到数据文件
    Note right of DataFile: 可能早于/晚于 redo commit,<br/>崩溃恢复靠 redo 重做

    DataFile-->>Client: 数据最终持久化完成(异步)

关键配置参数详解:

-- 查看redo log相关配置
SHOW VARIABLES LIKE 'innodb_log%';

-- 重要参数说明:
-- innodb_log_file_size: 每个redo log文件大小,建议设置1-2GB
-- innodb_log_files_in_group: redo log文件数量,通常为2-4个
-- innodb_log_buffer_size: redo log缓冲区大小,建议16-64MB

-- 持久性保证配置
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
-- 参数值说明:
-- 1: 最高安全级别,每次提交都刷盘,保证零数据丢失(金融系统标准配置)
-- 2: 折中方案,日志写入OS缓存,每秒刷盘一次(大多数OLTP系统的推荐选择)
-- 0: 最高性能,每秒由后台线程刷盘一次(仅适用于可接受数据丢失的场景)

-- 生产建议:对于高并发系统,结合group commit机制(innodb_log_write_ahead_size)优化redo log写入
-- 通过SHOW ENGINE INNODB STATUS监控redo log等待时间
-- MySQL 8.0+支持redo log加密,可通过innodb_redo_log_encrypt启用

checkpoint机制详解: Checkpoint是InnoDB的重要机制,用于定期将内存中的脏页刷新到磁盘,并推进redo log的清理点。

  • 模糊检查点(Fuzzy Checkpoint):渐进式刷盘,只确保某个LSN之前的修改已持久化,是正常运行时的默认方式
  • Sharp检查点:确保所有脏页都刷盘,用于关闭实例、切换redo log文件等场景,会阻塞用户操作
  • 检查点算法:基于LRU列表和Flush列表进行智能刷盘,由Page Cleaner线程执行
  • 监控指标:通过SHOW ENGINE INNODB STATUS中的Log sequence numberLast checkpoint at差值评估checkpoint压力

2.3 隔离性(Isolation)

隔离性通过锁机制多版本并发控制(MVCC) 共同实现,确保并发事务之间相互隔离。

锁机制详细分类:

InnoDB锁体系.png

说明:InnoDB主要依赖行级锁实现高并发,页面锁在InnoDB中罕见。AUTO-INC锁用于自增列,MDL(元数据锁)在DDL操作中常见,是DDL与DML冲突的常见原因。

其中,行级锁是InnoDB高并发能力的核心。其加锁行为高度依赖查询条件与索引设计。例如,对主键等值查询:

-- 基于主键的等值查询,加记录锁

SELECT * FROM users WHERE id = 1 FOR UPDATE; 

仅锁定id=1这一行;但对非唯一索引范围查询:

-- 非唯一索引范围查询,加临键锁(记录锁 + 间隙锁)

SELECT * FROM orders WHERE amount > 100 FOR UPDATE; 

不仅锁定现有满足条件的记录,还锁定其右侧间隙(如(100, +∞)),防止其他事务插入新记录——即防止幻读。

MVCC与锁的协同关系明确:

  • 快照读(如普通SELECT):优先使用MVCC,读取历史版本,无锁,依赖undo log构建版本链;
  • 当前读(如SELECT ... FOR UPDATEUPDATEDELETE):读取最新已提交版本,并加锁,确保写操作的独占性。
  • 写操作:必须加锁,确保数据修改的独占性
  • 冲突检测:通过锁机制防止写-写冲突
  • 版本管理:通过undo log维护多版本数据

注意:InnoDB的乐观锁(如基于版本号的CAS操作)是应用层实现,非数据库内核机制。

意向锁作用示例:

-- 事务1:加行级X锁,自动在表上加IX锁
SELECT * FROM users WHERE id=1 FOR UPDATE;

-- 事务2:尝试加表级S锁
LOCK TABLES users READ;
-- 由于检测到表上存在IX锁,直接判断冲突,无需逐行检查
-- 大幅提升加锁效率

这种设计使InnoDB在保证强一致性的同时,最大化读吞吐能力。

2.4 一致性(Consistency)

一致性是ACID的最终目标,需由原子性、持久性、隔离性共同保障,并依赖数据库的完整性约束体系。

完整性约束包括:

  • 实体完整性:主键唯一且非空;
  • 参照完整性:外键关联一致性;
  • 域完整性:数据类型、非空、检查约束;
  • 用户定义完整性:通过触发器、存储过程实现业务规则。

一致性保障存在三个层次:

  • 语句级:单条SQL执行具有原子性;
  • 事务级:整个事务提交/回滚具有原子性;
  • 系统级:崩溃恢复后数据库恢复至一致状态。

在Java应用中,@Transactional注解本身只保障事务级一致性,若业务规则涉及跨库、跨服务,则需引入分布式事务方案保障全局一致性:

  • XA协议:MySQL原生支持,但性能较差,适合对一致性要求极高的场景
  • Seata AT/TCC模式:适合微服务架构下的分布式事务
  • Saga模式:适合长事务场景,通过补偿机制实现最终一致性
  • 消息队列:如RocketMQ事务消息,实现最终一致性,避免强一致性开销

生产建议:在微服务架构中,优先使用Saga模式或消息队列实现最终一致性,避免分布式强一致性带来的性能开销和可用性降低。

三、事务隔离级别与并发问题深度分析

3.1 四种隔离级别的技术对比

隔离级别脏读不可重复读幻读实现机制
读未提交无锁读取最新数据
读已提交(RC)MVCC+行锁,语句级快照
可重复读(RR)⚠️ 部分避免MVCC+间隙锁,事务级快照
串行化表级锁,完全串行化

符号说明:✅表示避免该问题,❌表示可能发生,⚠️表示部分避免。

重要提示

  • RR级别通过Next-Key Lock避免多数幻读,但对纯快照读(如无FOR UPDATE的范围查询)仍可能发生
  • 串行化级别性能最低,仅用于极端一致性需求场景
  • MySQL默认RR级别,与Oracle(默认RC)不同,需特别注意
  • 使用INSERT ... ON DUPLICATE KEY UPDATE时仍可能出现幻读,需显式通过SELECT ... FOR UPDATE加锁

3.2 并发问题技术详解

脏读(Dirty Read)

脏读发生在一个事务读取了另一个未提交事务修改的数据。

技术场景再现:

-- 时间点T1:事务A开始并修改数据
START TRANSACTION;
UPDATE products SET stock = stock - 10 WHERE id = 1;
-- 此时stock从100变为90,但未提交

-- 时间点T2:事务B读取数据
START TRANSACTION;
SELECT stock FROM products WHERE id = 1; 
-- 事务B读取到90(脏数据)

-- 时间点T3:事务A回滚
ROLLBACK;
-- stock恢复为100,但事务B已基于90进行了业务处理

-- 时间点T4:事务B提交
COMMIT;
-- 此时事务B使用了不存在的数据90 

注意:将隔离级别提升至RC或者更高可以解决。

不可重复读(Non-repeatable Read)

在同一事务中,多次读取同一数据返回不同结果。

技术场景深度分析:

-- 事务A:银行余额验证事务
START TRANSACTION;

-- 第一次查询:检查余额
SELECT balance INTO @balance1 FROM accounts WHERE id = 1;
-- 返回1000元,事务A基于此进行业务逻辑判断

-- 此时事务B执行并提交:
START TRANSACTION;
UPDATE accounts SET balance = balance - 200 WHERE id = 1;
COMMIT;
-- 余额变为800元

-- 事务A第二次查询:验证余额一致性
SELECT balance INTO @balance2 FROM accounts WHERE id = 1;
-- 返回800元,与第一次查询结果不一致! 

注意:RC级别下此现象正常;RR级别则通过事务级ReadView避免——首次查询即生成快照,后续查询复用,结果一致。

幻读(Phantom Read)

在同一事务中,多次查询返回不同的记录集合。

技术场景深度解析:

-- 事务A:生成月度销售报告
START TRANSACTION;

-- 第一次查询:获取当前订单数量
SELECT COUNT(*) INTO @order_count FROM orders 
WHERE order_date >= '2025-01-01' AND order_date < '2025-02-01';
-- 返回500个订单

-- 此时事务B插入新订单并提交:
START TRANSACTION;
INSERT INTO orders(user_id, amount, order_date) 
VALUES (1001, 500.00, '2025-01-15');
COMMIT;

-- 事务A第二次查询:验证数据一致性
SELECT COUNT(*) INTO @order_count2 FROM orders 
WHERE order_date >= '2025-01-01' AND order_date < '2025-02-01';
-- 返回501个订单,出现幻行! 

**注意:**RR级别下,对范围查询加临建锁(Next-Key Lock)可以防止幻读

-- 使用SELECT ... FOR UPDATE加临键锁
START TRANSACTION;
SELECT COUNT(*) FROM orders 
WHERE order_date >= '2025-01-01' AND order_date < '2025-02-01'
FOR UPDATE;
-- 此时(‘2025-01-01’, ‘2025-02-01’)区间被锁定,插入被阻塞 

四、MVCC(多版本并发控制)技术深度解析

4.1 MVCC架构核心组件

数据行物理结构:

字段名大小说明
DB_ROW_ID6字节行ID(无主键时生成)
DB_TRX_ID6字节最近修改事务ID
DB_ROLL_PTR7字节回滚指针指向undo log
用户数据可变实际存储的用户数据

当一行被多次修改时,各版本通过DB_ROLL_PTR形成版本链,并由undo log保存旧值:

graph LR
    A[当前版本] --> B[旧版本1]
    B --> C[旧版本2]
    C --> D[旧版本N]
    D --> E[初始版本]
    
    A -->|DB_ROLL_PTR| F[undo log记录1]
    B -->|DB_ROLL_PTR| G[undo log记录2]
    C -->|DB_ROLL_PTR| H[undo log记录N] 

该结构使InnoDB能为不同事务提供各自时间点的数据快照,实现无锁读。

Purge机制:旧版本数据通过background purge线程异步清理。可通过SHOW ENGINE INNODB STATUS中的History list length监控undo版本链长度,数值过大(如>1000000)表明存在长事务或purge延迟,会导致查询性能下降。

4.2 ReadView机制核心技术

执行快照读时,InnoDB生成一个ReadView结构,作为可见性判断依据:

struct ReadView {
    trx_id_t    low_limit_id;    // 高水位:大于等于此值的事务不可见
    trx_id_t    up_limit_id;     // 低水位:小于此值的事务可见
    trx_id_t    creator_trx_id;  // 创建ReadView的事务ID
    ids_t       ids;             // 活跃事务ID列表
    // 其他管理字段...
}; 

可见性判断算法详细流程:

数据版本可见性.png

具体判断示例:

-- 假设当前ReadView:
-- low_limit_id = 150, up_limit_id = 100, 
-- active_trx_ids = [110, 120, 130], creator_trx_id = 140

-- 判断不同事务ID的可见性:
-- trx_id = 90:  < up_limit_id(100) → 可见
-- trx_id = 110: 在活跃列表中 → 不可见 
-- trx_id = 125: 不在活跃列表,但 < low_limit_id → 可见
-- trx_id = 160: >= low_limit_id → 不可见 

4.3 不同隔离级别的ReadView管理

READ COMMITTED下,每次SELECT语句都创建新ReadView

SELECT * FROM table WHERE ... ;  -- 创建ReadView1
-- 其他事务提交修改...
SELECT * FROM table WHERE ... ;  -- 创建ReadView2,可能看到新数据 

因此可能出现不可重复读。

REPEATABLE READ下,事务中首次SELECT创建ReadView,后续查询复用

START TRANSACTION;
SELECT * FROM table WHERE ... ;  -- 创建ReadView
-- 其他事务提交修改...
SELECT * FROM table WHERE ... ;  -- 复用ReadView,结果不变 

从而实现可重复读,这是MySQL默认级别,与Oracle等数据库不同,需特别注意。

五、InnoDB锁机制全面深入解析

5.1 锁兼容性矩阵

锁的兼容性决定了并发能力。核心四种锁的兼容关系如下:

请求锁类型 \ 当前持有锁共享锁(S)排他锁(X)意向共享锁(IS)意向排他锁(IX)说明
共享锁(S)✅ 兼容❌ 冲突✅ 兼容❌ 冲突读-读兼容,读-写冲突
排他锁(X)❌ 冲突❌ 冲突❌ 冲突❌ 冲突写操作完全独占
意向共享锁(IS)✅ 兼容❌ 冲突✅ 兼容✅ 兼容表级意向锁
意向排他锁(IX)❌ 冲突❌ 冲突✅ 兼容✅ 兼容表级意向锁

意向锁是表级锁,用于快速判断表中是否存在行锁,避免逐行检查,提升加锁效率。

其他重要锁类型:

  • AUTO-INC锁:用于自增列,MySQL 8.0默认使用轻量级互斥模式(innodb_autoinc_lock_mode=2
  • MDL(Metadata Lock):DDL操作时加的元数据锁,是DDL阻塞DML的常见原因,可通过performance_schema.metadata_locks监控

5.2 行级锁技术深度解析

记录锁(Record Lock)

基于主键或唯一索引的等值查询,退化为记录锁:

-- 基于主键或唯一索引的等值查询
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 加锁过程:
-- 1. 在id=1的索引记录上加X锁
-- 2. 如果存在二级索引,在相应索引项也加锁
-- 3. 锁住具体的数据行 
间隙锁(Gap Lock)

防止幻读的关键;非唯一索引等值查询或范围查询会触发

-- 防止幻读的关键技术
SELECT * FROM orders WHERE amount BETWEEN 100 AND 200 FOR UPDATE;
-- 加锁范围:
-- 1. 锁定amount在(100, 200)开区间内的所有间隙
-- 2. 防止其他事务插入amount在此范围内的新记录
-- 3. 允许更新区间外的现有记录 

间隙锁的特殊情况:

  • 唯一索引等值查询:退化为记录锁,不加间隙锁
  • 非唯一索引等值查询:加间隙锁,防止幻读
  • 范围查询:加临键锁(Next-Key Lock)
临键锁(Next-Key Lock)

记录锁 + 间隙锁的组合,默认用于RR级别下的范围查询:

-- 记录锁 + 间隙锁的组合
SELECT * FROM products WHERE price > 100 FOR UPDATE;
-- 加锁效果:
-- 1. 对price>100的所有现有记录加记录锁
-- 2. 对price>100的范围加间隙锁
-- 3. 综合防止幻读和保证当前读的一致性 

5.3 死锁检测与处理机制

InnoDB使用wait-for graph(等待图)算法检测死锁,时间复杂度O(n),高并发时可能有一定开销。

MySQL死锁检测与处理流程图.png

死锁避免:

-- 1. 统一数据访问顺序(如始终先操作user表,再order表)
-- 2. 使用较低隔离级别降低锁强度
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 3. 设置锁等待超时,避免无限等待
SET SESSION innodb_lock_wait_timeout = 30;

-- 4. 避免长事务(不在@Transactional方法中调用HTTP、复杂计算)

-- 5. 死锁监控与排查
-- 使用SHOW ENGINE INNODB STATUS查看最近死锁日志
SHOW ENGINE INNODB STATUS\G
-- 或使用Percona Toolkit的pt-deadlock-logger进行持续监控

-- 6. 高并发场景可考虑禁用死锁检测(不推荐)
-- SET GLOBAL innodb_deadlock_detect = OFF;
-- 禁用后需依赖innodb_lock_wait_timeout超时机制

六、事务日志系统协同工作机制

6.1 三日志协同工作原理

一个事务的完整生命周期涉及undo logredo logbinlog三者协同:

MySQL事务处理流程图.png

关键日志协同时序:

  1. undo log先行:在数据修改前记录旧值
  2. redo log prepare:事务操作过程中记录重做日志
  3. binlog写入:事务提交时写入二进制日志
  4. redo log commit:确认事务提交完成
  5. 异步刷盘:由后台线程负责数据页持久化

6.2 undo log详细技术实现

undo log记录结构:

struct undo_log_record {
    uint64 trx_id;          // 事务ID
    uint64 roll_ptr;        // 回滚指针
    uint16 type;            // 操作类型:INSERT/UPDATE/DELETE
    uint64 table_id;        // 表ID
    byte[] old_data;        // 旧数据
    byte[] new_data;        // 新数据(UPDATE时)
    // 其他管理字段...
}; 

undo log类型分类:

  • INSERT undo log:INSERT操作的逆操作,事务提交后可立即删除
  • UPDATE undo log:UPDATE/DELETE操作的逆操作,需要支持MVCC,事务提交后不能立即删除

6.3 redo log两阶段提交技术细节

两阶段提交协议:

sequenceDiagram
    participant T as 事务管理器
    participant R as redo log
    participant B as binlog
    participant S as 存储引擎
    
    T->>R: 1. redo log prepare
    R-->>T: prepare完成
    T->>B: 2. 写binlog
    B-->>T: binlog写入成功
    T->>R: 3. redo log commit
    R-->>T: commit完成
    T->>S: 4. 事务提交确认 

崩溃恢复处理:

  • 情况1:只有redo log prepare,binlog未写 → 回滚事务
  • 情况2:有redo log prepare,binlog已写 → 提交事务
  • 情况3:redo log commit完成 → 事务已提交,无需处理

该机制确保:主库崩溃后恢复的数据状态,与从库通过binlog重放的结果完全一致

高可用配置(双1配置):在MySQL主从复制中,启用sync_binlog=1innodb_flush_log_at_trx_commit=1是高可用底线,确保主从数据一致性。

日志类型区别

  • binlog:逻辑日志,记录SQL语句或行变更,用于主从复制和数据恢复
  • redo log:物理日志,记录数据页的物理修改,用于崩溃恢复

七、完整事务执行流程技术深度解析

7.1 复杂事务示例:订单创建

-- 开始分布式事务
START TRANSACTION;

-- 阶段1:库存检查与预留
SELECT stock INTO @current_stock FROM inventory WHERE product_id = 1001;
IF @current_stock >= 5 THEN
    UPDATE inventory SET stock = stock - 5, reserved = reserved + 5 
    WHERE product_id = 1001;
ELSE
    ROLLBACK;
    SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '库存不足';
END IF;

-- 阶段2:创建订单主记录
INSERT INTO orders (user_id, total_amount, status) 
VALUES (123, 500.00, 'pending');
SET @order_id = 5932453;

-- 阶段3:创建订单明细
INSERT INTO order_items (order_id, product_id, quantity, price) 
VALUES (@order_id, 1001, 5, 100.00);

-- 阶段4:扣减用户余额
UPDATE accounts SET balance = balance - 500 WHERE user_id = 123;

-- 阶段5:记录审计日志
INSERT INTO audit_log (action, user_id, details) 
VALUES ('create_order', 123, CONCAT('创建订单:', @order_id));

-- 提交事务
COMMIT; 

技术要点:

  • 所有操作在一个事务内,保证原子性;
  • 每步业务校验(如库存检查)前置,避免无效操作;
  • 日志记录放在最后,减少锁持有时间。

7.2 事务回滚的完整流程

当异常发生时,回滚流程如下:

sequenceDiagram
    participant C as 客户端
    participant SE as SQL执行器
    participant TM as 事务管理器
    participant UL as Undo日志管理器
    participant LM as 锁管理器
    participant BP as Buffer Pool
    participant RL as Redo日志

    C->>SE: 执行DML语句
    SE->>TM: 检查事务状态
    TM-->>SE: 事务正常
    
    SE->>SE: 执行遇到异常
    alt 违反约束
        SE->>SE: 数据约束检查失败
    else 死锁检测
        SE->>LM: 检测到死锁
        LM-->>SE: 选择牺牲者
    else 超时
        SE->>TM: 事务超时检测
    end
    
    SE->>TM: 报告异常,请求回滚
    TM->>TM: 设置事务状态为ROLLBACK
    TM->>UL: 开始回滚流程
    
    Note over UL,TM: 阶段1:逆向回滚数据操作
    UL->>UL: 获取undo log链
    loop 针对每个undo log记录
        UL->>UL: 读取undo记录类型和内容
        alt type = UPDATE
            UL->>BP: 应用更新回滚
            BP->>BP: 恢复旧数据版本
        else type = INSERT
            UL->>BP: 删除新插入记录
            BP->>BP: 移除数据页记录
        else type = DELETE
            UL->>BP: 重新插入被删记录
            BP->>BP: 恢复完整数据行
        end
        UL->>UL: 标记undo记录为已回滚
    end
    
    Note over TM,LM: 阶段2:释放资源
    TM->>LM: 请求释放所有锁
    LM->>LM: 释放行级锁
    LM->>LM: 释放表级锁
    LM-->>TM: 锁释放完成
    
    TM->>TM: 清理事务上下文
    TM->>RL: 写入回滚记录
    
    Note over TM,C: 阶段3:返回结果
    TM->>SE: 回滚完成
    SE-->>C: 返回错误和回滚结果
    Note right of C: 收到错误信息:<br/>ERROR/ROLLBACK COMPLETE

回滚是逆序执行undo log中记录的逆操作,每步恢复一个历史版本,最终使数据回归事务开始前状态。

八、总结

又是没有大厂约面日子😣😣😣,小编还在找实习的路上,这篇文章是我的笔记汇总整理。

参考文献