MySQL系列之事务(三)

有事务注解的方法,一旦进入到方法,就已经和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
提交或回滚事务

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值