事务原理
事务原理
事务性质 ACID
- A: undo log
- I: MVCC
- D: redo log
redo log
保证:持久性:事务一旦提交或回滚,他对数据库中数据的改变就是永久的。
物理日志:存操作的指令
该日志文件由两部分组成:
- 重做日志缓冲(redo log buffer)
- 重做日志文件(redo log file)
前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中,用于在刷新脏页到磁盘,发生错误时,进行数据恢复使用。
WAL,先写日志,再往磁盘写,这样可以先记录下来
保证刷新脏页发生错误时,可以恢复数据
既然 redo log 也需要在事务提交时将日志写入磁盘,为什么它比直接将 Buffer Pool 中修改的数据写入磁盘(即刷脏)要快呢?主要有以下两方面的原因:
- 刷脏是随机 IO,因为每次修改的数据位置随机,但写 redo log 是追加操作,属于顺序 IO。
- 刷脏是以数据页(Page)为单位的,MySQL 默认页大小是 16KB,一个 Page 上一个小修改都要整页写入;而 redo log 中只包含真正需要写入的部分,无效 IO 大大减少。
undo log
保证:原子性:事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
作用:
- 提供回滚
- MVCC
逻辑日志:存操作的指令的反向操作(操作以前会把历史版本记入)
- Undo log 销毁:undo log 在事务执行时产生,事务提交时,并不会立即别除 undo log,因为这些日志可能还用于 MVCC。
- Undo log 存储:undo log 采用段的方式进行管理和记录,存放在前面介绍的 rollback segment 回滚段中,内部包含 1024 个 undo log segment。
MVCC
保证:隔离性:保证事务在不受外部并发操作影响的独立环境下运行。
实现:隔离级别
概念
MVCC
全称 Multi-Version Concurrency Control,多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,快照读为 MySQL 实现。
MVCC 提供了一个非阻塞读功能。MVCC 的具体实现,还需要依赖于:
- 数据库聚簇索引记录中的三个隐式字段
- undo log 日志
- read view
当前读/锁定读
特点:读取的记录是最新版本,加锁防止别人修改。(可以完全解决插入或删除导致的幻读)
select xxx lock in share mode
select ... for update
insert
、update
、delete
操作
快照读/一致性非锁定读
简单的 select (不加锁) 就是快照读,快照读,读取的是记录数据的可见版本,有可能是历史数据,不加锁,是非阻塞读。
- Read Committed: 每次 select,都生成一个快照读。
- Repeatable Read: 开启事务后第一个 select 语句才是快照读的地方。
- Serializable: 快照读会退化为当前读。
在一致性非锁定读下,即使读取的记录已被其它事务加上 X
锁,这时记录也是可以被读取的,即读取的快照数据。
上面说了,在 Repeatable Read
下 MVCC
防止了部分幻读,这边的 “部分” 是指在 一致性非锁定读
情况下,只能读取到第一次查询之前所插入的数据(根据 Read View 判断数据可见性,Read View 在第一次查询时生成)。但是如果是 当前读
,每次读取的都是最新数据,这时如果两次查询中间有其它事务插入数据,就会产生幻读。所以, InnoDB
在实现Repeatable Read
时,如果执行的是当前读,则会对读取的记录使用 Next-key Lock
,来防止其它事务在间隙间插入数据。
隐藏字段
DB_TRX_ID(6字节)
:表示最后一次插入或更新该行的事务 id。此外,delete
操作在内部被视为更新,只不过会在记录头Record header
中的deleted_flag
字段将其标记为已删除DB_ROLL_PTR(7字节)
: 回滚指针,指向该行的undo log
。如果该行未被更新,则为空DB_ROW_ID(6字节)
:如果没有设置主键且该表没有唯一非空索引时,InnoDB
会使用该 id 来生成聚簇索引
ibd2sdi xxx.ibd
可以查看这张表的定义格式,可以看到所有的字段包括隐藏字段
undo log
undo log 中是一个版本链(链表)
不同事务或相同事务对同一条记录进行修改,会导致该记录的 undolog 生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。
作用
undo log
主要有两个作用:
- 当事务回滚时用于将数据恢复到修改前的样子
- 另一个作用是
MVCC
,当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过undo log
读取之前的版本数据,以此实现非锁定读(快照读)
分类
undo log
分为两种: insert undo log
和 update undo log
:
insert undo log
:指在insert
操作中产生的undo log
。因为insert
操作的记录只对事务本身可见,对其他事务不可见,故该undo log
可以在事务提交后直接删除。不需要进行purge
操作update undo log
:update
或delete
操作中产生的undo log
。该undo log
可能需要提供MVCC
机制(快照读),因此不能在事务提交时就进行删除。提交时放入undo log
链表,等待purge线程
进行最后的删除
Read View
读视图,是快照读 SQL 执行时 MVCC 提取数据的依据,记录并维护系统当前活动的事务(未提交的)id。确定当前快照读应该读哪个版本。 主要是用来做可见性判断,里面保存了 “当前对本事务不可见的其他活跃事务”。
核心字段
ReadView 中包含了四个核心字段:
字段 | 含义 |
---|---|
m_ids | 当前活跃的事务 ID 集合,注意是一个列表,“活跃事务”指的就是,启动了但还没提交的事务。 |
min_trx_id | 最小活跃事务 ID,即m_ids 的最小值 |
max_trx_id | 预分配事务 ID,当前最大事务 ID+1(因为事务 ID 是自增的) |
creator_trx_id | 该 ReadView 创建者的事务 ID |
m_low_limit_no | 事务 Number, 小于该 Number 的 Undo Logs 均可以被 Purge |
数据可见性算法
一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况:
- 如果记录的 trx_id 值小于 Read View 中的
min_trx_id
值,表示这个版本的记录是在创建 Read View 前已经提交的事务生成的,所以该版本的记录对当前事务可见。 - 如果记录的 trx_id 值大于等于 Read View 中的
max_trx_id
值,表示这个版本的记录是在创建 Read View 后才启动的事务生成的,所以该版本的记录对当前事务不可见。 - 如果记录的 trx_id 值在 Read View 的
min_trx_id
和max_trx_id
之间,需要判断 trx_id 是否在 m_ids 列表中:- 如果记录的 trx_id 在
m_ids
列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见。 - 如果记录的 trx_id 不在
m_ids
列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见。
- 如果记录的 trx_id 在
遇到不可见的记录就会利用 DB_ROLL_PTR 沿着undo log版本链往下找,直到找到满足的快照版本或返回空。
生成时机
不同的隔离级别,生成 Readview 的时机不同:
- READ COMMITTED:会在事务中每一次执行快照读时生成 Read View。
- REPEATABLE READ:仅在事务中第一次执行快照读时生成 Read View,后续复用该 Read View,(小林中说是事务启动的时候生成)
未解决幻读情况
锁定读下完全解决了因插入和删除导致的幻读,但是非锁定读的情况下没有完全解决。
情况一:快照读
情况二:当前读
- T1 时刻:事务 A 先执行「快照读语句」:select * from t_test where id > 100 得到了 3 条记录。
- T2 时刻:事务 B 往插入一个 id= 200 的记录并提交;
- T3 时刻:事务 A 再执行「当前读语句」 select * from t_test where id > 100 for update 就会得到 4 条记录,此时也发生了幻读现象。
要避免这类特殊场景下发生幻读的现象的话,就是尽量在开启事务之后,马上执行 select ... for update 这类当前读的语句,因为它会对记录加 next-key lock,从而避免其他事务插入一条新记录。
一致性保证
一致性是事务追求的最终目标,实现一致性的措施包括:
- 保证原子性、持久性和隔离性,如果这些特性无法保证,事务的一致性也无法保证.
- 数据库本身提供保障,例如不允许向整形列插入字符串值、字符串长度不能超过列的限制等
- 应用层面进行保障,例如如果转账操作只扣除转账者的余额,而没有增加接收者的余额,无论数据库实现的多么完美,也无法保证状态的一致.
CAP 和 ACID 中的一致性
- CAP 定理中的数据一致性,其实是说分布式系统中的各个节点中对于同一数据的拷贝有着相同的值
- ACID 中的一致性是指数据库的规则,如果 schema 中规定了一个值必须是唯一的,那么一致的系统必须确保在所有的操作中,该值都是唯一的。
由此来看 CAP 和 ACID 对于一致性的定义有着根本性的区别。
总结事务的 ACID 四大基本特性是保证数据库能够运行的基石,但是完全保证数据库的 ACID,尤其是隔离性会对性能有比较大影响,在实际的使用中我们也会根据业务的需求对隔离性进行调整,除了隔离性,数据库的原子性和持久性相信都是比较好理解的特性,前者保证数据库的事务要么全部执行、要么全部不执行,后者保证了对数据库的写入都是持久存储的、非易失的,而一致性不仅是数据库对本身数据的完整性的要求,同时也对开发者提出了要求-写出逻辑正确并且合理的事务。最后,也是最重要的,当别人在讲一致性的时候,一定要搞清楚他的上下文。