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

前言

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

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

相关推荐
用户67570498850227 分钟前
Celery 太重了?这可能是你一直在找的 asyncio 任务队列
后端·python·消息队列
Cloud_Shy61828 分钟前
Python 数据分析基础入门:《Excel Python:飞速搞定数据分析与处理》学习笔记系列(第十一章 Python 包跟踪器 下篇)
前端·后端·python·数据分析·excel
神奇小汤圆1 小时前
为什么Redis能称霸缓存界?揭秘其每秒10万+读写的核心技术
后端
楼田莉子1 小时前
C++17新特性:结构化绑定/inline变量/if相关的变化
c++·后端·学习
无限进步_1 小时前
【C++】C++11的类功能增强与STL变化
java·前端·数据结构·c++·后端·算法
字节跳动数据库1 小时前
TRAE × 火山引擎 Supabase:为你的 AI 应用装上“数据引擎”
人工智能·后端
用户6757049885021 小时前
Python 统一大业:uv 如何整合 Pip、Pyenv 和 Venv?
后端·python
倚栏听风雨1 小时前
Spring AI 流式工具调用:你的 TOOL_CALLS Chunk 去哪了?
后端
归故里1 小时前
harmony-next.skills 为 AI 而生!
前端·后端·github
木雷坞1 小时前
NAS 放模型,GPU 跑 vLLM:一次启动卡住的排查记录
后端