MySQL 的事务实现原理

2022/06/23 • 预计阅读时间 4 分钟

事务有四个衡量标准,即 ACID。

Atomicity - 原子性

含义:同一事务内的操作,要么全都做,要么全都不做。

实现原理:回滚日志 - Redo Log

一个事务内可以有若干个操作,这些操作按照顺序执行。当其中某个操作出现异常时,在此之前的操作都应该被撤销。撤销的过程就是回滚;就是使用事务执行之前的数据,对数据库进行覆盖,将已被修改的条目重新修改回原样。那么就需要一个文件来记录本次事务提交前的数据,以备回滚之用;redo log 的引入就是为了解决这个问题(仅 InnoDB 引擎)。

Redo Log 在事务提交时生成:引擎先把主键、要修改的列、修改前的值、修改后的值等信息写入 redo log,然后再执行操作。当某个操作出现异常,引擎就会从 Redo Log 中读出前述信息,然后使用旧值对已修改的值进行覆盖,再把异常返回给客户端。

Consistency - 一致性

含义:事务执行结束后,数据的完整性约束不被破坏,执行前后都是合法的数据状态。

实现原理:

  • 保证原子性、隔离性和持久性
  • 数据库层面的约束,比如数据类型和长度的约束
  • 应用层面的约束,通俗来说就是业务系统的约束,比如转账前后货币的总额恒不变

Isolation - 隔离性

含义:一个事务内的操作与另一个事务的操作互相隔离,并发执行时互不干扰。

实现原理:

  • 锁机制,使一个事务的写操作不影响另一个事务的写操作
  • MVCC 机制,使一个事务的写操作不影响另一个事务的读操作

事务在并发执行时可能会出现这些问题:

  • 脏读:一个事务读到了另一个事务还没有提交的数据
  • 不可重复读:同一个事务中,对同一份数据读取到的结果不一致;因为另一个并发事务修改了数据
  • 幻读:同一个事务中,同一个查询读取到的结果数量不一致;因为另一个并发事务增加或减少了条目

这些问题在不同的隔离级别下有不同的表现:

隔离级别 脏读 不可重复读 幻读 隔离性 并发性能
Read Uncommitted - 读未提交 Yes Yes Yes * ****
Read Committed - 读已提交 No Yes Yes ** ***
Repeated Read - 可重复读 No No Yes *** **
Serialization - 可串行化 No No No **** *

(现代数据库在隔离性和性能的平衡中,会选择中间两种隔离级别)

锁表示对资源进行独占,不允许其他进程同时读写资源。MySQL 中常用的锁有行锁和表锁,行锁即对某一行数据进行锁定,表锁则对整个表进行锁定。MyIsam 引擎只支持表锁,InnoDB 引擎则同时支持行锁和表锁,但 InnoDB 默认使用行锁。因为加锁和解锁需要消耗一定的资源,因此,当需要在同一个表同时锁定较多数据时,行锁的性能就不如表锁。

MVCC 即多版本并发控制(Multiversion Concurrency Control),指的是在同一时刻,不同的事务读到的数据可能是不同的、多个版本的。

MySQL 的解决方法是:读操作不加锁,写操作加锁。读写操作不冲突,并发性能较好。

Durability - 持久性

含义:事务一旦提交,对数据库的影响就是永久的。

实现原理:回滚日志 - Redo Log

通常,读写数据都会使用到缓存(Buffer Pool):

  • 读数据:先尝试到 Buffer Pool 中读,如果 Buffer Pool 中没有,再到磁盘中读,然后把结果缓存到 Buffer Pool 中,再返回给客户端
  • 写数据:先写到 Buffer Pool 中,数据库根据策略定期把 Buffer Pool 中的数据刷新到磁盘

计算机硬件上,缓存使用到的物理设备是内存,内存中的数据必须在加电情况下才能保存,断电即消失。因此可以预见,当宕机发生时,已写入到 Buffer Pool 中的数据可能来不及刷新到磁盘中,存在数据丢失的问题。

为了解决这个,需要对写数据流程进行优化,就再次利用到 Redo Log(Redo Log 存储在磁盘上):写入数据时,先把修改记录写入 Redo Log 中,再写入 Buffer Pool,数据库再按策略刷新磁盘上的数据文件。当宕机重启时,数据库可以从 Redo Log 中读取还没来得及刷新的数据,把这些数据重新刷新到磁盘中。

但是,先写入到磁盘上的 Redo Log,不会影响数据库性能吗?为什么不直接一步到位直接写入同样在磁盘上的数据文件?

因为写 Redo Log 比刷新数据文件要快:

  • 写 Redo Log 是追加写入,是顺序 IO;而刷新数据文件是在随机位置写入,是随机 IO;顺序 IO 比随机 IO 快
  • 写 Redo Log 只包含本次操作的必要数据,全部是有效 IO;而刷新数据文件是以数据页(Page)为单位进行整页写入,可能包含一些无效 IO

扩展补充

Redo Log 是 InnoDB 引擎提供的特性,MyIsam 引擎并不支持。

MySQL 中还有一个类似的日志文件是 BinLog,但两者存在很大区别,Redo Log 只是引擎层面提供的,只用来保证数据的安全。

InnoDB 引擎至少要有一个 Redo Log Group,每个组默认包含两个相同大小的 Redo Log,所以一般我们在文件系统里能看到 ib_logfile0、ib_logfile1 两个文件。

Redo Log 是循环读写的,不会超出设置的个数。有一个 Write Pos 记录当前写入位置,一个 Check Ponit 记录擦除位置;随写入或擦除往后移动,移到最后一个文件时时跳回第一个文件。擦除之前要把记录刷新到数据文件;两个位置之间的文件就用来记录新的数据。如果 Write Pos 追上了 Check Ponit 就说明 Redo Log 文件已满,要擦除一些才能继续写入。

本站内容如无特别说明,均为原创,转载请注明出处(即遵循 CC BY 4.0 协议)。

Linux LVM 挂载新硬盘到目录