2026/2/23 21:31:32
网站建设
项目流程
2个网站做的链接怎么用一个域名,空滤网站怎么做,九江市区,如何把网站加入白名单前言
并发冲突是多用户系统的常见问题#xff1a;两人同时编辑同一条数据#xff0c;后提交的覆盖了先提交的。这篇给你3种并发控制策略的完整对比和PRD写法。
一、3种并发控制策略对比
策略原理适用场景优点缺点乐观锁提交时检查版本号读多写少性能高#xff0c;无锁等待…前言并发冲突是多用户系统的常见问题两人同时编辑同一条数据后提交的覆盖了先提交的。这篇给你3种并发控制策略的完整对比和PRD写法。一、3种并发控制策略对比策略原理适用场景优点缺点乐观锁提交时检查版本号读多写少性能高无锁等待冲突时需重试悲观锁操作前先加锁写多读少强一致性性能低可能死锁最终一致性允许短暂不一致分布式系统高可用业务复杂度高二、乐观锁推荐原理每条记录有一个版本号version更新时检查版本号是否一致。如果版本号不匹配说明数据已被他人修改返回冲突。适用场景读多写少冲突频率低如编辑商品信息、编辑用户资料等优点性能高无锁等待适合高并发场景缺点冲突时需要重试用户体验稍差实现步骤数据库设计在表中添加version字段整数类型默认值为1查询数据查询时返回version字段提交更新更新时带上version字段作为条件检查结果如果更新影响行数0说明版本号不匹配返回冲突用户提示提示用户刷新页面重新编辑PRD写法场景编辑商品信息 并发控制策略乐观锁版本号机制 【数据库设计】 表结构product表添加version字段整数类型默认值为1 示例id, name, price, version 【实现方式】 1. 查询商品时返回版本号version SELECT id, name, price, version FROM product WHERE id ? 2. 提交更新时带上版本号 UPDATE product SET name?, price?, versionversion1 WHERE id? AND version? 3. 检查更新结果 - 如果更新影响行数0说明更新成功 - 如果更新影响行数0说明版本号不匹配返回冲突 【用户提示】 冲突提示数据已被他人更新请刷新后重试 恢复路径刷新页面重新编辑不要自动重试 【技术实现】 // 伪代码 function updateProduct(productId, newName, newPrice, version) { const affectedRows db.execute( UPDATE product SET name?, price?, versionversion1 WHERE id? AND version?, [newName, newPrice, productId, version] ); if (affectedRows 0) { return { success: false, message: 数据已被他人更新请刷新后重试 }; } return { success: true, message: 更新成功 }; }真实案例场景两人同时编辑商品价格时间线 10:00:00 - 用户A查询商品price100, version1 10:00:01 - 用户B查询商品price100, version1 10:00:05 - 用户A提交price120, version1 → 成功version变为2 10:00:06 - 用户B提交price110, version1 → 失败version已变为2 10:00:07 - 系统提示用户B数据已被他人更新请刷新后重试 10:00:08 - 用户B刷新页面看到price120, version2 10:00:10 - 用户B重新编辑price110, version2 → 成功version变为3 结果用户A的修改price120被保留用户B的修改price110在刷新后成功提交最佳实践版本号字段使用整数类型每次更新自动1版本号初始化新记录version默认为1版本号检查更新时必须检查version不能跳过冲突处理不要自动重试提示用户刷新页面重新编辑前端提示冲突时明确提示数据已被他人更新不要只说更新失败常见错误错误1更新时不检查version导致覆盖他人修改❌ 错误UPDATE product SET name? WHERE id? ✅ 正确UPDATE product SET name?, versionversion1 WHERE id? AND version?错误2冲突时自动重试导致用户修改丢失❌ 错误冲突时自动重试可能导致用户修改丢失 ✅ 正确冲突时提示用户刷新页面重新编辑错误3版本号字段类型错误导致精度问题❌ 错误version字段使用浮点数类型 ✅ 正确version字段使用整数类型INT或BIGINT三、悲观锁原理操作前先加锁SELECT FOR UPDATE其他人只能等待锁释放后才能操作。保证同一时间只有一个请求能修改数据。适用场景写多读少冲突频率高如秒杀扣减库存、抢票等优点强一致性不会丢失更新缺点性能低可能死锁不适合高并发场景实现步骤开启事务在事务中执行操作加锁查询使用SELECT FOR UPDATE加锁执行业务逻辑在锁保护下执行业务逻辑提交事务提交事务时自动释放锁异常处理如果发生异常回滚事务释放锁PRD写法场景秒杀扣减库存 并发控制策略悲观锁SELECT FOR UPDATE 【实现方式】 1. 开启事务 BEGIN TRANSACTION; 2. 查询库存时加锁 SELECT * FROM stock WHERE id? FOR UPDATE; 其他请求会等待直到锁释放 3. 检查库存 IF qty 0 THEN 扣减库存UPDATE stock SET qtyqty-1 WHERE id? ELSE 返回库存不足 END IF 4. 提交事务释放锁 COMMIT; 如果发生异常回滚事务ROLLBACK; 【用户提示】 库存不足库存不足请选择其他商品 恢复路径无库存已售罄 【技术实现】 // 伪代码 function deductStock(productId) { try { db.beginTransaction(); // 加锁查询 const stock db.query(SELECT * FROM stock WHERE id? FOR UPDATE, productId); if (stock.qty 0) { db.rollback(); return { success: false, message: 库存不足 }; } // 扣减库存 db.execute(UPDATE stock SET qtyqty-1 WHERE id?, productId); db.commit(); return { success: true, message: 扣减成功 }; } catch (error) { db.rollback(); return { success: false, message: 扣减失败请重试 }; } }真实案例场景秒杀活动1000个用户同时抢购最后10件商品时间线使用悲观锁 10:00:00 - 用户A查询库存加锁qty10 10:00:00 - 用户B查询库存等待锁... 10:00:00 - 用户C查询库存等待锁... 10:00:01 - 用户A扣减库存qty9提交事务释放锁 10:00:01 - 用户B获得锁查询库存qty9 10:00:01 - 用户C继续等待锁... 10:00:02 - 用户B扣减库存qty8提交事务释放锁 10:00:02 - 用户C获得锁查询库存qty8 ... 10:00:10 - 用户J获得锁查询库存qty0 10:00:10 - 用户J返回库存不足 10:00:10 - 用户K获得锁查询库存qty0 10:00:10 - 用户K返回库存不足 结果只有10个用户成功购买其他用户收到库存不足提示 如果使用乐观锁可能会有超卖问题最佳实践锁粒度尽量缩小锁的范围只锁必要的行锁超时设置锁超时时间如30s避免死锁事务时间尽量缩短事务时间减少锁持有时间死锁检测数据库自动检测死锁自动回滚其中一个事务索引优化WHERE条件使用索引减少锁范围常见错误错误1忘记提交事务导致锁一直持有❌ 错误加锁后忘记提交事务导致锁一直持有 ✅ 正确加锁后立即执行业务逻辑然后提交事务错误2锁范围过大导致性能问题❌ 错误SELECT * FROM stock FOR UPDATE锁整个表 ✅ 正确SELECT * FROM stock WHERE id? FOR UPDATE只锁一行错误3没有设置锁超时导致死锁❌ 错误没有设置锁超时死锁时一直等待 ✅ 正确设置锁超时时间如30s超时自动释放死锁处理死锁场景两个事务互相等待对方释放锁时间线死锁场景 10:00:00 - 事务A锁定商品1等待商品2 10:00:01 - 事务B锁定商品2等待商品1 10:00:02 - 死锁发生数据库自动检测并回滚其中一个事务 【预防措施】 1. 按相同顺序加锁如按id排序 2. 尽量缩小锁范围 3. 设置锁超时时间 4. 使用索引减少锁范围四、最终一致性原理允许短暂不一致通过消息队列/事件驱动最终达到一致。适合分布式系统保证高可用性。适用场景分布式系统允许短暂不一致如订单支付后更新库存、用户注册后发送欢迎邮件等优点高可用性能好适合分布式系统缺点业务复杂度高需要处理消息丢失、重复消费等问题实现步骤业务操作执行业务操作如订单支付发送消息发送消息到消息队列如RabbitMQ、Kafka消费消息消费者服务消费消息执行业务逻辑如扣减库存重试机制如果消费失败重试3次人工介入如果重试3次仍失败发送告警人工介入PRD写法场景订单支付后更新库存 并发控制策略最终一致性消息队列 【实现方式】 1. 订单支付成功后发送消息到队列 - 消息内容{orderId, productId, quantity} - 消息队列RabbitMQ / Kafka - 消息持久化是防止消息丢失 2. 库存服务消费消息扣减库存 - 消费者库存服务 - 消费逻辑扣减库存 - 幂等性使用订单号作为幂等键防止重复消费 3. 如果扣减失败重试3次 - 重试间隔1秒、3秒、5秒指数退避 - 重试次数3次 - 重试失败发送告警人工介入 4. 仍失败则人工介入 - 告警方式邮件、短信、钉钉 - 处理方式人工检查手动处理 【用户提示】 支付成功支付成功库存更新中预计1-2分钟 恢复路径等待系统处理如有问题联系客服 【技术实现】 // 伪代码订单服务 function payOrder(orderId) { // 1. 支付订单 const payment processPayment(orderId); if (!payment.success) { return { success: false, message: 支付失败 }; } // 2. 发送消息到队列 const message { orderId: orderId, productId: payment.productId, quantity: payment.quantity }; messageQueue.send(stock.deduct, message); return { success: true, message: 支付成功库存更新中 }; } // 伪代码库存服务 function consumeStockMessage(message) { try { // 1. 检查幂等键防止重复消费 const existingDeduction redis.get(deduct:${message.orderId}); if (existingDeduction) { return { success: true, isDuplicate: true }; } // 2. 扣减库存 const result deductStock(message.productId, message.quantity); if (!result.success) { throw new Error(库存扣减失败); } // 3. 保存幂等键 redis.set(deduct:${message.orderId}, { productId: message.productId }, 3600); return { success: true }; } catch (error) { // 重试3次 if (retryCount 3) { retryCount; setTimeout(() consumeStockMessage(message), retryCount * 1000); } else { // 发送告警 sendAlert(库存扣减失败, message); } } }真实案例场景订单支付后更新库存、发送短信、更新积分时间线最终一致性 10:00:00 - 用户支付订单订单状态变为已支付 10:00:01 - 订单服务发送3条消息到队列 - 消息1扣减库存 - 消息2发送短信 - 消息3更新积分 10:00:02 - 库存服务消费消息1扣减库存成功 10:00:03 - 短信服务消费消息2发送短信成功 10:00:04 - 积分服务消费消息3更新积分失败重试 10:00:05 - 积分服务重试消息3更新积分成功 结果订单支付成功库存已扣减短信已发送积分已更新 虽然积分更新延迟了3秒但最终达到一致最佳实践消息持久化消息必须持久化防止消息丢失幂等性消费者必须支持幂等防止重复消费重试机制消费失败时重试使用指数退避策略死信队列重试3次仍失败发送到死信队列人工处理监控告警监控消息积压、消费延迟及时告警常见错误错误1消息没有持久化服务重启后消息丢失❌ 错误消息不持久化服务重启后消息丢失 ✅ 正确消息必须持久化防止消息丢失错误2消费者不支持幂等重复消费导致数据错误❌ 错误消费者不支持幂等重复消费导致库存扣减2次 ✅ 正确消费者支持幂等使用订单号作为幂等键错误3没有重试机制消费失败后消息丢失❌ 错误消费失败后直接丢弃消息 ✅ 正确消费失败后重试3次仍失败则发送到死信队列不适合的场景最终一致性不适合以下场景金额操作支付、退款等金额操作必须强一致性库存扣减秒杀、抢票等库存扣减必须强一致性账户余额账户余额变更必须强一致性适合的场景订单支付后更新库存允许短暂不一致最终达到一致用户注册后发送邮件允许短暂延迟最终发送成功数据同步主从数据库同步允许短暂延迟五、选择策略的决策树根据业务场景选择合适的并发控制策略以下是决策流程Q1是否允许短暂不一致 ├─ 是 → 最终一致性消息队列 │ └─ 适用场景订单支付后更新库存、用户注册后发送邮件等 └─ 否 → Q2必须强一致性 Q2冲突频率高吗 ├─ 高写多读少如秒杀、抢票→ 悲观锁SELECT FOR UPDATE │ └─ 优点强一致性不会丢失更新 │ └─ 缺点性能低可能死锁 └─ 低读多写少如编辑商品、编辑用户→ 乐观锁版本号 └─ 优点性能高无锁等待 └─ 缺点冲突时需要重试策略对比表策略适用场景性能一致性复杂度乐观锁读多写少编辑商品、编辑用户高强一致性低悲观锁写多读少秒杀、抢票低强一致性中最终一致性分布式系统订单支付后更新库存高最终一致性高实际项目案例案例1电商系统编辑商品信息乐观锁读多写少冲突频率低秒杀扣减库存悲观锁写多读少冲突频率高订单支付后更新库存最终一致性允许短暂不一致案例2内容管理系统编辑文章乐观锁读多写少冲突频率低发布文章悲观锁写多读少冲突频率高文章发布后发送通知最终一致性允许短暂延迟案例3金融系统账户余额变更悲观锁必须强一致性不能丢失更新交易记录查询不需要锁只读操作交易后发送短信最终一致性允许短暂延迟六、常见错误与陷阱错误1乐观锁更新时不检查version问题更新时不检查version导致覆盖他人修改。❌ 错误示例 UPDATE product SET name? WHERE id? 问题不检查version可能覆盖他人修改 ✅ 正确示例 UPDATE product SET name?, versionversion1 WHERE id? AND version? 检查version如果version不匹配更新失败错误2悲观锁忘记提交事务问题加锁后忘记提交事务导致锁一直持有其他请求一直等待。❌ 错误示例 BEGIN TRANSACTION; SELECT * FROM stock WHERE id? FOR UPDATE; UPDATE stock SET qtyqty-1 WHERE id?; -- 忘记提交事务锁一直持有 ✅ 正确示例 BEGIN TRANSACTION; SELECT * FROM stock WHERE id? FOR UPDATE; UPDATE stock SET qtyqty-1 WHERE id?; COMMIT; -- 提交事务释放锁错误3最终一致性用于强一致性场景问题金额操作、库存扣减等强一致性场景使用最终一致性导致数据错误。❌ 错误示例 支付扣款使用最终一致性消息队列 问题支付成功后库存可能还没扣减导致超卖 ✅ 正确示例 支付扣款使用悲观锁强一致性 库存扣减必须在支付时同步完成不能异步处理错误4乐观锁冲突时自动重试问题乐观锁冲突时自动重试可能导致用户修改丢失。❌ 错误示例 乐观锁冲突时自动重试使用旧数据重新提交 问题用户的新修改可能丢失 ✅ 正确示例 乐观锁冲突时提示用户刷新页面重新编辑 不要自动重试让用户看到最新数据后重新编辑错误5悲观锁范围过大问题悲观锁范围过大锁整个表或大量行导致性能问题。❌ 错误示例 SELECT * FROM stock FOR UPDATE 问题锁整个表其他请求全部等待性能极差 ✅ 正确示例 SELECT * FROM stock WHERE id? FOR UPDATE 只锁一行其他请求可以正常访问其他行错误6最终一致性没有幂等性问题消息队列消费者不支持幂等重复消费导致数据错误。❌ 错误示例 消费者不支持幂等重复消费导致库存扣减2次 问题消息重复消费时库存扣减多次 ✅ 正确示例 消费者支持幂等使用订单号作为幂等键 重复消费时检查幂等键已处理则直接返回七、FAQQ1乐观锁冲突了怎么办答提示用户刷新页面重新编辑。不要自动重试因为用户的修改可能已经过时自动重试可能导致用户修改丢失。处理流程检测到version不匹配返回冲突错误前端提示用户数据已被他人更新请刷新后重试用户刷新页面获取最新数据和version用户重新编辑使用最新的version提交Q2悲观锁会死锁吗答会。两个事务互相等待对方释放锁时会发生死锁。预防措施按相同顺序加锁如按id排序尽量缩小锁范围设置锁超时时间如30s超时自动释放使用索引减少锁范围处理方式数据库自动检测死锁自动回滚其中一个事务。Q3最终一致性适合所有场景吗答不适合。以下场景必须使用强一致性金额操作支付、退款等金额操作必须强一致性库存扣减秒杀、抢票等库存扣减必须强一致性账户余额账户余额变更必须强一致性适合的场景订单支付后更新库存允许短暂不一致最终达到一致用户注册后发送邮件允许短暂延迟最终发送成功数据同步主从数据库同步允许短暂延迟Q4乐观锁和悲观锁哪个性能更好答乐观锁性能更好因为不需要加锁无锁等待。但冲突时需要重试用户体验稍差。性能对比乐观锁读多写少场景性能高无锁等待悲观锁写多读少场景性能低有锁等待选择建议根据冲突频率选择冲突频率低用乐观锁冲突频率高用悲观锁。Q5如何监控并发控制效果答监控以下指标乐观锁冲突率version不匹配的次数 / 总更新次数悲观锁等待时间锁等待的平均时间死锁次数死锁发生的次数最终一致性延迟消息消费的平均延迟告警阈值乐观锁冲突率 10%考虑使用悲观锁悲观锁等待时间 1秒考虑优化锁范围死锁次数 0立即排查死锁原因最终一致性延迟 5分钟检查消息队列积压Q6分布式系统如何保证强一致性答分布式系统保证强一致性有以下方案两阶段提交2PC保证所有节点同时提交或回滚但性能低三阶段提交3PC改进2PC减少阻塞时间但复杂度高分布式锁使用Redis或Zookeeper实现分布式锁保证同一时间只有一个请求执行最终一致性允许短暂不一致通过消息队列最终达到一致选择建议根据业务需求选择强一致性场景用分布式锁允许短暂不一致用最终一致性。Q7如何测试并发控制答使用压力测试工具如JMeter、LoadRunner模拟并发请求乐观锁测试模拟多个用户同时编辑同一条数据检查是否覆盖他人修改悲观锁测试模拟多个用户同时扣减库存检查是否超卖最终一致性测试模拟消息重复消费检查是否幂等测试指标并发用户数100、500、1000请求成功率 99%数据一致性100%不能丢失更新响应时间 1秒工具入口生成并发控制思维导图