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 临键锁解决幻读,以此保障并发更新安全。

相关推荐
ruleslol1 小时前
InnoDB 执行 UPDATE 时的完整操作
mysql
河北品高电子安安1 小时前
智慧井盖:物联网技术赋能城市地下管网安全监测
大数据·物联网·安全
禁默1 小时前
信创实战:Python + SQLAlchemy 接入金仓数据库(从驱动安装到完整 CRUD)
开发语言·数据库·python
淼澄研学1 小时前
NVIDIA NeMo Guardrails 2.0实战:大模型安全护栏集成指南
人工智能·python·安全
范什么特西1 小时前
知识总结04(redis)
数据库·redis·缓存
小黑技术栈1 小时前
web前端基础到入门——14day
前端·数据库·oracle
喜欢的名字被抢了2 小时前
FastAPI 接口安全与工程化 JWT、测试和配置管理
安全·fastapi
福大大架构师每日一题2 小时前
agno v2.8.7 发布:顾问模型、精准路线、调度能力全面升级,10项关键修复一次看懂
java·开发语言·数据库
深念Y2 小时前
老Flyme 官方 root 开启 ADB TCP 方案
linux·数据库·网络协议·tcp/ip·adb·智能手机·emmc
c2385610 小时前
MySQL 基础用法(上):库表管理与数据增删改
c语言·数据库·c++·mysql