简介
MVCC(Multi-Version Concurrency Control)是多版本并发控制,以乐观锁为理论基础,对数据表进行多版本管理来实现数据库的并发控制。这样就可以通过比较版本号,看保证事务的隔离性。
数据库事务
事务四大经典特性:
- 原子性:事务由一个有限的数据库操作序列组成,要么全部执行,要么全部不执行。
- 一致性:事务结束后和开始前,数据不会被破坏,例如转账,总数是不变的。确保操作合法。
- 隔离性:多个事务并发访问时,事务之间是互相隔离的,不会互相影响。
- 持久性:事务完成提交后,该操作会持久性的保存在数据库中
事务并发会产生的问题
- 脏读:一个事务读取到了另一个未提交事务修改过的数据(因为未提交的事务可能发生回滚,导致数据错误)。
- 不可重复读:同一个事务前后多次读取,读取到的数据内容不一致(因为中间有其他事务修改了数据)。
- 幻读:类似于不可重复读,不过幻读针对的是范围查询的结果集。例如查询age大于10的用户,第一次结果为三位,不过中途另一个事务插入了新数据,并提交事务。再次查询相同范围时,结果为四位,两次读取的结果集不一样了,这就是幻读。
四大隔离级别
为了解决并发事务存在的脏读,不可重复读,幻读等问题,数据库设计了四种隔离级别:
- 读未提交:只限制了数据不能被两个事务同时修改,但是修改数据的时候即使事务未提交,都是可以被其他事务读取到的,这种隔离级别存在脏读,重复读,幻读的问题。
- 读已提交:当前事务只能读取到其他事务已经提交的数据,解决了脏读,但是还存在幻读,重复读的问题。
- 可重复读:限制了读取数据的时候,不可以进行修改,解决了重复读的问题,但是读取范围数据的时候,还是可以插入数据的,所以还会存在幻读的问题。
- 串行化:最高级别的隔离。所有事务都是进行串行化孙旭执行的,可以避免所有并发问题,但是事务的执行很耗性能。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
| 读未提交 | ✓ | ✓ | ✓ |
| 读已提交 | × | ✓ | ✓ |
| 可重复读 | × | × | ✓ |
| 串行化 | × | × | × |
核心原理:版本链和读视图
版本链
- 事务ID(TRX_ID):每当事务对聚簇索引中的记录进行修改时,都会把当前事务的id记录到TRX_ID中。
- 回滚指针(ROLL_PTR):每当事务对聚簇索引中的记录进行修改时,都会把该记录的旧版本写到undo日志中,通过ROLL_PTR指指针可以获得该记录的旧版本。
如果一个事务被多次修改,每次修改都会产生undo日志,通过ROLL_PTR指针串联成一个版本链,头结点是该记录的最新值,尾结点是最开始的初始值。
读视图
MVCC实现原理分析
查询流程
- 获取事务自己的版本号,即事务ID
- 获取Read View
- 查询得到的数据,与自己的事务版本号对比
- 如果不符合Read View 的可见性原则,就需要到Undo log种查看历史快照
- 返回符合规则的数据
快照读:读取的记录时可见版本(有旧的版本),如果不加锁,普通select语句都是快照读。
当前读:读取的是记录数据的最新版本,显式加锁都是当前读。
可重复读隔离级别解决不可重复读问题的分析
在读已提交隔离级别下,同一个事务在每次查询时都会产生一个新的Read View副本,这样同一个事务前后读取到的数据就可能不一致。
在可重复读隔离级别下,同一个事务种只会获取一次Read View,副本时共用的,之后的查询也会从这个副本中读取数据,所以查询到的结果都是一样的。
乐观锁
乐观锁是一种思想,总是以乐观的态度,认为自己在操作的时候,其他事务不会对数据进行修改。操作时不会对数据加锁,在第一次读的时候获取这个字段的版本号,需要再次查看该字段的时候再读一次版本号,对两个版本号进行比较,如果一致,代表数据正常,如果不一样,则报错。
悲观锁
悲观的看待操作,操作的时候阻止其他事务对数据进行操作,如果需要操作的数据被锁住,那么会等待到另一个事务操作结束再进行操作。
排他锁,是悲观锁的一种实现,再操作数据时,将这部分数据加锁,等待操作完毕后再解锁,这段时间内禁止其他事务读取或者修改表中的数据。
