尤其在MySQL这样的广泛使用的关系型数据库中,锁机制的选择与应用直接关系到系统的性能和稳定性
MySQL提供了两种主要的锁机制:悲观锁(Pessimistic Lock)和乐观锁(Optimistic Lock)
这两种锁机制各自有着独特的适用场景和优缺点,深入理解并合理运用它们,对于开发高效可靠的数据库应用程序至关重要
一、悲观锁:谨慎行事,确保安全 悲观锁,顾名思义,它持有一种悲观的态度,认为在并发环境中,数据很可能会被其他事务修改
因此,悲观锁在事务开始时,就对要操作的数据进行加锁,直到事务完成并提交或回滚后才释放锁
这样可以确保在事务执行期间,其他事务无法对锁定的数据进行修改操作
1. 悲观锁的实现方式 在MySQL中,悲观锁通常通过以下两种方式实现: -SELECT...FOR UPDATE:这是最常用的悲观锁实现方式
通过在SELECT语句后添加FOR UPDATE子句,可以对查询到的数据行进行加锁
在锁定期间,其他事务如果尝试对该行数据进行修改操作,将会被阻塞,直到锁被释放
-事务设计:合理设计事务,减少事务的执行时间,可以降低锁的持有时间,从而减少锁冲突的可能性
2. 悲观锁的应用场景 悲观锁适用于写多读少且对数据一致性要求极高的场景
例如: -银行系统:在转账操作中,涉及到对账户余额的修改,必须确保在整个转账事务过程中,账户余额数据不会被其他并发事务干扰
此时使用悲观锁可以有效避免数据不一致的问题
-高并发点赞:在高并发环境下对同一篇文章或商品进行频繁点赞时,使用悲观锁可以确保每次点赞操作的数据一致性
3. 悲观锁的优缺点 -优点:能够确保数据的一致性和完整性,避免并发冲突
-缺点:可能会导致其他事务阻塞,影响系统性能
特别是在高并发场景下,悲观锁可能会成为系统性能的瓶颈
二、乐观锁:乐观面对,提高效率 与悲观锁相反,乐观锁持一种乐观的态度,认为在大多数情况下,并发事务之间不会发生冲突
因此,乐观锁不会在事务开始时就对数据进行加锁,而是在提交数据更新之前,检查数据是否在事务执行期间被其他事务修改过
1. 乐观锁的实现方式 乐观锁通常通过以下两种方式实现: -基于版本号(Version Number):这是乐观锁最常用的实现方式
在数据库表中增加一个数字类型的“version”字段,作为数据的版本号
当读取数据时,将version字段的值一同读出
数据每更新一次,对此version值加一
在提交更新时,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对
如果数据库表当前版本号与第一次取出来的version值相等,则予以更新;否则,认为是过期数据,更新失败
-基于时间戳(Timestamp):与基于版本号的实现方式类似,只是在数据库表中增加一个时间戳字段
在更新提交时,检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比
如果一致,则更新成功;否则,就是版本冲突
2. 乐观锁的应用场景 乐观锁适用于读多写少的场景,因为在这种情况下,并发冲突的概率相对较低
例如: -电商系统:商品的浏览次数统计就是一个典型的读多写少的操作
多个用户可以同时查看商品详情页面,而对浏览次数的更新相对较少
此时使用乐观锁可以提高系统的吞吐量
-在线阅读平台:用户主要进行文章或书籍的阅读操作,而很少进行评论或点赞等写操作
使用乐观锁可以减少锁带来的性能损耗
3. 乐观锁的优缺点 -优点:不需要在事务开始时对数据进行加锁,可以提高系统的并发性能和吞吐量
-缺点:在并发冲突频繁的场景下,乐观锁可能会导致大量的更新操作失败,需要上层应用进行重试处理
这可能会增加系统的复杂性和延迟
三、悲观锁与乐观锁的选择策略 在实际开发中,选择使用悲观锁还是乐观锁,需要根据业务需求、数据读写比例以及对数据一致性的要求等因素进行综合考虑
-业务需求:如果业务场景对数据一致性要求极高,且写操作较多,那么悲观锁可能更适合
因为悲观锁能够确保在事务执行期间数据不会被其他事务修改
-数据读写比例:如果系统中读操作远多于写操作,那么乐观锁可能更合适
因为乐观锁不需要在事务开始时对数据进行加锁,可以提高系统的并发性能和吞吐量
-性能考虑:在高并发场景下,悲观锁可能会导致大量的事务阻塞,影响系统性能
而乐观锁则能够减少锁带来的性能损耗,但在并发冲突频繁时可能会导致更新操作失败
因此,需要根据系统的实际性能需求进行选择
四、实战案例:电商系统中的库存更新 以电商系统中的库存更新为例,来说明悲观锁和乐观锁的实际应用
1. 使用悲观锁更新库存 在电商系统中,商品的库存量是固定的
为了保证商品数量不超卖,需要在用户下单时更新库存数量
此时可以使用悲观锁来确保库存数量的正确性
sql -- 开启事务 START TRANSACTION; -- 使用悲观锁查询商品库存信息并锁定行 SELECT number FROM tb_product_stock WHERE product_id ={productId} FOR UPDATE; -- 模拟业务逻辑:检查库存数量并减少 IF(number >0) THEN UPDATE tb_product_stock SET number = number -1 WHERE product_id ={productId}; END IF; --提交事务 COMMIT; 在上述SQL代码中,SELECT...FOR UPDATE语句会对指定商品行的库存数量进行加锁
在锁定期间,其他事务如果尝试对该行数据进行修改操作(如下单减库存),将会被阻塞,直到锁被释放
这样可以确保在事务执行期间库存数量的正确性
2. 使用乐观锁更新库存 在电商系统中,对于非热销商品的库存调整,可以使用乐观锁来减少锁带来的性能损耗
首先,在商品库存表中增加一个version字段作为版本号
sql ALTER TABLE tb_product_stock ADD COLUMN version INT DEFAULT0; 然后,在更新库存时使用乐观锁机制
java // 查询商品库存信息和版本号 ProductStock product = query(SELECT number, version FROM tb_product_stock WHERE product_id ={productId}, productId); // 模拟业务逻辑:检查库存数量并减少 if(product.getNumber() >0){ int newVersion = product.getVersion() +1; int updateCnt = update(UPDATE tb_product_stock SET number = number -1, version ={newVersion} WHERE product_id ={productId} AND version ={oldVersion}, productId, newVersion, product.getVersion()); if(updateCnt >0){ // 更新库存成功 } else{ //乐观锁冲突,更新失败 } } 在上述Java代码中,首先查询商品的库存数量和版本号
然后,根据业务需求计算新的库存数量,并在更新时使用WHERE子句中的版本号条件来检查数据是否被其他事务修改
如果更新的行数为0,则表示发生了乐观锁冲突,更新失败
五、总结 悲观锁和乐观锁是MySQL中两种重要的并发控制机制