一、数据库事务的四大特性
1. 原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
2.一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
3.隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
4.持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
四大特性的联系 (加深理解)
1.原子性与一致性的区别?
原子性和一致性的的侧重点不同:原子性关注事务的执行的状态,要么全部成功,要么全部失败,不存在部分成功的状态。而所谓一致性,关注的是数据的一致性,即,从实际的业务逻辑上来说,最终结果是对的、是跟程序员的所期望的结果完全符合的。
2.ACID之间的关系
可以理解为,事务的ACID属性中,一致性是最基本的属性,也是最终目的,其他三个属性都是为了保证一致性而存在的,具体如下。
一致性指的是数据处于一种有意义的状态,这种状态是语义上的而不是语法上的,最常见的例子是转账。要求同一个事务中的操作要么全部成功,要么失败回滚,这就是原子性。
怎么实现原子性?
为了实现事务的原子性,需要通过日志:在日志中保存所有对数据库的更新操作,如果事务执行到一半,由于发生错误而无法继续执行,则通过回溯日志,将已执行成功的操作撤销,达到回滚的目的。
但是,原子性并不能完全保证数据的一致性。在多个事务并行运行的情况下,即使保证了每个事务的原子性,仍然可能导致数据不一致的结果。例如,事务1要做两个操作:读取账户A的余额,将余额修改为当前余额+100;事务2在这两个操作中间执行了修改账户A的余额,使其增加100。那么正确的结果应该是余额总共增加200,但是实际上,当事务1执行完毕后,账户A余额只增加了100,是由于事务2的操作结果被事务1覆盖掉了。
因此,为了保证并发情况下数据的一致性,又引入了隔离性的概念,即事务之间感知不到彼此的存在,就好像只存在本身一个事务一样。数据库有多重隔离级别,在后面会对此详细介绍。
怎么实现隔离性?
实现隔离性,原则上无非就是两种锁,乐观锁与悲观锁,在后面会对此详细介绍。
二、数据库的隔离级别
首先,我们先了解下,当多个线程都开启事务操作数据库中的数据时,如果不考虑事务的隔离性,会发生的几种问题:
1.脏读
脏读是指一个事务读取了未提交事务执行过程中的数据。
当一个事务的操作正在多次修改数据,而在事务还未提交的时候,另外一个并发事务来读取了数据,就会导致读取到的数据并非是最终持久化之后的数据,这个数据就是脏读的数据。
最典型的例子就是银行转账,从A账户转账100到B账户,脚本命令为
update account set money = money + 100 where username = 'B';
update account set money = money - 100 where username = 'A';
在这个事务执行过程中,另外一个事务读取结果发现B账户中的钱已经到账,提示B钱已到账,B就进行了下一步的操作。但是最终转账事务失败,导致操作回滚。实际上B并未收到钱,但是进行了下一步的操作,造成了损失,这就是脏读。
2.不可重复读
不可重复读是指对于数据库中的某个数据,一个事务执行过程中多次查询返回不同查询结果,这就是在事务执行过程中,数据被其他事务提交修改了。
不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
在某些情况下,不可重复读并不是问题,比如我们多次查询某个数据当然以最后查询得到的结果为主。但在另一些情况下就有可能发生问题,例如对于同一个数据A和B依次查询就可能不同,A和B就可能打起来了……
3.幻读(虚读)
幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。
幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
隔离级别
接下来我们再看一下MySQL为我们提供的四种隔离级别:
①Read uncommitted (读未提交):最低级别,任何情况都无法保证。
② Read committed (读已提交):可避免脏读的发生。
③ Repeatable read (可重复读):可避免脏读、不可重复读的发生。
④ Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低。像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。在MySQL数据库中默认的隔离级别为Repeatable read (可重复读)。
在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);而在Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别。
另外,注意两点:
设置数据库的隔离级别一定要是在开启事务之前。
隔离级别的设置只对当前链接有效。
三、数据库中的锁
从数据库系统角度分为三种:排他锁、共享锁、更新锁。
从程序员角度分为两种:一种是悲观锁,一种乐观锁。
这篇文章讲的很明白了:https://blog.csdn.net/weixin_39651041/article/details/79985715
参考:
https://www.cnblogs.com/fjdingsd/p/5273008.html
https://www.zhihu.com/question/30272728
https://www.jianshu.com/p/b0d0c0b04fb7
网友评论