事务
本章从了解为什么需要事务到讲述事务的四大特性和概念,最后讲述MySQL中的事务使用语法以及一些需要注意的性质。
再额外讲述一点Springboot中@Transactional注解的使用。
1.为什么需要事务?
我们以用户转账为例,假设用户A和用户B的银行账户中都有余额500元,用户A向用户B转账200元,在数据库中可以分解为如下操作:
序号 | 操作 |
---|---|
1 | 用户A的余额-200 |
2 | 用户B的余额+200 |
如果服务器在执行完第一条操作后,服务器断电了,那么用户A的余额就是300,用户B的余额还是500,为了不出现这种情况,我们就需要用到事务来解决。
2.事务的四大特性(ACID)
事务四大特性的英文开头分别为AICD,为了方便记忆,调整顺序后为ACID(酸的意思)。
2.1 原子性(Atomicity)
顾名思义,原子在某种意义上意味着不可分割,事务的原子性将一个事务看作一个原子,中间不能被分割。
以用户A向用户B的转账为例,用户A余额-200,用户B的余额+200这个过程被看作一个事务,要么这两个操作全都执行成功,要么这两个操作全部执行失败,不能出现其中一个执行成功,另一个执行失败,这中间状态就相当于将事务分裂了,一个事务要成功执行不能只执行其中的某些操作,必须完整的按顺序执行整个事务。
2.2 隔离性(Isolation)
隔离性指的是多个事务之间的隔离性,事务之间不能相互干扰,一个事务的运行不影响另一个事务的运行。
这里可能会有疑问,什么情况下事务之间会相互影响,我们同样以转账为例,举例一种破坏隔离性的情况:用户A、用户B和用户C都拥有500元的余额,用户A向用户B转账200元的同时,用户C向用户B转账200元。这里我们令事务T1=用户A向用户B转账200元,T2=用户C向用户B转账200元。如果让A转账完再让C去转账这样不会出现问题,但是如果A和C的转账过程是同时的可能就会出现问题,下面以表格来说明:从上到下为时间线,同一行表示在同一个时间点执行的语句。
T1 | 说明 | T2 | 说明 |
---|---|---|---|
A=read(A) | 读取用户A的余额,当前A余额为500 | C=read© | 读取用户C的余额,当前C的余额为500 |
A=A-200 | A的余额减少200,当前A的余额为300 | ||
C=C-200 | C的余额减少200,当前C的余额为300 | ||
Write(A) | 将A的变化写回数据库 | Write© | 将C的变化写回数据库 |
B=Read(B) | 读取用户B的余额,当前B余额为500 | ||
B=Read(B) | 读取用户B的余额,当前B余额为500 | ||
B=B+200 | B的余额增加200,当前B的余额为700 | B=B+200 | B的余额增加200,当前B的余额为700 |
Write(B) | 将B的变化写回数据库 | Write(B) | 将A的变化写回数据库 |
这里我们会发现,最终B的余额为700,正确的应该为900才对,这种就是两个事务之间相互影响了。
2.3 一致性(Consistency)
一致性表示数据库处理前后结果应与其所表示的客观世界中真实状况保持一致。
这怎么理解呢,我们以隔离性中的例子来举例,用户A和用户C同时向用户B转账,隔离性出错后数据库中错误的状态下用户A的余额为300,用户C的余额为300,用户B的余额为700,客观世界真实状态下应表示为用户A的余额为300,用户C的余额为300,用户B的余额为900,这种状况下就出现了数据库前后处理结果与其所表示的客观世界的真实状况不一致。一致性就是要保证数据库中的结果和客观世界中的情况保持一致。
2.4 持久性(Durability)
持久化这个词可能都有听过,就是让数据能够永久保存下来,事务的持久性表示事务提交之后,必须对数据库进行修改,并且这个结果是永久保存的,除非后来经过了更改。
3.事务的概念
把需要保证事务四大特性的一个或多个数据库操作称为事务。一个事务可能会经理的状态如下:
- 活动的:事务对应的数据库操作正在执行中。
- 部分提交的:事务对应的最后一个数据库操作执行完成后,当前数据都还在内存中,还没有刷盘,这个状态下叫部分提交的。
- 失败的:由于停电、服务器死机或手动停止事务导致事务对应的数据库操作执行到一半就没办法继续执行了,这种状态下成为失败的状态。
- 中止的:失败的状态下将已经执行过的数据库操作回滚后完成后,也就是恢复到当前事务处理之前的状态后,称为中止状态。
- 提交的:事务执行成功,并且数据都刷盘成功。
只有当事务的状态处于中止的或者提交的时,事务的生命周期才结束。
4.MySQL中事务的语法
4.1 开启事务
-
BEGIN [WORK]
开启一个事务,WORK可有可无。
-
START TRANSACTION
开启一个事务,后面可以跟几个修饰符:
#修饰符 # READ ONLY表示只读,READ WRITE可读写,WITH CONSISTENT SNAPSHOT一致性读,修饰符直接加在START TRANSACTION后面,多个修饰符用逗号分隔,READ ONLY和READ WRITE不能同时使用 #只读 START TRANSACTION READ ONLY; #可读写 START TRANSACTION READ WRITE; #一致性读 START TRANSACTION WITH CONSISTENT SNAPSHOT; #多个修饰符 START TRANSACTION READ ONLY,WITH CONSISTENT SNAPSHOT;
4.2 提交事务
-
COMMIT [WORK]
提交事务,
WORK
可有可无。BEGIN; #一系列数据库操作 #提交事务 COMMIT;
4.3 手动中止事务
-
ROLLBACK [WORK]
回滚一个事务,
WORK
可有可无。BEGIN; #一系列数据库操作 #回滚 ROLLBACK;
4.4 支持事务的存储引擎
目前只有InnoDB和NDB支持事务。
4.5 自动提交
MySQL中的一个系统变量autocommit
,用来自动提交事务。
SHOW VARIABLES LIKE 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
可以看到autocommit
默认为ON开启状态,如果不显示的使用BEGIN
或START TRANSACTION
开启事务,那么每一条语句都会算作一个单独的事务进行提交。
如果设置autocommit
为off,那么运行的所有语句必须COMMIT后才能成功更新数据库。
4.6 隐式提交
当使用START TRANSACTION
或BEGIN
开启事务后,在COMMIT之前使用某些语句可能会导致事务偷偷被提交了,这就叫隐式提交。会导致事务隐式提交的语句有如下:
-
DDL语句:
CREATE
、ALTER
、DROP
等语句修改数据库对象时,就会隐式提交前面语句所属的事务。 -
隐式的使用或修改了mysql数据表。
比如:
ALTER USER
、CREATE USER
、DROP USER
、GRANT
、RENAME USER
、REVOKE
、SET PASSWORD
等。 -
事务控制或关于锁定的语句:
- 在事务
COMMIT
之前又使用了一次START TRANSACTION
或BEGIN
会隐式提交。 - 使用
LOCK TABLES
、UNLOCK TABLE
S也会隐式提交。
- 在事务
-
关于MySQL复制的一些语句:
SRART SLAVE
、STOP SLAVE
、RESET SLAVE
、CHANGE MASTER TO
等语句也会隐式提交前面语句所属的事务。 -
其他语句:
ALALYZY TABLE
、CHANGE INDEX
、CHECK TABLE
、FLUSH
、LOAD INDEX INTO CHANGE
、OPTIMIZE TABLE
、REPAIR TABLE
、RESET
等语句。
4.7 保存点
MySQL事务支持在事务的执行过程中设置保存点,可以使用ROLLBACK语句回滚到事务的某个特定保存点的状态。
-
SAVEPOINT 保存点名称;
设置保存点。
-
ROLLBACK [WORK] TO [SAVEPOINT] 保存点名称;
回滚到对应的保存点的状态。
-
RELEASE SAVEPOINT 保存点名称;
释放对应的保存点。
示例:
#开启事务
BEGIN;
#更新日志表里的所有用户名为123
UPDATE log_tbl SET user_name = '123';
#设置保存点
SAVEPOINT s1;
#更新日志表里的所有用户名为124
UPDATE log_tbl SET user_name = '1234';
#回滚到保存点s1时的状态
ROLLBACK TO s1;
#释放保存点
RELEASE SAVEPOINT s1;
#回滚整个事务
ROLLBACK;
5.@Transactional
再Springboot+MyBatis中使用事务非常简单,只需要再需要以事务方式运行的函数上加@Transactional
注解即可,下面以一个Controller接口为例:
@RequestMapping("/getToken")
@ResponseBody
@Transactional
public String reloadTokenByAppIdAndAppSecret(AccessApiConfirm data){
return apiAccessConfirmService.reloadTokenByAppIdAndAppSecret(data);
}
该注解可以加在接口、接口方法、类和类的方法上,加在类上相当于给类的所有public方法加上了@Transactional
注解,默认情况下,加了@Transactional
注解的方法,在抛出RuntimeException()异常后会回滚所有的数据库操作。
还可以进行参数配置:
参数名称 | 功能描述 |
---|---|
readOnly | 设置当前事务只读,例:@Transactional(readOnly=true) |
rollbackFor | 当抛出对应的异常的时候进行回滚操作,例:@Transactional(rollbackFor={RuntimeException.class, Exception.class}) 指定抛出RuntimeException()和Exception()异常时回滚。 |
rollbackForClassName | 当抛出对应的异常的时候进行回滚操作,例:@Transactional(rollbackForClassName={“RuntimeException”,”Exception”}) 指定抛出RuntimeException()和Exception()异常时回滚。 |
noRollbackFor | 该属性指定对应的异常抛出时不进行回滚,例:@Transactional(noRollbackFor={RuntimeException.class, Exception.class}) ,表示抛出RuntimeException()和Exception()异常时不进行回滚。 |
Exception | 该属性指定对应的异常抛出时不进行回滚,例:@Transactional(noRollbackForClassName={“RuntimeException”,”Exception”}) ,表示抛出RuntimeException()和Exception()异常时不进行回滚。 |