数据库事务定义
一组逻辑操作单元,使数据从一种状态变换到另一种状态。简而言之,就是一组事务若包含多个对数据库的操作,则这多个操作要么全部执行,要么全部不执行(执行一半出现异常,则之前的所有执行操作全部回滚)。
事务的ACID属性
-
原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 -
一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。 -
隔离性(Isolation)
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。 -
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
数据库的并发问题
针对同一个表中的数据,多个事务同时操作某一张表时,可能出现如下的并发问题:
- 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
- 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新且已提交了该字段。之后, T1再次读取同一个字段, 值就不同了。
- 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入且已提交了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
数据库的隔离级别
针对于数据库可能出现的三种并发问题,数据库提供了四种隔离级别,依次解决不同问题:
重点:不同隔离级别下的并发问题的演示:
前情提要:本次演示,通过命令行窗口登录两个不同的数据库用户,在两个会话连接都保持同一个隔离级别下同时开启事务,对同一张数据表进行操作,依次演示不同隔离级别下的并发问题,如有错误还望各位指出。
隔离级别一:read uncommitted(读未提交)
顾名思义:读未提交,就是本事务可以读到其他事务已经修改但还未提交的脏数据,此隔离级别下,会出现脏读、不可重复读、幻读。
- step1: 将两个会话同时修改隔离级别一
# 设置当前会话连接的隔离级别
set session transaction isolation level read uncommitted;
# 查看当前会话连接的隔离级别
select @@tx_isolation;
- step2: 对同一个数据库中的同一个表进行操作
对test数据库下的account表进行操作测试:
-
step3: 两边同时开启事务,分别查询该表
-
step4: 左边窗口对account表的某一行数据进行修改,但不提交数据,事务仍未结束
注意:此时左边窗口已经对account做了修改,但没结束事务,接下来让右边窗口查看该表中的数据。 -
step5: 右边窗口查看account表
查询结果发现,本来左边窗口还未提交的数据,右边窗口却已经读取到了,此时让左边窗口对修改了的数据进行事务回滚并结束事务。 -
step6: 左窗口回滚事务并结束,右窗口再次查看该数据表
神奇的事情发生了,左边窗口对事务进行了回滚后,右边的窗口在自己的事务对同一张表两次查询中,查询出来的结果不一致。即出现了脏读问题。
既然脏读问题已出现,那么不可重复读和幻读问题同样也会存在,第一个隔离级别的演示结束。
隔离级别二:read committed(读已提交)
顾名思义:读已提交,就是本事务可以读到其他事务已经修改且提交的数据,未提交的数据无法读取到。此隔离级别下,避免了脏读、但会出现不可重复读、幻读。
-
step1: 设置两个会话连接的隔离级别为read committed;
注意:测试新的隔离级别时,要注意将两边窗口的上一个事务先提交,即执行commit指令。
-
step2: 两边同时开启事务
-
step3: 左边窗口在事务中对account表中的一行数据进行修改,但不提交
-
step4: 右边窗口查看account表
可以看到,虽然左边窗口的事务已经成功执行了update操作,但因为还没提交数据。所以右边窗口在同一个事务的两次查询中得到的查询结果是一致的,即解决了脏读问题。 -
step5: 左边窗口提交修改的数据,结束事务
-
step6: 右边窗口再次查看account表
此时可以看出,左边窗口提交数据后结束了事务。但右边窗口的事务仍在继续,且此时再度查询该表,发现同一个事务中的前后两次同一张表查询结果出现了不一致,此即为所谓的不可重复读。
那么,不可重复读现象已经出现,幻读也会出现。所以该隔离级别只解决了幻读,隔离级别二演示结束。
补充:该隔离级别是日常生活中常见的事务隔离级别,不可重复读是我们开发可以接受的情况,比如最常见的秒杀,我们在连接上商家的服务器后进行秒杀时,会发现前后看到的库存量不一致,这就是不可重复读的具体体现。所以Oracle的默认隔离级别为read committed。
隔离级别三:repeatable read(可重复读)
顾名思义:可重复度,就是本事务在执行过程中,即使其他事务对同一张表进行了修改且提交了数据,在本事务未来结束前,对同一张表的查询结果始终保持一致,实现了对该表的可重复读。此隔离级别下,避免了脏读、不可重复读,但会出现幻读。
-
step1: 两个会话连接的隔离级别设置为repeatable read,同时都开启事务
-
step2: 左边窗口对account中的某一行数据进行修改,但不提交
-
step3: 右边窗口查看account表
可以看出,此时左边窗口的事务还未提交,因此右边窗口在同一个事务的多次查询中,并未读取到左窗口未提交的数据,即避免了脏读。 -
step4: 左边窗口提交数据,结束该事务
-
step5: 右边窗口再次查看account表
此时可以看出,左边窗口虽然成功提交了事务,数据库实际也得到了修改。但由于右边窗口的事务仍在继续,因此多次对account表的查询结果仍然保持一致,不受到其他事务提交的修改数据的影响,此即为避免了不可重复读。
补充:mysql的默认隔离级别即为repeatable read。
-
step6: 左边窗口开启事务,向account表插入一条数据,提交事务
-
step7: 右边窗口再次查看account表
此时发现,左边提交了事务后,数据表确实是插入了一行新数据,但右边在同一个事务中,前后却查询不出行数的变化,注意,此时并不是因为出现了幻读,而是因为在该隔离级别下,不可重复读问题被解决了!所以查询结果保持了一致!
下面的演示,才是幻读的演示。 -
step8: 右边窗口对account表进行批量修改,查看受影响行数
右边的窗口中,明明查询出来的结果是五行数据,但在对整张表进行批量修改时,却是六行数据受到了影响,这就是幻读的体现。同时,我们可以试下,如果只对最新一行的数据进行修改,是否会成功。 -
step9: 右边窗口对account表的最后一行数据进行修改,查看是否成功
可以看出,同样是成功的。即虽然没能查询出新插入的数据,但在做增删改时,仍然是可以对该新插入的数据进行操作的。
因此,该隔离级别下, 避免了脏读,不可重复读,但没能避免幻读。
说说两个结论,区分不可重复读和幻读:
- 解决不可重复读,是在开启事务后,先查询某个表后,对该表的结果进行了保存;此后在该事务中,有关此次表的查询,都是以副本表来查出结果的。
演示:
假如开启事务后,A事务没有先对account表进行查询结果保存做副本,而B事务又对account表进行了新数据的插入,之后A事务再查询该account表,是可以直接看到新插入的数据的。
-
step1: 两边都开始事务,左边查询行数,右边不查询(以防止留下结果副本)
-
step2: 左窗口的事务直接进行新数据的插入后提交
-
step3: 右窗口查看account表
由图可以看出,由于右边的事务在开启后,没有先对account表进行查询,因此没有留下查询结果副本。等到左边的事务提交了对account表的插入修改后,右边在原事务中第一次查询该account表时,会直接查看到新插入的数据,即结论1得证。
- 幻读,是本事务先对某表进行查询,之后其他事务对该表插入数据后提交,然后本事务再次对该表进行查询时,仍无法查看到新插入的数据(受结论1的影响),但在执行增删改操作时,虽然查询出的结果仍然没有新插入的数据,但增删改由于需要对底层的数据做修改了,因此会出现新数据受影响的提示,修改完再次查询该表时,会重新从数据库获取最新的数据情况,此时就可以看到被修改了的新插入的数据行了。
隔离级别四:serializable(串行化)
顾名思义:串行化,类似于java中多线程的加锁机制,多个事务操作同一个数据块时,会进行加锁,将并行操作转化为串行操作,那么,没有了并行操作,自然就不存在并发问题,所以此隔离级别下,避免了脏读、不可重复读和幻读,但缺点是效率低下,无法实现高并发。
-
step1: 两个会话连接同时设置隔离级别为serializable,且同时开启事务
-
step2: 左边窗口对account进行查询,然后右窗口也对account进行查询
-
step3: 左边窗口对account的某一行数据进行修改
注意!!!此时博主已经是按了回车键执行update操作的,结果该事务是进入了阻塞状态,无法进行修改,所以命令行一直没有往下显示提示信息。阻塞的原因是还有其他的事务(右边窗口的事务一开始对该表进行了查询操作)在操作这张表,所以对该表的增删改操作,必须等待也在操作这张表的其他事务结束后,才可以进行操作。 -
step4: 右边窗口结束事务
右边窗口commit了事务后,左边窗口原本在阻塞状态的update操作就可以顺利往下执行了。这就是串行化,避免了数据库的所有并发问题,当然带来的缺陷也显而易见,效率那叫一个低下啊。
总结:
serialiable隔离级别下,多个事务同时操作同个表,除了查询可以同时进行,增删改必须等其他的事务都结束后,才可以进行增删改(不分哪个事务操作该表先后)。
以上就是我在学习数据库事务时的演示,如有哪些不足欢迎各位好兄弟指出。对本文有任何疑问,欢迎评论区留言指出,最后祝大家学习愉快~