西安网站建设网络公司熊掌号,外贸建站教程,seo关键词分类,计算机专业培训机构有哪些“优惠券风波”#xff1a;一段代码引发的线上事故
起因#xff1a;优惠券功能上线
故事的开始源于公司新上线的一项促销活动——在用户未使用优惠券时#xff0c;系统会自动赠送一张优惠券。这个功能不仅能提升用户体验#xff0c;还能拉动平台的销售额。为了赶上活动上…“优惠券风波”一段代码引发的线上事故
起因优惠券功能上线
故事的开始源于公司新上线的一项促销活动——在用户未使用优惠券时系统会自动赠送一张优惠券。这个功能不仅能提升用户体验还能拉动平台的销售额。为了赶上活动上线时间组长将这项任务交给了刚转正的开发小张。
小张心中既紧张又兴奋立刻投入工作。经过一夜的奋战当晚他就提交了代码并信心满满地对组长说“放心吧功能已经写好测试通过没有任何问题”小张对自己的代码很满意认为加锁和状态校验都已经足够周全。在他的测试环境里功能表现一切正常组长也批准了代码上线。
转折生产环境的“优惠券雨”
功能上线后活动初期数据非常亮眼——大量用户在收到优惠券后进行了消费。然而短短几个小时后运营部却收到了大量用户的投诉有用户发现自己的账户里收到的优惠券数量成倍增长.
有些用户账户里多出了数十张甚至上百张优惠券。数据库的CPU使用率飙升写操作排队严重导致整个系统几乎瘫痪。线上出现多起订单错误日志运维团队忙得焦头烂额。
与此同时后台的服务压力剧增数据库的CPU使用率飙升监控系统发出了一片告警声。
运维火速介入,更严重的是因为系统的故障公司损失了几笔大额订单高层震怒扬言如果问题不能解决整个团队都可能面临解散的危机
话不多说直接上代码 public void getCouponsByOrder(String orderId) {//获取订单Order order orderService.getOrderById(orderId);if(order null){return ;}//加锁lock.lock();order getOrderForUpdate(orderId);try{//判断订单是否有优惠券if(!order.getHasCoupons()){//如果没有使用优惠卷系统默认赠送优惠卷couponsService.createCouponsAndSave(orderId);}}finally {lock.unlock();}}危机组内的混乱排查
团队进入了“战斗模式”。大家围绕这段代码进行分析但一时半会儿谁也看不出问题。 “逻辑没问题啊加了锁的这不就是标准的并发处理方式吗”小王一脸茫然。 “是不是数据库问题或者是缓存同步有问题”测试工程师提出猜测。 “这种锁到底管不管用”运维开始怀疑架构本身。 大家讨论了整整两天依然没有找到根本原因。
转机老员工的登场
无奈之下组长拨通了一个“传说中”的号码。这是团队里已经调岗的资深工程师老李他曾是系统的核心开发者对架构的每个细节了如指掌。
老李接到电话时正在家中喂猫。听完情况后他轻轻一笑“这事儿听起来有点意思。把代码发我看看。”
老李摇摇头“这代码确实像个实习生写的不过问题也不复杂改改就行了。”
1. MySQL事务默认隔离级别
MySQL默认隔离级别可重复读 (REPEATABLE READ)
MySQL默认的事务隔离级别是 可重复读 (REPEATABLE READ)。在这种隔离级别下事务开始后事务内的所有查询在同一事务中多次读取数据时结果是一致的即使其他事务对该数据进行了修改。为了实现这一点MySQL通过 MVCC多版本并发控制 实现快照读。
问题分析逻辑与隔离级别的交互
代码中的逻辑和隔离级别产生了以下几个潜在问题
快照读导致状态不一致 在调用 orderService.getOrderById(orderId) 时读取的是快照数据。如果这时有其他事务更新了订单的 hasCoupons 状态这些修改不会被当前事务看到。加锁不生效 MySQL的SELECT默认是快照读只有显式使用 FOR UPDATE 或类似语法才能触发行锁。如果没有正确使用锁即使在事务中操作订单状态的并发修改也无法避免。重复赠送优惠券 当多个事务几乎同时执行读取 hasCoupons 时可能都为 false并发调用了 createCouponsAndSave(orderId)最终导致用户多次收到优惠券。
总的来说就是当线程AB进入事务时生成了此刻的快照然后A先获取到锁A修改了数据但是B此时已经生成了快照所以B后面拿到的是之前的旧数据。
如何从隔离级别下手解决
以下是从隔离级别与事务设计入手的解决方案 改进方案 将数据库的隔离级别更改成读已提交即可完美解决 读已提交是在每一次select的时候生成快照 使用分布式锁。