news 2026/6/2 1:08:27

探秘MySQL MVCC机制如何基于Undo Log版本链实现Redis持久化RDB与AOF原理解析事务隔离级别

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
探秘MySQL MVCC机制如何基于Undo Log版本链实现Redis持久化RDB与AOF原理解析事务隔离级别

探秘MySQL MVCC机制如何基于Undo Log版本链实现Redis持久化RDB与AOF原理解析事务隔离级别

一、MySQL MVCC机制概述

1.1 MVCC机制的定义

MVCC(Multi-Version Concurrency Control,多版本并发控制)是MySQL InnoDB存储引擎实现事务隔离级别的核心技术。它通过维护数据的多个版本,实现读操作不阻塞写操作、写操作不阻塞读操作的并发控制机制。

1.2 MVCC机制的价值

  • 并发性能:读写不互斥,大幅提升并发吞吐量
  • 隔离级别:实现READ COMMITTED和REPEATABLE READ隔离级别
  • 一致性读:提供快照读能力,保证一致性非锁定读
  • 版本管理:通过Undo Log管理数据历史版本
  • 事务回滚:支持事务回滚到任意保存点
  • 无锁读操作:普通SELECT不需要加锁

1.3 MVCC机制的特点

  • 多版本:数据存在多个历史版本
  • 快照读:基于快照的一致性读
  • 读写不冲突:读操作无需等待写操作释放锁
  • 版本链管理:通过Undo Log版本链管理历史数据
  • 垃圾回收:定期清理不再需要的旧版本

二、MVCC核心组件架构

2.1 MVCC架构图

flowchart TD subgraph 事务管理系统 A[事务ID分配器] --> B[事务ID: 100, 101, 102...] C[Read View管理器] --> D[当前活跃事务列表] end subgraph 数据行结构 E[数据行] --> F[DB_TRX_ID] E --> G[DB_ROLL_PTR] E --> H[DB_ROW_ID] end subgraph Undo Log G --> I[Undo Log版本链] I --> J[版本1 - trx_id:100] I --> K[版本2 - trx_id:101] I --> L[版本3 - trx_id:102] end subgraph 事务隔离 M[READ UNCOMMITTED] --> N[读最新版本] O[READ COMMITTED] --> P[每次查询重建Read View] Q[REPEATABLE READ] --> R[事务开始时创建Read View] S[SERIALIZABLE] --> T[所有读都加锁] end B --> F C --> P C --> R

2.2 数据行隐藏字段

隐藏字段大小含义用途
DB_TRX_ID6字节最近修改该行的事务ID判断数据版本可见性
DB_ROLL_PTR7字节回滚指针,指向Undo Log构建版本链
DB_ROW_ID6字节行ID(无主键时使用)内部行标识

2.3 Read View结构

// Read View核心数据结构 struct read_view_t { ulint m_low_limit_id; // 创建Read View时的最大事务ID+1 ulint m_up_limit_id; // 活跃事务列表中的最小事务ID ib_id_t* m_indices; // 活跃事务ID列表 ulint n_vals; // 活跃事务数量 bool m_creator_trx_id; // 创建当前Read View的事务ID };

三、Undo Log版本链机制

3.1 Undo Log架构

flowchart LR subgraph 回滚段 A[Rollback Segment] --> B[Undo Log Slot 1] A --> C[Undo Log Slot 2] A --> D[Undo Log Slot N] end subgraph 版本链 B --> E[Version 1 ← trx_id:100] E --> F[Version 2 ← trx_id:101] F --> G[Version 3 ← trx_id:102] G --> H[Current Version] end subgraph 存储 I[Insert Undo] --> J[insert操作的回滚信息] K[Update Undo] --> L[update/delete操作的回滚信息] end B --> I C --> K

3.2 Undo Log类型

Undo类型触发操作内容清理时机
INSERT UndoINSERT插入行的主键事务提交后立即清理
UPDATE UndoUPDATE被更新列的旧值所有快照不再需要时
DELETE UndoDELETE被删除行的完整数据所有快照不再需要时

3.3 版本链工作原理(C++伪代码)

// 版本链核心实现 class MVCCEngine { public: /** * 根据Read View判断数据行可见性 * @param row 数据行 * @param read_view 当前读视角 * @return true表示可见 */ bool is_row_visible(const row_t& row, const read_view_t& read_view) { trx_id_t trx_id = row.get_trx_id(); // 1. 当前事务自己修改的行,总是可见 if (trx_id == read_view.m_creator_trx_id) { return true; } // 2. 事务ID大于m_low_limit_id,不可见 if (trx_id >= read_view.m_low_limit_id) { return false; } // 3. 事务ID小于m_up_limit_id,且不在活跃列表,可见 if (trx_id < read_view.m_up_limit_id) { return true; } // 4. 检查是否在活跃事务列表中 for (ulint i = 0; i < read_view.n_vals; i++) { if (trx_id == read_view.m_indices[i]) { // 在活跃列表中,不可见 return false; } } // 5. 不在活跃列表,可见 return true; } /** * 通过Undo Log版本链查找可见版本 * @param row 当前行数据 * @param read_view 读视角 * @return 可见版本的数据 */ row_t find_visible_version(const row_t& row, const read_view_t& read_view) { const row_t* current = &row; // 沿着版本链回溯查找 while (current != nullptr) { if (is_row_visible(*current, read_view)) { return *current; } // 通过DB_ROLL_PTR回溯到上一个版本 current = current->get_roll_pointer(); } // 没有可见版本,返回空 return row_t::empty(); } /** * 更新操作:创建新版本 */ void update(row_t& row, trx_id_t trx_id) { // 1. 记录旧版本到Undo Log undo_log_t* undo_log = create_undo_log(row); // 2. 设置行的DB_ROLL_PTR指向Undo Log row.set_roll_pointer(undo_log); // 3. 更新DB_TRX_ID为当前事务ID row.set_trx_id(trx_id); // 4. 更新数据内容 // ... } };

3.4 Undo Log版本链实战示例

from dataclasses import dataclass from typing import Optional, List from enum import Enum class IsolationLevel(Enum): READ_UNCOMMITTED = "READ UNCOMMITTED" READ_COMMITTED = "READ COMMITTED" REPEATABLE_READ = "REPEATABLE READ" SERIALIZABLE = "SERIALIZABLE" @dataclass class UndoLog: """Undo Log条目""" trx_id: int old_data: dict prev_undo: Optional['UndoLog'] = None @dataclass class RowData: """数据行""" data: dict trx_id: int roll_pointer: Optional[UndoLog] = None class ReadView: """读视角""" def __init__(self, creator_trx_id: int, active_trx_ids: List[int]): self.creator_trx_id = creator_trx_id self.active_trx_ids = set(active_trx_ids) self.low_limit_id = max(active_trx_ids, default=0) + 1 self.up_limit_id = min(active_trx_ids, default=0) def is_row_visible(self, row: RowData) -> bool: """判断数据行是否可见""" trx_id = row.trx_id # 1. 当前事务修改的,可见 if trx_id == self.creator_trx_id: return True # 2. 事务ID超过上限,不可见 if trx_id >= self.low_limit_id: return False # 3. 事务ID小于下限,且不在活跃列表,可见 if trx_id < self.up_limit_id: return True # 4. 在活跃事务列表中,不可见 if trx_id in self.active_trx_ids: return False # 5. 不在活跃列表,可见 return True def find_visible_version(self, row: RowData) -> Optional[dict]: """查找可见版本的数据""" current = row while current is not None: if self.is_row_visible(current): return current.data # 回溯版本链 if current.roll_pointer: current_data = current.roll_pointer.old_data # 构造上一行版本 current = RowData( data=current_data, trx_id=current.roll_pointer.trx_id, roll_pointer=current.roll_pointer.prev_undo ) else: break return None class MVCCEngine: """MVCC引擎模拟""" def __init__(self): self.global_trx_id = 100 self.active_trxs: List[int] = [] self.tables: dict = {} def begin_transaction(self) -> int: """开始事务""" trx_id = self.global_trx_id self.global_trx_id += 1 self.active_trxs.append(trx_id) return trx_id def commit(self, trx_id: int): """提交事务""" self.active_trxs.remove(trx_id) def rollback(self, trx_id: int): """回滚事务""" self.active_trxs.remove(trx_id) # 通过Undo Log回滚 # ... def create_read_view(self, trx_id: int) -> ReadView: """创建读视角""" return ReadView(trx_id, self.active_trxs[:]) def update(self, table: str, key: str, new_data: dict, trx_id: int): """更新操作""" if table not in self.tables: self.tables[table] = {} if key in self.tables[table]: old_row = self.tables[table][key] # 创建Undo Log undo = UndoLog( trx_id=old_row.trx_id, old_data=old_row.data.copy(), prev_undo=old_row.roll_pointer ) else: undo = None # 创建新版本 self.tables[table][key] = RowData( data=new_data, trx_id=trx_id, roll_pointer=undo ) def select(self, table: str, key: str, read_view: ReadView) -> Optional[dict]: """查询操作""" if table not in self.tables or key not in self.tables[table]: return None row = self.tables[table][key] return read_view.find_visible_version(row) # 使用示例演示不同隔离级别的行为 def demo_mvcc_isolation(): engine = MVCCEngine() # 事务A:插入一行数据 trx_a = engine.begin_transaction() engine.update('users', '1', {'name': 'Alice', 'age': 25}, trx_a) engine.commit(trx_a) # 事务B:读取快照 trx_b = engine.begin_transaction() snapshot_b = engine.create_read_view(trx_b) # 事务C:修改数据 trx_c = engine.begin_transaction() engine.update('users', '1', {'name': 'Alice', 'age': 30}, trx_c) # 事务B读取(RR隔离级别:使用事务开始时的快照) result = engine.select('users', '1', snapshot_b) print(f"事务B快照读: {result['age']}") # 输出: 25(旧版本) # 事务C提交 engine.commit(trx_c) # 事务B再读(RR隔离级别:仍然使用旧的快照) result = engine.select('users', '1', snapshot_b) print(f"事务B再次快照读: {result['age']}") # 输出: 25(同一快照) # RC隔离级别:每次查询重建快照 snapshot_b_rc = engine.create_read_view(trx_b) result = engine.select('users', '1', snapshot_b_rc) print(f"事务B RC模式读: {result['age']}") # 输出: 30(最新已提交) engine.commit(trx_b)

四、Redis持久化RDB与AOF原理解析

4.1 Redis持久化架构图

flowchart TD subgraph Redis Server A[内存数据] --> B[RDB持久化] A --> C[AOF持久化] A --> D[混合持久化] end subgraph RDB B --> E[bgsave/save] E --> F[创建子进程] F --> G[fork写时复制] G --> H[写入临时RDB文件] H --> I[原子替换RDB文件] end subgraph AOF C --> J[写命令追加到aof_buf] J --> K[AOF重写] K --> L[合并冗余命令] K --> M[减少文件大小] J --> N[fsync刷盘] N --> O[恢复时重放命令] end subgraph 混合持久化 D --> P[RDB全量快照] D --> Q[AOF增量日志] P --> R[快速加载] Q --> S[增量恢复] end

4.2 RDB持久化原理

// RDB持久化核心逻辑(简化伪代码) /** * RDB保存格式 * +------------------+------------------+------------------+ * | REDIS (5字节) | RDB_VERSION (4) | AUX_FIELDS | * +------------------+------------------+------------------+ * | DATABASES | DATABASE_N | KEY_VALUE_PAIRS | * +------------------+------------------+------------------+ * | KEY_VALUE_TYPE | KEY | VALUE | * +------------------+------------------+------------------+ * | CHECKSUM (8字节) | EOF (1字节) | | * +------------------+------------------+------------------+ */ void perform_bgsave(redis_db* db) { pid_t child_pid = fork(); if (child_pid == 0) { // 子进程 // 利用fork的写时复制(Copy-on-Write)机制 // 子进程拥有完整的内存快照 FILE* temp_file = tmpfile(); // 写入RDB文件头 write_rdb_header(temp_file); // 写入所有数据库 for_each(db, { write_database_header(temp_file, db->id); for_each(db->keys, { write_key_value(temp_file, key, value); }); }); // 写入校验和 write_checksum(temp_file); // 原子替换 rename(temp_file, "dump.rdb"); exit(0); } else { // 父进程继续服务 // 记录bgsave开始时间 } } // 写时复制(COW)机制 // 当父进程或子进程修改内存页时,操作系统会复制该页 // 这样可以保证子进程看到的是fork时刻的内存快照

4.3 AOF持久化原理

/** * AOF持久化工作流程 * * 1. 写命令执行后,追加到aof_buf缓冲区 * 2. 根据appendfsync策略决定何时刷盘 * 3. AOF文件过大时触发AOF重写 */ // AOF文件格式 // *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n // *2\r\n$3\r\nGET\r\n$3\r\nkey\r\n // 使用Redis序列化协议(RESP)格式 enum append_fsync { APPENDFSYNC_ALWAYS, // 每次写入都fsync(最安全,最慢) APPENDFSYNC_EVERYSEC, // 每秒fsync一次(平衡) APPENDFSYNC_NO // 由操作系统决定(最快,最不安全) }; void feed_append_only_file(redis_client* c, robj* cmd) { // 1. 将命令序列化为RESP格式 sds buf = cat_append_redis_command(cmd); // 2. 追加到AOF缓冲区 server.aof_buf = sdscat(server.aof_buf, buf); // 3. 根据策略刷盘 if (server.aof_fsync == APPENDFSYNC_ALWAYS) { // 同步写盘 flushAppendOnlyFile(1); } } void flushAppendOnlyFile(int force) { // 将aof_buf写入AOF文件 write(server.aof_fd, server.aof_buf, sdslen(server.aof_buf)); if (server.aof_fsync == APPENDFSYNC_ALWAYS) { // 立即刷盘 fsync(server.aof_fd); } else if (server.aof_fsync == APPENDFSYNC_EVERYSEC && force) { // 每秒刷盘,由后台线程执行 if (server.aof_last_fsync < time(NULL)) { fsync(server.aof_fd); server.aof_last_fsync = time(NULL); } } }

4.4 AOF重写机制

class AOFRewrite: """AOF重写器""" def __init__(self, redis): self.redis = redis def rewrite(self): """ AOF重写核心逻辑 1. 读取当前内存中的键值对 2. 生成最小化的命令序列 3. 将新命令写入临时文件 4. 原子替换旧AOF文件 """ # 创建子进程 pid = os.fork() if pid == 0: # 子进程 temp_fd = self._create_temp_aof_file() # 遍历所有数据库 for db_id in range(16): db = self.redis.databases[db_id] # 为每个非空数据库生成SELECT命令 if db.keys(): temp_fd.write(f"SELECT {db_id}\n") for key, value in db.items(): # 根据数据类型生成最小化命令 if isinstance(value, str): temp_fd.write(f"SET {key} {value}\n") elif isinstance(value, list): # List: 生成RPUSH命令 for item in value: temp_fd.write(f"RPUSH {key} {item}\n") elif isinstance(value, set): # Set: 生成SADD命令 for item in value: temp_fd.write(f"SADD {key} {item}\n") elif isinstance(value, dict): # Hash: 生成HMSET命令 for field, val in value.items(): temp_fd.write(f"HSET {key} {field} {val}\n") elif isinstance(value, zset): # Sorted Set: 生成ZADD命令 for member, score in value.items(): temp_fd.write(f"ZADD {key} {score} {member}\n") # 追加重写期间的增量命令 self._append_incremental_commands(temp_fd) # 原子替换旧文件 temp_fd.close() os.rename(temp_fd.name, server.aof_filename) exit(0) # 父进程记录重写期间的增量命令到缓冲区 self.redis.aof_rewrite_buffer = [] def _append_incremental_commands(self, temp_fd): """追加重写期间的增量命令""" for cmd in self.redis.aof_rewrite_buffer: temp_fd.write(cmd + "\n")

4.5 RDB与AOF对比

维度RDBAOF
数据完整性可能丢失最后一次快照后的数据最高可配置为不丢失数据
恢复速度快(加载二进制快照)慢(重放命令)
文件大小小(二进制压缩)大(文本命令日志)
性能影响fork子进程,COW写入追加,影响较小
可读性二进制,不可读文本格式,可读可编辑
重写机制自动触发自动触发(AOF重写)
适用场景可接受分钟级数据丢失对数据完整性要求高的场景
推荐策略混合持久化:RDB快照 + AOF增量

五、MVCC与Redis持久化的协同

5.1 基于Undo Log的版本链与Redis持久化的协同

class DatabaseCacheSync: """数据库-缓存同步方案""" def __init__(self, mvcc_engine, redis_client): self.mvcc = mvcc_engine self.redis = redis_client self.change_log = [] def update_and_cache(self, table: str, key: str, new_data: dict, trx_id: int): """ 更新数据并同步缓存 利用MVCC版本链保证一致性 """ # 1. 更新数据库(MVCC创建新版本) self.mvcc.update(table, key, new_data, trx_id) # 2. 记录变更日志 change = { 'table': table, 'key': key, 'trx_id': trx_id, 'timestamp': time.time() } self.change_log.append(change) # 3. 更新Redis缓存 cache_key = f"{table}:{key}" self.redis.setex(cache_key, 3600, json.dumps(new_data)) # 4. 记录AOF变更(用于Redis持久化) self.redis.aof_append(f"SET {cache_key} {json.dumps(new_data)}") def read_with_cache(self, table: str, key: str, read_view: ReadView) -> Optional[dict]: """ 带缓存的读取 利用MVCC可见性判断 """ cache_key = f"{table}:{key}" # 1. 尝试从缓存读取 cached = self.redis.get(cache_key) if cached: return json.loads(cached) # 2. 缓存未命中,从数据库读取 data = self.mvcc.select(table, key, read_view) if data: # 3. 写入缓存(防止缓存穿透) trx_id = read_view.creator_trx_id self.redis.setex(cache_key, 3600, json.dumps(data)) return data

六、隔离级别实战分析

6.1 四种隔离级别对比

隔离级别脏读不可重复读幻读MVCC实现方式
READ UNCOMMITTED可能可能可能不创建快照,直接读最新
READ COMMITTED不可能可能可能每条语句创建新Read View
REPEATABLE READ不可能不可能可能(MVCC避免)事务开始时创建Read View
SERIALIZABLE不可能不可能不可能所有读加锁

6.2 RR隔离级别下避免幻读的MVCC机制

-- 事务A BEGIN; -- RR隔离级别,创建Read View SELECT * FROM orders WHERE amount > 100; -- 结果:0行(起点快照) -- 事务B BEGIN; INSERT INTO orders (id, amount) VALUES (1, 200); COMMIT; -- 事务A再次查询 SELECT * FROM orders WHERE amount > 100; -- 结果:0行(使用同一快照,看不到事务B插入的行) -- 这就是MVCC版本链的效果 -- 但是如果事务A执行: UPDATE orders SET status = 'checked' WHERE amount > 100; -- 结果:1行受到影响(当前读,能看到最新提交的数据) -- 然后 SELECT * FROM orders WHERE amount > 100; -- 结果:1行(事务A更新后,该行DB_TRX_ID变为事务A,可见)

七、总结

MySQL MVCC机制通过Undo Log版本链实现了高效的并发控制,与Redis持久化RDB/AOF原理有着不同的设计哲学。

核心要点

  1. MVCC通过数据行的DB_TRX_ID、DB_ROLL_PTR和Undo Log版本链实现多版本并发控制
  2. Read View决定了哪些版本可见,RR和RC的区别在于Read View的创建时机
  3. Undo Log版本链支持事务回滚和一致性快照读
  4. Redis RDB通过fork子进程和写时复制生成全量快照
  5. Redis AOF通过追加写命令并支持重写来保证数据完整性
  6. 混合持久化结合RDB的快速恢复和AOF的数据完整性

理解MVCC和持久化机制,有助于设计高并发、高可靠的数据库系统。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/2 1:08:23

GPU 测试开发的一些概念总结

1. 常见概念1. NCCL&#xff08;NVIDIA Collective Communications Library&#xff09;做什么&#xff1a;专门优化 多 GPU / 多节点 的集体操作&#xff1a;AllReduce&#xff08;最常用&#xff0c;梯度汇总&#xff09;Broadcast、Reduce、AllGather 等特点&#xff1a;拓扑…

作者头像 李华
网站建设 2026/6/2 1:06:35

Xshell分屏实战:一边看日志一边执行命令,Linux运维效率神器这样用

Xshell分屏实战&#xff1a;高效运维的窗口管理艺术凌晨三点&#xff0c;服务器告警铃声刺破夜空——又一次线上故障紧急排查。作为运维工程师&#xff0c;你是否经历过这样的场景&#xff1a;左手忙着tail -f追踪实时日志&#xff0c;右手需要不断切换窗口执行诊断命令&#x…

作者头像 李华
网站建设 2026/6/2 1:06:11

3个实战技巧揭秘PyInstaller逆向分析:从黑盒到源码的深度解析

3个实战技巧揭秘PyInstaller逆向分析&#xff1a;从黑盒到源码的深度解析 【免费下载链接】pyinstxtractor PyInstaller Extractor 项目地址: https://gitcode.com/gh_mirrors/py/pyinstxtractor 你是否曾经面对一个由PyInstaller打包的Python可执行文件&#xff0c;想要…

作者头像 李华
网站建设 2026/6/2 0:58:21

Redis 集群方案详解:主从复制、哨兵、脑裂、分片集群和哈希槽

单节点 Redis 再快&#xff0c;也会遇到三个问题&#xff1a; 单节点并发能力有上限。单节点宕机会导致服务不可用。单节点内存有限&#xff0c;无法承载海量数据。 Redis 的集群方案就是围绕这三个问题展开的&#xff1a;主从复制解决读扩展&#xff0c;哨兵解决自动故障恢复&…

作者头像 李华
网站建设 2026/6/2 0:57:50

AI 智能体依赖管理存风险:“最新”并非“安全”,该如何应对?

AI 智能体依赖管理的风险与应对建议AI 智能体通过层层委托让工作变得更轻松&#xff0c;然而&#xff0c;这些委托层会形成依赖关系&#xff0c;而这些依赖关系又会带来风险。米切尔哈西莫托&#xff08;Mitchell Hashimoto&#xff09;建议大家停止更新依赖项&#xff0c;从历…

作者头像 李华