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

前言

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

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

相关推荐
Persistence___1 小时前
SpringBoot中的拦截器
java·spring boot·后端
嘵奇1 小时前
Spring Boot 跨域问题全解:原理、解决方案与最佳实践
java·spring boot·后端
景天科技苑3 小时前
【Rust泛型】Rust泛型使用详解与应用场景
开发语言·后端·rust·泛型·rust泛型
lgily-12255 小时前
常用的设计模式详解
java·后端·python·设计模式
意倾城6 小时前
Spring Boot 配置文件敏感信息加密:Jasypt 实战
java·spring boot·后端
火皇4056 小时前
Spring Boot 使用 OSHI 实现系统运行状态监控接口
java·spring boot·后端
薯条不要番茄酱6 小时前
【SpringBoot】从零开始全面解析Spring MVC (一)
java·spring boot·后端
懵逼的小黑子14 小时前
Django 项目的 models 目录中,__init__.py 文件的作用
后端·python·django
小林学习编程15 小时前
SpringBoot校园失物招领信息平台
java·spring boot·后端
java1234_小锋17 小时前
Spring Bean有哪几种配置方式?
java·后端·spring