这样分析MySQL中的事务,面试官对你刮目相看!
前言
相信大部分小伙伴在面试过程中,只会针对面试官提出的表面问题来进行回答。其实不然,面试官问的每一个问题都是经过深思熟虑的,面试的时间相对来说也是短暂的,面试官不可能在很短的时间内就对你非常了解,他想通过几个问题来考察你所掌握的知识的深度和广度,如果你只是回答面试官表面问你的问题,向挤牙膏一样,问一点,答一点,结果不用说,肯定是凉凉。
面试问题
说说什么是事务?并发事务会带来哪些问题呢?
分析问题
表面上看,面试官是问了两个问题。一个是:什么是事务,也就是让你说说事务的基本概念;另一个是:并发事务会带来哪些问题。
实则不然,听到面试官这样问,你不要随意回答。要用极短的时间思考一下,面试官究竟想要得到什么答案。
对于第一个问题:说说什么是事务?就只是让你简单的说说事务的基本概念吗?基本概念相信是个学过数据库的小学生都会,面试官为什么会问你这个问题呢?此时,你需要揣测面试官的心理。此时的面试官其实想问你的不是事务的基本概念,而且他也想让你说出事务的特性,也就是四大属性。这才是这个问题的核心所在!
对于第二个问题:并发事务会带来哪些问题呢?就只是想问一下会带来哪些问题吗?知道问题,不知道如何解决问题,这样的面试者面试官能要吗?究其本质,面试官是想问你并发事务会带来哪些问题,有哪些解决方案能够解决这些问题!这才是面试官想要的答案!
综上,面试官本质上问的问题是:什么是事务?事务的四大特性是什么?并发事务会带来哪些问题?有哪些解决方案?你只有深刻理解了面试官提问的本质,才能更好的回答面试官所提出的问题。不然,你应付面试官,面试官也会应付你。
什么是事务?
事务的概念理解起来还比较简单的:事务是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。
事务的四大特性
原子性(Atomicity)
事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。比如转账,要么转账成功,账户余额增加(减少);要么转账失败,账户余额不变。
一致性(Consistency)
事务在完成时,必须使所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。事务结束时,所有的内部数据结构(如 B 树索引或双向链表)都必须是正确的。某些维护一致性的责任由应用程序开发人员承担,他们必须确保应用程序已强制所有已知的完整性约束。例如,当开发用于转帐的应用程序时,应避免在转帐过程中任意移动小数点。
隔离性(Isolation)
由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。这称为隔离性,因为它能够重新装载起始数据,并且重播一系列事务,以使数据结束时的状态与原始事务执行的状态相同。当事务可序列化时将获得最高的隔离级别。在此级别上,从一组可并行执行的事务获得的结果与通过连续运行每个事务所获得的结果相同。由于高度隔离会限制可并行执行的事务数,所以一些应用程序降低隔离级别以换取更大的吞吐量。
持久性(Durability)
事务完成之后,它对于系统的影响是永久性的。该修改即使出现致命的系统故障也将一直保持。
例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
并发事务带来的问题
脏写问题
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题。每个事务都不知道其它事务的存在。最后的更新将重写由其它事务所做的更新,这将导致数据丢失。
例如,T1和T2同时修改一条数据,T2的修改覆盖了T1的修改;如果在T1之后T2才能进行更改,则可以避免该问题。
我们来看一个经典的转账问题,开始小明和小刚都有1000元钱,在事务T1中,小明为小刚转账100元,在事务T2中,小刚为小明转账200元。则正常情况下,结果为:小明有1100元,小刚为900元。如果发生了脏写的问题,则结果可能为:小明1200元,小刚800元。如下图所示。
脏读问题
一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做”脏读”。
例如:在事务T1中,小明为小刚转账100元,在转账的过程中,事务未提交或者未回滚时,此时事务T2读取到了事务T1未提交的内容,也就是说在事务T2中读取到了小明900元,小刚1100元的记录。可以用下图表示。
不可重复读问题
一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变!这种现象就叫做“不可重复读”。
指事务T2读取数据后,事务T1执行更新操作,使T2无法读取前一次结果。
例如,在事务T1中执行小明为小刚转账100元的操作,在事务未提交之前,在事务T2中读取的数据还是小明为1000元,小刚为1000元。待事务T1提交后,事务T2中读取的数据为小明900元,小刚1100元。如下图所示。
幻读问题
一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。
事务t2读取到了事务t1体提交的新增、删除数据,不符合隔离性。
幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
例如,在事务T1中插入两条分别为小明和小刚的数据,在事务提交之前,事务T2中读取的数据记录为10,随后事务T1提交,则在事务T2中读取的记录为12,如下所示。
并发事务问题的解决方案
为了避免上面出现的几种情况,在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同。以下四种不同的隔离级别限制由低到高,性能从高到底。
读未提交
读未提交(Read Uncommitted):允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。
不可避免 脏读、不可重复读、虚读。
读已提交
读已提交(Read Committed):允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
可避免 脏读,不可避免 不可重复读、虚读。Oracle采用读已提交。
可重复度
可重复读取(Repeatable Read):禁止不可重复读取和脏读取,但是有时可能出现幻读数据。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
可避免 脏读、不可重复读, 不可避免 虚读。MySQL采用可重复读。
序列化
序列化(Serializable):提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
可避免 脏读、不可重复读、幻读情况的发生。
事务的四种隔离级别总结起来如下图所示。
常看当前数据库的事务隔离级别: show variables like 'tx_isolation';
设置事务隔离级别:set tx_isolation='REPEATABLE-READ';
MySQL默认的事务隔离级别是可重复读,用Spring开发程序时,如果不设置隔离级别默认用MySQL设置的隔离级别,如果Spring设置了就用已经设置的隔离级别