核心内容摘要
YOLOv9训练只需一条命令?官方镜像太方便了
摘要本篇聚焦 InnoDB 锁与事务核心机制系统讲解了各种锁机制解析脏读、不可重复读等并发问题及应对方案。
同时深入剖析事务 ACID 特性的底层实现Redo Log、Undo Log、隔离级别差异。
锁
3 InnoDB 存储引擎中的锁
6.
1 锁的类型意向锁是 InnoDB 为支持多粒度锁而设计的一种 预告性 锁机制。
它的核心作用是在不同粒度的锁之间建立协作关系避免冲突从而提升并发性能。
简单来说当事务需要对某一行细粒度加锁时会先在表粗粒度层面加上意向锁就像提前 打个招呼告诉其他事务我准备在这张表里的某一行加锁了。
它的主要作用体现在两点避免锁冲突提升并发效率意向锁让数据库可以快速判断 表级锁 和 行级锁 是否冲突。
例如当一个事务想给整张表加排他锁时只要检查表上是否存在意向锁就能立刻知道是否有其他事务正在操作表中的行不用逐行去检查大幅提升了锁判断的效率。
支持多粒度锁共存意向锁让表锁和行锁可以安全地共存满足不同业务场景的需求。
如一个事务可以对表加意向锁同时对行加行锁另一个事务也可以对表加意向锁对其他行加行锁实现更细粒度的并发控制。
常见的意向锁有两种意向共享锁IS和意向排他锁IX。
当事务要对行加共享锁S 锁时会先加 IS 锁要对行加排他锁X 锁时会先加 IX 锁。
6.
2 一致性非锁定读前提为什么加了 X 锁还能读锁的作用对象不同X 锁是加在当前正在修改的数据行上的用来阻止其他事务对这一行做写操作如UPDATE/DELETE也阻止加 S 锁。
但 InnoDB 的一致性非锁定读读的并不是这个被 X 锁锁住的 当前行而是从回滚段Undo Log里读取这一行的历史快照版本。
这个历史版本本身没有被加任何锁所以可以直接读取。
MVCC 的核心思想它通过为每行数据维护多个历史版本让读操作可以 绕开 被锁住的当前行去读历史快照从而实现了 读写不阻塞。
读的时候不加读锁吗这要分两种读场景来看一致性非锁定读默认读也就是普通的SELECT语句InnoDB 默认不会加任何读锁S 锁而是直接读取快照数据。
这是 InnoDB 默认的读方式目的就是为了提升并发性能。
锁定读显式加读锁如果你用SELECT ... FOR SHARE或SELECT ... FOR UPDATE这类语句InnoDB 就会对读取的行加 S 锁或 X 锁这时候才会和其他锁产生互斥。
这种场景一般用于需要保证 “读 - 写” 原子性的业务逻辑。
一致性非锁定读是 InnoDB 实现高并发读的核心机制它通过行多版本控制Multi-Versioning来避免读操作被写操作阻塞。
工作原理无锁读机制当读取的数据行正在被执行DELETE或UPDATE操作时读操作不会等待行上的排他锁X 锁释放而是直接读取该行的历史快照数据。
快照数据来源这些快照数据存储在 InnoDB 的回滚段Undo Log中是数据修改前的版本保证了读操作的一致性和非阻塞性。
隔离级别关联在READ COMMITTED隔离级别下每次读取都会获取最新的快照在REPEATABLE READInnoDB 默认隔离级别下整个事务内只会在第一次读时获取快照后续读操作都基于同一个快照避免了不可重复读问题。
主要作用提升并发性能读写操作不再互相阻塞读操作无需等待写锁释放写操作也无需等待读锁释放大幅提升了数据库的并发处理能力。
保证数据一致性通过多版本快照确保读操作能看到符合事务隔离级别的一致数据避免了脏读。
MVCCMulti-Version Concurrency Control多版本并发控制是 InnoDB 实现高并发读写的核心技术它通过为数据行维护多个历史版本让读写操作可以互不阻塞同时保证事务的隔离性。
核心原理多版本存储InnoDB 会为每一行数据保存多个历史版本这些版本存储在回滚段Undo Log中。
每次对数据进行UPDATE或DELETE时旧版本不会立即删除而是被保留下来作为快照。
版本可见性判断每个事务在启动时会获得一个唯一的事务 IDTransaction ID。
当事务读取数据时InnoDB 会根据事务的隔离级别判断哪个历史版本对当前事务是可见的。
例如在REPEATABLE READ级别下事务只会看到启动时已提交的数据版本。
读写不阻塞写操作UPDATE/DELETE会对当前数据行加锁但读操作可以直接读取历史快照无需等待锁释放。
这就避免了传统锁机制中 读写互斥 的问题大幅提升了并发性能。
关键作用提升并发性能读写操作不再互相阻塞读无需等写写也无需等读。
保证事务隔离性通过版本可见性规则实现了READ COMMITTED和REPEATABLE READ隔离级别避免了脏读、不可重复读等问题。
避免幻读在默认的REPEATABLE READ级别下MVCC 配合间隙锁可以有效防止幻读。
6.
3 一致性锁定度读一致性锁定读是 InnoDB 提供的一种显式加锁的读取方式用于在需要保证 读 - 写 原子性的场景中通过加锁来确保数据逻辑的一致性。
它和默认的一致性非锁定读相对是一种主动加锁的读操作。
两种锁定读的具体行为语句类型加锁类型作用与互斥规则典型应用场景SELECT ... FOR UPDATE排他锁对读取的行加 X 锁阻止其他事务对这些行加任何锁也阻止其他事务修改这些行。
读取数据后要立即进行更新操作比如扣减库存、转账等防止数据在读取和修改之间被其他事务篡改。
SELECT ... LOCK IN SHARE MODE共享锁对读取的行加 S 锁允许其他事务加 S 锁但阻止其他事务加 X 锁。
多个事务需要共同读取同一数据且不希望被修改的场景比如查询商品当前库存并确保在事务期间不被修改。
关键
注意事项必须在事务中执行这两种锁定读语句必须在事务BEGIN/START TRANSACTION或关闭自动提交SET AUTOCOMMIT0中执行锁会在事务提交或回滚后释放。
与非锁定读的兼容性即使某行被SELECT ... FOR UPDATE加了 X 锁其他事务依然可以通过一致性非锁定读读取该行的历史快照不会被阻塞。
隔离级别关联在默认的REPEATABLE READ隔离级别下锁定读的行为会配合间隙锁等机制进一步防止幻读。
4 锁的算法
6.
1 行锁的3种算法InnoDB 提供了三种核心的行锁算法以实现不同粒度的并发控制。
Record Lock最基础的行锁它会对索引上的单个行记录进行锁定仅作用于目标行若表没有显式索引InnoDB 会使用隐式主键索引来完成锁定典型场景是精准更新或锁定某一行数据例如执行SELECT * FROM t WHERE id10 FOR UPDATE时就会对id10的行加 Record Lock。
Gap Lock主要是为了防止插入型幻读它会锁定一个索引范围但不包含边界记录本身目的是阻止其他事务在该间隙中插入新数据。
比如表中已有id5, 10, 15执行SELECT * FROM t WHERE id BETWEEN 5 AND 15 FOR UPDATE时会对(5,
和(10,
这两个间隙加 Gap Lock避免插入id7或12这类新数据。
Next-Key Lock Record Lock 与 Gap Lock 的组合既锁定索引范围也锁定范围边界的记录本身是 InnoDB 在可重复读隔离级别下的默认行锁算法可彻底防止幻读防止插入型幻读和修改型幻读。
例如上述相同的范围查询Next-Key Lock 会锁住(5,10]和(10,15]两个区间既锁定了现有记录也阻止了间隙插入新数据。
5 锁问题
6.
1 脏读脏读的定义与危害定义一个事务读取到另一个事务尚未提交的数据即脏数据这就是脏读。
危害脏读会破坏数据库的隔离性导致读取到的数据可能被回滚从而引发业务逻辑错误。
例如事务 A 修改了用户余额但未提交事务 B 读取到这个未提交的余额并执行了扣款操作之后事务 A 回滚事务 B 的扣款就会基于错误的数据。
如何避免脏读脏读可以通过设置合适的事务隔离级别来避免在READ COMMITTED及更严格的隔离级别REPEATABLE READ、SERIALIZABLE下InnoDB 会通过 MVCC 或锁机制确保事务只能读取到已提交的数据从而避免脏读。
6.
2 不可重复读不可重复读是数据库并发场景下的典型问题指在同一个事务内多次读取同一数据时由于其他已提交事务的修改导致前后两次读取结果不一致的现象。
不可重复读与脏读的关键区别问题类型读取的数据状态核心差异脏读读取到其他事务未提交的数据数据是未确认的中间态可能被回滚不可重复读读取到其他事务已提交的数据数据是已确认的终态但破坏了当前事务的一致性不可重复读的危害它违反了事务的一致性要求。
在同一个事务内业务逻辑依赖的同一数据发生变化会导致计算或判断结果出现偏差。
例如事务 A 第一次读取商品库存为 100 件。
事务 B 修改库存为 50 件并提交。
事务 A 再次读取库存时发现变为 50 件这会导致基于第一次读取结果的下单逻辑出现错误。
如何避免不可重复读在 InnoDB 中可以通过事务隔离级别来解决REPEATABLE READInnoDB 默认级别通过 MVCC 机制事务在启动时会获取一个数据快照整个事务期间都基于该快照读取从而保证同一事务内多次读取结果一致。
SERIALIZABLE通过强制加锁实现完全串行化彻底避免不可重复读但会大幅降低并发性能。
6.
3 丢失更新丢失更新是指一个事务的更新操作被另一个事务的更新覆盖导致数据不一致的问题它分为数据库理论意义上的丢失更新和逻辑意义上的丢失更新两类。
数据库理论意义上的丢失更新发生场景两个事务同时对同一行数据执行更新操作后提交的事务覆盖先提交事务的修改。
数据库的防护机制在任何隔离级别下InnoDB 都会通过行锁X 锁来阻止这类问题。
当事务 T1 对行加 X 锁后事务 T2 的更新操作会被阻塞直到 T1 提交或回滚因此理论上的丢失更新不会发生。
逻辑意义上的丢失更新发生场景这是生产中更常见的问题源于 读取 - 修改 - 提交 的业务流程存在时间差事务 T1 和 T2 先后读取同一行数据到本地内存。
用户 1 基于 T1 的数据修改并提交。
用户 2 基于 T2 的旧数据修改并提交覆盖了用户 1 的更新。
本质原因这类问题不是数据库锁机制能直接解决的因为读取操作默认是无锁的两个事务都能读到初始数据最终导致后提交的修改覆盖了先提交的内容。
如何解决逻辑意义上的丢失更新使用锁定读在读取数据时用SELECT ... FOR UPDATE加 X 锁确保在修改前独占该行数据阻止其他事务读取和修改。
乐观锁在表中增加版本号或时间戳字段更新时校验版本号例如UPDATE t SET value new_value, version version 1 WHERE id ? AND version old_version如果版本号不匹配说明数据已被其他事务修改需要重新读取并尝试更新。
6 阻塞阻塞是 InnoDB 中不同事务因锁的互斥关系而产生的等待现象它是保证事务并发安全的正常机制但也需要合理配置参数来避免长时间等待影响系统可用性。
阻塞的本质当一个事务持有的锁与另一个事务请求的锁互斥时请求锁的事务会进入等待状态直到持有锁的事务释放资源。
这不是错误而是数据库为了保证数据一致性而设计的并发控制逻辑。
关键控制参数参数名作用特性innodb_lock_wait_timeout控制事务等待锁的超时时间默认 50 秒。
动态参数可在运行时调整。
innodb_rollback_on_timeout设定超时后是否回滚整个事务默认OFF仅回滚当前语句。
静态参数只能在数据库启动时修改。
超时行为当等待时间超过innodb_lock_wait_timeout的设定值时MySQL 会抛出1205 错误ERROR 1205 (HY
: Lock wait timeout exceeded; try restarting transaction默认情况下仅会回滚导致等待的当前语句而非整个事务。
如何优化阻塞问题合理设置超时时间根据业务场景调整innodb_lock_wait_timeout避免过长等待占用资源也避免过短导致正常事务频繁超时。
优化锁粒度尽量使用行锁而非表锁减少锁的冲突范围。
优化业务逻辑减少事务持有锁的时间避免长事务对热点数据的更新操作进行限流或排队。
7 死锁
8 锁升级这段内容解释了 InnoDB 为什么不存在锁升级Lock Escalation问题这是它与其他数据库如 MySQL 的 MyISAM、SQL Server的一个重要区别。
什么是锁升级锁升级是指当一个事务在同一对象上持有大量细粒度锁如行锁时数据库为了降低锁的管理开销会自动将这些细粒度锁升级为粗粒度锁如表锁。
这虽然减少了锁的管理成本但会大幅降低并发性能因为表锁会阻塞其他事务对整个表的操作。
InnoDB 为什么没有锁升级锁的管理方式不同InnoDB 不是为每一行记录单独维护锁而是以数据页Page为单位用位图Bitmap来管理锁。
开销一致无论一个事务在一个数据页中锁住 1 条记录还是 100 条记录InnoDB 只需要修改该页对应的位图标记锁的管理开销是一致的。
因此InnoDB 没有必要通过锁升级来降低开销行锁可以一直保持细粒度从而保证了高并发性能。
事务
1 认识事务核心概念解读事务是数据库区别于文件系统的核心特性它的本质是保证一系列操作的原子性与一致性避免文件系统中 “部分更新导致数据不一致” 的问题。
InnoDB 的事务完全遵循ACID四大特性。
事务的
核心价值在文件系统中如果更新两个文件时中途系统崩溃会导致文件状态不一致。
事务的引入就是为了解决这个问题它确保数据库从一个一致状态原子性地转换到另一个一致状态。
简单来说就是 要么所有修改都成功提交要么所有修改都回滚到初始状态。
ACID四大特性详解特性含义InnoDB 实现方式原子性事务中的所有操作是一个不可分割的整体要么全部执行成功要么全部失败回滚。
依赖 Undo Log 记录操作的反向逻辑事务失败时通过 Undo Log 回滚到初始状态。
一致性事务执行前后数据库的完整性约束如主键唯
外键关联等始终保持有效。
由原子性、隔离性、持久性共同保证同时依赖业务逻辑的正确性。
隔离性多个事务并发执行时一个事务的执行不能被其他事务干扰每个事务都感觉不到其他事务在并发执行。
通过锁机制和 MVCC多版本并发控制实现不同隔离级别对应不同的并发控制策略。
持久性一个事务一旦提交它对数据库中数据的修改就应该是永久性的接下来的其他操作或故障不应该对其执行结果有任何影响。
依赖 Redo Log 确保已提交的修改在系统崩溃后可以恢复并且会异步刷盘保证数据持久化。
2 事务的实现这段内容揭示了 InnoDB 事务四大特性ACID的底层实现机制核心是通过锁、RedoLog、Undo Log这三大组件来分工协作。
事务特性与底层实现的对应关系事务特性实现机制隔离性通过第 6 章所述的锁机制行锁、间隙锁、临键锁等实现防止并发事务之间的干扰。
原子性通过 Redo Log 和 Undo Log 共同保证Redo Log 确保已提交的修改不会丢失Undo Log 确保未提交的修改可以回滚。
一致性通过 Undo Log 回滚未提交的修改结合锁机制保证并发下的数据状态一致。
持久性通过 Redo Log 实现已提交的修改会被记录到 Redo Log 并持久化到磁盘即使系统崩溃也能通过 Redo Log 恢复数据。
RedoLog与 Undo Log 的本质区别特性Redo LogUndo Log日志类型物理日志记录数据页的物理修改操作如 将页 X 的偏移 Y 处的值从 A 改为 B。
逻辑日志记录每行数据的逻辑修改操作如 对 id10 的行将 value 从 100 改回 50。
核心作用恢复已提交事务的修改保证原子性和持久性回滚未提交事务的修改保证一致性同时为 MVCC 提供历史快照数据。
触发时机事务提交时刷盘系统崩溃后重启时恢复。
事务回滚时触发或事务提交后被异步清理。
7.
1 redo log这段内容详细介绍了 Redo Log重做日志的组成、作用和核心机制它是 InnoDB 保证事务持久性Durability的关键组件。
RedoLog的组成Redo Log 由两部分构成分别对应内存和磁盘RedoLogBuffer存在于内存中是易失性的事务执行过程中产生的 Redo Log 会先写入这里。
RedoLog File存在于磁盘上是持久化的事务提交时日志会从缓冲刷入文件。
核心机制ForceLogat CommitInnoDB 通过ForceLogat Commit机制保证事务的持久性当事务执行COMMIT时必须先将该事务的所有 Redo Log 从缓冲写入到磁盘上的 Redo Log File。
只有当日志写入完成后COMMIT操作才算成功。
这个机制确保即使系统崩溃已提交事务的修改也能通过 Redo Log 恢复不会丢失。
RedoLog与 Undo Log 的关键区别特性Redo LogUndo Log核心作用保证事务的持久性恢复已提交的修改。
支持事务回滚和 MVCC回滚未提交的修改。
写入方式顺序写性能极高。
随机读写因为需要定位到具体行的历史版本。
读取时机仅在系统崩溃后重启时读取用于恢复数据在事务回滚或 MVCC 读取历史版本时读取。
这段内容详细说明了 InnoDB 中 Redo Log 的存储单元 ——Redo Log Block重做日志块的设计细节它的大小和结构是为了保证日志写入的原子性和高效性。
RedoLogBlock 的核心设计固定大小每个日志块的大小为512 字节与磁盘扇区的物理大小完全一致。
结构组成每个日志块由三部分组成日志块头logblock header12 字节用于存储块的元信息如块编号、校验等。
日志内容实际可存储的日志数据为492 字节512 - 12 - 8。
日志块尾logblock tailer8 字节用于校验块的完整性。
自动分割如果一条日志的长度超过 492 字节会自动分割到多个日志块中存储。
512 字节设计的关键优势原子写入保证因为一个日志块的大小和磁盘扇区完全一致写入时可以保证整个块的原子性不会出现 部分写入 的情况。
无需 Doublewrite普通数据页16KB的写入需要依赖 Doublewrite 技术来避免部分写失效但 Redo Log Block 因与扇区大小一致天然保证了原子性因此不需要 Doublewrite。
高效顺序写固定大小的块结构让 Redo Log 可以以极高的效率进行顺序写入这也是 InnoDB 事务提交性能高的关键原因之一。
7.
2 undo log这段内容解释了 Undo Log回滚日志的基本概念、作用和存储方式它是 InnoDB 实现事务原子性、一致性以及 MVCC 的基础。
UndoLog的核心作用事务回滚当事务执行失败或用户执行ROLLBACK时Undo Log 会记录数据修改前的状态让数据可以回滚到修改前的样子保证事务的原子性。
支持MVCC为读操作提供数据的历史版本让事务可以读取到已提交的快照数据实现 “读写不阻塞” 的并发性能。
UndoLog与 Redo Log 的本质区别特性Undo LogRedo Log存储位置存储在数据库内部的 Undo 段存储在独立的 Redo Log 文件如 ib_logfile0日志类型逻辑日志记录数据的逻辑修改如 将 id10 的 value 从 100 改为 50。
物理日志记录数据页的物理修改如 将页 X 的偏移 Y 处的值从 A 改为 B。
核心价值保证事务回滚和 MVCC 功能。
保证事务的持久性崩溃后恢复已提交的修改。
生命周期事务提交后不会立即删除会保留一段时间供 MVCC 读取之后被异步清理。
事务提交后刷入磁盘日志被覆盖后会被循环使用。
6 事务隔离级别InnoDB 默认隔离级别的独特优势InnoDB 默认使用REPEATABLE READ但与标准 SQL 不同避免幻读通过Next-Key Lock临键锁算法既锁定记录本身也锁定记录之间的间隙彻底防止新数据插入导致的幻读。
达到SERIALIZABLE级别的一致性在REPEATABLE READ级别下InnoDB 已经能完全保证事务的隔离性要求达到了 SQL 标准中SERIALIZABLE级别的效果。
无性能损失根据 Jim Gray 在《Transaction Processing》中的观点REPEATABLE READ与SERIALIZABLE的开销几乎一致甚至SERIALIZABLE可能更优因此选择默认级别不会带来性能损失。
隔离级别与性能的关系隔离级别越低事务请求的锁越少或持有锁的时间越短理论上并发性能越高。
实际性能差异在 InnoDB 中REPEATABLE READ和READ COMMITTED的性能差异很小降低隔离级别并不会带来性能的大幅提升。
这段内容揭示了在 MySQL 主从复制架构下事务隔离级别与二进制日志格式如何共同影响主从数据一致性并给出了具体的解决方案。
主从数据不一致的两大原因原因 1READ COMMITTED隔离级别缺少间隙锁在READ COMMITTED级别下InnoDB 不会使用 Gap Lock间隙锁。
这会导致在主库的事务执行期间其他会话可以在锁定范围内插入新数据。
从库回放日志时会出现与主库不一致的数据。
原因 2STATEMENT格式二进制日志的逻辑顺序问题STATEMENT格式记录的是主库执行的 SQL 语句本身。
如果主库执行顺序是 先删后插日志可能会记录为 先插后删导致逻辑顺序错乱。
从库按错误的顺序执行 SQL会产生数据不一致。