MySQL事务原理
事务的四个特性
我们首先回忆下事务的4个特性
- 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
- 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态,指数据逻辑的前后一致。
- 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
- 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
其中原子性、一致性、隔离性主要由redo log
和undo log
实现
而隔离性主要由锁和MVCC实现
redo log
重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性。
该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file),前者是在内存中,后者在磁盘中。
在事务中,当修改数据后,会将修改的信息存入到重做日志缓冲中;当事务提交之后会把所有修改信息都存到该日志文件中,当在刷新脏页到磁盘发生错误时,进行数据恢复使用。脏页指的是在缓冲区中被修改,但还未刷入到磁盘的数据。如果脏页成功刷新到磁盘中,那么redo log也就没什么用了,是需要定时清理的
重做日志文件有两份,是循环写的
undo log
回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚和MVCC(多版本并发控制,后面详细介绍)。
undo log和redo log记录物理日志不一样,它是逻辑操作日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。
- undo log销毁:undo log在事务执行时产生,事务提交时,并不会立即删除undo log,因为这些日志可能还用于MVCC。
- undo log存储:undo log采用段的方式进行管理和记录,存放的rollback segment回滚段中,内部包含1024个undo log segment.
MVCC
全称Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,快照读为MySQL实现MVCC提供了一个非阻塞读功能。MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log日志、readView。
- 快照读:简单的select(不加锁)就是快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。
- 当前读:读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。对于我们日常的操作,如:select … lock in share mode(共享锁),select … for update、update、insert、delete(排他锁)都是一种当前读。
当隔离级别不同时,读的情况也有所不同,具体如下
Read Committed:每次select,都生成一个快照读。因此有不可重复读问题
Repeatable Read:开启事务后第一个select语句才是快照读的地方。
Serializable:快照读会退化为当前读。
隐藏字段
InnoDB中,每行记录中会包含如下三个隐藏字段
- DB_TRX_ID:最近修改事务D,记录插入这条记录或最后一次修改该记录的事务D。
- DB_ROLL_PTR:回滚指针,指向这条记录的上一个版本,用于配合undo log,指向上一个版本。
- DB_ROW_ID:隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。如果有主键,则不会有该隐藏字段。
undo log
回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志。
当insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除。
而update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会立即被删除。
undo log版本链指的是,不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条记录版本链表,链表的头部是最新的记录,链表尾部是最早的旧记录。如下图
read view
ReadView(读视图)是快照读SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃(未提交)的事务id。
ReadView中包含了四个核心字段:
m_ids
:当前活跃的事务id集合min_trx_id
:最小活跃事务Dmax_trx_id
:预分配事务ID,当前最大事务ID+1(因为事务ID是自增的)creator_trx_id
:ReadView创建者的事务ID
不同的隔离级别,生成ReadViewl的时机不同:
- READ COMMITTED:在事务中每一次执行快照读时生成ReadView。
- REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。
从上图可以推测出,在RC隔离级别下,第一个ReadView可以读取到undo log为0x00002的数据,而第二个ReadView可以读取到undo log为0x00003的数据。而在RR隔离级别下,不存在第二个ReadView,事务5第二次查询时会直接复用第一个ReadView。