1 相关知识

MySQL 服务器逻辑架构图

存储引擎负责 MySQL 中数据的存取,事务是在存储引擎中被实现的,但不是所有的存储引擎都支持事务。多个事务需要在同一时刻修改数据,都会产生并发控制问题。处理并发读或写可以通过读写锁来解决问题,读锁共享,多个客户在同一时刻同时读取同一个资源,写锁是排他性的,一个写锁会阻塞其他的写锁和读锁。

表锁:锁定整张表的锁策略,读写锁是在表的粒度下进行。
行锁:锁定行记录。在存储引擎层实现。

读取共同资源的时候,会产生并发控制,通过对表或行记录范围,实行读写锁来解决问题。当多个事务在同一资源上相互占用,并请求对方占用的资源,存在死锁现象,那么需要实现死锁检测和死锁超时机制进行处理。

2 事务

事务是一个独立的工作单元,事务内的语句要么全部执行成功,要么全部执行失败。事务的属性:原子性、一致性、隔离性、持久性。事务中的所有操作要么成功,要么失败回滚,数据在转换的过程中是一致的,比如 A 的余额-100,B 的余额 +100,多个事务对同一资源进行操作的时候,能够不互相影响,要不然无法保证事务的一致性,当事务提交之后,所做的修改就会永久保存在数据中。

针对事务的隔离性,有四种隔离级别,规定了事务内和事务间数据的可见性,越低的隔离可以执行更高的并发,系统开销更低。

Read Uncommitted:事务中未提交的修改,其他事务可见,也被称为脏读。
Read Committed:事务执行期间,只能看见已经提交的事务所做的修改,多次查询的事务,会存在前后查询同样的数据,结果不同,即 norepeatable read。
Repeatable Read:同一个事务在执行期间,前后查询同样的记录结果是一致的。但是,当新纪录插入的时候,之前事务读取的范围会不一致,产生幻读。
SERIALIZABLE:强制事务串行执行。

MySQL 中的事务

默认事务模式:MySQL 提供了两种事务型的存储引擎,InnoDB 和 NDB Cluster。默认 autocommit,如果不是显式开始一个事务,则每个查询都被当作一个事务执行提交操作。

事务隔离级别的生效范围: SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL

隐式和显示锁定:InnoDB 采用两阶段锁定协议(隐式),事务执行的过程中,随时都可以锁定,锁只有在执行 commit 或 rollback 的时候才会释放,并且所有的所在同一个时刻被释放。InnoDB 也支持通过特定的语句进行显示锁定。

关闭 auto commit,事务数量可以减少,减少并发,锁减少,性能有所提高。

3 多版本并发控制 MVCC

行级锁的变种,非阻塞的读操作,写操作只锁定必要的行。通过保存数据在某个时间点的快照来实现的。根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。

不同的存储引擎对 MVCC 的实现是不同的,有 optimistic MVCC 和 pessimistic MVCC。MVCC 保障的是 Read Committed、Repeatable Read 这两种级别工作。

InnoDB MVCC 实现

事务版本号:每次事务开启会从数据库获得一个自增长的事务 ID,从而可判断事务的执行先后顺序。
表的隐藏列
DB_TRX_ID : 操作改记录的事务 ID;
DB_ROLL_PTR:上一版本数据在 undo log 里位置指针 (从 undo log 中分析出该行记录以前的数据是什么,从而提供该行版本信息);
DB_ROW_ID: 行 ID,B+ 树索引特性要求每个表必须要有一个主键,没设置的话,会自动寻找第一个不包含 NULL 的唯一索引列作为主键。如果还找不到,会在这个 DB_ROW_ID 上自动生成一个唯一值,以此来当作主键。
undo log:记录数据被修改之前的日志,在表信息修改之前先会把数据拷贝到 undo log 里,当事务进行回滚时可以通过 undo log 里的日志进行数据还原。
Read View
每个事务开启后都会得到一个 Read View,其数据结构:
trx_ids: 当前未提交的事务 id(事务版本号)
min_trx_id:当前未提交的事务 id 中的最小值,即 m_ids 的最小值。
max_trx_id:创建 Read View 时当前数据库中应该给下一个事务的 id 值,即当前系统最大事务版本号 +1。
creator_trx_id :创建该 Read View 的事务的事务 id。

玩法

假设事务 A 修改记录,事务 B 要查询该条记录(事务 B 在多个事务都在用这条记录的情况下使用,事务 B 在查询的时候,它只是在这条记录的事务版本中某个时间点,所以说事务处于不同的时间点,Read View 可能是不同的,目的:实现 Read Committed、Repeatable Read 隔离级别)

DB_TRX_ID < min_trx_id:操作该记录的事务 id < 当前未提交的事务 id 的最小值,那么该条记录已被事务 A 修改提交完成,当前版本的记录可用。
DB_TRX_ID > max_trx_id: 操作该记录的事务 id > 当前系统最大事务版本号,那么该条记录将被事务 A 修改,根据 Read Committed,当前版本的记录是不可用的。
当 min_trx_id <= DB_TRX_ID <= max_trx_id:
DB_TRX_ID 在 trx_ids 里,该记录的版本在未提交的事务里,那么当前版本的记录不可用;
DB_TRX_ID 不在 trx_ids 里,该记录的版本不在未提交的事务里,那么当前版本的记录是已经提交的版本,可用。

当 DB_TRX_ID 不满足 Read View 的规则,就要从 undo_log 里拿上一个版本的值重新进行比较,直至到满足规则。

我想,记录是一个版本链,串行,基于之前的记录逐链修改,空间换时间吧。

学习资料:
《高性能 MySQL》
https://zhuanlan.zhihu.com/p/442643107
https://zhuanlan.zhihu.com/p/52977862?utm_id=0


↙↙↙阅读原文可查看相关链接,并与作者交流