数据库事务和@Transaction注解

数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。

事务的基本要素(ACID)

  • 原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。
  • 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
  • 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
  • 持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

当单进程执行时,很容易保证事务的4中基本要素,并且不会引发问题,然而当多个并发进程共同执行事务时候,可能会引发不同的问题。

事务隔离级别

隔离级别 脏读 不可重复读 幻读 特点
未提交读(read-uncommitted) 优点在于并发能力高,适合那些对数据一致性没有要求而追求高并发的场景,缺点是脏读
读写提交(read-committed) × 出现不可重复读
可重复读(repeatable-read) × × mysql的默认事务隔离级别。会出现幻读
串行化(serializable) × × × 并行性低,一般不用。通常消除幻读使用数据库锁的方式
  • 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
  • 不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
  • 幻读:幻读是针对多条数据库记录的。例如,系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

数据库锁

乐观锁

相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。一般的实现乐观锁的方式就是记录数据版本,并且不会造成线程的阻塞。

但是,版本的冲突会造成请求失败的概率剧增,这时往往需要通过重入的机制将请求失败的概率降低。而多次的重入又会带来过多执行SQL的问题,为了克服这个问题,可以考虑使用按时间戳或者限制重入次数的办法。

悲观锁

正如其名,它指的是对数据被外界修改持保守态度。因此在整个数据处理过程中,将数据处于锁定状态。 悲观锁的实现,往往依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

数据库两种锁可以实现悲观锁,排他锁(Exclusive Lock,也叫X锁)和共享锁(Shared Lock,也叫S锁)。排他锁表示对数据进行写操作,如果一个事务对对象加了排他锁,其他事务就不能再给它加任何锁了;共享锁会为操作对象加共享锁,可以允许其他共享锁的加锁操作,但是不允许排它锁加锁操作。

锁粒度

MySQL锁的粒度几种,从小到大分别是,行锁(Record Lock)、间隙锁(Gap Lock)、Next-Key Lock、表锁。
1、 行锁:锁定一行记录
2、 间隙锁:锁定一个范围,但是不包含记录本身
3、Next-Key Lock:Record Lock和Gap Lock的组合,锁定一个区间以及对应的记录
4、 表锁:锁定整张表

MySQL innoDB加锁是通过索引项判断加锁的范围,即如果操作的对象上没有索引或者没有使用索引,则会导致锁的粒度扩大。因为InnoDB索引采用的BTree结构,所以,如果不是唯一索引,则如果使用查询值索引不存在,或者使用的条件查询则会引发Next-Key Lock。

意向锁

有了共享锁(S锁)和排它锁(X锁),能够实现数据库操作不同粒度的加锁,但是在一个操作需要给整个表加X锁的时候,需要确定整个表以及表的每一行没有被加S锁或者X锁,那么如何确定表中每一行没有被加锁,需要扫描整个表吗?如果表中有海量记录需要长时间等待,此时需要有意向锁。

Spring的@Transaction注解

在Spring中,为了 “擦除”令人厌烦的 try...catch...finally..语句,减少代码中数据库连接开闭和事务回滚提交的代码, Spring利用其 AOP 为我们提供了一个数据库事务的约定流程,这样开发的代码可读性就更高,也更好维护。

Spring数据库事务约定

@Transactional源码分析:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    // 通过 bean name 指定事务管理器
    @AliasFor("transactionManager")
    String value() default "";
    // 同 value 属性
    @AliasFor("value")
    String transactionManager() default "";
    // 传播行为
    Propagation propagation() default Propagation.REQUIRED;
    // 隔离级别
    Isolation isolation() default Isolation.DEFAULT;
    // 超时时间
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
    // 是否只读事务
    boolean readOnly() default false;
    // 方法在发生指定异常时回滚,默认是所有异常都囚滚
    Class<? extends Throwable>[] rollbackFor() default {};
    // 方法在发生指定异常名称时回滚,默认是所有异常都回滚
    String[] rollbackForClassName() default {};
    // 方法在发生指定异常时不回滚,默认是所有异常都回滚
    Class<? extends Throwable>[] noRollbackFor() default {};
    // 方法在发生指定异常名称时不回滚,默认是所有异常都回滚
    String[] noRollbackForClassName() default {};
}

事务管理器

事务的打开、回滚和提交是由事务管理器来完成的。在Spring中,事务管理器的顶层接口为PlatformTransactionManager,Spring也定义了一些其他的接口和类。

在Spring Boot中,当你依赖于mybatis-spring-boot-starter之后,它会自动创建一个DataSource­ TransactionManager对象作为事务管理器;如果依赖于spring-boot-starter-data-jpa,则它会自动创建JpaTransactionManager对象作为事务管理器,所以我们一般不需要自己创建事务管理器而直接使用它们即可。

传播行为

传播行为是方法之间调用事务采取的策略。在大部分的情况下,我们会认为数据库事务要么全部成功,要么全部失败。但当执行批量任务时,我们有时不希望因为极少数的任务不能完成而回滚所有的批量任务 。

在Spring中,当一个方法调用另外一个方法时,可以让事务采取不同的策略工作,如新建事务或者挂起当前事务等,这便是事务的传播行为。例如,批量任务我们称之为当前方法,当它调用单个任务时,称单个任务为子方法。当前方法调用子方法的时候,让每一个子方法不在当前事务中执行,而是创建一个新的事务,我们就说当前方法调用子方法的传播行为为新建事务。此外,还可能让方法在无事务、独立事务中执行,这些完全取决于业务需求。

源码分析:

public enum Propagation {
    // 需要事务。它是默认传播行为,如果当前存在事务,就沿用当前事务,否则新建一个事务运行子方法
    REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
    // 支持事务,如果当前存在事务,就沿用当前事务,如果不存在 ,则继续采用无事务的方式运行子方法
    SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
    // 必须使用事务,如果当前没有事务,则会抛出异常,如果存在当前事务 ,就沿用当前事务
    MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
    // 无论当前事务是否存在,都会创建新事务运行方法,这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立
    REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
    // 不支持事务,当前存在事务时,将挂起事务,运行方法
    NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
    // 不支持事务,如果当前方法存在事务,则抛出异常,否则继续使用无事务机制运行
    NEVER(TransactionDefinition.PROPAGATION_NEVER),
    // 在当前方法调用子方法时,如果子方法发生异常,只因滚子方法执行过的 SQL,而不回滚当前方法的事务
    NESTED(TransactionDefinition.PROPAGATION_NESTED);

    private final int value;

    Propagation(int value) { this.value = value; }

    public int value() { return this.value; }

}

常用的传播行为是REQUIRED、REQUIRES_NEW、NESTED三种。

@Transactional自调用失效问题

Spring数据库事务的实现原理是AOP,而AOP的原理是动态代理。在事务自调用的过程中,是类自身的调用,而不是代理对象去调用,那么就不会产生AOP,这样Spring就不能把你的代码织入到约定的流程中,于是就产生了@Transactional自调用失效的场景。

有两种解决方法:用一个Service去调用另一个Service,这样就是代理对象的调用,Spring就会将代码织入事务流程;或者从Spring IoC容器中获取代理对象去启用AOP。

参考资料

  • 《深入浅出Spring Boot 2.X》
  • 《mysql数据库事务与锁》--尧亦
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容