在介绍 MySQL 锁之前,我们先看一下事务隔离的发展历史;
事务隔离是数据库系统设计中根本的组成部分,本文主要从标准层面来讨论隔离级别的发展历史,首先明确隔离级别划分的目标;之后概述其否定之否定的发展历程;进而引出 Adya给出的比较合理的隔离级别定义,最终总结隔离标准一路走来的思路。
事务隔离是事务并发产生的直接需求,最直观的、保证正确性的隔离方式,显然是让并发的事务依次执行,或是看起来像是依次执行。但在真实的场景中,有时并不需要如此高的正确性保证,因此希望牺牲一些正确性来提高整体性能。通过区别不同强度的隔离级别使得使用者可以在正确性和性能上自由权衡。随着数据库产品数量以及使用场景的膨胀,带来了各种隔离级别选择的混乱,数据库的众多设计者和使用者亟需一个对隔离级别划分的共识,这就是标准出现的意义。一个好的隔离级别定义有如下两个重要的目标:
-
正确:每个级别的定义,应该能够将所有损害该级别想要保证的正确性的情况排除在外。也就是说,只要实现满足某一隔离级别定义,就一定能获得对应的正确性保证。
-
实现无关:常见的并发控制的实现方式包括,锁、OCC以及多版本 。而一个好的标准不应该限制其实现方式。
ANSI SQL标准(1992):基于phenomenas【现象】
1992年 ANSI 首先尝试指定统一的隔离级别标准,其定义了不同级别的phenomenas, 并依据能避免多少现象来划分隔离标准。现象包括:
- 脏读(Dirty Read): 读到了其他事务还未提交的数据;
- 不可重复读(Non-Repeatable/Fuzzy Read):由于其他事务的修改或删除,对某数据的两次读取结果不同;
- 幻读(Phantom Read):由于其他事务的修改,增加或删除,导致Range的结果失效(如where 条件查询;
通过阻止不同的异象发生,得到了四种不同级别的隔离标准:
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
未提交读 | 发生 | 发生 | 发生 |
已提交读 | 避免 | 发生 | 发生 |
可重复读 | 避免 | 避免 | 发生 |
串行化 | 避免 | 避免 | 避免 |
ANSI SQL标准看起来是非常直观的划分方式,不想要什么就排除什么,并且做到了实现无关。然而,现实并不像想象美好。因为它并不正确
A Critique of ANSI(1995):基于锁
几年后,微软的研究员们在A Critique of ANSI SQL Isolation Levels一文中对ANSI的标准进行了批判,指出了其问题
1. 不完整,缺少对Dirty Write【脏写】的排除
简单的来说就是,一个事务的更新覆盖了另一个事务的更新
假设 s = 100
1.A操作:s =200
2.B 操作 s = 300,B提交
3.此时A回滚,s=100
4.B读取时,发现自己的更新操作丢失
2.歧义
ANSI SQL的英文表述有歧义。以Phantom为例,如下图历史H3:
H3:r1[P] w2[insert y to P] r2[z] w2[z] c2 r1[z] c1
假设T1根据条件P查询所有的雇员列表,之后T2增加了一个雇员并增加了雇员人数值z,之后T1读取雇员人数z,最终T1的列表中的人数比z少,不一致。但T1并没有在T2修改链表后再使用P中的值,是否就不属于ANSI中对Phantom的定义了呢?这也导致了对ANSI的表述可能有严格和宽松两种解读。对于Read Dirty【脏读】和Non-Repeatable【不可重复读】/Fuzzy Read【幻读】也有同样的问题。
那么,如何解决上述两个问题呢?Critique of ANSI的答案是:宁可错杀三千,不可放过一个,即给ANSI标准中的异象最严格的定义。Critique of ANSI改造了异象的定义:
P0: w1[x]…w2[x]…(c1 or a1) (Dirty Write)
P1: w1[x]…r2[x]…(c1 or a1) (Dirty Read)
P2: r1[x]…w2[x]…(c1 or a1) (Fuzzy or Non-Repeatable Read)
P3: r1[P]…w2[y in P]…(c1 or a1) (Phantom)
此时定义已经很严格了,直接阻止了对应的读写组合顺序。仔细可以看出,此时得到的其实就是基于锁的定义:
Read Uncommitted,阻止P0:整个事务阶段对x加长写锁
Read Commited,阻止P0,P1:短读锁 + 长写锁
Repeatable Read,阻止P0,P1,P2:长读锁 + 短谓词锁 + 长写锁
Serializable,阻止P0,P1,P2,P3:长读锁 + 长谓词锁 + 长写锁
MySQL 锁概要介绍
- MyISAM 存储引擎默认支持表级锁,InnoDB 默认支持行级锁,详细介绍
- MySQL锁之数据库事务的四大特性
- MySQL 锁之事务并发访问产生的问题以及事务隔离机制
- MySQL 锁之InnoDB可重复读隔离级别下如何避免幻读
- MySQL 锁之RC、RR 级别下的InnoDB的非阻塞读如何实现
后续会逐步分析 MySQL 锁的详细内容
总结
完整的隔离标准
事务隔离级别 | 脏写【更新丢失】 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
未提交读 | 避免 | 发生 | 发生 | 发生 |
已提交读 | 避免 | 避免 | 发生 | 发生 |
可重复读 | 避免 | 避免 | 避免 | 发生 |
串行化 | 避免 | 避免 | 避免 | 避免 |
数据库是一个多用户使用的共享资源,比如一个库存表,当两个线程并发的减少库存时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性(脏读,不可重复读,幻读等),可能产生死锁。
为了解决这个问题,锁是一个非常重要的技术,对实现数据库并发控制是一个好的方案。简单说,当一个执行sql语句的事务想要操作表记录之前,先向数据库发出请求,对你访问的记录集加锁,在这个事务释放这个锁之前,其他事务不能对这些数据进行更新操作
网友评论