数据库事务与并发的浅谈

本文通过实例分析了在MySQL数据库中,即使设置为'可重复读'事务隔离级别,仍可能出现并发下的更新丢失问题。事务在防止更新丢失方面有限制,对于特定情况,代码层面的同步锁更为关键。文章讨论了事务隔离级别的局限性和在实际业务中如何选择合适的控制策略,同时提出了降低数据库隔离级别以提升性能的可能性。
摘要由CSDN通过智能技术生成

在复习多线程方面,产生了一些关于事务的疑问。
疑问:mysql数据库的默认级别是‘可重复读’,也就是一个事务中,它读到值就是一个真实的值,既然这个事务保证了真实值,那么我并发还需要加一个同步锁吗?

以下是具体测试:
mysql数据表:
对库存进行操作
(对stock字段进行操作)

springmvc:

@GetMapping("stock/test1")
    public void stockTest(){
        goodsService.test1();
    }

@GetMapping("stock/test2")
public void stockTest2(){
    goodsService.test2();
}

serviceImpl:

@Override
@Transactional
public void test1() {
    Long l =2600242L;
    Stock stock = stockMapper.selectByPrimaryKey(l);//查询语句
    stock.setStock(stock.getStock()-5000);
    try {
        Thread.sleep(10000L);//线程陷入10s的睡眠
        stockMapper.updateByPrimaryKey(stock);//插入语句
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

@Override
@Transactional
public void test2() {
    Long l =2600242L;
    Stock stock = stockMapper.selectByPrimaryKey(l);//查询语句
    stock.setStock(stock.getStock()+2000);
    stockMapper.updateByPrimaryKey(stock);//插入语句
}

测试环境,在浏览器下用url对stock/test1进行访问,在新开窗口对stock/test2进行访问,
test1会在10s内进行睡眠,相当于tomcat开了2个线程
结果:
大家不妨猜想一下,我们要得到值因该是9999-5000+2000=6999
实际结果:
在这里插入图片描述
在这里插入图片描述
库存变为了4999,那么事务并没有起到作用,test1查询到的真实值在一个事务中并不是最终的真实值。
那么这个数据库‘可重复读’的作用到底是什么呢?细心的网友可能发现上面网址test1一直在转圈,而test2早就访问完了,这是因为两个线程并没有约束关系。
那么来看下一个测试:
修改代码,将Thread.sleep(10000);移动到插入语句后面。

@Override
@Transactional
public void test1() {
    Long l =2600242L;
    Stock stock = stockMapper.selectByPrimaryKey(l);//查询语句
    stock.setStock(stock.getStock()-5000);
    try {
        stockMapper.updateByPrimaryKey(stock);//插入语句
        Thread.sleep(10000L);//线程陷入10s的睡眠
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

测试结果:

在这里插入图片描述
发现没有?两者都在转圈,这说明test2在等test1的更新语句执行完毕,这里没有同步锁,说明这是事务在进行一个约束,防止更新丢失,大家可以猜一下数据库的值:

在这里插入图片描述
非常遗憾的发现,还是造成了更新丢失,说明事务的约束有其局限性,这里因为读写语句的分离,即读和写的时间不同造成了更新丢失,可参考redis单线程的get(),set(),一样会造成更新丢失。如果是只用update语句,例如:'update bank set money = money+500’就不会产生这样的问题。
在这个情景下,用到同步锁就很必要了:

public static Lock lock = new ReentrantLock();//声明一个全局锁
@Override
@Transactional
public void test1() {
	lock.lock();//加锁
    Long l =2600242L;
    Stock stock = stockMapper.selectByPrimaryKey(l);//查询语句
    stock.setStock(stock.getStock()-5000);
    try {
        Thread.sleep(10000L);//线程陷入10s的睡眠
        stockMapper.updateByPrimaryKey(stock);//插入语句
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    lock.unlock();//解锁
}

@Override
@Transactional
public void test2() {
	lock.lock();//加锁
    Long l =2600242L;
    Stock stock = stockMapper.selectByPrimaryKey(l);//查询语句
    stock.setStock(stock.getStock()+2000);
    stockMapper.updateByPrimaryKey(stock);//插入语句
    lock.unlock();//解锁
}

结果:
在这里插入图片描述
得到一个想要的值。

结论:
数据库的事务隔离级别有一定作用,但是并没有代码中的同步锁这么灵活,要根据实际业务进行选择判断,还有一个值得思考的点是在代码加了同步锁后,数据库的隔离级别是否能够降低,这样有助于提高数据库的性能,不当之处,欢迎讨论。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值