并发测试产生的锁、脏数据最全解决方案
实战能用、面试能背、落地见效的方案按优先级从高到低排序覆盖 99% 并发问题锁等待、死锁、脏读、超卖、丢失更新。只要记住一句话锁问题 优化锁粒度 缩短持有时间脏数据 加锁控制 隔离级别 幂等一、解决【脏数据】最有效方案必用1. 乐观锁推荐无锁竞争性能最好适用库存、余额、订单状态、高并发写原理用 version 版本号控制只有版本匹配才允许更新。表加字段sqlALTER TABLE account ADD version INT DEFAULT 1;更新 SQL不会产生脏数据sqlUPDATE account SET money money - 50, version version 1 WHERE id 1 AND version 当前版本;效果并发下只有一个线程能更新成功其他更新失败绝对不会超卖、不会覆盖。2. 悲观锁for update强一致适用金钱交易、强一致性业务原理查询时直接加锁其他事务阻塞等待。sqlBEGIN; SELECT * FROM account WHERE id1 FOR UPDATE; -- 业务计算 UPDATE account SET money50 WHERE id1; COMMIT;注意必须用主键 / 索引否则会锁全表3. 数据库隔离级别调整MySQL 默认 RR可重复读已经解决脏读 ❌不可重复读 ❌幻读基本解决❌生产推荐固定sqlSET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;脏读问题直接消失。4. 接口幂等防重复提交、重复下单、重复扣款并发测试最容易出现重复下单、重复支付解决唯一单号orderNo数据库唯一索引Redis 分布式锁防重sqlALTER TABLE order ADD UNIQUE INDEX uk_order_no (order_no);唯一索引 天然防重。二、解决【锁问题】锁等待、死锁、表锁、TPS 上不去1. 必须给查询条件加索引最关键无索引 → 行锁变表锁 → 整个表卡死例如sqlUPDATE account SET money100 WHERE name张三;name 无索引 →锁全表加索引sqlCREATE INDEX idx_name ON account(name);立刻变回行锁并发性能提升 10~100 倍。2. 缩短事务不要长事务事务越长锁持有越久 → 死锁概率飙升错误写法plaintextBEGIN; 查询数据 调用第三方接口1秒 发送MQ500ms UPDATE COMMIT;正确写法plaintext查询数据 调用第三方 发MQ事务外执行 BEGIN; UPDATE COMMIT;事务只放数据库操作越快提交越好。3. 固定更新顺序防止死锁死锁原因A 锁 1再锁 2B 锁 2再锁 1解决所有接口统一按 id 从小到大更新plaintext先更新 id1 再更新 id2死锁直接消失。4. 使用分布式锁Redis Lock适用秒杀、库存、优惠券、抢单plaintext获取锁userId/orderNo 执行业务 释放锁控制同一时间只有一个线程操作绝对不会脏数据。5. 直接用原子更新 SQL最简洁不要先查后改错误plaintextselect money; money money -50; update ...正确原子操作sqlUPDATE account SET money money - 50 WHERE id1;数据库单 SQL 天然原子性无并发问题。三、终极解决方案总结直接背解决脏数据乐观锁 version高并发首选悲观锁 for update强一致隔离级别 RR杜绝脏读原子更新 SQL唯一索引 幂等防重复解决锁问题表锁、死锁、锁等待必须加索引行锁不变表锁缩短事务不要长事务统一更新顺序防死锁分布式锁秒杀场景四、一句话万能答案面试必背并发产生锁是因为多线程同时修改同一行数据解决思路是缩小锁范围、缩短持有时间、加索引避免表锁脏数据是因为事务交叉覆盖解决思路是乐观锁、悲观锁、原子 SQL、接口幂等、数据库隔离级别控制。