📑事务
事务的基本概念
事务是用来解决一类特定场景的问题的,在有些场景中,完成某个操作,需要多个sql配合完成的。
比如说:1号给2号转账1000000元
转账的流程大概是这样的
- 给 1 号的账户余额减去1000000,update 账户余额表 set balance = balance - 1000000 where id = 1;
- 给 2 的账户余额加上1000000,update 账户余额表 set balance = balance + 1000000 where id = 2;
显然,以上的转账操作,涉及了2个sql语句,在实际开发中,我们一般都是通过C++/Java等这样的语言来实现的,这个时候就涉及到1个问题,我们必须确保两个sql都执行完毕,转账才算完成,如果第一个sql执行完,在执行第二个sql之前,出现严重问题了(程序崩溃,主机断电…),这时候就会使数据库中的内容出错。你的小钱钱可能就会不翼而飞了。
为了避免出现“转账转一半”这样的问题,于是就有了事务。
所谓事务,就相当于是把 多个要执行的sql,打包成一个“整体”,这个“整体”在执行过程中就能够做到,要么整个都执行完,要么就一个都不执行,就可以避免出现上述转账一半的中间状态。
回滚
以上的“一个都不执行”不是这些sql真的没执行,而是执行一半,发现出错的时候,数据库会自动进行“还原操作”,相当于把前面执行过的sql,给进行“撤销”,最终的效果看起来好像就是一个都没执行。 这样的机制,称为“回滚”(rollback)。
- 举个例子方便理解:
回滚类似于,我上淘宝买个东西,货拿到手后,我不满意,我就退货~
商家就会把这个货,重新进行包装,做成好像从来没有卖出去一样,这样就可以继续卖给其他人了。
重新进行包装的过程,就可以看成是回滚。
同时,也把事务支持的上述的“特性”称为“原子性”(过去人们认为“原子”就是不可拆分的最小单位)。
-
数据库是如何知道要怎么回滚呢,如何知道前面的sql做出了啥样的修改呢?
- 其实,数据库内部存在一系列的“日志体系”,记录在文件中。(把记录存到文件中,既可以应对“程序崩溃”,也能够应对“主机掉电”)。当开启事务的时候,此时每一步执行的sql,都会记录在日志体系中,后续如果需要回滚,就可以参考之前记录的内容,来进行还原。
- 其实,数据库内部存在一系列的“日志体系”,记录在文件中。(把记录存到文件中,既可以应对“程序崩溃”,也能够应对“主机掉电”)。当开启事务的时候,此时每一步执行的sql,都会记录在日志体系中,后续如果需要回滚,就可以参考之前记录的内容,来进行还原。
-
那么问题来了,drop database这样的操作能不能回滚呢?
- 不能的,回滚操作只是针对“事务来说的”,开启事务之后,就会记录回滚日志,事务执行过程中,如果出现问题,就会自动触发回滚…(开启事务之后,一个事务内,虽然是执行多个sql,但执行的内容也不能太多…)
一方面,drop database这样的操作不能放到事务中去执行,
另一方面,这个操作也不算执行出错,而算是“正确执行了sql”。
- 不能的,回滚操作只是针对“事务来说的”,开启事务之后,就会记录回滚日志,事务执行过程中,如果出现问题,就会自动触发回滚…(开启事务之后,一个事务内,虽然是执行多个sql,但执行的内容也不能太多…)
事务最核心的特性,就是“原子性”,能够解决的问题,就是批量执行sql的问题。
开启事务的sql语句
-- 开启事务
start transaction;
-- 其他sql
...
-- 提交事务(告诉服务器,over!!)
commit;
但是一般在实际开发中不会使用上述sql
实际开发都是通过“代码”的方式来开启事务,批量执行的…
事务的基本特性
关于事务,在面试中,除了问你基本的概念(上面的内容),还会考察到一个比较麻烦的东西:事务的基本特性。
事务涉及到4个核心特性
- 原子性(最重要的特性)
- 一致性(事务执行前和执行后,数据库中的数据一定都是合法的数据,不会出现非法的临时结果的状态)
- 持久性,事务执行完毕之后,就会修改硬盘上的数据,事务都是会持久生效的。
- 隔离性 (非常不好解释)隔离性,描述了 多个事务 并发执行 的时候,相互之间产生的影响是怎样的。如果这些同时执行的事务,恰好也是针对同一个表来进行一些增删改查,此时就可能会引入一些问题。
- 脏读
- 不可重复读
- 幻读问题
并发执行介绍:MySQL是一个“客户端-服务器”结构的程序,一个服务器,通常会给多个客户端同时提供服务,因此很可能,这多个客户端,就同时给这个服务器提交事务来执行,与之相对,服务器就需要同时执行这多个事务,此时就是“并发执行”。
我们来一个一个介绍,可能会有一些烧脑。
-
脏读
- 有两个事务 A 和 B 并发执行,其中事务 B 在针对某个表的数据进行修改,B 执行过程中,A也去读取这个表的数据,当A读完之后,B把表里的数据又改成别的,这就导致A读到的数据,就不是最终的“正确数据”,而是读到了临时性的“脏数据”。
- 举个例子方便理解,假设我是一个老师,我在准备下节课要讲的内容,于是我就准备一个代码 class Student{ … } 。此时,有一个同学,暗中观察,看到了,我这里写了一个class Student,里面有一个id,有一个name,有score…看了一会,就溜了~
- 当同学走了之后,我想了一下,又对代码做出来修改,把id 改成 studentId,name 改成 studentName,score 改成 studentScore,当后续上课的时候,这个之前看我屏幕的同学,就发现,老师不讲武德,讲的代码和之前他看到的怎么就不一样了呢?
如何解决上述问题呢?
- 很简单,我和同学们,约定好,我写代码的时候,你们不要看我的屏幕,如果你们想预习下节课的内容,可以来看我的“码云”,我会把修改好的代码,最终提交到码云上。我在修改的时候,同学们不能读,称为“给写操作加锁”。
-
不可重复读
接着上个例子,我已经和同学们约定好,同学们不会看我的屏幕了,而是去我的码云上看代码。有一天,我写了一些代码,提交到码云上了,此时有一些同学正在码云上看我的代码,就在他们看的过程中,我发现了,我刚才的提交的代码中有问题,于是我就修改了一下代码,重新提交一次
此时,以同学们的视角看,代码看着看着,突然就变样了~
这个问题,就是“不可重复读”
此时有三个事务ABC,首先事务A执行一个修改操作,A执行完毕的时候,提交数据,接下来事务B执行,事务B读取刚才A提交的数据…在B读取的过程中,又来了一个事务C,C又对刚才A修改的数据再次做出了修改,此时对于B来说,后续再读取这个数据,读到的结果就和第一次读到的结果是不一样的。
以上过程就叫做“不可重复读”。
如何解决不可重复读?
- 和上面的解决方法类似,再与同学们进行约定~~
同学们通过码云看我的代码的时候,我不能修改。(给读操作加锁,一个事务在读取数据的过程中,其他的事务不能修改它正在读的数据)
- 幻读
相当于不可重复读的“特殊情况”
我与同学们已经约定好了,我在写代码的时候,同学们不能读,同学读代码的时候,我不能去修改(针对同一个代码才有这样的限制)。
同学们在读studen的时候,此时我创建了另一个代码,Teacher进行编写。此时,站在同学的角度,看到的情况就是,虽然studen没变,但读着读着突然冒出来一个Teacher。
有一个事务A在读取数据,读的过程中,另外一个事务B,新增了\删除了一些其他的数据…此时站在A的视角,多次读取的数据内容虽然一样,但是“结果集”不同。结果集不同,是否算是问题,需要根据情况来定。
如何解决幻读问题?
- 还是继续和同学们约定,如果有同学正在读代码,我就不做任何操作。
这样的操作我们称为“串行化”
比如多个客户端,同时提交了多个事务过来,但是服务器一个一个的执行事务(执行完第一个事务,再执行第二个,再执行第三个…)
总结一下涉及到的三个问题
在并发执行事务的过程中,涉及到了三个问题:
- 脏读
- 不可重复读
- 幻读
这三个问题与隔离性有啥关系呢?
在MySQL中提供了四个隔离级别,可以通过配置文件来设置当前服务器的隔离级别是哪个级别。设置不同的隔离级别,就会使事务之间的并发执行的影响产生不同的差别,从而会影响到上述三个问题是否会发生~
1) read uncommitted读未提交
这种情况下,一个事务可以读取另一个事务未提交的数据
此时,就可能会产生脏读,不可重复读,幻读三种问题
但是此时,多个事务并发执行程度是最高的,执行速度也是最快的(并发程度越高,速度就越快,并发程度越低,速度就越慢)
2) read committed 读已提交
这种情况下,一个事务只能读取另一个事务提交之后的数据(给写操作加锁了)
此时,可能会产生不可重复读,幻读问题(脏读问题解决了)
此时并发程度降低,执行速度会变慢,但是事务之间的隔离性提高了(事务之间的相互影响变小了,得到的数据更准了~)
3) repeatable read 可重复读 (MySQL默认的隔离级别)
这个情况下,相当于是给写操作和读操作都加锁了
此时,可能产生幻读问题,解决了脏读和不可重复读问题
并发程度进一步降低,执行速度进一步变慢,事务之间的隔离性,进一步提高了
4) serializable 串行化
此时,所有的事务都是在服务器上一个接一个的执行的
此时,解决了脏读,不可重复读,幻读问题
并发程度最低,执行速度最慢,隔离性最高,数据最准确~
在写代码时我们该如何选哪个隔离级别呢?
- 答:根据需要,看是需要执行速度快,还是需要数据比较准,从而选择合适的隔离级别,快和准无法兼得。