MVCC 与 事务的隔离级别
一、为什么要隔离事务
在只有一个事务对数据库进行操作的时候,不需要用到事务隔离,但在多个事务并发的时候,问题就来了,如果不采取任何措施,可能产生的结果如下:
我们这里假设有事务 T1 和 事务 T2 ,以及一个数据段 x = 0,y = 0; T1 和 T2 事务并行执行; 我们规定一致性要求 x 始终等于 y
1、脏写:
当 T1 执行了 x = 2 ,并且写入内存,此时事务 T1 还没有提交,这时事务 T2 执行了 x = 1, y = 1, 而此时 T1 又执行了 y = 2;当两个线程都提交的时候之后,x = 1, y=2,显然破坏了我们本身的一致性。
而导致这一情况的原因就是,在事务 T1 修改了 x 但没有提交的时候, 事务 T2 就又一次修改了 x ,这就称为脏写
2、脏读
事务 T1 执行了 x = 1,事务 T2 此时进行读取,读取的结果为 x = 1,y = 0,然后 T1 才执行了 y = 1;T2 事务读取的值违背了我们规定的一致性。
在事务 T1 修改了数据,但没有进行提交的时候,T2 就开始了读取工作,这样的读取就称为脏读
3、不可重复读
当事务 T1 读取了 x = 0, y = 0, 事务 T2 进行了 x = 1,y = 1 的写工作,并且 T2 提交了事务。此时 T1 又一次读了 x = 1,y = 1,发现和第一次读取的值不一样,就发生了不可重复读
4、幻读
当 T1 事务根据 P 约束查询出了 k 条数据,而 T2 此时 插入 或者 删除了 一条符合 P 约束的数据, T2 事务第二次根据 P 约束进行查询的时候,会查出 K+1 或者 K-1 条符合的数据,这些减少或者增加的数据就像虚幻出现一样,称为幻读
幻读似乎和不可重复读有些相似之处,都是在一个两次读取中间被另一个事务修改了数据,造成两次读取的结果不一样。
它们的区别就在于 幻读强调 删除 和 插入,不可重复读强调的是 修改
针对在并发事务中产生的几种问题,数据库设计了四种隔离级别
二、SQL 标准中的四种隔离级别
如果我们给上述几种错误的严重性排个序:脏写 > 脏读 > 不可重复读 > 幻读,针对这个排序,设定了四种隔离级别:
隔离级别 | 脏读 | 不可重复读 | 幻影读 |
---|---|---|---|
READ-UNCOMMITTED | √ | √ | √ |
READ-COMMITTED | × | √ | √ |
REPEATABLE-READ | × | × | √ |
SERIALIZABLE | × | × | × |
如上表所示:四种级别分别是
READ UNCOMMITTED(未提交读):不可能出现脏写,可能出现脏读、不可重复读、幻读。
READ COMMITTED(提交读):不可能出现脏写、脏读,可能出现不可重复读、幻读
REPEATABLE READ(可重复读):不可能出现脏写、脏读、不可重复读,可能出现幻读(可以极大的避免幻读)
SERIALIZABLE(可串型化):以上现象都不可能发生
那这几种隔离级别的实现原理是什么?
三、MVCC
在我之前的博客中提到过,在聚簇索引的数据段中其实有两个隐藏属性(见《从根上理解MySQL》388页):
trx_id: 一个事务对该条数据进行修改后,就会记录该 事务 ID
roll_pointer: 每次对该条数据进行修改的时候,就会把旧的版本保存下来,类似链表的形式,链表头保存的是最新的版本,这就是 undo 日志,而这也是MVCC的关键
每次事务进行修改的时候,会将事务的 ID 存储在 trx_id 并且将旧的版本连接在新版本的 roll_pointer 后面,这样形成的一条版本链就称为 MVCC (多版本并发控制)
回到我们上面所介绍的四种隔离方式:
1、READ UNCOMMITTED :由于它的意思是允许读未提交事务修改的数据,所以直接读取数据的最新版本就行
2、READ COMMITTED 和 REPEATABLE READ 都必须保证读到已经提交事务的值(没有提交事务的不能读)
3、SERIALIZABLE:这个需要加锁来实现(之后我会单独写)
那前面两个隔离方式实现的重点就是 :需要读取数据的版本不同
为了实现这个功能,InnoDB 提出了 ReadView(一致性视图),其主要包含四个内容:
每一个事物被创建都有创建这个 ReadView ,来判断该访问的版本
1、m_ids: 在生成 ReadView 时,当前系统中活跃的事务的 id 列表
2、min_trx_id: 在生成 ReadView 时,当前系统中活跃的读写事务中最小的事务 id,也就是 m_ids 中最小 id 值
3、max_trx_id: 在生成 ReadView 时,当前系统应该分配给下一个事务的事务 id 值
4、creator_trx_id: 生成该 ReadView 的事务的事务 id
有了这个 ReadView 就可以进行访问策略,有以下几种情况:
1、如果被访问版本的 trx_id(即事务 id ) 属性值与 ReadView 中的 creator_trx_id值相同,意味着当前事务在访问自己修改的记录,可以访问
2、如果被访问版本的 trx_id 属性值小于 ReadView 中 min _trx_id,说明该事务修改已经在本事务创建的时候就已经提交,可以访问
3、如果被访问版本的 trx_id 属性值大于等于 ReadView 中 max_trx_id,说明该事务在本事务之后才开启,不能访问
4、如果被访问版本的 trx_id 属性值在 max_trx_id 和 min _trx_id之间,这时需要判断 trx_id 是否还在 m_ids 列表里,如果在说明还在活跃状态,不能访问此条记录,如果不在说明已经提交,可以访问。
具体举例见:《从根上理解MySql》392 - 396
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)