事务基础
事务概念
事务特性
- 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
- 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
- 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
- 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
那实际上,我们研究事务的原理,就是研究MySQL的InnoDB引擎是如何保证事务的这四大特性的。
- 原子性通过undo log日志来保证
- 持久性通过redo log日志来保证
- 一致性通过undo log日志、redo log日志两份日志来保证
- 隔离性通过数据库的锁,加上MVCC来保证的
我们在讲解事务原理的时候,主要就是来研究一下redolog,undolog以及MVCC。
redo log
如果没有redolog,可能会存在什么问题的?
通过redolog解决这个问题
为什么不直接将buffer pool中的脏页刷新到磁盘呢 ?
undo log
版本链
某张表的一条数据如下
DB_TRX_ID : 代表最近修改事务 ID ,记录插入这条记录或最后一次修改该记录的事务 ID ,是自增的。DB_ROLL_PTR : 由于这条数据是才插入的,没有被更新过,所以该字段值为 null 。
同一事务或不同事务经过多次的对同一数据的操作形成如下的版本链
我们发现,不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。
MVCC
MVCC(Multi-Version Concurrency Control,多版本并发控制)是一种数据库管理系统中用来实现并发事务的技术。
基本概念
1.当前读:读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于我们日常的操作,如:select ... lock in share mode(共享锁),select ...for update、update、insert、delete(排他锁)都是一种当前读。
2.快照读:简单的select(不加锁)就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读
- Read Committed:每次select,都生成一个快照读。
- Repeatable Read:开启事务后第一个select语句才是快照读的地方。
- Serializable:快照读会退化为当前读。
在测试中,我们看到即使事务B提交了数据,事务A中也查询不到。 原因就是因为普通的select是快照 读,而在当前默认的RR隔离级别下,开启事务后第一个select语句才是快照读的地方,后面执行相同的select语句都是从快照中获取数据,可能不是当前的最新数据,这样也就保证了可重复读。
隐藏字段
隐藏字段 | 含义 |
DB_TRX_ID | 最近修改事务 ID ,记录插入这条记录或最后一次修改该记录的事务 ID 。 |
DB_ROLL_PTR | 回滚指针,指向这条记录的上一个版本,用于配合 undo log ,指向上一个版本。 |
DB_ROW_ID | 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段 |
而上述的前两个字段是肯定会添加的, 是否添加最后一个字段DB_ROW_ID,得看当前表有没有主键,如果有主键,则不会添加该隐藏字段。
readview
字段 | 含义 |
m_ids | 当前活跃的事务 ID 集合 |
min_trx_id | 最小活跃事务 ID |
max_trx_id | 预分配事务 ID ,当前最大事务 ID+1 (因为事务 ID 是自增的) |
creator_trx_id | ReadView 创建者的事务 ID |
条件 | 是否可以访问 | 说明 |
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 中, 是可以访问该版本的 | 成立,说明数据已经提交。 |
- READ COMMITTED :在事务中每一次执行快照读时生成ReadView。
- REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。
原理分析
RC隔离级别
A.先来看第一次快照读具体的读取过程:
在进行匹配时,会从undo log的版本链,从上到下进行挨个匹配:
- 先匹配版本链的第一条记录,这条记录对应的trx_id为4,也就是将4带入右侧的匹配规则中。 ①不满足 ②不满足 ③不满足 ④也不满足,都不满足,则继续匹配undo log版本链的下一条。
- 再匹配第二条,这条记录对应的trx_id为3,也就是将3带入右侧的匹配规则中。①不满足 ②不满足 ③不满足 ④也不满足 ,都不满足,则继续匹配undo log版本链的下一条。
- 再匹配第三条,这条记录对应的trx_id为2,也就是将2带入右侧的匹配规则中。①不满足 ②满足 终止匹配,此次快照读,返回的数据就是版本链中记录的这条数据。
B. 再来看第二次快照读具体的读取过程
在进行匹配时,会从undo log的版本链,从上到下进行挨个匹配:
- 先匹配版本链的第一条,这条记录对应的trx_id为4,也就是将4带入右侧的匹配规则中。 ①不满足 ②不满足 ③不满足 ④也不满足 ,都不满足,则继续匹配undo log版本链的下一条。
- 再匹配第二条,这条记录对应的trx_id为3,也就是将3带入右侧的匹配规则中。①不满足 ②满足 。终止匹配,此次快照读,返回的数据就是版本链中记录的这条数。