乐观锁 悲观锁 完整讲解一、核心概念总览悲观锁Pessimistic Lock思想默认一定会发生并发冲突提前上锁别人都不能修改直到我操作完成释放锁。全程独占资源操作期间阻塞其他读写请求。乐观锁Optimistic Lock思想默认不会发生并发冲突不上锁提交修改时再校验数据是否被别人改动过没改动则更新改动则重试 / 失败。全程无阻塞只有提交时做冲突校验。二、悲观锁详解1. 实现原理依赖数据库行锁 / 表锁事务开启时直接锁住目标数据其他事务阻塞等待锁释放。两种常用悲观锁语法1共享锁 S Lock读锁加锁后别人只能读不能改 / 删sql-- 查询商品并加读锁 SELECT * FROM product WHERE id 1 FOR SHARE;2排他锁 X Lock写锁业务常用加锁后别人不能读、不能改、不能删完全阻塞sql-- 查询商品库存并加写锁防止并发扣减超卖 SELECT * FROM product WHERE id 1 FOR UPDATE;2. 完整扣库存悲观锁流程下单防超卖开启事务BEGIN;SELECT stock FROM product WHERE id1 FOR UPDATE;锁住这条商品数据判断库存 0执行UPDATE product SET stock stock -1 WHERE id1;COMMIT;提交事务自动释放锁其他并发请求执行到第二步时会阻塞等待锁释放3. 悲观锁优缺点✅ 优点强一致性并发场景不会出现超卖、脏写逻辑简单数据库原生支持无需额外字段。❌ 缺点高并发下大量线程阻塞吞吐量极低容易出现锁等待超时长事务风险大事务执行时间越长锁持有越久堆积阻塞请求容易发生死锁多个事务互相持有对方需要的行锁。4. 适用场景并发量低、数据一致性要求极高、短事务操作单笔订单、单笔库存 高并发秒杀场景不推荐。5. 注意点踩坑FOR UPDATE必须走索引否则会升级为表锁锁住整张表性能雪崩。三、乐观锁详解无阻塞高并发首选1. 实现思想不上锁只在更新时校验数据版本是否变更查询数据时同时读取一个版本号 / 时间戳更新时携带查询到的旧版本数据库校验当前版本 旧版本 才允许更新版本不一致说明数据被别人修改更新失败。2. 方案 1版本号机制MyBatis-Plus 内置乐观锁项目在用1表新增 version 字段sqlALTER TABLE product ADD version INT DEFAULT 1 COMMENT 乐观锁版本号;2实体标记版本字段java运行Version private Integer version;3更新逻辑底层自动生成 SQL查询得到商品id1, stock10, version1执行扣减库存sqlUPDATE product SET stock stock - 1, version version 1 WHERE id 1 AND version 1;如果这条 SQL 影响行数 1更新成功如果影响行数 0说明期间有其他线程修改过数据version 已经变成 2更新失败业务可重试。MP 自动配置插件必须配置才生效java运行Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 乐观锁插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; }3. 方案 2时间戳机制无 version 字段时使用用update_time替代 version更新时校验时间戳和查询时一致原理完全相同。4. 乐观锁优缺点✅ 优点全程无锁、无阻塞高并发性能远优于悲观锁不会产生死锁数据库压力小适合秒杀、高并发下单场景。❌ 缺点并发极高时大量更新失败需要业务层循环重试仅能拦截更新冲突无法解决长时间读取期间的数据变更需要额外版本字段重试逻辑需要自己编码。5. 适用场景高并发秒杀、商品库存扣减、多用户同时修改商品信息互联网项目主流方案。四、悲观锁 vs 乐观锁 对比表表格对比维度悲观锁FOR UPDATE乐观锁version 版本号核心思想提前加锁独占资源阻塞并发不加锁提交时校验冲突失败重试阻塞情况并发请求阻塞等待锁释放无阻塞更新失败直接返回性能低并发友好高并发卡顿高并发吞吐量极高死锁风险存在多事务互相等待行锁完全不存在依赖MySQL 事务 行锁版本号字段 业务重试逻辑一致性强一致性最终一致性需处理失败重试典型场景低并发金融转账、短事务数据修改秒杀库存、高并发商品更新五、拓展Redis 分布式锁补充区分数据库悲观锁 / 乐观锁仅适用于单机单实例服务集群多实例分布式场景数据库锁失效需要 Redis 分布式锁属于分布式悲观锁业务分层选型单机 SpringBoot数据库乐观锁 version 扣库存分布式微服务Redis 分布式锁 数据库乐观锁双重保障防超卖。六、电商库存实战选型总结普通下单并发一般MyBatis-Plus 乐观锁 version简单高效秒杀高并发下单Redis 分布式悲观锁 数据库乐观锁双重校验金融转账、资金变动低并发、零差错要求MySQL 悲观锁FOR UPDATE保证强一致性。七、高频面试问题1. 乐观锁更新失败怎么处理循环重试机制捕获更新行数为 0 的情况重新查询数据再尝试更新设置最大重试次数防止死循环。java运行// 伪代码重试3次 int retry 3; while(retry 0){ Product product mapper.selectById(id); int row mapper.updateById(product); if(row 0) break; retry--; }2. 乐观锁会出现超卖吗单线程数据库层面不会但分布式集群场景下多服务同时查询到相同 version会同时更新失败不会超卖但会大量下单失败需要 Redis 分布式锁前置拦截。3. 悲观锁 FOR UPDATE 什么时候升级表锁查询条件没有索引数据库无法定位单行直接锁住整张表性能灾难开发必须保证 id 为主键索引。