一、LambdaQuery 复杂查询LambdaQuery 用法构建复杂查询若是简单的根据id的增删改查还是建议用IService中的xxxById()这类传统方法。示例一般做法要加很多if条件判断很繁琐。使用LambdaQuery方法构建where查询条件的时候可以加条件最后根据你的查询要求查询一个.one()查询多个list()查询数量count()分页查询......二、LambdaUpate 复杂更新做更新的时候可以加上condition条件条件满足才更新。示例【注意】LambdaUpdate 最后一定要跟上update()方法才能执行三、乐观锁与悲观锁示例3-1、超市购物的故事想象你和朋友都想买超市里最后一瓶可乐1、悲观锁的做法悲观派你一进超市就把可乐所在的货架锁起来只有你拿完可乐其他人才能靠近缺点其他人只能干等着效率低2、乐观锁的做法乐观派大家都可以自由看货架你看到可乐时记住哦现在有 1 瓶去收银台结账时收银员检查现在还是 1 瓶吗如果还是 1 瓶 → 卖给你数量改成 0如果已经是 0 瓶被别人买走了→ 告诉你不好意思已经卖完了3-2、在数据库中的应用假设商品表有这样一条数据商品ID: 1 商品名: iPhone 库存: 10 版本号: 1 ← 关键字段不用乐观锁的问题小明和小红同时买 iPhone小明查询库存 10小红查询库存 10小明买 1 个更新库存 9小红买 1 个更新库存 9错了应该是 8结果卖了 2 个但库存只减了 1 个使用乐观锁版本号机制// 1. 查询商品记住版本号 SELECT id, stock, version FROM product WHERE id 1; // 结果stock10, version1 // 2. 更新时检查版本号 UPDATE product SET stock stock - 1, -- 库存减 1 version version 1 -- 版本号加 1 WHERE id 1 AND version 1; ← 关键只有版本号没变才更新执行过程小明执行更新version 1 ✓ 更新成功库存变 9版本变 2小红执行更新version 1 ✗ 失败版本号已经是 2 了小红需要重新查询拿到新的版本号再试3-3、MyBatis-Plus 中的使用TableName(product) public class Product { private Long id; private Integer stock; Version // 就这么简单 private Integer version; } // 使用时 Product product productMapper.selectById(1); product.setStock(product.getStock() - 1); productMapper.updateById(product); // MyBatis-Plus 会自动检查版本号乐观锁不一定要用version字段可以直接用业务字段来做比较3-4、业务字段做乐观锁1、用余额字段做乐观锁// 用户表 public class User { private Long id; private BigDecimal balance; // 直接用余额做比较 } // 扣款操作 public boolean deduct(Long userId, BigDecimal amount) { // 1. 先查询当前余额 User user userMapper.selectById(userId); BigDecimal oldBalance user.getBalance(); // 2. 计算新余额 BigDecimal newBalance oldBalance.subtract(amount); if (newBalance.compareTo(BigDecimal.ZERO) 0) { return false; // 余额不足 } // 3. 更新时比较余额乐观锁 int rows userMapper.update(null, new UpdateWrapperUser() .set(balance, newBalance) .eq(id, userId) .eq(balance, oldBalance) // 关键用余额做比较 ); return rows 0; // 返回是否更新成功 }SQL 语句UPDATE user SET balance 900 WHERE id 1 AND balance 1000; -- 只有余额还是 1000 才更新2、两种方式对比方式1用 version 字段UPDATE user SET balance balance - 100, version version 1 WHERE id 1 AND version 5;优点通用性强适用于所有更新场景版本号递增便于追踪修改次数MyBatis-Plus 自动支持Version注解缺点需要额外的字段方式2用业务字段余额UPDATE user SET balance 900 WHERE id 1 AND balance 1000; -- 直接用余额比较优点不需要额外字段更直观业务语义清晰同时解决了余额不能为负的问题缺点只适用于特定场景需要手动编写代码3、更简洁的 SQL 写法其实用余额做乐观锁还可以更简单-- 直接在 SQL 中判断余额是否足够 UPDATE user SET balance balance - 100 WHERE id 1 AND balance 100; -- 确保余额足够才扣款// MyBatis-Plus 写法 int rows userMapper.update(null, new UpdateWrapperUser() .setSql(balance balance - amount) .eq(id, userId) .ge(balance, amount) // balance amount ); if (rows 0) { // 更新失败可能是余额不足或并发冲突 }乐观锁的核心思想是更新时检查数据是否被修改过。version只是一种实现方式任何能反映数据状态的字段都可以用来做乐观锁对于余额、库存这类数值字段直接用它们做比较更自然、更高效这种方式在高并发扣款、扣库存的场景中非常常用3-5、总结乐观锁 先不锁做事的时候再检查有没有被别人改过优点不用等待效率高适合读多写少的场景缺点如果冲突多需要反复重试关键用版本号或时间戳来判断数据有没有被修改过乐观锁的核心思想是更新时检查数据是否被修改过。compare and udate四、为什么乐观锁适合适合读多写少的场景4-1、用抢票的例子来说明场景1演唱会门票读多写少10000 人在看票读操作只有 100 人能抢到票写操作用乐观锁10000 人都能自由查看余票信息不用等待 ✓100 人抢票时才检查版本号大部分人能成功少数人失败了重试一下就好效率高场景2秒杀活动读少写多10000 人同时抢 1 件商品几乎所有人都在写操作下单用乐观锁用户1 尝试购买 → 版本号1成功库存0版本2 用户2 尝试购买 → 版本号1失败版本已经是2了 用户3 尝试购买 → 版本号1失败 用户4 尝试购买 → 版本号1失败 ... 用户10000 尝试购买 → 版本号1失败问题9999 人都失败了需要重新查询、重新尝试反复循环大量的数据库查询大量的更新失败CPU 和数据库压力巨大效率很低4-2、数据对比假设 100 个人操作同一条数据读多写少90 读 10 写乐观锁 - 90 次读操作顺畅完成 ✓ - 10 次写操作大概 8-9 次成功1-2 次重试 ✓ - 总操作约 100 次 悲观锁 - 100 个人都要排队等锁 - 每次都要加锁解锁 - 总操作100 次但都要等待写多读少10 读 90 写乐观锁 - 10 次读操作顺畅完成 - 90 次写操作只有 1 次成功89 次失败 - 89 次重试 → 只有 1 次成功88 次失败 - 88 次重试 → 只有 1 次成功... - 总操作可能需要几百次甚至上千次 ✗ 悲观锁 - 100 个人排队一个一个来 - 总操作100 次虽然慢但稳定 ✓4-3、实际业务举例适合乐观锁商品详情页// 每天 10000 次浏览只有 50 次购买 GetMapping(/product/{id}) public Product getProduct(PathParam Long id) { return productService.getById(id); // 读操作不加锁 } PostMapping(/buy) public void buy(RequestBody Order order) { productService.buy(order); // 写操作用乐观锁 }大部分人只是看看少数人购买时才竞争冲突概率低效率高 ✓不适合乐观锁秒杀活动// 10000 人同时抢 10 件商品 PostMapping(/seckill) public void seckill(RequestBody Order order) { // 用乐观锁会导致 9990 人失败重试 // 数据库压力爆炸 ✗ }应该用悲观锁排队消息队列削峰Redis 分布式锁4-4、总结为什么乐观锁适合读多写少读操作不加锁大家都能自由读取性能高写冲突少失败重试的次数少成本低写冲突多时大量失败重试数据库压力大性能反而变差记住一句话乐观锁假设一般不会有冲突所以适合真的不怎么冲突的场景读多写少。如果冲突很多写多这个假设就错了效率会很差。