DB事务隔离级别之可重复读

1. 隔离级别

读未提交,读已提交,可重复读,串行读
2. 可重复读

应用场景

订单处理:在订单处理系统中,多个事务可能同时读取和修改订单数据。使用"可重复读"隔离级别可以确保在一个事务处理期间,其他事务不能修改已读取的订单数据,从而保证数据的一致性。

问:
上面提到订单处理的例子中,使用"可重复读"隔离级别可以确保在一个事务处理期间,其他事务不能修改已读取的订单数据,从而保证数据的一致性。 是说事务A在读取订单数据时,事务B不能修改订单数据,要知道事务A执行完成,事务B才能执行修改查询操作吗?

答:

是的,使用"可重复读"隔离级别可以确保在一个事务处理期间,其他事务不能修改已读取的订单数据,直到事务A执行完成。

具体来说,在"可重复读"隔离级别下,当事务A开始执行读取订单数据的操作时,数据库会对相关的数据行进行加锁,确保其他事务无法修改这些数据。其他事务(比如事务B)如果要修改或查询这些订单数据,需要等待事务A执行完成并释放锁之后才能进行。

这种机制可以保证事务A在执行期间看到的数据是一致的,不会受到其他事务的修改影响,从而确保了数据的一致性。通过避免并发事务之间的冲突,"可重复读"隔离级别提供了较高的数据隔离性。

需要注意的是,事务B虽然不能修改已读取的订单数据,但是在事务A执行期间,事务B仍然可以读取其他未被锁定的订单数据,只有对已经被锁定的数据进行修改操作时会被阻塞。这样可以确保在并发环境下,事务之间能够同时进行读取操作,提高系统的并发性能。

问:

限量抢购场景是不是也应该应用可重复读隔离级别?

答:

在限量抢购场景中,多个用户同时竞争有限的商品数量,确保数据的一致性和避免冲突是非常重要的。使用"可重复读"隔离级别可以是一个合适的选择,但具体是否适用还需要考虑其他因素。

在限量抢购场景中,如果不使用合适的隔离级别,可能会出现以下问题:

并发读取问题:多个用户同时读取商品的库存信息,并进行相应的判断和操作。如果使用较低的隔离级别,可能会导致一个用户读取到的库存数量与另一个用户读取到的不一致,从而导致超卖或错失购买的问题。

并发修改问题:多个用户同时尝试减少商品库存数量。如果使用较低的隔离级别,可能会导致多个用户同时减少库存数量,并最终导致库存数量出现负数的情况。

使用"可重复读"隔离级别可以一定程度上解决上述问题。在该隔离级别下,每个用户在进行购买操作时,能够看到一致的库存数量,并且其他用户无法修改已读取的库存数据,从而避免了冲突和数据不一致性。

然而,需要注意的是,在高并发的限量抢购场景下,使用"可重复读"隔离级别可能会导致锁竞争增加,降低系统的并发性能。如果系统能够承受一定的数据不一致性或其他容忍度,可以考虑使用较低的隔离级别(如读已提交),以提高系统的并发性能。

最佳实践会因应用的具体需求和系统设计而有所差异,因此在实际中需要综合考虑并发性能、数据一致性和竞争条件等因素,选择合适的隔离级别。

问:

假设你现在的角色是java开发工程师,需要开发限量抢购的需求,涉及的接口有增删改查,现在你需要写一个可落实的技术开发方案,方案要写出应该注意的功能点,性能点,数据安全问题及解决方案,可能出现的问题及解决方案?

答:

作为Java开发工程师,你需要为限量抢购需求制定一个可落实的技术开发方案。以下是一些关键的功能点、性能点、数据安全问题以及相应的解决方案和可能出现的问题与解决方案示例:

  1. 功能点:

    • 商品管理:实现商品的增删改查功能,包括库存管理和商品信息管理。

    • 用户购买:处理用户购买请求,判断库存是否充足,并进行相应的处理。

    • 抢购倒计时:提供倒计时功能,确保在指定时间内进行抢购操作。

  2. 性能点:

    • 并发处理:处理大量的并发购买请求,确保系统能够承受高并发的抢购压力。

    • 缓存优化:使用缓存技术,如Redis,缓存商品信息和库存数量,提高读取性能和减少数据库压力。

    • 异步处理:使用消息队列,如Kafka或RabbitMQ,异步处理购买请求,减轻系统压力。

  3. 数据安全问题及解决方案:

    • 超卖问题:多个用户同时购买同一件商品导致库存数量超卖。解决方案可以使用乐观锁或悲观锁来控制并发访问,确保库存减少的原子性。

    • 数据一致性:避免脏读、不可重复读和幻读等问题。可以使用数据库事务和合适的隔离级别(如"可重复读")来保证数据一致性。

    • 用户认证和授权:确保只有授权用户才能进行抢购操作。可以使用身份验证和访问权限控制来保护接口安全。

  4. 可能出现的问题及解决方案:

    • 系统崩溃:在高并发情况下,系统可能会因为负载过高而崩溃。解决方案可以通过水平扩展和负载均衡来提高系统的可用性和稳定性。

    • 防止刷单:为防止用户刷单,可以设置抢购频率限制、验证码验证或者使用滑动验证码等技术手段。

    • 数据库性能问题:在高并发场景下,数据库可能成为性能瓶颈。解决方案可以使用数据库连接池、读写分离、分库分表等技术来提高数据库性能和扩展性。

以上只是一些基本的功能点、性能点、数据安全问题及解决方案示例,具体的开发方案需要根据实际需求和系统设计进行详细规划和评估。在开发过程中,还需注意代码的可维护性、错误处理和日志记录等方面,以确保系统的稳定性和可靠性。

问:
关于超卖问题的出现及解决详细一点,以代码输出,并说明每一行代码的作用,其中重点说明如何用悲观锁实现,如何用乐观锁实现?

答:
当多个用户同时购买同一件商品时,可能会出现超卖问题,即多个用户购买的数量超过了实际的库存数量。下面是使用悲观锁和乐观锁来解决超卖问题的示例代码,以Java语言为例:

悲观锁实现:

// 假设存在一个名为Product的商品类
public class Product {
    private int id;
    private String name;
    private int stock;

    // 省略构造函数和其他方法

    // 悲观锁实现
    public synchronized boolean purchase(int quantity) {
        if (stock >= quantity) {
            stock -= quantity;
            return true;
        } else {
            return false;
        }
    }
}

解释:

在purchase方法中,使用synchronized关键字修饰方法,保证同一时刻只有一个线程可以执行该方法。
通过判断当前库存stock是否大于等于购买数量quantity来进行购买操作。
如果库存充足,减少库存数量并返回true表示购买成功。
如果库存不足,返回false表示购买失败。
使用悲观锁的方式通过synchronized关键字保证了同一时刻只有一个线程可以执行购买操作,从而避免了超卖问题。

乐观锁实现:

public class Product {
    private int id;
    private String name;
    private int stock;
    private int version;

    // 省略构造函数和其他方法

    // 乐观锁实现
    public boolean purchase(int quantity) {
        while (true) {
/** version值的来源,也可以是在修改数据前,先从库里查出数据的更新时间作为版本,
然后在修改时判断数据的更新时间与之前查出来的是否一致,一致则更新成功,不一致则更新失败。*/
            int currentVersion = version;
            if (stock >= quantity) {
                int newStock = stock - quantity;
                if (compareAndSetVersion(currentVersion, currentVersion + 1)) {
                    stock = newStock;
                    return true;
                }
            } else {
                return false;
            }
        }
    }

    private boolean compareAndSetVersion(int expect, int update) {
        synchronized (this) {
            if (version == expect) {
                version = update;
                return true;
            }
            return false;
        }
    }
}

解释:

在乐观锁实现中,引入了一个version字段用于记录商品的版本号。
在purchase方法中,使用一个循环来不断尝试购买操作,直到成功或者库存不足。
先记录当前版本号currentVersion,在购买操作之前验证库存和版本号是否满足要求。
如果库存充足,计算新的库存数量newStock,并通过compareAndSetVersion方法尝试更新版本号。
compareAndSetVersion方法使用synchronized关键字来保证同一时刻只有一个线程可以修改版本号。
如果版本号符合预期,更新版本号并执行购买操作,返回true表示购买成功。
如果版本号不符合预期,说明在当前线程执行期间有其他线程已经修改了版本号,需要重新尝试购买操作。
使用乐观锁的方式通过版本号的比较和CAS操作(compare-and-set)来实现并发控制,从而避免了超卖问题。在购买操作前通过验证版本号来确保数据的一致性,并在更新版本号时采用同步操作保证原子性。

问:
在java代码中,除了用synchronize实现悲观锁,用版本号+synchronize实现乐观锁,还有什么其他方式实现吗?
答:
除了使用synchronized实现悲观锁和使用版本号+ synchronized实现乐观锁之外,还有其他一些常见的方式来实现并发控制和解决超卖问题。其中包括:

分布式锁:使用分布式锁来实现并发控制,保证在分布式环境下只有一个线程可以执行关键代码块。常见的分布式锁实现方式包括基于ZooKeeper的分布式锁、Redisson等第三方库提供的分布式锁。

CAS操作:CAS(Compare and Swap)是一种乐观锁的实现方式,通过原子比较和交换操作来保证数据的一致性。在Java中,可以使用AtomicInteger、AtomicLong等原子类来实现CAS操作。

数据库的行级锁:在数据库层面使用行级锁来实现并发控制。不同的数据库提供了不同的行级锁机制,如MySQL中的行级锁、Oracle中的行级事务等。

乐观锁算法:除了版本号,还可以使用其他乐观锁算法,如时间戳、哈希值等。这些算法可以辅助判断数据的一致性,避免更新冲突。

需要根据具体的业务场景、技术栈和系统需求来选择适合的并发控制方式。每种并发控制方式都有其特点和适用场景,开发人员需要综合考虑性能、复杂度、可维护性等因素来选择最合适的实现方式。

菜鸟学习笔记,不对请指正!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值