MySQL InnoDB 保障并发更新安全核心机制(底层原理)

MySQL InnoDB 保障并发更新安全核心机制(底层原理)

MySQL 只有 InnoDB 引擎支持事务与行级锁,MyISAM 是表锁,并发差;并发更新安全主要依靠:事务隔离级别、MVCC、行锁(Record‑Lock/Gap‑Lock/Next‑Key‑Lock)、undo‑log、redo‑log、锁等待、死锁检测、事务原子性协同实现。

一、核心概念

  1. 并发更新安全要解决 2 个问题
    • 问题 1:脏写:事务 A 修改一行,事务 B 覆盖 A 未提交的数据(最严重)。
    • 问题 2:脏读、不可重复读、幻读。 InnoDB 靠锁解决写‑写冲突;MVCC 解决读写冲突(读不加锁)。
  2. InnoDB 默认隔离级别:RR(可重复读),MySQL‑RR 级别通过 Next‑Key‑Lock 彻底解决幻读。

一、写‑写并发:靠 InnoDB 行锁体系阻止脏写(核心)

场景:多个事务同时更新同一行数据,例如update t set num = num‑1 where id = 1;

1. 锁的粒度

  1. 如果命中索引:行锁(Record Lock),只锁住匹配行,其他行可正常更新,并发高
  2. 如果没有索引:InnoDB 会退化成表锁,并发完全失效,这是开发大忌。

2. RR 级别下 3 种锁(底层核心)

  1. Record Lock(记录锁):锁定索引上的某一条实际记录
    • 只锁住命中的索引行;其他行不受影响。
  2. Gap Lock(间隙锁):锁住索引记录之间的空隙
    • 禁止其他事务在间隙插入新数据,用来防止幻读;
    • 仅在 RR 隔离级别生效;RC 级别没有间隙锁。
  3. Next‑Key Lock(临键锁)= Record‑Lock + Gap‑Lock InnoDB RR 默认使用 Next‑Key‑Lock,左开右闭区间。
    • 例:索引有 1,5,10;执行where id>5 and id<10;锁住区间(5,10],既不能修改 id=10,也不能插入 6‑9 之间数据,杜绝幻读。

上锁规则(底层执行逻辑)

  1. 更新语句执行时,InnoDB 会对扫描到的索引条目加 排他锁(X 锁)
  2. 其他事务尝试获取该行 X 锁时,会进入锁等待状态;
  3. 事务提交之后锁才释放,保证同一时刻只能一个事务修改该行,彻底避免脏写。

锁等待与死锁处理

  1. 锁等待:事务拿不到锁默认等待 50 秒(innodb_lock_wait_timeout),超时抛出锁等待超时异常;
  2. 死锁:InnoDB 底层有死锁检测线程,主动选择代价更小的事务回滚,打破死锁,不需要人工干预。

二、读写并发:MVCC(多版本并发控制)实现读不加锁

如果读取也加锁,并发会极差。InnoDB 通过 MVCC 做到:写操作加行锁,普通 select 快照读不加锁

1. MVCC 依赖的底层物理文件:undo‑log(回滚日志)

  1. 每更新一行数据,旧版本数据放到 undo‑log;
  2. 每行隐藏字段(非常关键):
    • DB_TRX_ID:最后修改这行的事务 ID;
    • DB_ROLL_PTR:指针指向 undo‑log 里历史版本;
    • DB_ROW_ID:隐藏主键,如果没有主键就自动生成。
  3. undo‑log 里多个历史版本通过 roll‑ptr 串联形成版本链。

2. Read View(读视图),控制事务能看到哪些版本

Read‑View 包含:

  • m_ids:当前活跃事务集合;
  • up_limit_id:最小活跃事务 id;
  • low_limit_id:下一个将要分配的事务 ID。

RR 与 RC 的区别(核心)

  1. RC(读已提交):每次 select 都会生成新 Read‑View,会出现不可重复读,没有间隙锁
  2. RR(可重复读):事务第一次 select 创建 Read‑View,之后全程复用同一个 Read‑View,避免不可重复读;配合临键锁解决幻读

快照读(普通 select):走 MVCC 读取 undo 里历史版本,不加锁;

当前读(select ... for update /update/delete):走行锁读取最新数据。

举例:

  • T1 开启事务更新 id=1,加 X 锁;
  • T2 普通 select 查询 id=1,不去读取正在修改的未提交数据,而是读取 undo‑log 旧版本,实现读写并行;
  • T2 如果执行select ... for update当前读,则会被阻塞等待行锁释放。

三、事务原子性、持久性:redo‑log + undo‑log 保障更新安全

就算并发期间服务器宕机,更新也不会错乱:

  1. undo‑log:保证事务回滚 更新前先把旧数据写入 undo‑log,事务失败就依靠 undo‑log 回滚修改;保证事务原子性。
  2. redo‑log:崩溃恢复 InnoDB 修改数据时先修改内存页(Buffer‑Pool),不会立刻刷磁盘;修改内容先写入 redo‑log(WAL 预写日志);
    • 如果宕机,重启后通过 redo‑log 把内存中没刷盘的数据恢复到磁盘,保证已提交的数据持久化;
    • redo‑log 是顺序写入,性能很高。

更新简化流程:先写 undo‑log → 写 redo‑log(prepare 阶段)→ 修改内存数据 → 提交事务,redo‑log 置 commit 标记。这就是著名的两阶段提交。

四、完整并发更新流程

  1. 执行 update 语句,InnoDB 通过索引找到对应记录;
  2. 给索引记录加 Next‑Key‑Lock(X 排他锁);
  3. 将修改前的数据写入 undo‑log,构建版本链;
  4. 修改 Buffer‑Pool 里的数据页;
  5. 将本次修改记录写入 redo‑log prepare 阶段;
  6. 事务提交:redo‑log 写入 commit 标记;事务结束释放行锁;
  7. 其他事务更新同一行:拿不到 X 锁进入等待;普通 select 读取 undo 历史版本;select for update 等待锁释放;
  8. 服务器宕机:依靠 redo‑log 恢复已提交修改,undo‑log 回滚未提交修改。

五、开发层面避坑

  1. 更新条件必须命中索引,否则行锁退化成表锁,并发直接报废;
  2. 尽量缩小事务执行时间,事务越长持有锁时间越长,并发越低;
  3. RC 隔离级别无间隙锁,可以减少锁范围,但会存在不可重复读;RR 级别杜绝幻读但是锁范围更大;
  4. 更新顺序统一(例如总是先更新 id 小的)减少死锁产生概率。

总结

MySQL‑InnoDB 依靠排他行锁解决写‑写竞争防止脏写;

MVCC 基于 undo‑log 实现快照读解决读写冲突;

redo‑log 保证宕机后数据持久化,

undo‑log 保证事务回滚;

RR 隔离级别下 Next‑Key‑Lock 临键锁解决幻读,以此保障并发更新安全。

相关推荐
Qimooidea8 小时前
祁木 CAD Translator 工程图纸出海实战指南
服务器·前端·安全
m0_738120729 小时前
AI安全实战系列(六):Lab06 Model Extraction——模型提取攻击
人工智能·安全·网络安全·语言模型·系统安全
whn19779 小时前
如何避免以root用户执行达梦
数据库
brave_zhao10 小时前
openEuler下安装tar包解压工具
linux·服务器·数据库
珠***格10 小时前
XGF10-Z-4典型接入方式解析:10千伏专变用户380伏多点分布式光伏如何并网?
网络·人工智能·分布式·安全·边缘计算
九硕智慧建筑一体化厂家10 小时前
零触电高安全!盘山弯道直流感应照明提质增效改造方案
安全
盟接之桥10 小时前
跨越数字鸿沟,重塑全球信任:盟接之桥EDI如何以长期主义赋能中国制造出海
网络·人工智能·安全·汽车·制造
fu159357456811 小时前
【边缘计算实战】P1:从零搭建边云任务卸载仿真实验台(Python 可复现)
数据库·python·边缘计算
隔壁阿布都11 小时前
MySQL 间隙锁死锁问题处理
数据库·mysql
qiuziqiqi12 小时前
Shopify推送订单及异常状态错误码说明
数据库