有事务注解的方法,一旦进入到方法,就已经和mysql建立连接了,如果在方法中写大量的代码,就会变慢,将这些代码写到别的方法中,这也是调优的一部分
ACID
事务基本特性ACID分别是:
原子性:一个事务中的操作要么全部成功,要么全部失败。
一致性:数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如A给B转账2块钱,假设A只有1块,支付之前数据库里的数据都是符合约束的,但如果事务执行成功了,就变成负数了,因此事务不能成功,这就是事务提供了一致性的保证。
隔离性:一个事务的修改在最终提交前,对其他事务是不可见的。
持久性:一旦事务提交,所做的修改就会永久保存到数据库中。
ACID靠什么保证
A原子性:由undo log日志保证,记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql
C一致性:由其他三大特性保证,程序代码要保证业务一致性
I隔离性:由MVCC机制保证
D持久性:由内存+redo log保证,mysql修改数据同时在内存和redo log记录这次操作,宕机的时候从redo log恢复
流程:innodb的redolog写盘,innodb事务进入prepare状态,如果prepare成功,binlog写盘,再继续将事务日志持久化到binlog,如果持久化成功,那么innodb事务则进入commit状态(在redo log里面写一个commit记录)
手动提交事务
begin;
update tablename set name = 'zs' where id = 1;
update tablename set age = 18 where id = 2;
commit;
手动回滚事务
begin;
update tablename set name = 'zs' where id = 1;
update tablename set age = 18 where id = 2;
rollback;
自动提交
show variables like 'autocommit';
默认情况下,若不显式的使用start transaction或者begin语句开启一个事务,那么每一条语句都是一个独立的事务,这种特性称为事务的自动提交;
如果想关闭可以使用如下方法:
- 显式的使用begin或start transaction开启一个事务,这样本次事务提交或者回滚前会暂时关闭自动提交功能。
- 把系统提交autocommitted的值设为off,set autocommit = OFF;这样写入的多条语句就属于一个事务,直到显示的写出commit或rollback语句。
隐式提交
当我们使用START TRANSACTION或者BEGIN语句开启了一个事务,或者把系统变量autocommi的值设置为OFF时,事务就不会进行自动提交,但是如果我们输入了某些语句之后就会阴式的提交掉,就像输入了COMMIT语句一样,这种因为某些特殊的语句而导致事务提交的情况称为隐式提交,这些会导致事务隐式提交的语句包括:
定义或修改数据库对象的数据定义语言(Data deinition language,缩写为: DDL):
- 数据库对象是指数据库、表、视图、存储过程等。当使用CREATE、ALTER、DROP等语句去修改数据库对象时,就会隐式的提交前边语句的事务。
- 隐式使用或修改mysql数据库中的表:当使用ALTER USER、CREATE USER、DROP USER、GRANT、RENAME USER、SET PASSWORD等语句时也会隐式的提交前边语句所属事务。
事务控制或关于锁定的语句:
- 当在一个事务还没提交或者回滚时就又使用START TRANSACTION或者BEGIN语句开启另一个事务时,会隐式的提交上一个事务。
- 当前的autocommit系统变量的值为OFF,手动把它调为ON时,也会隐式的提交前边语句所属的事务。
- 使用LOCKTABLES、UNLOCK TABLES等关于锁定的语句也会隐式的提交前边语句所属的事务。
加载数据的语句:
- 使用LOAD DATA语句来批量往数据库中导入数据时,也会隐式的提交前边语句所属的事务。
- 使用analyze table,cache index,check table,flush,load index into cache,optimize tablerepair table,reset等语句也会隐式的提交前边语句所属的事务。
保存点
如果开启了一个事务,写了很多语句,却发现上一条语句有问题,如果没有设置保存点,就只好使用ROLLBACK语句来让数据库状态恢复到事务执行之前的样子,然后从头再写。
MYSQL提出了一个保存点(Savepoint)的概念,就是在事务对应的数据库语句中打几个点,在调用ROLLBACK语句时可以指定会滚到哪个点,而不是回到最初的原点。
定义保存点的语法如下:
SAVEPOINT 保存点名称;
当想回滚到某个保存点时,可以使用下边这个语句(WORK和SAVEPOINT可不写):
ROLLBACK [WORK] TO [SAVEPOINT] 保存点名称;
如果ROLLBACK语句后边不跟随保存点名称的话,会直接回滚到事务执行之前的状态。
当想删除某个保存点,可以使用这个语句:
RELEASE SAVEPOINT 保存点名称;
事务隔离级别
read uncommitted
读未提交(脏读,不可重复读,幻读),可能会读到其他事务未提交的数据。
例如:用户应该读取到userId=1的age=10,但是读取到了其他事务还没有提交的事务,因此读取到的结果age=20,这就是脏读。
脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,那么后一个事务读取的数据就是不正确的。
read committed
读已提交(不可重复读,幻读),在同一个事务里,同样的查询语句,读取到不同的结果叫做不可重复读。解决了脏读的问题,该隔离级别只会读取已经提交的事务。
例如:用户开启事务读取userId=1的age=10,再次读取发现age=20。
不可重复读(Non-repeatable read):在一个事务的两次查询中数据不一致,这可能是两次查询过程中间又插入了一个事务操作,更新了原有的数据。
repeatable read
可重复读(幻读),这是mysql的默认级别,就是每次读取结果都一样,但是有可能产生幻读。
mysql在repeatable read级别下,可以解决幻读。
幻读(Phantom Read):一个事务查询了几列数据,而另一个事务却在此时新插入了几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它之前没有查到的。
serializable
串行(不出错),一般是不会使用的,他会给每一行读取的数据加锁,会导致大量超时和锁竞争的问题。
查看隔离级别
mysql8:select @@transaction_isolation;
mysql5.7:select @@tx_isolation;
修改隔离级别
set transaction_isolation = 'repeatable-read';
set session transaction_isolation = 'repeatable-read';
事务传播级别
Propagation.REQUIRED
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED)
@Transactional(propagation=Propagation.REQUIRED)
pubilc void A(){
doPreSomething;
B();
doSufSomething;
}
@Transactional(propagation=Propagation.REQUIRED)
pubilc void B(){
doSomething;
}
调用方法A,开启A事务,在A中调用方法B,B不会再开启新事务,而是加入A事务,如果方法A中或方法B中抛出异常,则A和B都回滚,如果doSufSometing方法抛出异常触发回滚,则doSufSomething()、方法B和doPreSomething()都会回滚。
如果不通过方法A,单独调用方法B,会开启一个事务。
Propagation.REQUIRED所有方法共用一个事务,要么一起成功提交,要么一起失败回滚,如果单独执行,各自单独开启各自事务。
如果要求一起执行成功或者一起回滚,则选择该事物传播级别。
执行逻辑:
开启事务
执行方法A的doPreSomething
执行方法B
执行方法A的doSufSomething
提交或回滚事务
Propagation.REQUIRED_NEW
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW)
@Transactional(propagation=Propagation.REQUIRED)
pubilc void A(){
doPreSomething;
B();
dosufSomething;
}
@Transactional(propagation=Propagation.REQUIRED_NEW)
pubilc void B(){
doSomething;
}
调用方法A,开启A事务,在A中调用方法B,B再新开启B事务,方法B在B事务中执行,若B抛出异常,事务B回滚,事务A不受影响,B执行成功,提交B事务,继续执行A的doSufSomething()方法,如果A的方法都执行成功,提交A事务,如果失败,只回滚A事务,B事务不受影响,不会回滚。
如果不通过方法A,单独调用方法B,会开启一个事务。
Propagation.REQURIED_NEW所有方法使用各自事务,各自提交或者各自回滚,互不影响。
如果要求各自事务独立,不能进行相互影响,则选择本事务传播级别。
执行逻辑
开启事务
执行方法A的doPreSomething
开启另一个事务2
执行方法B
提交或者回滚事务2
执行方法A的doSufSomething
提交或回滚事务
Propagation.SUPPORTS
@Transactional(propagation=Propagation.REQUIRED)
pubilc void A(){
doPreSomething;
B();
doSufSomething;
}
@Transactional(propagation=Propagation.SUPPORTS)
pubilc void B(){
doSomething;
}
调用A方法,开启A事务,在A中调用B方法,由于已经存在A事务且A事务未提交,B会加入到A事务中执行,若B方法抛异常回滚,A和B都回滚。如果执行A的doSufSomething()方法抛异常回滚,那么doSufSomething()、B方法和doPreSomething()都会回滚。
如果不通过方法A而单独调用方法B,则方法B不会开启事务,直接会以非事务的方式执行。
如果存在事务就加入,如果当前不存在事务,则不会创建新的事务,以非事务的方式执行。
如果要求一起执行成功或者一起回滚,单独执行时候以非事务方式执行,则选择该事物传播级别。
执行逻辑:
开启事务
执行方法A的doPreSomething
执行方法B
执行方法A的doSufSomething
提交或回滚事务
Propagation.NOT_SUPPORTED
@Transactional(propagation=Propagation.REQUIRED)
pubilc void A(){
doPreSomething;
B();
doSufSomething;
}
@Transactional(propagation=Propagation.NOT_SUPPORTED)
pubilc void B(){
doSomething;
}
调用方法A,开启A事务,在A中调用B,由于已经存在A事务且未提交,B不允许在事务内部执行,A事务挂起,在非事务中执行B,方法B不论成功还是失败,都不影响A事务,如果执行方法A 的doSufSomething()时抛出异常回滚,则A中的方法都回滚,方法B不受影响。
如果不通过方法A而单独调用方法B,则方法B不会开启事务,直接会以非事务的方式执行。
Propagation.NOT_SUPPORTED如果存在事务就挂起当前事务,以非事务的方式运行自己,如果当前不存在事务,则不会创建新的事务,以非事务的方式执行。
如果要求内部嵌套方法不对外部方法事务造成影响,并且内部方法不需要事务,单独执行时候以非事务方式执行,则选择该事物传播级别。
执行逻辑:
开启事务
执行方法A的doPreSomething
挂起事务
执行方法B
重新启用挂起的事务
执行方法A的doSufSomething
提交或回滚事务
Propagation.MANDATORY
@Transactional(propagation=Propagation.REQUIRED)
pubilc void A(){
doPreSomething;
B();
doSufSomething;
}
@Transactional(propagation=Propagation.MANDATORY)
pubilc void B(){
doSomething;
}
调用方法A,开启A事务,在A中调用方法B,由于A事务已经存在且未提交,B会直接加入A事务执行,若执行B的时候异常回滚,则A和B都回滚,若执行doSufSomething()方法时异常回滚,则doSufSomething()、B()和doPreSomething()都会回滚。
如果不通过方法A而单独调用方法B,则方法B会直接报错,因为方法B的事务传播级别是Propagation.MANDATORY,不允许在没有事务的环境下执行。
Propagation.MANDATORY如果存着事务就加入和Propagation.REQUIRED传播级别一致,如果当前不存在事务,会直接进行报错,不允许以非事务的方式执行。
如果要求一起执行成功或者一起回滚,单独执行时候不允许以非事务方式执行,则选择该事物传播级别。
执行逻辑:
开启事务
执行方法A的doPreSomething
执行方法B
执行方法A的doSufSomething
提交或回滚事务
Propagation.NEVER
@Transactional(propagation=Propagation.REQUIRED)
pubilc void A(){
doPreSomething;
B();
doSufSomething;
}
@Transactional(propagation=Propagation.NEVER)
pubilc void B(){
doSomething;
}
调用方法A,开启A事务,在A中调用方法B,由于A事务已经存在且未提交,方法B其不允许在事务内部执行,会直接报错。
如果不通过方法A而单独调用方法B,则方法B会直接在没有事务的环境中执行。
Propagation.NERVER如果存着事务就直接报错,如果当前不存在事务,会以非事务的方式执行。
如果要求内部方法不允许在事务中执行,单独执行时候必须以非事务方式执行,则选择该事物传播级别。
执行逻辑:
开启事务
执行方法A的doPreSomething
执行方法B,直接报错
回滚事务
Propagation.NESTED
@Transactional(propagation=Propagation.REQUIRED)
pubilc void A(){
doPreSomething;
B();
doSufSomething;
}@Transactional(propagation=Propagation.NESTED)
pubilc void B(){
doSomething;
}
调用方法A,开启A事务,在A中调用方法B,由于A事务已经存在且未提交,方法B会加入A事务,但是在执行B方法之前创建一个事务的回滚点,然后再执行方法B,如果方法B执行失败,A事务会回滚到回滚点,也就是B方法之前,如果B执行成功,执行A的doSufSomething()方法出错,则会回滚整个A事务,也就是doSufSomething()、B()和doPreSomething()的执行都会回滚。
如果直接调用方法B,则会开启一个事务,和Propagation.REQUIRED传播级别一致。
如果要求内部方法出错只回滚自己,外部方法执行失败回滚所有,单独执行时候自动开启一个执行,则选择该事物传播级别。
执行逻辑:
开启事务
执行方法A的doPreSomething
创建回滚点savepoint
执行方法B,失败只回滚到savepoint
执行方法A的doSufSomething
提交或回滚事务