线上问题---又又又又来生产事故了,有人要倒霉了

前言

不好啦❗ 天塌了❗ 又又又又来生产事故了❗

最近公司发生了一起生产事故,WMS在库存扣减时产生了 重复扣减,事件报告中指出是因为针对重复MQ消息做幂等控制时,幂等控制方案失效,导致重复处理了两条退款消息,最终造成重复退款。

失效的幂等控制方案其实很简单,就是基于数据库的 唯一索引 来进行幂等控制,这其实是很常用也很简单的一种实现方案,但为什么在这起生产事故中,唯一索引实现幂等就失效了呢,问题就出在这个唯一索引上。

一. 为了便于理解,这里先给出整个场景的抽象交互图。

上游重复投递消息后,重复投递的消息有两个下游消费,两个下游都基于唯一索引做了幂等控制,但是结果就是下游-1幂等控制失败,另一个下游-2幂等控制成功。

上游投递的消息有四个字段,记为field_1,field_2,field_3和field_4,其中下游-1的幂等控制表的创表语句如下。

java 复制代码
CREATE TABLE idempotent_1 (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  field_1 VARCHAR(255) NOT NULL,
  field_2 VARCHAR(255) NOT NULL,
  field_3 VARCHAR(255) DEFAULT NULL,
  field_4 VARCHAR(255) NOT NULL,
  UNIQUE INDEX idempotent_index(field_1, field_2, field_3)
)

下游-2的幂等控制表的创表语句如下

java 复制代码
CREATE TABLE idempotent_2 (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  field_1 VARCHAR(255) NOT NULL,
  field_2 VARCHAR(255) NOT NULL,
  field_3 VARCHAR(255) DEFAULT NULL,
  field_4 VARCHAR(255) NOT NULL,
  UNIQUE INDEX idempotent_index(field_2)
)

二. 问题分析

不知道你猜到没有,原因就是下游-1的唯一索引会失效。

下游-1 的唯一索引是一个复合索引,包含field_1field_2field_3 ,其中field_3 允许为NULL ,而在MySQL 中,NULL 表示 未知 ,也就是前后两次插入数据时,如果field_3 都是NULL ,此时就算field_1field_2完全一样,也是能够插入成功的,因此唯一索引的唯一约束就失效了,最终幂等控制也就失败了。

我们可以把idempotent_1表创建出来自行做一下测试,执行如下插入语句两次,是可以插入成功的。

sql 复制代码
INSERT INTO idempotent_1 (field_1, field_2, field_3, field_4) VALUES ('A', 'B', NULL, 'D')

三. 问题解决

问题的解决很简单,为field_3 添加NOT NULL 约束,就能够规避因为存在NULL而导致的唯一索引失效,进而幂等控制就能成功。

进一步的,其实建议所有字段都添加上NOT NULL约束,这样能够规避很多问题。

总结

情况是一个简单情况,就是对重复MQ 消息基于唯一索引做幂等控制时,因为唯一索引是一个复合索引且存在字段允许为NULL,从而唯一索引失效,最终幂等控制失败。

解决方案也十分简单,就是为所有字段都添加上NOT NULL 约束,从而唯一索引就不会因为存在NULL而失效。

那么最后思考一个问题,为什么MySQL 允许字段可以为NULL 呢,毕竟很多问题都是因为NULL的存在而出现的。

个人认为最本质的原因就是NULL 可以节约空间,比如一个字段是VARCHAR ,当该字段允许为NULL 且实际就是NULL 时,是不会占用空间的,但如果该字段不允许为NULL ,那么至少都会存储一个空字符串,而空字符串的占用空间包含两部分,即 长度信息实际内容 ,因为是空字符串,所以实际内容是0 字节,但长度信息至少都会占用1 字节,所以 节约空间NULL存在的意义。

相关推荐
钡铼技术ARM工业边缘计算机22 分钟前
【成本降40%·性能翻倍】RK3588边缘控制器在安防联动系统的升级路径
后端
CryptoPP1 小时前
使用WebSocket实时获取印度股票数据源(无调用次数限制)实战
后端·python·websocket·网络协议·区块链
白宇横流学长1 小时前
基于SpringBoot实现的大创管理系统设计与实现【源码+文档】
java·spring boot·后端
草捏子2 小时前
状态机设计:比if-else优雅100倍的设计
后端
考虑考虑3 小时前
Springboot3.5.x结构化日志新属性
spring boot·后端·spring
涡能增压发动积3 小时前
一起来学 Langgraph [第三节]
后端
sky_ph3 小时前
JAVA-GC浅析(二)G1(Garbage First)回收器
java·后端
涡能增压发动积3 小时前
一起来学 Langgraph [第二节]
后端
hello早上好4 小时前
Spring不同类型的ApplicationContext的创建方式
java·后端·架构
roman_日积跬步-终至千里4 小时前
【Go语言基础【20】】Go的包与工程
开发语言·后端·golang