爱可生研发中心工程师,负责项目的需求与维护工作。其他身份:柯基铲屎官。
*爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。
一、MySQL InnoDB 引擎事务隔离级别与并发问题
本文以 MySQL 5.7 版本为例进行说明,开始前让我们先简单复习一下 InnoDB 引擎下的四种隔离级别与三种并发场景下存在的问题,内容如下:

二、Undo Logs
MySQL 的 Undo Logs 保证了数据的原子性,它保存了事务发生之前的数据的一个版本,可以用于事务回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读。Undo Logs 内部分为两种类型,它们分别是:

TRX_UNDO_INSERT:
TRX_UNDO_UPDATE:
除 TRX_UNDO_INSERT 以外,其他都属于该类型(包括删除),事务提交后还可能会被 MVCC 用到,不会立即清理;

三、多版本并发控制「MVCC」(Multi Version Concurrency Control)
Acronym for “multiversion concurrency control”. This technique lets InnoDB transactions with certain isolation levels perform consistent read operations; that is, to query rows that are being updated by other transactions, and see the values from before those updates occurred. This is a powerful technique to increase concurrency, by allowing queries to proceed without waiting due to locks held by the other transactions.
四、快照读「Read View」
当前读
直接从磁盘或 buffer 中获取当前内容的最新数据,读到什么就是什么。根据隔离级别的不同期间会产生一些锁,防止并发场景下其他事务产生影响;
快照读
官方叫做 Consistent Nonlocking Reads(一致性非锁定读取,也叫一致性读取): https://dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html

下面来看一下,在 MySQL 5.7 的源码中,对于 MVCC read view 结构是如何定义的:

| 字段名 | 释义 |
|---|---|
| m_low_limit_id | 下一个待分配的事务ID(当前Max trx_id + 1) |
| m_up_limit_id | 最小活跃事务ID |
| m_creator_trx_id | 触发创建该ReadView的事务ID |
| m_ids | 该ReadView创建时,处于【当前活跃】状态的事务ID有序集合 |
| m_low_limit_no | 最后一个提交的事务number,事务提交时候获取同时写入Undo log中的值,事务Number小于该值的对该ReadView不可见。利用该信息可以Purge不需要的Undohttps://dev.mysql.com/doc/refman/5.7/en/innodb-purge-configuration.html |
| m_closed | 标记该ReadView是否已经closed,用于优化减少trx_sys->mutex这把大锁的使用 |
| m_view_list | 用于存储ReadView的链表信息 |
这里的字段大部分都是为了确保这份 Readview 是否能被其他事务所"看见"的条件,关于可见性的判断就不在此详细说明了。
五、ReadView的创建与关闭

对于 ReadView 来说,在不同的隔离级别下它们的创建与关闭时机会存在区别,具体如下:
读提交 ( READ COMMITTED )「RC」
可重复读 ( REPEATABLE READ )「RR」
innobase_start_trx_and_assign_read_view(
/*====================================*/
handlerton* hton, *!< in: InnoDB handlerton */
THD* thd) *!< in: MySQL thread handle of the user for
whom the transaction should be committed */
{
......
* 在RR级别下,使用start transaction with consistent snapshot会直接创建视图,否则会在第一条SELECT语句时创建 */
* 快照会在事务结束时关闭,整个事务生命周期内共享同一个快照 */
* Assign a read view if the transaction does not have it yet.
Do this only if transaction is using REPEATABLE READ isolation
level. */
trx->isolation_level = innobase_map_isolation_level(
thd_get_trx_isolation(thd));
if (trx->isolation_level == TRX_ISO_REPEATABLE_READ) {
trx_assign_read_view(trx);
} else {
push_warning_printf(thd, Sql_condition::SL_WARNING,
HA_ERR_UNSUPPORTED,
"InnoDB: WITH CONSISTENT SNAPSHOT"
" was ignored because this phrase"
" can only be used with"
" REPEATABLE READ isolation level.");
}
六、为什么说 RR 级别下并没有完全解决幻读?
以下通过两个例子来说明 RR 级别下也能出现幻读的场景,创建一个 test 表,只有一个 id(pk AUTO_INCREMENT) 的字段:
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
案例一:

案例二:

在 RR 级别下普通查询是快照读,并不会看到其他事务插入的数据。这种幻读情况只有在快照读与当前读混合使用的情况下才会出现,这部分也是争议比较多的地方。
然而当前读的定义就是能从 buffer 或磁盘获取到已提交数据的最新值,所以这跟事务的可见性其实并不矛盾。
七、相关资料:
https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
https://dev.mysql.com/doc/dev/mysql-server/latest/classReadView.html
https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.html
本文关键字:#MVCC# #并发事务#
文章推荐:
OceanBase Proxy 无法连接 OBserver 集群
技术分享 | TiUP工具 - TiDB集群滚动升级核心流程解析
关于SQLE
SQLE 获取
| 类型 | 地址 |
|---|---|
| 版本库 | https://github.com/actiontech/sqle |
| 文档 | https://actiontech.github.io/sqle-docs-cn/ |
| 发布信息 | https://github.com/actiontech/sqle/releases |
| 数据审核插件开发文档 | https://actiontech.github.io/sqle-docs-cn/3.modules/3.7_auditplugin/auditplugin_development.html |





