秒杀系统中乐观锁(Optimistic Lock)和悲观锁(Pessimistic Lock)的对比

参考九章的讲座。
悲观锁:用户先判断是否拿到锁,如果没有拿到,则排队等待。若可以拿到,则减库存,然后释放锁,这时排队等待的线程有一个可以得到锁。

SQL实现: 注意"for update"表示执行完step 1语句后,把product_id = xxxxx 锁住,其它线程则不能对product_id = xxxxx操作。

//step 1:
select * from stock where product_id = xxxxx for udpate
//step 2:
udpate stock set count = count - 1 where product_id = xxxxx and count > 0

缺点: 1) 会占用大量线程资源,因为一个线程更新不成功,其他线程全部等待。比如说Mysql的线程资源全部耗尽,从前端客户的角度看起来是一直在等待。2) 更新会很慢。
使用场景:在流量不大,对数据一致性要求又非常高的时候,可以用悲观锁。


乐观锁:我不锁,先更新。更新失败了再重新来过

注意这里更新是要结合版本号。如果结合版本号查询有库存,则扣库存, 否则说明版本号被更新。


SQL实现:
查version字段,如果相等,说明可以更新,同时把version+1,否则说明更新失败(因为其他线程更新过了)。

udpate stock set count = count - 1 and version = verison + 1 where product_id = xxxxx and count > 0 and version = xxxxx

一个更新失败的例子:
1. select version 1
2. do some logic. At this time, another thread change the verison to 2
3. update stock version param 1 //发现失败了,则更新version to 1,再重新来过。 

使用场景:如果只要保证数据最终一致性,可以用乐观锁。
秒杀场景不适合乐观锁,因为verion字段会一直变! 后台会一直忙于更新,大量线程hold住。

个人总结:从上面可以看出, 因为MySql的并发量很低(单机1000),无论乐观锁和悲观锁好像都不适用。那怎么办呢?把库存表放在Redis(并发量单机10万)里面就好了。Redis 6.0之前只有一个线程可以访问(单线程模式),自动满足高并发模式。

<think>好的,我现在需要解释乐观锁悲观锁。用户之前问了幻读的问题,现在接着问锁机制,可能是想深入理解并发控制。首先要确定两者的基本定义区别。 乐观锁悲观锁是两种不同的并发控制策略。悲观锁假设冲突很可能发生,所以在访问数据时先加锁,防止其他事务修改。而乐观锁假设冲突较少,只在提交时检查是否有冲突。 接下来要比较它们的应用场景。比如悲观锁适用于写多读少的情况,而乐观锁适合读多写少,冲突少的环境。需要举具体的例子,比如版本号控制或者CAS操作。 还要提到实现方式。悲观锁通常用数据库的锁机制,比如SELECT FOR UPDATE。乐观锁可能用版本号、时间戳或者业务字段进行比较。 需要区分两者的优缺点。悲观锁可能引起性能问题,比如死锁等待时间。乐观锁在冲突多时会导致频繁重试,影响效率。 可能用户想知道在什么情况下选择哪种锁。比如高并发写入用悲观,低冲突用乐观。还要结合实际例子,比如电商库存扣减,可能用乐观锁来避免超卖。 最后要确保解释清晰,结构合理,用用户容易理解的语言,避免技术术语过多,同时保持准确。需要检查是否有遗漏的关键点,比如CAS的具体实现,或者数据库隔离级别的影响。</think>--- 乐观锁悲观锁是两种**并发控制策略**,用于解决多线程/多事务场景下的数据竞争问题,但实现思路完全不同。 --- ### 🌟 核心差异总结 | | **悲观锁** | **乐观锁** | |--------------|------------------------------------|--------------------------------| | **核心思想** | "先加锁,后操作"(假设冲突必然发生) | "先操作,后验证"(假设冲突较少发生) | | **实现方式** | 数据库锁(行锁、表锁) | 版本号/时间戳/CAS机制 | | **适用场景** | 写操作多、冲突频繁 | 读操作多、冲突概率低 | | **性能特点** | 高并发下锁竞争严重 | 冲突少时性能优势明显 | --- ### 🔒 悲观锁详解 **实现原理**:操作数据前先加锁(如`SELECT ... FOR UPDATE`),确保独占访问权 **典型应用**: 1. 银行转账(必须锁定双方账户) 2. 库存扣减(避免超卖) **代码示例(MySQL)**: ```sql BEGIN; SELECT quantity FROM products WHERE id=1 FOR UPDATE; -- 加锁 UPDATE products SET quantity=quantity-1 WHERE id=1; COMMIT; ``` **优缺点**: ✅ 强一致性保证 ❌ 死锁风险、并发性能下降 --- ### 🚀 乐观锁详解 **实现原理**:通过版本号验证数据是否被修改 **典型应用**: 1. 文档协同编辑(如Google Docs) 2. 秒杀系统(通过CAS重试) **实现方式**: 1. **版本号法**: ```sql UPDATE products SET quantity=quantity-1, version=version+1 WHERE id=1 AND version=当前版本; ``` 2. **CAS(Compare-And-Swap)**: ```java AtomicInteger count = new AtomicInteger(100); count.compareAndSet(expect:100, update:99); // 原子操作 ``` **优缺点**: ✅ 无锁竞争、高并发性能 ❌ 需处理重试逻辑、ABA问题(可通过追加时间戳解决) --- ### 📊 如何选择? | **场景** | **推荐方案** | |------------------------------|------------------| | 高频修改(如账户余额) | 悲观锁 | | 读多写少(如文章点赞) | 乐观锁 | | 分布式系统(如Redis集群) | 乐观锁+重试机制 | | 实时性要求高(如股票交易) | 悲观锁 | --- ### 💡 经典案例对比 **库存扣减场景**: - **悲观锁方案**:10个请求串行处理,总耗时1秒 - **乐观锁方案**:10个请求并行处理,失败请求自动重试,总耗时0.3秒 **统计计数场景**: - 使用CAS实现无锁计数器,QPS可达百万级(如Disruptor框架) --- 两种锁机制没有绝对优劣,需根据业务场景选择。现代分布式系统常结合使用(如ZooKeeper使用悲观锁选主,Redis使用乐观锁实现事务)。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值