前言
最近啃MySQL相关的知识,了解到事务以及事务隔离机制,想要深入了解下事务是如何实现的下面是此次主要探讨的几点方向。这里先推荐几篇文章
- 事务及其ACID特性
- 并发事务带来的问题
- 事务隔离机制以及解决并发事务问题
- 事务隔离机制如何实现(MVCC多版本并发控制机制)
事务及其ACID特性
事务一般具有4个特性,这4个特性一般简称ACID特性
- A(原子性):事务要么全部成功,要么全部失败
- C(一致性):不管什么时候,数据都是一致的,不会读到中间状态的数据
- I(隔离性):多个事务之间相互不影响
- D(持久性):在事务执行过程中,不管怎么样数据都不能丢,MySQL是使用了redo log实现的。
并发事务带来的问题
并发事务带来了更新丢失,脏读,不可重读,幻读4种问题
- 更新丢失:当多个事务同时执行一行数据时,第一个事务已经对行做了修改但第二个事务不知道就把第一个事务更新的数据给修改了,出现了更新丢失的情况。当前事务的更新覆盖了其他事务的更新。
- 脏读:第一个事务已经做了写操作,但此时并没有提交事务,然后第二个事务读取到了这条数据,也就是第二条数据读取到了脏数据,这就是所谓的脏读。当前事务读取了其他事务的已经修改但未提交的数据。
- 不可重读:一个事务开始查询了某个语句,第二次执行同样的语句发现查询的东西不一样了。当前事务在不同时刻执行同样的查询语句,查询出的结果不一样
- 幻读:一个事务按相同的查询条件查询读取已经检索过的数据,却发现其他事务插入了满足条件的数据。当前事务读取到了其他事务插入的数据
事务隔离机制
事务隔离机制有4种,读未提交,读已提交,可重读,串行化
- RU(Read uncommitted):读未提交
- RC(Read committed):读已提交
- RR(Repeatable read):重复读
- SERIALIZABLE(Serializable):串行化
隔离级别对并发事务的解决
| 脏读 | 不可重读 | 幻读 | |
|---|---|---|---|
| 读未提交 | × | × | × |
| 读已提交 | √ | × | × |
| 重复读 | √ | √ | × |
| 串行化 | √ | √ | √ |
读已提交,重复读是通过MVCC机制解决的并发问题。串行化是通过对数据库加锁控制其他事务对数据的操作解决并发问题。事务隔离级别越高对并发的问题越小,但相应的付出的代价也会越大;串行化肯定是并发影响最小的,当你访问某个数据时串行化直接来个行锁或表锁,其他的数据只能阻塞在后面等这边的数据处理完成后才可以进行访问。串行化对数据库性能影响太大所以MySQL综合考虑默认使用的是重复读隔离级别。
MVCC多版本并发控制机制
MVCC(Multi-Version Concurrency Control):多版本并发控制机制,对一行数据的读写操作不通过加锁来保证隔离性,提高性能。MVCC不是MySQL所特有的在Oracle,PostgreSQL都有关于MVCC的实现,实现可能是不一样的。还有MVCC只在读已提交和重复度2个隔离级别下才干活。读未提交只读最新的数据(不管你事务提不提交)它不需要任何锁,串行化直接搞把锁,所以也不需要MVCC来控制。
在介绍MVCC机制前先介绍2个概念,undo log和read view
undo log
undo log是日志版本链,当我们对记录做了变更操作就会产生一条undo记录,undo记录主要在每条数据后面加2个隐藏字段DATA_TRX_ID,DATA_ROLL_PTR其实应该是3个,如果表没有主键还会生成一个DB_ROW_ID。InnoDB多版本控制
- DATA_TRX_ID:事务编号,6字节,记录更新这条行记录的事务编号
- DATA_ROLL_PTR:回滚指针,7字节,记录
- DB_ROW_ID:主键编号,6字节,没有主键会自动生成
事务id的生成是在对innodb进行了写操作而生成的。在InnoDB看来所有的事务在启动时候都是只读状态是只读事务此时不会生成事务编号。数据库内核月报 - 2017 / 12
undo log记录过程:首先表里有一个数据,伴随着隐藏字段DATA_TRX_ID,DATA_ROLL_PTR,然后有一个事务100对其进行了修改,这时候我们会把原先的数据copy一份到undo log中去,然后把修改后的数据也放进undo log,但这条数据的DATA_TRX_ID = 100,DATA_ROLL_PTR指向原来的数据。如果这时候又来了一个事务200,此时事务100还没提交,事务200又进行了一次更新会把新数据写进undo log并且DATA_TRX_ID = 200,DATA_ROLL_PTR指向事务100的那条数据。

最后在undo log中的日志版本链就是

read view(快照)
read view在MVCC里面主要做的就是可见性判断,read view 的生成时机就是RR,RC两种隔离级别的主要区别
- RR隔离级别,只要是开启事务后的第一次select查询操作便会生成一次read view,后面便不再维护read view了
- RC隔离级别,开启事务后,只要进行了查询语句便会生成一次read view
read view创建过程
- 首先看当前所有未提交的事务,存储在数组中trx_ids
- 选取未提交事务的最小的事务编号为up_limit_id,
- 选取已提交事务的最大的事务编号加1为low_limit_id
1)trx_id<up_limit_id;小于最小未提交事务肯定是已提交事务,可见
2)trx_id>low_limit_id;大于最大已提交事务肯定是后面创建的事务,不可见
3)up_limit_id<=trx_id<=low_limit_id;分2种情况
- trx_id在trx_ids中说明是未提交事务,不可见
- trx_id不在trx_ids中说明是已提交事务,可见
MVCC如何利用日志版本链来进行查询的?
根据图第二次查询结果还是hello,但此时undo log日志版本链最新的是hello2,这里主要走的过程是,看undolog日志版本链从最新往下看,最新是事务1,发现事务1在已提交事务和未提交事务区间内而且是在trx_ids中,说明是不可见的,所以继续undolog日志版本链往下看。发现是事务3,发现事务3在已提交事务&未提交事务区间而且不再trx_id中所以是可见的。
