Hibernate 性能之事务与并发

1. 前言

本节课和大家一起聊一聊事务和并发。通过本节课程的学习,你将了解到:

  • 什么是事务 ;
  • 事务的隔离机制。

2. 事务

什么是事务?

一讲到事务,就有一个典型的案例:转账。

转账业务涉及到 2 个账号的变更。例如 A 现在要把自己账户上仅有的 100 元大钞转给自己的好朋友 B。

转账过程中至少要涉及到两条更新语句:

  • A 账面上的钱减少,使用更新语句实现:
Update 账号表 set 我的钱=我的钱-100 where 账号拥有人=A
  • B 账户上的钱增多,使用更新语句实现:
Update 账号表 set 我的钱=我的钱+100 where 账号拥有人=B

如果、万一这两条更新语句中有一条没有执行成功,会发生什么情况了?

  • 发生在 A 身上的更新语句没有成功,B 的更新语句成功了。也就是说 A 账面上的钱没有减少,B 账面上的钱却增加了。天呀!这是何等好事,关键是这钱是从哪里来的,左想右想,看来只能是银行里来的,但是,你觉得银行会做这种傻事吗?不会!那银行又是如何保证不让这种事情发生了。

  • 发生在 B 身上的更新语句没有成功,A 的更新语句成功了。也就是说 A 的钱减少了,但是 B 没有增加。傻眼了吧,莫名其妙的钱就不见了。钱去哪儿了?你会让这种事情发生吗?也不会,你会投诉银行的。

当然,如果这 2 条 SQL 语句执行成功或者失败,则不会发生任何损失,可见,咱们必须控制这两条 SQL 语句要么都成功,要么都失败。

对!这就是事务。

所谓事务,就是把一个业务逻辑当成一个逻辑整体单元,其中的执行代码要么一起成功,但凡执行过程中出现了某些错误,就恢复到最原始的状态。

转账就是一个业务逻辑,整个业务逻辑中至少包括 2 条 SQL 语句,这 2 条 SQL 语句互为依靠,彼此脱单对业务逻辑没有任何意义。所以,必须当成一个整体看待。

一个事务单元有 4 个特性,也就是事务的 ACID 特性:

  • 原子性(atomicity): 表示一个事务内的所有操作是一个整体,要么全部成功,要么全部失败;
  • 一致性(consistency): 表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改前的状态;如前面转账案例,转账前 A 和 B 加起来有多少钱,无论转账是否成功,最后 A 和 B 加起来的钱应该和前面相等;
  • 隔离性(isolation): 事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据;
  • 持久性(durability): 事务完成之后,它对于系统的影响是永久性的。事务结束后,就没有什么后悔药了。

4 个特性中除了隔离性都比较好理解。所以,剩下的篇幅中,咱们好好聊一聊隔离性。要讲透隔离性,肯定就离不开并发概念。

什么是并发?

并发是计算机中的一个概念,但是在现实生活中还是能找到一个能类比的例子。

火车上,2个人同时去一间洗手间,这个过程可以称其为并发。换成计算机概念就是说两段逻辑代码同时使用同一个资源。当然,这里只是从宏观上理解并发。

图片描述

老公和老婆共用一个账号,大家需要钱时都从这个账号上取,可假设老公和老婆就是两个事务,这 2 个事务如果同时取钱时,就必须隔离,否则就会出现麻烦。

图片描述

2 个事务同时进入这个账号,一查看,很高兴,账面上有钱,于是都想取出这 1000 块钱。银行肯定只能让一个事务成功,要不然银行就亏大了。

这便是事务的隔离机制,当一个事务对一个资源进行操作时,必须隔离另一个事务对其进行相关操作。

但是,如果隔离的太严格了,事务之间就如同排队,需要一个一个来,将会降低系统的响应时间,使用者会认为,切!这系统设计的够糟糕的,睡了一觉,还没有响应。

如果隔离的太宽松了,受事务之间的影响,会发生数据的异常。

所以在 JDBC 中一般会提供多种隔离机制,让开发者根据需要进行选择。
事务隔离级别从低到高:

  • 读取未提交(Read Uncommitted);
  • 读取已提交 (Read Committed);
  • 可重复读(Repeatable Read);
  • 序列化(serializable)。

如何选择,当然是需要根据业务需要进行设定。

不同的隔离机制下,并发的事务之间会发生一些什么样的事情?

3. 隔离与并发

3.1 读取未提交(Read Uncommitted)

字面上理解,可以读取没有提交的数据,这是最低的事务隔离级别:

  • 读事务不会阻塞读事务和写事务,写事务也不会阻塞读事务,但是会阻塞写事务;
  • 写事务不阻塞读事务,可以读取未提交的数据。

后果是,会出现脏读现象。

如果财务工作人员在更新公司员工工资时,不小心把 A 同事的原工资 2000 改成了 8000,但马上发现了错误,立马更正过来。改过就好,不伤大雅。

问题是,如果在财务工作人员修改时,A 同事恰好也在查看自己的账号,因为可看到别的事务没有提交的数据,天降财富呀,眼睛没花吧,工资涨成 8000 元了。

如果晚上回家告诉了老婆,第二天再一查,工资还是 2000 。请问下班后该如何向老婆交待!

这就叫脏读。当然,并不是没有解决的办法。

脏读解决方案:在财务人员的事务提交前,任何其他事务不可读取其修改过的值,则可以避免 A 同事回家挨骂的悲剧。

3.2 读取已提交 (Read Committed)

这个比前面的要严格点,只能读已经提交的,就不会出现脏读情况。

  • 写事务会阻塞读事务和写事务,但是读事务不会阻塞读事务和写事务。
  • 读事务不阻塞写事务

但是有可能造成不可重复读。

如果 A 同事听说老板要给自己涨工资,查看了一下账号,唉!还是只有 1000 元,心情好糟糕。

此时财务人员对 A 同事的账号进行了修改了,变成 2000,并提交了事务。A 同事再次读取时,工资变成 2000。此时就不知道是自己眼花还是在做梦。这就是不可重复,意思就是说,在这种隔离下,你最好不要有事没事的反复读,可能会出现前后读出来的数据不一致的情况。

3.3 可重复读(Repeatable Read)

  • 读事务会阻塞写事务,但是读事务不会阻塞读事务,写事务会阻塞写事务和读事务;
  • 读事务不阻塞读事务 (针对的是记录而不是表)。

可能会造成幻读问题:

幻读是针对事务期间所插入的新数据而言。

如财务人员统计一下所有工资为 1000 的员工,这时人事向员工表插入了一条新记录,工资也是 1000 。财务人员再次读取所有工资为 1000 的员工, 共读取到了 11 条记录。

可采用对表加锁的方式避免幻读的事情发生,一般情况下不会过于关心是否会发生幻读。

3.4 序列化(serializable)

这算是最严格的隔离级别,如果设置成这个级别,就不会出现以上所有的问题题(脏读,不可重复读,幻影读)。但是,性能极低,一般不用!

4. 小结

好了,到了该总结的时候了。本节课向大家介绍了事务机制,重点讲解了事务的隔离机制。

隔离策略,就如同交通法则,压一点点实线;红灯时,车头出了暂停线一点点都要罚钱,大家开车就只能小心翼翼啦, 交通的畅通性就会降低。

但是,如果太放宽,就容易发生交通事故。

所以呀!隔离机制要根据实际情况进行选择。