InnoDB存储引擎-事务

发布于:2025-09-05 ⋅ 阅读:(17) ⋅ 点赞:(0)

1. 事务概述


事务可由一条简单的SQL语句组成,也可以由一组复杂的SQL语句组成. 事务是访问并更新数据库中各种数据项的一个程序执行单元. 在事务中的操作, 要么都做修改, 要么都不做.

对于 InnoDB存储引擎而言, 其默认的事务隔离级别 RR , 完全遵循和满足了事务的 ACID 特性.


1.1 原子性


原子性指整个数据库事务是不可分割的工作单位. 只有使事务中所有的数据库操作都执行成功, 才算整个事务成功. 事务中任何一个 SQL 语句执行失败, 已经执行成功的 SQL 语句也必须撤销, 数据库状态应该退回到执行事务前的状态.


1.2 一致性


一致性指事务将数据库从一种状态转变为下一种一致的状态. 在事务开始之前和事务结束后, 数据库的完整性约束没有被破坏.


1.3 隔离性


事务的隔离性要求每个读写事务的对象对其他事务的操作对象能相互分离, 即该事务提交前对其他事务都不可见.


1.4 持久性


事务一旦提交, 其结果是永久性的. 即使发生宕机等故障, 数据库也能恢复.



2. 事务的实现


隔离性是由锁实现的, 原子性, 一致性, 持久性通过数据库的 redo log 和 undo log 来完成.


2.1 redo

基本概念

重做日志用来实现事务的持久性. 其由两部分组成: ①重做日志缓冲 (redo log buffer), 其在内存; ②是重做日志文件 (redo log file), 其是持久的.

InnoDB存储引擎, 其通过 Force Log at Commit 机制实现事务的持久性, 即当事务提交的时候, 必须先将该事务的所有日志文件进行持久化, 待事务 COMMIT 操作完成后才算完成.

为了保证每次日志都写入重做日志文件, 在每次将重做日志缓冲写入重做日志文件后, InnoDB存储引擎都需要调用一次 fsync 操作.

参数 innodb_flush_log_at_trx_commit 用来控制重做日志刷新到磁盘的策略. 该参数默认1. 参数解释如下:

0 事务提交时不进行刷盘, 由后台线程去每秒刷盘一次.
1(默认) 事务提交时,必须调用 fsync 函数进行刷盘.
2 事务提交时,仅写入操作系统的缓冲种,不进行 fsync.

随然用户可以通过设置为0或2来提高并发性能, 但是这样会丧失事务的持久性.


重做日志和二进制日志


  1. 重做日志是在 InnoDB存储引擎层产生; 而二进制日志是在 MySQL 数据库层面产生的.
  2. 二进制日志是一种逻辑日志; 重做日志是物理日志, 记录的是对于每个页的修改.
  3. 两种日志记录的时间点不同, 二进制日志是在事务提交完成后进行一次写入; 重做日志文件在事务种不断的写入.

两阶段提交


在事务提交时, 先写二进制日志, 再写InnoDB存储引擎的重做日志. 对于上述两个操作也是原子的, 即二进制日志和重做日志必须同时写入.

若二进制日志写完了, 而在 写入InnoDB存储引擎时(第三步)发生了宕机, 那么 slave 可能会接受到 master 传过去的二进制日志并执行, 最终导致主从不一致, 如下图示



为了解决上述问题, MySQL数据库在 binlog 与 InnoDB存储引擎之间采用两阶段提交. 当事务提交时, InnoDB存储引擎会先做一个 PREPARE 操作, 将事务的 xid 写入, 接着进行二进制日志的写入, 如下图演示.


MySQL数据库中InnoDB存储引擎的两阶段提交用于确保事务在主库(Master)和从库(Slave)之间的一致性。下面是对图中各个步骤的解释:

  1. innodb prepare(步骤①)
    • 在主库上,事务首先进入准备阶段。此时,InnoDB会记录事务的所有变更到内存中的事务日志(redo log),但这些变更还没有被实际应用到数据库文件中。
    • 这个阶段确保了即使在事务提交之前发生故障,数据库也能通过重做日志恢复到一致的状态。
  2. write binlog(步骤②)
    • 主库将事务的变更记录写入到二进制日志(binlog)中。二进制日志记录了所有修改数据库状态的操作,用于复制到从库或在主库故障时恢复。
    • 写入binlog是两阶段提交的第一阶段,称为“prepare”阶段。
  3. 发送binlog到从库(步骤③)
    • 主库将二进制日志发送到从库。从库接收到binlog后,将其写入到自己的中继日志(relay log)中。
    • 这个过程确保了从库能够接收到主库上所有事务的变更,以便进行数据复制。
  4. innodb write redo log(步骤④)
    • 主库在确认从库已经接收到二进制日志后,将事务的变更从内存中的事务日志(redo log)应用到数据库文件中,完成事务的提交。
    • 这个阶段是两阶段提交的第二阶段,称为“commit”阶段。

通过这种两阶段提交机制,MySQL确保了即使在主库和从库之间发生故障,事务的一致性和完整性也能得到保证。这种机制对于分布式数据库系统来说非常重要,因为它可以防止数据不一致和丢失。



3. undo


3.1 基本概念


在对数据库进行修改时, InnoDB 还会产生一定量的 undo . 这样如果用户执行的事务或语句由于某种原因失败时, 就可以用这些 undo 信息将数据回滚到修改之前的样子.

undo 是逻辑日志.

undo 存放在共享表空间.

除了 回滚操作, undo 的另一个作用是 MVCC , 即在 InnoDB 存储引擎种 MVCC 实现是通过 undo 读取之前的行版本信息, 以此来实现非锁定读.

当事务提交后, 并不会立马删除 undo , 这是因为 MVCC 中可能有使用到.



4. 事务的隔离级别及其实现原理


SQL 表中定义了四个隔离级别:

读未提交

读已提交

可重复读 (InnoDB存储引擎默认隔离级别)

串行化读

串行化读 的事务隔离级别下, InnoDB 存储引擎会对每个 SELECT 语句后自动加上 LOCK IN SHARE MODE (共享锁), 因此在整个事务隔离级别下, 就不是一致性的非锁定读了.

隔离级别的实现原理

实现隔离机制的方法主要有两种:

  • 读写锁
  • 一致性快照读,即 MVCC

MySql使用不同的锁策略(Locking Strategy)/MVCC来实现四种不同的隔离级别。RR、RC的实现原理跟MVCC有关,RU和Serializable跟锁有关。


4.1 读未提交(Read Uncommitted)


官方说法:

SELECT statements are performed in a nonlocking fashion, but a possible earlier version of a row might be used. Thus, using this isolation level, such reads are not consistent.

读未提交,采取的是读不加锁原理。

  • 事务读不加锁,不阻塞其他事务的读和写
  • 事务写阻塞其他事务写,但不阻塞其他事务读;

4.2 读已提交

MVCC

4.3 可重复读

MVCC

4.4 串行化

  • 所有 <font style="color:rgb(180, 180, 180) !important;background-color:rgb(36, 36, 41) !important;">SELECT</font> 语句会隐式转化为 <font style="color:rgb(180, 180, 180) !important;background-color:rgb(36, 36, 41) !important;">SELECT … FOR SHARE</font>,即加共享锁。
  • 读加共享锁,写加排他锁,读写互斥。如果有未提交的事务正在修改某些行,所有 select 这些行的语句都会阻塞。



5. 不好的事务习惯

  1. 在循环中提交
  2. 长事务 (应该拆解为小事务)


6. 实现原理(来自DeepSeek)


MySQL 5.7通过以下机制实现事务的ACID特性及隔离级别:

一、ACID实现原理

  1. 原子性(Atomicity)
    • 实现机制:Undo Log
      当事务执行时,所有修改会生成对应的Undo Log。若事务失败或回滚,MySQL利用Undo Log逆向操作恢复数据。例如,插入操作会记录删除的Undo Log,删除操作会记录插入的Undo Log。
  2. 持久性(Durability)
    • 实现机制:Redo Log + 刷盘策略
      事务提交时,修改先写入Redo Log(顺序写,性能高),再异步刷到磁盘。崩溃恢复时,通过Redo Log重放未落盘的修改。InnoDB的innodb_flush_log_at_trx_commit=1强制每次提交刷Redo Log到磁盘。
  3. 隔离性(Isolation)
    • 实现机制:锁(Locking) + MVCC(多版本并发控制)
      • :写操作加行锁、间隙锁(Gap Lock)、临键锁(Next-Key Lock),解决脏写和部分幻读。
      • MVCC:通过ReadView和Undo Log链实现非阻塞读,解决脏读、不可重复读。
  4. 一致性(Consistency)
    • 由应用逻辑(如约束、触发器等)和数据库其他三个特性(原子性、隔离性、持久性)共同保证。

二、四个隔离级别的实现原理

MySQL支持四种隔离级别,隔离性由MVCC共同实现:

1. 读未提交

  • 行为:允许读取其他事务未提交的数据(脏读)。
  • 实现
    • 写操作加行级排他锁(X Lock),事务结束后释放。
    • 读操作不加锁,直接读取最新版本(包括未提交的数据)。

2. 读已提交

  • 行为:事务内每次查询生成新快照,解决脏读,但存在不可重复读。
  • 实现
    • MVCC:每条SELECT语句生成独立的ReadView,仅读取已提交的数据版本。
    • 锁机制:写操作加行锁,事务提交后释放锁。例如,UPDATE操作加X锁,提交后释放。

3. 可重复读

  • 行为:事务内首次读生成快照,后续读复用该快照,解决不可重复读。通过临键锁解决幻读。
  • 实现
    • MVCC:事务开始时生成ReadView,后续所有读操作基于该视图,通过Undo Log链访问历史版本。
    • 锁机制
      • 写操作(如UPDATE)加临键锁(Next-Key Lock),锁住记录+间隙,阻止其他事务插入。
      • 普通SELECT使用快照读(无锁),SELECT ... FOR UPDATE使用当前读(加临键锁)。

4. 串行化

  • 行为:强制事务串行执行,无并发问题,性能最低。
  • 实现
    • 所有SELECT隐式转换为SELECT ... FOR SHARE(加S锁),读写互斥。
    • 写操作加X锁,读操作加S锁,通过严格的锁冲突实现串行化。

三、MVCC与锁的协作

  • MVCC核心结构
    • Undo Log链:每行记录包含DB_TRX_ID字段(最后修改的事务ID),通过Undo Log构建历史版本链。
    • ReadView:记录活跃事务ID列表,判断数据版本可见性(通过比较DB_TRX_ID与ReadView的up_limit_idlow_limit_id)。
  • 不同隔离级别的ReadView生成策略
    • RC:每次SELECT生成新ReadView。
    • RR:事务首次SELECT生成ReadView,后续复用。
  • 临键锁(Next-Key Lock)的作用
    • 锁住记录及之前的间隙(如id=5的锁范围是(-∞,5]),阻止其他事务插入,解决幻读。


四、总结

  • ACID依赖机制
    • 原子性→Undo Log,持久性→Redo Log,隔离性→锁+MVCC,一致性→三者共同保障。
  • 隔离级别权衡
    • 隔离级别越高,锁冲突越多,并发性能越低。
    • MySQL的RR通过MVCC+临键锁在性能与一致性间取得平衡。

通过理解这些机制,可以更好地设计事务逻辑和调优数据库并发性能。



网站公告

今日签到

点亮在社区的每一天
去签到