Mysql事务底层原理

143 阅读5分钟

前言

MySQL 事务是用来管理数据库中的多个操作的一组指令。这些操作可以是 DML (数据操纵语言,如 SELECT、INSERT、UPDATE 和 DELETE) 操作或者是 DDL (数据定义语言,如 CREATE TABLE、ALTER TABLE 和 DROP TABLE) 操作。

事务的特性

  • 原子性:事务中的所有操作要么全部执行,要么全部不执行。这意味着,如果在事务中执行的多个操作中有任何一个失败,那么整个事务都将回滚,之前执行的所有操作都将被撤销。
  • 一致性:事务在执行过程中,必须保证数据的完整性。这意味着,在事务开始之前和事务结束之后,数据库的状态必须保持一致。
  • 隔离性:事务隔离性指的是多个事务之间的隔离程度。
  • 持久性:持久性是指一旦事务已经提交,它对数据库中的数据所做的更改就会永久保存下来,即使数据库发生故障也不会丢失

原子性是Undo Log实现的,一致性是由代码逻辑层面保证的,隔离性是由mvcc机制实现的,持久性是基于Redo Log实现的。

Redo Log

Redo log,也称作重做日志,是一种用来记录数据库中所有更改操作的日志。用来保证服务崩溃后,仍能把事务中变更的数据持久化到磁盘上。

Redo Log.png
流程说明

  • 第1步: 先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝
  • 第2步: 生成一条重做日志并写入Redo Log Buffer,记录的是数据被修改后的值
  • 第3步: 当事务commit时,将Redo Log Buffer中的内容刷新到 Redo Log File,对 Redo Log File采用追加写的方式
  • 第4步: 定期将内存中修改的数据刷新到磁盘中

Undo Log

Undo log,也称作回滚日志,是一种用来记录数据库中所有撤销操作的日志。Undo log 用于在事务回滚或数据库发生故障时恢复数据库到正确的状态。比如:当我们执行一条insert语句时,Undo Log就记录一条相反的delete语句。

Undo Log.png

MVCC

MVCC(多版本并发控制)是一种用来支持并发访问的技术,常用于数据库系统。

在 MVCC 模型下,数据库系统维护了多个版本的数据。当一个事务修改数据时,数据库系统会在原来的数据版本的基础上创建一个新的数据版本。这样,就可以保证在一个事务修改数据的同时,其他事务仍然可以访问原来的数据版本。

隐藏字段

对于一个InnoDB存储引擎,一个聚簇索引(主键索引)的记录之中,一定会有两个隐藏字段trx_id和roll_pointer,这两个字段存储于B+树的叶子节点中,分别对应记录着两列信息:

  • trx_id:只要有任意一个事务对某条聚簇索引记录进行修改,该事务id就会被记录到该字段里面。
  • roll_pointer:当任意一个聚簇索引记录被修改,上一个版本的数据记录就会被写入Undo Log日志里面。那么这个roll_pointer就是存储了一个指针,这个指针是一个地址,指向这个聚簇索引的上一个版本的记录位置,通过这个指针就可以获得到每一个历史版本的记录。

Read View

Read View 存放着一个列表,这个列表用来记录当前数据库系统中活跃的读写事务,也就是已经开启了,正在进行数据操作但是还未提交保存的事务。可以通过这个列表来判断某一个版本是否对当前事务可见。其中,有四个重要的字段:

  • creator_trx_id:创建当前Read View所对应的事务ID
  • m_ids:所有当前未提交事务的事务ID,也就是活跃事务的事务id列表
  • min_trx_id:m_ids里最小的事务id值
  • max_trx_id:InnoDB 需要分配给下一个事务的事务ID值

MVCC如何实现可重复读

假设现在有两个事务同时对同一条数据进行操作

事务操作.png
两个事务会先创建各自的Read View

事务操作 (1).png
事务1去读取主键id为1的数据,找到了记录后就会去查看该记录的trx_id,事务1查看到该记录的trx_id值为0。随后和自己的creator_trx_id值进行比较,发现主键id=1这条记录.trx_id = 0 < 自己.creator_trx_id = 1,就判断到该记录的事务id不存在于活跃的事务列表中并且小于自己的事务id,这代表本次记录的值是在自己查询之前提交的,就可以正常读取并修改trx_id为自己的事务id。再将age改为20。隐藏指针也要改为刚刚改之前的地址。

事务操作 (2).png
事务2这时来进行操作的过程同刚刚事务1的过程

事务操作 (3).png
这时候如果事务1又来操作这条数据会发现trx_id被改为了2,就会再次进行值的区间比较:发现自己.trx_id(1)<主键id=1这条记录.trx_id(2)<max_trx_id(3),并且trx_id为2的值存在于m_ids中,就代表自己读取到的是和自己同一时间范围内一块启动的另一个未提交的活跃事务所修改的值。
那么此时事务1是不会去读取这条记录对应的数据的,它会通过roll_pointer指向的地址去查找上一个旧版本的记录,直到找到第一条trx_id小于等于自己的事务id并且不存在于m_ids列表中的记录,代表是别的事务已经提交的最后一条记录,然后读取它。