文档章节

事务隔离级别新看法!

Awisper
 Awisper
发布于 2016/01/10 17:01
字数 2522
阅读 315
收藏 19

前言

我前段时间在写代码的时候,经常考虑并发问题,对事物的安全性、隔离级别需要更深的了解,所以翻看了网上绝大部分关于事务的文章。但是看了之后还是有些疑惑,例如事务的四种隔离级别,虽然有些文章举出了生动的例子,但并没有提到编程中的如何选择使用。

大部分介绍事务的文章,都是介绍什么事务隔离级别的、各种锁的概念,好像举得概念越多,就显示作者了知识更丰富一样,然而并没有实际编程的例子,就像英文教科书般将本该实际运用的东西变成一种学术,就算看懂讲是什么东西也没办法使用。这种教科书式、百科式的文章害人不浅,因此我才写这篇文章。

事务隔离级别

简单来说就是要么一起过,要么全部取消,保证数据的完整性,在普通情况下,事就是这么简单,提交回滚罢了。然而遇到并发问题,数据安全问题,这点了解是不够的。

有4种隔离级别,为什么是4种而没有5种、6种?可能是研究数据库的鼻祖们总结最后得出来的吧。那么下面我要先引用网上绝大部分关于这4种级别的介绍,以下为网上摘抄。

在介绍4种事隔离级别前,需要三个概念:

1. 脏读:一个事务读取到另一个事务尚未提交的数据。

2. 不可重复读:同一事务,两次读取同一数据,得到不同的结果。

3. 幻读:同一事务,用相同的条件读取两次,得到的结果集数据条数不同(数据条数多了或者少了)。

然后为了解决这些个问题,数据库有了4种隔离级别:

1. TRANSACTION_READ_UNCOMMITTED:防止更新丢失,允许脏读、不可重复读、幻读。

2. TRANSACTION_READ_COMMITTED:防止脏读,允许不可重复读、幻读,这也是多数数据库默认的隔离级别。

3. TRANSACTION_REPEATABLE_READ:防止脏读、不可重复读,允许幻读。

4. TRANSACTION_SERIALIZABLE:防止脏读、不可重复读,幻读。(事务完全串行化执行,事务一个一个按顺序依次执行,可以不会产生并发问题。)

还附带了一张表:


丢失更新 脏读
不可重复读 幻读
未提交读
N Y Y Y
已提交读
N N Y Y
可重复读
N N N Y
串行化
N N N N

还有一些文章对隔离级别的理解:“级别越高越能保证数据完整性一致性”,“一个级别解决一个问题”,有的还画出隔离级别与并发的关系坐标图。。。

更合理的级别划分

网上几乎所有文章都是这么写,其实有些数据库官方的文档也是这么解释的。这么解释从某个角度是正确的,但是我就有些疑问了,“READ_UNCOMMITTED”这个隔离级别不就是废物了么?为什么Oracle和SQLservice的默认隔离级别是READ_COMMITTED,Mysql的默认隔离级别是REPEATABLE_READ,Oracle这种商业的数据库默认隔离级别安全性比开源的数据库还要低?java项目使用的Hibernate为的是跨数据库,能从Oracle和Mysql之间切换,那么切换后默认事隔离级别变了不会影响安全性么?解决幻读一定要使用SERIALIZABLE么,怎么我看很多项目基本上都没用这个呢?等等各种疑问。

通过结合实际案例研究,我认为隔离级别应该这么划分:

REPEATABLE_READ < READ_COMMITTED < READ_UNCOMMITTED < SERIALIZABLE

而且,我要说的是我这种隔离级别的划分才是合理的!可能你从没见过有人这么定义隔离级别,而且官方都不这么定义的!我只用事实说话,下面我结合实际编程的例子,分析为什么隔离级别为什么按我那样划分才合理。

举个简单的例子:注册。

我们在做注册这个功能的时候,通常要进行这么个步骤:

在这个需求下,是不能简单说使用哪种隔离级别好的,这是分开几个步骤,并不是一瞬间,有可能在这几个步骤之间,其他操作了数据库,所以必须结合时间顺序!

场景1:

假如使用REPEATABLE_READ ,例如Mysql,它在实现该隔离级别是用MVCC,即读取记录的时候过滤时间戳,即使在当前线程的过程中其他对数据库进行增加、修改或删除,也是看不到的,达到了数据的一致性。然而这个”一致性“在这个场景并没有好处,因为若线程1和线程2都注册同一个用户名,线程2在时间1的时候提交了数据线程1看不到,会认为该用户名还没注册,允许用户注册,当插入数据库的时候,由于unique约束报错了。

在这种情况下,使用READ_COMMITTED ,能看到其他提交了的数据,明显更具有准确的检验结果!

场景2:

线程2的提交时间变成了时间3,此时READ_COMMITTED 就没什么作用了,这种场景下下READ_UNCOMMITTED 就发挥作用了。READ_UNCOMMITTED 其实是一个更加强大的隔离级别,连其他未提交的东西都能看到,尤其在一些简单的逻辑上,插入数据库紧接着下一步就是提交无误,这种情况下是比较适合使用READ_UNCOMMITTED 这个隔离级别的。

经过我在Mysql测试当隔离级别为READ_UNCOMMITTED 的时候,同时具有READ_COMMITTED 和READ_UNCOMMITTED 的能力,即能看到其他线程提交了的数据和未提交的数据!

场景3:

此场景中两个事物在检查用户名是否存在的时候,数据库确实没有记录,连未提交的也没有,它们在插入数据库的时候仅仅相差了一步!在Mysql中的测试情况下是,线程1和2都会进行插入记录这个动作,但是由于有唯一约束,线程1较晚插入会受到阻塞而不是报错,因为它要看另外一个线程是打算提交还是回滚,如果回滚,线程1将继续执行,如果线程2提交了,线程1报错。

这种情况除了使用SERIALIZABLE隔离级别,其他隔离级别都不能防止另一个线程报错,然而SERIALIZABLE隔离级别是完全阻塞,如该场景线程2先开启,线程1连检查用户名是否存在这一步都会阻塞,但其他查询业务也一样受到阻塞,从效率来说来是非常不好的。

举这个例子,是想借此介绍下各个隔离级别在同一个场景下的表现,而不是探讨怎么解决该例子的问题。我举的这个例子,“用户名”有唯一约束,这已经是保证数据正确性的终极防线了,检查用户名是否存在的作用,只是在页面注册的时候用Ajax提前检查,增加用户体验而已,即使没有数据的安全性也是能达到的,所以我觉得这个功能使用REPEATABLE_READ、READ_COMMITTED或READ_UNCOMMITTED都是可以的

并发问题

了解了隔离级别的真正意义后,我们要探讨下并发问题,因为事物隔离级别也是因为并发而产生的。上面我举了一个注册的例子,具有并发问题,但是问题并不严重,因为“用户名”这个字段有unique约束,即使不做插入前的校验步骤,也不会导致出现两个相同的用户名这种错误。

那么现在来讨论一个没有约束的例子,没有约束的话,就必须靠解决并发问题来保证数据安全性。

例子:买票

我们在做这个功能的时候,需要进行这么个步骤:

由于数据库没有正整数类型,没有约束来防止数据出错,如果出现注册那个例子中的场景3,是会导致余票变为负数。

那么,其他三个事物隔离级别都没有100%的作用,是不是得出杀手锏SERIALIZABLE了?答案是否的,因为使用SERIALIZABLE隔离级别会完全阻塞其他事物,会导致当有一个人进行购票的时候,其他对该涉及的表的查询都要等待,这种情况怎么能忍?除非只有该对涉及的表有操作,但这种情况几乎是不可能的,即使有也不能保证将来业务扩充。

解决办法1:利用synchronized让方法同步执行,是解决这个并发场景的常见办法。

解决办法2:Hibernate乐观锁,也就是@Version注解。在存放着“余票”字段的表中,增加一个int型字段,同时使用@Version注解,这个字段就表示版本号,每次修改版本号就会递增。当前线程持久化时如果检测到版本号变化,即有其他线程修改过该记录,将会抛异常,我们可以在抛异常的时候做一些其他措施,或者什么都不做。

结语

我对事物隔离级别的看法,还有解决并发问题的思路可能比较浅薄,但还是希望这篇文章能帮到大家,同时欢迎留言探讨共同进步!

© 著作权归作者所有

Awisper

Awisper

粉丝 1
博文 34
码字总数 23104
作品 0
广州
私信 提问
加载中

评论(3)

Awisper
Awisper 博主

引用来自“JackieRiver”的评论

我有一点不同的看法.
文章是分四个场景来区分四种事务隔离级别的影响,但是实际中四种场景都是存在的,不可能只根据那种场景去决定事务隔离级别;所以要从整个场景来分析哪些异常场景发生的多一些,再来决定由那个隔离级别;
四种隔离级别是从事务隔离,也就是从事务互补干扰的程度来排序的,而文中是以最后能否提交数据来判断的不是从事务隔离的角度,因此即是说排序的角度不同,大家所公认的是事务隔离级别排序,而文中应该是事务影响级别;
文中列举的场景只是用来分析在各个事务隔离级别下,其他事务对数据的操作是否影响当前事务的查询,就实际场景来说用哪个都不会影响最后数据的正确性。
JackieRiver
JackieRiver
我有一点不同的看法.
文章是分四个场景来区分四种事务隔离级别的影响,但是实际中四种场景都是存在的,不可能只根据那种场景去决定事务隔离级别;所以要从整个场景来分析哪些异常场景发生的多一些,再来决定由那个隔离级别;
四种隔离级别是从事务隔离,也就是从事务互补干扰的程度来排序的,而文中是以最后能否提交数据来判断的不是从事务隔离的角度,因此即是说排序的角度不同,大家所公认的是事务隔离级别排序,而文中应该是事务影响级别;
JackieRiver
JackieRiver
留下实际编程的demo呗
数据库事务的ACID特性和隔离级别

事务的四个特性 数据库事务(Transaction)是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。一方面,当多个应用程序并发访问数据库时,事务可以在应用程序间提...

foodon
2015/01/27
0
0
重新学习Mysql数据库8:MySQL的事务隔离级别实战

SELECT @@session.tx_isolation; //查询当前会话事务 //测试可以不用设置全局事务SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;(这个可以不用设,只设置上面一行就可以了进行测...

你的猫大哥
2018/01/30
0
0
DB2 SQL RR/RS/CS/UR四个级别(转)

1.RR隔离级别:在此隔离级别下, DB2会锁住所有相关的纪录。在一个SQL语句执行期间,所有执行此语句扫描过的纪录都会被加上相应的锁。具体的锁的类型还是由操作的类型来决定,如果是读取,则...

JackMo2015
2018/03/02
0
0
mysql乱七八糟的可重复读隔离级别实现

mysql的隔离级别并非是按照标准实现的,作为从pg切过来的程序员还真是不太适应,这篇文章讨论mysql隔离级别实现的,希望对大家能有帮助。 什么是事务 事务是数据库一组读写操作的集合,事务具...

徐文韬
2017/09/11
0
0
事务隔离级别和脏读的快速入门(转载)

关键要点 仅从ACID或非ACID角度考虑问题是不够的,你应知道你的数据库支持何种事务隔离级别。一些数据库宣称自己具有“最终一致性”,但却可能对重复查询返回不一致的结果。相比于你所寻求的...

treenewtreenew
2016/11/16
13
0

没有更多内容

加载失败,请刷新页面

加载更多

ubuntu 安装zoom

之前ubuntu安装zoom失败了,也没管。后来安装其他软件报出依赖错误,由于zoom安装失败导致其他并不想干的安装出错。 pvc@pvc-Vostro-3650:~$ sudo apt-get install libnet1-dev 正在读取软件...

琴麻岛
11分钟前
1
0
Spring Boot项目每次请求Session都不一样的记录

背景 网站注册模块有个带图片验证码验证的环节,实现思路为:前端请求获取图片验证码的接口,接口里生成图片验证码,并保存在session;验证图片验证码时,从session中获取图片验证码与当前请...

豫华商
12分钟前
1
0
Azure Monitor现可一次监控整个虚拟机扩展集

Azure Monitor现在可以用来分析以及监控虚拟机扩展集(Scale Set)的健康程度以及效能,支持的操作系统包含Windows和Linux,目前这项功能仍在公开预览阶段。虚拟机扩展集指的是由Azure VM Sc...

kocker
16分钟前
1
0
上篇:《对于HashMap,你知道多少?》

阅读目录 一、前言 二、源码解读 三、并发场景中使用HashMap会怎么样? 四、怎样合理使用HashMap? 一、前言 HashMap在面试中是个火热的话题,那么你能应付自如吗?下面抛出几个问题看你是否知...

Java干货分享
20分钟前
1
0
突破!阿里云CDN实现毫秒级全网刷新

通常在某网站使用了CDN节点来实现内容分发加速后,当源站内容更新的时候,CDN刷新系统会通过提交刷新请求将CDN节点上的指定缓存内容强制过期。当用户访问的时候,CDN节点将回源获取最新内容返...

zhaowei121
22分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部