本文章示例是以mysql8.0版本
事务是一组操作的集合,它是不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或者撤销操作请求。即这些操作要么同时成功,要么同时失败。
事务四大特性
特性 | 描述 |
原子性 | 事务是不可分割的最小单元,要么全部成功,要么全部失败 |
一致性 | 事务完成时,必须使所有的数据状态一致 |
隔离性 | 数据库系统提供隔离机制,保证事务在不受外部并非操作影响的独立环境运行 |
持久性 | 事务一旦提交或者回滚,它对数据库中的数据的改变就是永久的 |
事务并发问题
问题 | 描述 |
脏读 | 一个事务读取到了另一个事务还没提交的数据 |
不可重复读 | 一个事务先后读取同一个记录,但两次读取的数据不同 |
幻读 | 一个事务在第一次读取时不存在,当插入数据时发现数据存在 |
事务隔离级别
级别 | 可能出现问题 | 默认设置 |
读未提交 | 脏读、不可重复读、幻读 | |
读已提交 | 不可重复读、幻读 | Oracle |
可重复读 | 幻读 | Mysql |
串行化 | 无 |
InnoDB事务原理
redo log
- 目的:是为了保证事务的持久性
- 组成:redolog buffer 和redolog file
- 操作:当事务提交之后会把所有修改信息都存在该日志文件中,用于在刷新脏页到磁盘发生错误时,进行数据恢复使用
为什么不直接将Buffer Pool实时刷到磁盘?
因为一个事务操作是包含很多数据的,数据页是随机的,IO消耗较大
undo log
- 目的:是为了保证事务的原子性
- 概述:逻辑日志,记录每一条的操作记录,用于回滚数据
- insert,事务提交后,可以立即删除,update、delete,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会被立即删除
mvcc
基本概念
概念 | 描述 | 备注 |
当前读 | 读取的是最新的记录,保证不会有其他事务修改数据,需要对记录加锁 | select...lock in share mode(共享锁) select ... for update(排它锁) |
快照读 | select 读取的是记录数据可见版本,有可能是历史数据,不加锁,是非阻塞读 | 读已提交:每次select都生成快照读 可重复读:开启事务后第一个select语句才是快照读 串行化:快照读会退化为当前读 |
mvcc | 多版本控制 | 三个隐式字段、undolog、readView |
三个隐式字段
隐藏字段 | 含义 |
DB_TRX_ID | 最近修改事务ID,记录插入这条数据或最后一次需要改记录的事务ID |
DB_ROLL_PRE | 回滚指针,指向这条记录的上一个版本,用于配合undo log,指向上一个版本 |
DB_ROW_ID | 隐藏主键,如果表结构没有指定主键,将会生成该隐藏主键 |
示例:
感兴趣的同学,可以看一下
# 查看磁盘文件 一般是在这个目录下,test是数据库名
cd /var/lib/mysql/test
# 查看文件命令
ibd2sdi t_user.ibd
undo log版本链
不同事务或者相同事务对同一天记录进行修改,会导致该记录的undolog生成一条记录版本链表,链表的头部分是最新的记录,尾部是最早的旧记录
- 示例数据:
事务2 | 事务3 | 事务4 | 事务5 |
开启事务 | 开启事务 | 开启事务 | 开启事务 |
修改id为1记录,age修改为3 | 查询id为1的记录 | ||
提交事务 | |||
修改id为1,name改为王五 | 查询ID为1的记录 | ||
提交事务 | |||
修改id为1记录,age修改为10 | |||
查询ID为1的记录 | |||
查询ID为1的记录 | |||
提交事务 |
- 产生的版本链
ReadView
- 核心字段
字段 | 含义 |
m_ids | 当前获取的事务ID集合 |
min_trx_id | 最小活跃事务ID |
max_trx_id | 预分配事务Id,当前最大事务ID+1 |
creator_trx_id | ReadView创建者的事务ID |
- 判断逻辑顺序
trx_id:代表是当前事务ld。
- trx_id=creator_trx_id? 可以访问该版本 成立,说明数据是当前这个事务更改的。
- trx_id<min_trx_id?可以访问该版本 成立,说明数据已经提交了。
- trx_id>max_trx_id?不可以访问该版本 成立,说明该事务是在readview生成后才开启。
- min_trx_id<trx_id<max_trx_id?如果trx_id不在m_ids中是可以访问该版本的成立,说明数据已经提交。
实现原理
不同的隔离级别,生成ReadView的时机不同
RC:在事务中的每一次执行快照读生成ReadView
RR:仅在事务中第一次执行快照读生成ReadView,后续复用该ReadView
继续以上面的示例进行示范
- 并发事务
- 判断过程
调链 | trx_id=creator_trx_id | trx_id<min_trx_id | trx_id>max_trx_id | min_trx_id<trx_id<max_trx_id? trx_id不在m_ids | 是否成立 |
第一个调用链 | 4!=5 | 4>3 | 4<6 | 4在m_ids中 | 否 |
第二个调用链 | 3!=5 | 3=3 | 3<6 | 3在m_ids中 | 否 |
第三个调用链 | 2!=5 | 2<3 | 是 |