事务原理
redo log 保持持久性:
首先原来的情况是我们做一组操作的时候,先去操作bufferpool缓冲区,如果没有,那么后台线程将数据页换入换出到缓冲区,然后我们对这个buffer pool进行修改,为脏页,在写回磁盘的时候宕机了,那么不能保证持久性了,所以引出了redolog
redo log 在这里出现就是将更新的操作立马更新到redologbuffer中,然后等事务提交的时候,那么直接将redologbuffer的文件写回追加到磁盘的 log文件中,实现持久化。这样下次bufferpool中的脏页写回也不担心宕机了
undo log实现原子性
undolog记录的是逻辑日志,redolog记录的是物理日志;
实现原子性就是,当我们delete一条数据的时候,undolog会存有对应的一条insert数据,当执行rollback的时候,就可以从undolog中读取相应的内容进行回滚;保证原子性
mvcc和锁实现隔离性
基本概念:
当前读:执行当前读的时候,读取的是最新的数据版本;
快照读:就是事务读取的是事务开始的版本,有可能是旧版本,RR级别就是这个快照读
原来的两个事务其中一个修改了表的数据,然后另一个是读取不到的,因为事务的隔离级别为RR可重复读,然后我们加了个共享锁,或者是一下排他锁就变成了当前读,就可以读到最新的版本数据了
MVCC:
隐藏字段:事务id,回滚指针(指向上一个版本的事务),隐藏主键
undo log:
readview :
读已提交的隔离级别:每次执行快照读的时候生成Readview;
可重复读:仅在第一次执行快照读时生成readview,后续都用这个Readview;
MVCC实现原理:
1. 隐藏字段
每一行记录在数据库中(如MySQL InnoDB)都有两个隐藏字段:
- trx_id(创建事务ID):记录该行是由哪个事务插入或更新的,用于标识哪个事务创建或修改了这个版本的数据。
- roll_pointer(指向undo log):指向undo log中的前一个版本的记录,形成一个版本链。
这两个字段帮助维护数据的多个版本,在读取时通过这些字段判断数据版本是否可见。
2. undo log版本链
每次对数据行进行更新操作时,并不会直接覆盖数据,而是将旧版本数据保存在undo log中,并且利用roll_pointer指向前一个版本的数据,形成一个版本链。这些旧版本数据存储在undo log中,并可以通过回滚操作恢复或者进行历史版本读取。
undo log版本链允许在并发事务中查询过去某个时刻的数据库状态,而不影响当前正在进行的事务。每个事务可以通过遍历这个版本链来找到符合其可见性规则的数据版本。
3. readview
readview是MVCC在执行查询时创建的一致性视图,它用于决定当前事务能够看到哪些数据版本。readview中保存了当前正在活跃的事务列表,以及当前事务的ID。这个视图确定了当前事务可以看到哪些数据版本,避免读取到其他未提交事务的更改。
关系:
- trx_id记录了数据的创建时间(以事务为单位),可以通过它来判断某个版本的数据是否是当前事务或其他事务创建的。
- undo log版本链用于保存历史版本,并通过roll_pointer串联成链,回溯出需要的历史数据版本。
- readview则是事务在执行查询时生成的,帮助决定当前事务可以看到哪些版本的记录。
MVCC如何实现隔离级别
1. 可重复读(Repeatable Read)
在可重复读隔离级别下,数据库为每个事务生成一个固定的readview,该视图在事务开始时创建,并在整个事务期间保持不变。通过readview的规则,每次查询时事务都只会看到在事务开始时已经提交的版本。
具体流程:
- 当事务开始时,系统生成一个readview,记录当前系统中活跃的事务ID列表。
- 读取数据时,通过遍历undo log版本链,根据数据的trx_id判断当前事务是否能看到某个版本:
- 如果数据的trx_id小于当前readview生成时的最小活跃事务ID(即数据在事务开始前已经提交),则该版本可见。
- 如果数据的trx_id大于当前readview中的最大事务ID,说明这个版本是在当前事务之后创建的,不可见。
- 如果trx_id在活跃事务列表中,说明该事务还未提交,数据不可见。
- undo log中的每个版本根据以上规则遍历,找到第一个可见的版本进行返回。
通过这种机制,即使其他事务在当前事务执行期间对数据做了修改,当前事务也只会读取到事务开始时的数据版本,实现了可重复读。
2. 读已提交(Read Committed)
在读已提交隔离级别下,数据库在每次执行SELECT语句时都会生成一个新的readview。这意味着,每次查询都会看到最近已经提交的版本数据,而不必像可重复读那样锁定在事务开始时的版本。
具体流程:
- 在每次查询时,数据库生成一个新的readview,包含当前活跃的事务ID列表。
- 根据与可重复读相同的判断规则,遍历undo log版本链,查找当前readview下可见的数据版本。
- 因为readview是每次查询都生成的,因此每次查询都会看到最新已提交的数据,从而允许事务在执行期间读取到其他事务已经提交的更新数据。
总结:
- 隐藏字段存储了记录的事务ID和回滚指针,决定了当前版本的记录由哪个事务修改,并提供了回溯历史版本的路径。
- Undo Log 记录了历史版本数据,并通过 roll_pointer 构建版本链,确保事务可以基于不同的 ReadView 回溯到合适的历史版本。
- ReadView 决定了当前事务在读取时能看到哪些事务提交的版本数据。在 可重复读 下,ReadView 在事务启动时生成并固定,而在 读已提交 下,每次读取都会创建新的 ReadView。