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

前言

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

最近公司发生了一起生产事故,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存在的意义。

相关推荐
逸风尊者3 分钟前
开发可掌握的知识:推荐系统
java·后端·算法
Violet_YSWY7 分钟前
阿里巴巴状态码
后端
灵魂猎手12 分钟前
Antrl4 入门 —— 使用Antrl4实现一个表达式计算器
java·后端
moxiaoran575322 分钟前
Go语言的递归函数
开发语言·后端·golang
IT 行者1 小时前
Spring Security 7.0 新特性详解
java·后端·spring
华仔啊1 小时前
Java 的金额计算用 long 还是 BigDecimal?资深程序员这样选
java·后端
12344521 小时前
【MCP入门篇】从0到1教你搭建MCP服务
后端·mcp
okseekw1 小时前
Java多线程开发实战:解锁线程安全与性能优化的关键技术
java·后端
HuangYongbiao1 小时前
NestJS 架构设计系列:应用服务与领域服务的区别
后端·架构
技术不打烊1 小时前
MySQL主从延迟飙升?元数据锁可能是“真凶”
后端