博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
事务隔离级别新看法!
阅读量:6204 次
发布时间:2019-06-21

本文共 3432 字,大约阅读时间需要 11 分钟。

  hot3.png

前言

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

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

事务隔离级别

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

有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

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

举个简单的例子:注册。

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

224436_xnXL_2000700.png

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

场景1:

222452_IXqs_2000700.png

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

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

场景2:

222713_Rcs2_2000700.png

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

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

场景3:

121659_D5it_2000700.png

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

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

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

并发问题

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

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

例子:买票

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

111052_3Eyu_2000700.png

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

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

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

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

结语

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

转载于:https://my.oschina.net/js99st/blog/600376

你可能感兴趣的文章
MAC下面maven如何设置让其实下载源码
查看>>
PostgreSQL学习手册(二) 模式(Schema)
查看>>
[iOS Animation]-CALayer 性能优化实例
查看>>
Nagios 安装及常见错误
查看>>
我的友情链接
查看>>
Android版添加phonegap--websocket客户端插件教程
查看>>
MySQL 导出数据
查看>>
Siege压力工具
查看>>
rip
查看>>
使用ImageLoader来加载网络图片
查看>>
apache 重写和虚拟目录配置
查看>>
CentOS多网卡重命名配置
查看>>
滚动条样式设置
查看>>
变态青蛙跳
查看>>
计算机基础,你知道蓝屏的原因吗
查看>>
Git常用命令总结
查看>>
Vim 行号的显示与隐藏
查看>>
学习linux系统到底有没捷径?
查看>>
SpringBoot简要
查看>>
定时任务
查看>>