MySql InnoDB 事务实现之 undo log 日志

前言

前文我们讲了 InnoDB 通过 redo log 来实现事务的原子性和持久性,接下来我们继续讲解 InnoDB 如何实现事务的隔离性。

什么是隔离性?

事务与事务之间相互独立,且互不影响。

如何实现隔离性?

给每个事务开辟一个上下文,每个上下文相互隔离,即使读取和处理同一行数据记录,彼此之间不会相互影响,即每个事务都有数据的不同版本。

当事务中需要的版本数据和最新数据记录中的版本数据不一致,通过最新数据记录再利用 undo log 进行回滚,直到回滚到想要的数据版本即可获取对应版本的数据记录。 这便是 undo log 的基本原理。

可以看到的是,实现隔离性的关键在于每个事务持有各自的数据版本。但数据只仍记录一份,undo log 记录变更信息,利用 undo log + 最新数据进行回滚即可获取到对应版本数据。

简单来讲,利用 undo log 来对事物进行往前回退。当用户执行的事务或者语句由于某种原因失败了,又或者用户用一条 rollback 语句请求回滚,就可以利用这些 undo 信息将数据回滚到修改之前的样子。

undo log

undo log 是逻辑日志,只是将数据库逻辑地恢复到原来的样子,当对数据库中的数据进行修改(如 INSERT、UPDATE、DELETE 操作)时,InnoDB 会把这些修改前的数据信息记录到 undo log 里。

undo log 记录的是版本变更信息,当某个事务要回到之前的版本时,通过当数据库最新记录数据,再利用 undo log版本信息进行回滚。也就是说,数据记录也不会保存多份,避免不必要的空间浪费。

主要用途

事务回滚

在事务执行过程中,若发生错误或者用户主动执行 ROLLBACK 操作,数据库可以利用 undo log 将数据恢复到事务开始之前的状态,以此保证事务的原子性。

多版本并发控制(MVCC)

MVCC 是 InnoDB 实现高并发性能的重要手段。undo log 为 MVCC 提供了数据的历史版本,当一个事务需要读取旧版本的数据时,就可以从 undo log 中获取,从而避免了读写锁的冲突,提高了并发性能。

事务的隔离性

undo log 对事务隔离性的支持

事务隔离性是指多个事务并发执行时,一个事务的执行不能被其他事务干扰,就好像这些事务是串行执行的一样。InnoDB 使用多版本并发控制(MVCC)来实现高并发下的事务隔离,而 undo log 是 MVCC 的关键组成部分。

  • 提供数据的历史版本:当一个事务对数据进行修改时,InnoDB 会将修改前的数据记录到 undo log 中。这样,其他事务在读取数据时,如果需要旧版本的数据,就可以从 undo log 中获取。例如,在可重复读隔离级别下,一个事务在整个事务期间多次读取同一数据时,能够保证读到相同的结果。这是因为即使其他事务对该数据进行了修改,当前事务仍然可以从 undo log 中获取该数据的旧版本,从而实现了一定程度的隔离。
  • 避免读写冲突:通过 MVCC 和 undo log,数据库可以在不使用锁的情况下实现读写操作的并发执行。读操作可以读取 undo log 中的旧版本数据,而写操作则可以对当前数据进行修改,两者不会相互阻塞,提高了并发性能的同时也保证了一定的隔离性。

实现事务隔离性还需要其他机制

虽然 undo log 为事务隔离性提供了重要支持,但仅靠 undo log 是不够的,还需要锁机制等其他手段来共同实现不同级别的事务隔离:

  • 读未提交(READ UNCOMMITTED) :在该隔离级别下,一个事务可以读取另一个未提交事务的数据,基本不使用 undo log 来保证隔离性,因为它允许脏读,事务之间的隔离程度最低。

  • 读已提交(READ COMMITTED) :在每次读取数据时,都会读取最新的已提交版本。除了 undo log 提供数据的历史版本外,还需要锁机制来确保在读取数据时,其他事务不会对数据进行未提交的修改,避免脏读。

  • 可重复读(REPEATABLE READ) :InnoDB 默认的隔离级别,通过 undo log 提供数据的历史版本,保证一个事务在整个事务期间多次读取同一数据时结果一致。同时,也会使用间隙锁等锁机制来防止幻读。

  • 串行化(SERIALIZABLE) :最高的隔离级别,通过对事务进行串行执行来保证隔离性,主要依靠锁机制,对读写操作都加锁,防止并发事务之间的干扰。

综上所述,undo log 是实现事务隔离性的重要组成部分,但事务隔离性的实现是一个复杂的过程,需要 MVCC、锁机制等多种技术共同协作,根据不同的隔离级别提供不同程度的隔离保证。

多版本并发控制(MVCC)

例子:在可重复读的场景下,A、B 事务都读取了记录 r='a',此时 B 进行了修改 r='b',并进行了提交事务。当 A 事务再次读取记录 r,还是获取的 r='a'?没错

原理

可重复读是 MySQL 的 InnoDB 存储引擎支持的一种事务隔离级别。在该隔离级别下,一个事务在执行过程中多次读取同一数据时,会保证所读取到的数据是一致的,即不会受到其他事务并发修改的影响。这主要是通过多版本并发控制(MVCC)机制实现的。

MVCC 会为数据库中的每一行记录维护多个版本,每个版本都有一个对应的时间戳或事务 ID。当一个事务读取数据时,它会根据自身的开始时间戳来决定使用哪个版本的数据,而不受其他事务后续修改的影响。

A 事务再次读取 r 仍是 'a' 的原因

  • 快照读 :在可重复读隔离级别下,普通的 SELECT 语句使用的是快照读。当事务 A 开始时,会创建一个一致性视图(快照),该视图包含了事务 A 启动时刻数据库中所有数据的版本信息。在事务 A 的整个生命周期内,它读取的数据都是基于这个快照的,而不受其他事务提交的修改影响。
  • B 事务的修改对 A 不可见:虽然事务 B 对记录 r 进行了修改并提交,但事务 A 的快照是在事务 B 修改之前创建的,所以事务 A 再次读取记录 r 时,仍然会从快照中获取数据,也就是 r='a'。

例子:

sql 复制代码
-- 设置隔离级别为可重复读
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 事务 A
START TRANSACTION;
-- 事务 A 第一次读取记录 r
SELECT r FROM your_table WHERE id = 1;  -- 结果为 r='a'

-- 事务 B
START TRANSACTION;
-- 事务 B 修改记录 r
UPDATE your_table SET r = 'b' WHERE id = 1;
-- 事务 B 提交
COMMIT;

-- 事务 A 再次读取记录 r
SELECT r FROM your_table WHERE id = 1;  -- 结果仍然为 r='a'

-- 事务 A 提交
COMMIT;

在高并发场景下表现出色,其高并发特性主要体现在以下几个方面:

MVCC高并发性能表现

读写不阻塞

在传统的锁机制中,读写操作往往会相互阻塞,例如一个事务对数据进行写操作时,其他事务的读操作会被阻塞,直到写操作完成并释放锁。

而 MVCC 通过为数据保存多个版本,使得读操作可以读取旧版本的数据,从而避免了与写操作的冲突。读操作通常采用快照读的方式,无需加锁,写操作则通过加锁来保证数据的一致性。

支持并发事务

事务之前都有自己的版本数据,相互隔离不影响,可以同时进行,大大提升系统处理能力

小结

undo log 作为回滚日志,是实现事务隔离性的重要手段之一;同时也是高并发能力的依赖之一,比如多版本并发控制(MVCC) 依赖 undo log 实现并发多版本能力,整体提升系统的吞吐能力。

相关推荐
画个大饼13 分钟前
Go语言实战:快速搭建完整的用户认证系统
开发语言·后端·golang
OK_boom2 小时前
Dapper的数据库操作备忘
数据库
艺杯羹2 小时前
JDBC之ORM思想及SQL注入
数据库·sql·jdbc·orm·sql注入
blackA_3 小时前
数据库MySQL学习——day4(更多查询操作与更新数据)
数据库·学习·mysql
极限实验室4 小时前
Easysearch 迁移数据之 Reindex From Remote
数据库
朴拙数科4 小时前
基于LangChain与Neo4j构建企业关系图谱的金融风控实施方案,结合工商数据、供应链记录及舆情数据,实现隐性关联识别与动态风险评估
数据库·langchain·neo4j
小李学不完4 小时前
Oracle--SQL事务操作与管理流程
数据库
qq_441996054 小时前
为何 RAG 向量存储应优先考虑 PostgreSQL + pgvector 而非 MySQL?
数据库·mysql·postgresql
Ivan陈哈哈5 小时前
Redis是单线程的,如何提高多核CPU的利用率?
数据库·redis·缓存
AI军哥5 小时前
MySQL8的安装方法
人工智能·mysql·yolo·机器学习·deepseek