事务与锁当transactional遇上synchronized(代码片段)

一个有梦有戏的人 一个有梦有戏的人     2023-04-01     124

关键词:


事务与锁 - Transactional与Synchronize🥰

前言

最近工作中遇到某些七七八八的问题,就是与事务和锁、并发都有着紧密联系相关的问题所在。主要情况是:通过调用方法获取编号,而这个编号是递增有序的,并且存在于数据库中,简单理解就是需要用到这种编号(以下称任务编号),需要从数据库获取出来,在+1最为本次需要的编号,然后在存回数据库中,提供下次使用。直观来看是没得问题的,但是,可能在某次并发的时候出现编号相同,着属实很令人头疼,在经过领导的指导下是完美的解决了,接下来复盘一下。

问题回放

因为公司项目使用WQL作为持久层,但是我这次使用Mybatis-Plus,效果大差不差。新建springboot项目,基础创建不在赘述。主要创建一张简单的数据表,就一个id和number字段。之后用mp自动生成代码。

问题一

最外层加上Transactional注解,并且在对编码操作的方法也加了Transactional注解,为了防止并发问题,加了锁。此时模拟的问题是:在嵌套事务中,父事务延时提交导致获得到的数据出错。

1、代码与结果复现

直接在控制层中调用,这里会加上Transactional,并且一开始数据库数据为“id”:“1”, “number”:“460”

@GetMapping("/t1")
@Transactional(rollbackFor = Exception.class)
public void getTest1() 
    String n = countNumService.getCount();
    System.out.println(" t1 : " + n);
    try 
        Thread.sleep(6000);
     catch (InterruptedException e) 
        throw new RuntimeException(e);
    


@GetMapping("/t2")
@Transactional(rollbackFor = Exception.class)
public void getTest2() 
    String n = countNumService.getCount();
    System.out.println(" t2 : " + n);
    // 忽略其他的增删操作

对数据库获取操作的方法,加上Transactional与synchronized。

@Override
@Transactional(rollbackFor = Exception.class)
public synchronized String getCount() 
    // 获取
    CountNum countNum = countNumMapper.selectById(1);

    countNum.setNumber(countNum.getNumber() + 1);
    // 修改
    countNumMapper.updateById(countNum);

    return countNum.getNumber().toString();

通过IDEA的插件RestServices(也可以用postman)测试,先请求/t1接口,这里会睡眠6s来模拟事务延时提交,在去请求/t2接口,可以看出得到的数据会是相同的。

2、原因分析

只有两个线程都能形成两个相同的code,仔细分析一下,假设synchronized锁是锁住的,那为什么会出现这样的问题呢?当t1线程访问getCount()方法,此时他拿到synchronized的锁,在进行获取数据并且+1操作后进行update操作,此时synchronized的锁已经被释放了,但是父事务却没有提交,也就是没有写回到数据库中,接下来t2线程过来了,也拿到了这把锁,又从数据库获取了数据,此时获得的是脏数据,最后就会导致出现了相同的code。

3、解决方法

这里是因为事务的先后提交导致,可以使用Transactional注解的配置来解决,使用@Transactional(propagation = Propagation.REQUIRES_NEW),每次都会启动一个新的事务,是Spring事务传播机制的一种级别,表示当前方法必须在自己的事务中运行,如果当前已经存在一个事务,则会挂起该事务,创建一个新的事务用于执行当前方法。当当前方法执行完成后,新事务被提交,原事务恢复执行。

问题二

此次测试是使用apache-jmeter-5.4.3测试工具来测试20次请求的并发情况(接口每次会创建100次,一共会又20*100次),在以上代码的条件下编写新的接口。

1、问题复现

以下是每次请求都会创建100个线程,getCount()除了使用propagation = Propagation.REQUIRES_NEW,其他不变。

@GetMapping("/t3")
@Transactional(rollbackFor = Exception.class)
public void getTest1() 
    for (int i = 0; i <100 ; i++) 
        new Thread(()->
            String taskCode = countNumService.getCount();
            System.out.println("t1 " + Thread.currentThread().getName() + " code: " + taskCode);
        ).start();
    

测试结果就会发现并发下出现了种种问题,会出现相同的code。

2、原因分析

这是由于父子事务嵌套,子事务与锁一起使用,导致了synchronized锁的失效。当我把事务去掉的时候,则就不会出现并发的问题。

问题就是出在事务与synchronized锁的共同使用导致的,如果既要保证并发不出问题,又要保证在异常的时候需要回滚数据,在实际应用中,getCount()内部又其他对数据库的操作,因此需要事务来保证异常的回滚,也就是一定需要父子事务的嵌套,在这种情况下需要怎么做处理?
首先先来了解事务Transactional与锁synchronized一同使用会带来什么问题。

事务Transactional与锁synchronized

事务Transactional与锁synchronized他们是两种不一样的机制,两种机制要是一起使用,可能会出现一些问题。

1、synchronized与Transactional区别

首先,synchronized锁是用来实现线程同步,防止多个线程同时访问共享资源导致的并发问题。而Transactional是Spring框架中用于管理事务的机制,用于保证多个操作的四个特性(原子性、一致性、隔离性和持久性)。

2、可能带来的问题

  1. 事务可能被锁定:如果在一个方法中使用synchronized锁定了某个共享资源,同时该方法又使用了Transactional来管理事务,那么其他线程在访问该方法时可能会被阻塞,因为事务被锁定了。
  2. 死锁问题:如果在多个线程中同时使用synchronized和Transactional,可能会导致死锁问题,因为synchronized锁定的资源可能被多个线程同时访问,而Transactional又会对这些操作进行管理,可能会导致事务的死锁。
  3. 性能问题:如果在一个高并发的系统中同时使用synchronized和Transactional,可能会导致性能问题,因为synchronized会导致线程阻塞,而Transactional又会增加事务的开销,从而影响系统的性能。

3、针对问题二的解决

可以将子事务中的锁移到父事务中,优化一下细粒度,就只对获取任务编码的这条语句进行上锁。

@GetMapping("/t4")
@Transactional(rollbackFor = Exception.class)
public void getTest4() 
    for (int i = 0; i <100 ; i++) 
        new Thread(()->
            synchronized(CountNumController.class) 
                String taskCode = countNumService.getCount();
                System.out.println("t4 " + Thread.currentThread().getName() + " code: " + taskCode);
            
        ).start();
    

这样就能保证并发不出问题,也能保证将父子事务都存在。

👍创作不易,如有错误请指正,感谢观看!记得点赞哦!👍

事务4-事务与锁

事务与锁是不同的。事务具有ACID(原子性、一致性、隔离性和持久性),锁是用于解决隔离性的一种机制。事务的隔离级别通过锁的机制来实现。另外锁有不同的粒度,同时事务也是有不同的隔离级别的(一般有四种:读未提... 查看详情

数据库事务与锁

开启事务就自动加锁。事务与锁是不同的。事务具有ACID(原子性、一致性、隔离性和持久性),锁是用于解决隔离性的一种机制。事务的隔离级别通过锁的机制来实现。另外锁有不同的粒度,同时事务也是有不同的隔离级别的。一... 查看详情

事务4.3-事务与锁(锁)

...当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。加锁是实现数据库并发控制的一个非常重要的技术。在实... 查看详情

mysql事务与锁

数据库一般都会并发执行多个事务,多个事务可能会并发的对相同的一批数据进行增删改查操作,可能就会导致我们说的脏写、脏读、不可重复读、幻读这些问题。这些问题的本质都是数据库的多事务并发问题,为了解决多事务... 查看详情

数据库事务隔离级别与锁

一、事务的4个基本特征     所谓事务是用户定义的一个数据库操作序列,这些操作要么全做要么全不做,是一个不可分割的工作单位。例如,在关系数据库中,一个事务可以是一条SQL语句、一组SQL语句或整个程序。... 查看详情

mysql事务与锁表的问题?

后台语言用php,数据库为mysql,用InnoDB引擎,我先开启事务,然后用locktables锁住所有会受影响的表。但我发现如果先开启事务,再锁表。出现错误时事务不起作用,不能回滚,但程序执行过程中表是锁定的。如果把锁表放在事务... 查看详情

面试突击86:springboot事务不回滚?怎么解决?

...以下这些:非public修饰的方法中的事务不自动回滚;当@Transactional遇上try/catch事务不自动回滚;调用类内部的@Transactional方法事务不自动回滚;抛出检查异常时事务不自动回滚;数据库不支持事务,事务也不会自动回滚。那么对... 查看详情

[mssql]sqlserver之创建分布式事务

...法解析:示例:示例结果:显式事务定义显式事务以BEGINTRANSACTION语句开始,并以COMMIT或ROLLBACK语句结束。备注BEGINTRANSACTION使@@TRANCOUNT按1递增。BEGINTRANSACTION代表一点,由连接引用的数据在该点逻辑和物理上都一致的。如果遇上错... 查看详情

java事务以及嵌套事务(代码片段)

...java事务属性Propagation取值:  REQUIRED(默认值):在有transaction状态下执行;如当前没有transaction,则创建新的transaction;  SUPPORTS:如当前有transaction,则在transaction状态下执行;如果当前没有transaction,在无transaction状态下执... 查看详情

@transaction不回滚事务问题(代码片段)

发现项目中使用@Transactional注解事务,抛了异常却不回滚,分析下原因。一、声明式事务特性先来了解一下@Transactional注解事务的特性,以便于更好排查问题1、service类(一般不建议在接口上)上添加@Transactional,可以将整个类纳入sp... 查看详情

声明式事务基于注解@transactional的理解

...事务与声明式事务这里描述常用的声明式事务的原理。@Transactional 实现机制:  当在方法上使用@Transactional时,SpringFramework默认使用AOP代理,在代码运行时生成一个代理对象,根据@Transactional的属性配置信息,这个代理对象... 查看详情

十事务(transaction)

1.事务是什么?2.示例    查询事务的隔离级别,1>会话级2>全局级          查看详情

transactional事务管理操作

Transactional的属性:alueString可选的限定描述符,指定使用的事务管理器propagationenum:Propagation可选的事务传播行为设置isolationenum:Isolation可选的事务隔离级别设置readOnlyboolean读写或只读事务,默认读写timeoutint(insecondsgranularity)事务超... 查看详情

关于fmdb事务(transaction)的解读

 事务(Transaction)的描述:  事务(Transaction)是并发控制的基本单位。所谓的事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。例如,银行转账工作:从一个账号扣款并使另一个... 查看详情

laravel嵌套事务transactions(代码片段)

Laravel嵌套事务transactions前言laravel嵌套事务transactions实现调用示例:代码分析:总结:前言关于mysql的事务嵌套可以查看这个地址:https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html里面有这么一句话Transactionscannotbenested.Thisisaconsequenc... 查看详情

spring事务:@transactional

一、背景事务:原子性、一致性、隔离性、持久性 二、方式1.编程式事务:使用代码实现PublicinterfacePlatformTransactionManager{  //由TransactionDefinition得到TransactionStatus对象TransactionStatusgetTransaction(TransactionDefinitionde 查看详情

spring之注解事务@transactional

Spring在TransactionDefinition接口中规定了7种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播:事务传播行为类型事务传播行为类型说明PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如... 查看详情

事务注解@transactional不起作用

参考技术A    如果Transactional注解应用在非public修饰的方法上,Transactional将会失效。   1》在主方法上,不加@Transactional注解,在子方法上加@Transactional注解,主方法调用子方法,这种情况将会时效 ... 查看详情