一、事务及其四大特性
事务(Transaction):访问并可能更新数据库中各种数据项的一个程序执行单元(unit),它通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起。当在数据库中更改数据成功时,在事务中更改的数据便会提交,不再改变。否则,事务就取消或者回滚,更改无效。
举个例子来说,张三给李四转了1000元钱,那么在数据库操作时,就要先把张三的账户减去1000元,再把李四的账户加上1000元,两部分操作放在一起,才是一个完整的转账过程,也可称之为事务。
1)原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
就像你买东西要么交钱收货一起都执行,要么要是发不出货,就退钱。
(2) 一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
打个比方,你买东西这个事情,是不影响其他人的。
(3)隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
(4)持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务已经正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
打个比方,你买东西的时候需要记录在账本上,即使老板忘记了那也有据可查。
二、事务的隔离性及隔离级别
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
为什么要进行隔离
在很多情况下,事务不是一个一个的执行的。为了提升效率,很多时候,多个事务会同时执行(并发执行),那么如果没有一定约束条件,事务相互之间可能就会造成影响。
比如一个售票系统,此时售票表中的剩余票数为1
当两个客户端同时要执行买票操作:先检查剩余的票数是否 > 0 ,如果 > 0,就修改售票表里的计数,让计数 -1 ,如果没有一定的限制措施,是不是就有一定的概率把这个票给抢成了 - 1
上述问题就是两个事务之间产生了干扰和影响,为了避免相互干扰,就引入了”隔离性“,通过隔离性来降低上述的影响
为了解决并发执行事务带来的问题,MySQL等数据库引入了”隔离级别“,可以让用户自行选择一个适合自己当前业务场景的级别
下面我们先来研究一下,并发执行事务的时候都还会有哪些问题?
🍎脏读问题
比如,在做作业的时候,你不会写,但看到你旁边的同学写的头头是道,于是就看了旁边你同学的答案,并且你抄完了然后就去玩了。
可你刚走没一会,你的那个同学发现他刚才写错了,于是就又把刚才的答案给改了(但是你不知道,你抄的答案还是之前那个错的答案)。那么此时我们就把你刚才抄的错误答案称为”脏数据“(意思是数据被污染了,不准确了)同时你抄同学答案的过程就是”脏读“
用专业术语来说就是
那么如何解决脏读呢?
你和你同学说话,你同学在写的过程中,你不要看。只有当你同学完全写完了,马上就要提交给老师了,这个时候你才能看。这就相当于给读操作加了一把锁,降低了并发程度,提高了效率,提高了隔离性~
🍎不可重复读
比如, 有这样两个人,一个负责写代码、另一个负责读代码。写代码的人在9点开始了写代码,等到了10点,写完了,他把代码提交到了码云上。在提交之前,读代码的不能开始读。只有当写代码的提交了以后,才能开始读,于是读代码的等到了10:10,开始读代码了。
但是吧,在读代码的读的过程中,写代码的有开始改代码了,于是负责读代码的同学,读着读着就突然发现数据变了。
那怎么解决这个问题呢?
1、负责写代码的同学,在写代码的时候,负责读代码的同学不要读
2、负责读代码的同学,在读代码的时候,负责写代码的同学也不要修改
通过咱们上面的约束,现在的隔离性又提高了,并发性又降低了。数据更准确了,效率更低了~
🍎幻读
但是吧!我们上面规定了同学读的时候我不能改,但闲着也是闲着,我既然不能改你正在读的代码,那么我就改其他的代码~~~你们在读Student.java,我就改Teacher.java
如果Teacher.java本来就存在,也就罢了。但是如果是同学们在读的过程中,我新增了一个Teacher.java或者删除了一个已有的内容,就会产生”幻读“的问题~~~
🍎总结
并发和隔离其实是相悖的,实际使用的时候,就需要根据实际场景,来决定如何选择挡位!!!看场景对于性能比较敏感,还是对于准确性比较敏感~~~
MySQL中给我们提供了四个挡位,供我们自由选择
4个事务隔离级别:
Read uncommitted (读未提交):最低级别,以上问题均无法解决。
Read committed (读已提交):读已提交,可避免脏读情况发生。
Repeatable Read(可重复读):确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。
Serializable (串行化):最严格的事务隔离级别,要求所有事务被串行执行,不能并发执行,可避免脏读、不可重复读、幻读情况的发生