InnoDB存储引擎的事务是如何实现的?
作者:徐梦旗,发布于:2024年01月17日 20:00,字数:1.6k,预计阅读:6分钟
1. 事务
1.1. 什么是事务?
事务是指一组操作要么全部成功,要么全部失败。事务具有四大特性[1](ACID):
- 原子性(Atomicity):指一组操作是原子的,要么全部成功,要么全部失败。
- 一致性(Consistency):指事务执行前后,数据需要是一致的状态。
- 隔离性(Isolation):指一个事务执行时,不能受其他事务影响。
- 持久性(Durability):指事务提交后,对数据库的变更要能持久化到磁盘上。
原子性,隔离性和持久性的目的是为了达成一致性。
1.2. 事务有哪些并发问题?
当多个事务并发执行时,会出现以下并发问题:
- 丢失更新(Loss Update):一个事务对数据修改之后,另一个事务也对该数据进行了修改。
- 脏读(Dirty Read):一个事务读取到了另一个尚未提交的事务做出的变更。
- 不可重复读(Non-repeatable Read):一个事务通过相同的查询条件查询出来的数据不一样。
- 幻读(Phantom Read):与不可重复读类似,一个事务查询到了另一个事务新插入的记录。
不同的事务隔离级别[2]对并发问题的容忍度不同。
1.3. 事务有哪些隔离级别?
简称 | 丢失更新 | 脏读 | 幻读 | 幻读 | |
---|---|---|---|---|---|
读未提交 | Read Uncommitted,RU | 否 | 是 | 是 | 是 |
读已提交 | Read Committed,RC | 否 | 否 | 是 | 是 |
可重复读 | Repeatable Read,RR | 否 | 否 | 否 | 是 |
串行化 | Serializable | 否 | 否 | 否 | 否 |
可以通过
show variables like 'transaction_isolation'
命令查看当前事务的隔离级别。
2. 多版本并发控制
2.1. 什么是多版本并发控制?
多版本并发控制[3](MVCC)是指在MySQL数据库中,一条记录存在多个版本,通过一致性视图决定事务可见的版本,实现了读不加锁,读写不冲突的并发控制方式。多版本并发控制依赖记录的三个隐藏字段,undo日志和一致性视图来实现。
2.2. Undo Log的作用是什么?
事务对数据进行变更时,会记录Undo日志[4],用来维持记录的版本链。当事务被提交后且Undo日志不被其他事务使用时,会被回收。
2.3. 什么是一致性视图?
一致性视图[5](Read View)用来判断版本链中的哪个版本对当前事务是可见的,一致性视图包含以下四个部分:
creator_trx_id
(创建该视图的事务ID):指创建该一致性视图的事务ID。m_ids
(视图数组):指创建一致性视图时,活跃的事务ID列表。min_trx_id
(低水位):指创建一致性视图时,活跃的事务ID列表中最小的事务ID。max_trx_id
(高水位):指创建一致性视图时,系统分配给下一个事务的ID。
2.4. 如何判断记录的版本对事务是否是可见的?
版本的可见性判断规则:
- 当版本的事务ID小于
min_trx_id
时,代表创建该版本的事务已提交,故该版本可见。 - 当版本的事务ID等于
creator_trx_id
时,代表创建该版本的事务是当前事务,故该版本可见。 - 当版本的事务ID大于等于
max_trx_id
时,代表创建该版本的事务是在当前事务开启之后开启的,故该版本不可见。 - 当版本的事务ID在视图数组时,代表创建该版本的事务尚未提交,故该版本不可见。
- 当版本的事务ID不在以上情况时,代表创建该版本的事务已提交,故该版本可见。
版本可见性判断的源码如下,见MySQL源码storage\innobase\include\read0types.h
。
private:
/** The read should not see any transaction with trx id >= this
value. In other words, this is the "high water mark". */
// 低水位
trx_id_t m_low_limit_id;
/** The read should see all trx ids which are strictly
smaller (<) than this value. In other words, this is the
low water mark". */
// 高水位
trx_id_t m_up_limit_id;
/** trx id of creating transaction, set to TRX_ID_MAX for free
views. */
// 创建该视图的事务ID
trx_id_t m_creator_trx_id;
/** Set of RW transactions that was active when this snapshot
was taken */
// 活跃事务ID列表
ids_t m_ids;
public:
// 一致性视图
ReadView();
~ReadView();
/** Check whether transaction id is valid.
@param[in] id transaction id to check
@param[in] name table name */
static void check_trx_id_sanity(trx_id_t id, const table_name_t &name);
/** Check whether the changes by id are visible.
@param[in] id transaction id to check against the view
@param[in] name table name
@return whether the view sees the modifications of id. */
// 判断版本是否是可见的?
[[nodiscard]] bool changes_visible(trx_id_t id,
const table_name_t &name) const {
ut_ad(id > 0);
// 当待检查的事务ID小于低水位或等于创建该视图的事务ID时,该版本可见
if (id < m_up_limit_id || id == m_creator_trx_id) {
return (true);
}
check_trx_id_sanity(id, name);
// 当待检查的事务ID大于等于高水位时,该版本不可见
if (id >= m_low_limit_id) {
return (false);
} else if (m_ids.empty()) {
return (true);
}
const ids_t::value_type *p = m_ids.data();
// 当待检查的事务ID在视图数组中时,该版本不可见,否则该版本可见
return (!std::binary_search(p, p + m_ids.size(), id));
}
2.5. 创建一致性视图的时机?
对于不同的事务隔离级别,创建一致性视图的时机不同:
- 对于读未提交(RU),不会创建一致性视图,直接读取版本链上最新的版本。
- 对于读已提交(RC),在每次查询前都会创建一个一致性视图,根据一致性视图来决定记录版本的可见性。
- 对于可重复读(RR),在首次查询前会创建一个一致性视图,后续查询都根据该视图来决定记录版本的可见性。
- 对于串行化(Serializable),在事务开始时会创建一致性视图,通过加锁的方式使得记录只有一个版本。
2.6. 快照读和当前读有什么区别?
- 快照读(Snapshot Read)会借助一致性视图对记录进行可见性判断,不会加锁。
- 当前读(Current Read)会读取记录的最新版本,会加锁。
3. 参考文档
- 1.MySQL :: MySQL 8.0 Reference Manual :: 17.2 InnoDB and the ACID Model ↩
- 2.MySQL :: MySQL 8.0 Reference Manual :: 17.7.2.1 Transaction Isolation Levels ↩
- 3.MySQL :: MySQL 8.0 Reference Manual :: 17.3 InnoDB Multi-Versioning ↩
- 4.MySQL :: MySQL 8.0 Reference Manual :: 17.6.6 Undo Logs ↩
- 5.MySQL :: MySQL 8.0 Reference Manual :: 17.7.2.3 Consistent Nonlocking Reads ↩