后端开发必看!一文搞懂乐观锁和悲观锁

360影视 日韩动漫 2025-05-28 16:48 2

摘要:你在后端开发过程中,有没有遇到过这样的场景:多个线程同时对数据库中的同一条数据进行修改,最终导致数据错乱、业务逻辑异常?又或者在高并发请求下,系统性能突然大幅下降,排查后却毫无头绪?其实,这些问题的根源,很可能就出在锁机制的选择上!今天咱们就来深入聊聊后端开发

你在后端开发过程中,有没有遇到过这样的场景:多个线程同时对数据库中的同一条数据进行修改,最终导致数据错乱、业务逻辑异常?又或者在高并发请求下,系统性能突然大幅下降,排查后却毫无头绪?其实,这些问题的根源,很可能就出在锁机制的选择上!今天咱们就来深入聊聊后端开发中至关重要的两种锁 —— 乐观锁和悲观锁。

在互联网大厂的业务场景中,数据的并发访问是常态。以电商系统为例,在大促期间,成千上万的用户同时下单购买同一款商品,这就意味着会有大量线程同时对商品库存数据进行读取和修改操作。如果没有合适的锁机制来保障数据的一致性,就会出现超卖、库存数据不准确等严重问题。不仅影响用户体验,还可能给企业带来巨大的经济损失和声誉损害。

从技术原理角度来看,在多线程环境下,不同线程对共享资源的访问顺序和时间是不确定的。当多个线程同时尝试修改同一数据时,就可能出现数据竞争问题。比如线程 A 读取了数据,然后进行一些计算,准备更新数据;但在线程 A 还未完成更新时,线程 B 也读取了相同的数据,由于线程 A 还未将修改后的数据写回,线程 B 读取到的就是旧数据,之后线程 B 基于这个旧数据进行修改并写回,那么线程 A 的修改就被覆盖了,这就是典型的数据不一致问题。而乐观锁和悲观锁,正是解决这类问题的有效手段,它们有着不同的设计理念和应用场景 。

悲观锁,从名字就能看出它的 “性格”—— 十分悲观。它基于一种假设:在数据访问过程中,随时都可能发生冲突。所以,在对数据进行操作(尤其是写操作)之前,悲观锁会先获取锁,将数据锁定。这就好比你在图书馆看到一本心仪的书,担心被别人借走,于是直接把书占为己有,直到自己看完。

在数据库操作层面,以常见的关系型数据库 MySQL 为例,当一个事务通过SELECT... FOR UPDATE语句获取了悲观锁后,其他事务想要对同一数据进行修改,就只能进入等待队列,直到持有锁的事务提交或回滚,释放锁资源。例如,有一个订单表orders,表中有字段order_id、order_amount等,当一个事务执行SELECT order_amount FROM orders WHERE order_id = 1 FOR UPDATE时,就获取了order_id为 1 的这条记录的悲观锁。此时,如果另一个事务尝试执行UPDATE orders SET order_amount = order_amount - 100 WHERE order_id = 1,这个操作就会被阻塞,直到前一个事务完成并释放锁。

这种机制虽然能很好地保证数据的一致性,确保在同一时刻只有一个事务能修改数据,但缺点也很明显。由于长时间锁定数据,会导致其他事务阻塞,在高并发场景下,系统性能会受到严重影响。想象一下,大量请求被阻塞,等待获取锁,系统的响应时间会大幅增加,吞吐量也会急剧下降。而且,悲观锁使用不当还可能出现死锁情况,比如事务 A 持有资源 X 并等待资源 Y,事务 B 持有资源 Y 并等待资源 X,两个事务相互等待,导致系统陷入僵局。

从实现原理来看,悲观锁通常依赖数据库自身提供的锁机制,如行锁、表锁等。行锁是锁定某一行数据,表锁则是锁定整个表。行锁的锁定范围小,并发性能相对较好,但如果事务涉及到多行数据的操作,可能会导致锁争用加剧;表锁的锁定范围大,虽然能简化锁管理,但并发性能较差,一个事务操作表时,其他事务对该表的任何操作都要等待。开发者需要根据业务需求谨慎选择。

在编程语言层面,以 Java 为例,也可以通过synchronized关键字来实现悲观锁。比如有一个共享资源类Resource:

class Resource {private int data;public synchronized void modifyData(int newData) {this.data = newData;}}

当多个线程调用modifyData方法时,由于synchronized关键字的存在,同一时刻只有一个线程能进入该方法对data进行修改,其他线程只能等待。

乐观锁秉持着乐观的态度,它认为在数据访问过程中,冲突发生的概率较低。因此,乐观锁不会在一开始就锁定数据,而是在提交更新操作时,才去检查数据是否被其他事务修改过。

通常,乐观锁会利用数据版本号来实现这一功能。以数据库表设计为例,表中会增加一个version字段,初始值为 1。当事务 A 要更新数据时,会先执行SELECT语句读取数据及其version值,然后在更新语句中,将version值加 1,并通过UPDATE table_name SET column1 = value1, column2 = value2, version = version + 1 WHERE id = XXX AND version = 旧version这样的语句,与数据库中当前的version值进行比较。如果相同,说明在该事务执行期间,数据没有被其他事务修改,更新操作可以成功;如果不同,则表示数据已被修改,该事务需要重新读取数据并进行操作。

例如在一个用户信息表users中,有user_id、user_name、version等字段。假设当前有一个用户记录user_id为 1,version为 1。当事务 A 读取该用户信息准备修改用户名时,它读取到version为 1。在事务 A 执行修改操作过程中,事务 B 也读取了该用户信息并进行了修改,此时数据库中该用户记录的version变为 2。当事务 A 完成修改准备提交时,执行UPDATE users SET user_name = 'new_name', version = version + 1 WHERE user_id = 1 AND version = 1,由于此时数据库中version已经变为 2,条件不满足,更新操作失败,事务 A 需要重新读取数据并重新执行修改流程。

乐观锁的优势在于避免了锁等待,在高并发读多写少的场景下,能极大地提升系统性能。例如新闻资讯类应用,大量用户同时浏览新闻内容(读操作),偶尔有编辑更新新闻(写操作),使用乐观锁可以让读操作几乎不受影响,系统响应迅速。但它也有局限性,如果在事务执行过程中,数据被频繁修改,就会导致很多事务不断重试,反而降低了效率。而且,乐观锁无法解决 “ABA 问题”,即一个数据从 A 变为 B,再变回 A,版本号虽然没变,但数据实际已经被修改过了,在某些对数据状态敏感的业务中,这可能引发问题。

在一些分布式系统中,还可以通过时间戳来实现乐观锁。每个数据记录都包含一个时间戳字段,读取数据时记录当前时间戳,更新数据时检查当前时间戳与数据库中的时间戳是否一致。如果一致,则更新数据并更新时间戳;如果不一致,则表示数据已被其他事务修改,操作失败。例如在一个分布式缓存系统中,缓存的数据可以附带一个时间戳,当客户端读取数据时,记录时间戳,在更新缓存数据时,将本地记录的时间戳与缓存中数据的时间戳进行对比,以此判断数据是否被其他客户端修改过。

了解了乐观锁和悲观锁的原理、特点以及优缺点后,我们该如何在实际开发中进行选择呢?

如果你的业务场景是读操作远远多于写操作,并且对数据一致性要求不是特别苛刻,比如商品详情页的浏览(商品库存更新频率相对较低),那么乐观锁就是不错的选择。它可以减少锁竞争,提升系统并发处理能力。以电商平台商品详情页展示商品库存为例,大量用户同时浏览商品详情,对库存数据的读取操作频繁,而库存更新操作相对较少。此时使用乐观锁,在用户读取库存数据时不需要加锁,只有在库存更新时检查版本号或时间戳,这样可以大大提高系统的响应速度,减少用户等待时间。

而当业务场景中写操作频繁,对数据一致性要求极高,像金融系统的资金转账业务、银行账户余额修改等,悲观锁则更能保障数据的准确性和安全性。虽然它可能带来性能损耗,但在这类不容许数据出错的场景下,数据一致性的优先级更高。比如在银行转账业务中,每一笔转账都涉及到资金的准确变动,任何数据不一致都可能导致严重的资金损失,所以必须使用悲观锁来确保在同一时刻只有一个事务能对账户余额进行修改。

此外,还有一些场景可以结合两者的优势。比如先使用乐观锁进行操作,如果重试次数达到一定阈值,再切换为悲观锁,以平衡性能和数据一致性。例如在一个在线游戏的物品交易系统中,大部分情况下玩家之间的交易操作冲突较少,可以先使用乐观锁来处理交易流程,提高系统性能。但如果在短时间内某个物品的交易重试次数过多,说明该物品交易冲突频繁,此时可以切换为悲观锁,确保交易数据的一致性。

在一些复杂的业务场景中,还可以采用分布式锁结合乐观锁或悲观锁的方式。比如在微服务架构中,不同服务之间可能会对共享数据进行操作,为了保证数据一致性,可以先使用分布式锁来保证不同服务对共享数据操作的原子性,然后在每个服务内部根据业务特点选择乐观锁或悲观锁进一步处理数据的并发访问。

在后端开发的道路上,锁机制是我们必须熟练掌握的重要知识。乐观锁和悲观锁各有优劣,没有绝对的好坏之分,只有根据具体业务场景合理选择和运用,才能让我们开发的系统在高并发环境下稳定、高效地运行。

希望今天的分享能让你对这两种锁有更清晰、更深入的认识。如果你在实际开发中还有关于锁机制的问题,或者有不同的见解,欢迎在评论区留言讨论,咱们一起攻克后端开发中的难题,共同进步!

来源:从程序员到架构师一点号

相关推荐