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

前言

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

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

相关推荐
Y***98511 分钟前
【学术会议论文投稿】Spring Boot实战:零基础打造你的Web应用新纪元
前端·spring boot·后端
q***333719 分钟前
SpringMVC新版本踩坑[已解决]
android·前端·后端
武子康21 分钟前
大数据-166 Apache Kylin 1.6 Streaming Cubing 实战:Kafka 到分钟级 OLAP
大数据·后端·apache kylin
回家路上绕了弯26 分钟前
彻底解决超卖问题:从单体到分布式的全场景技术方案
分布式·后端
8***293144 分钟前
能懂!基于Springboot的用户增删查改(三层设计模式)
spring boot·后端·设计模式
IT_陈寒1 小时前
Python高手都在用的5个隐藏技巧,让你的代码效率提升50%
前端·人工智能·后端
Qiuner1 小时前
Spring Boot 机制二:配置属性绑定 Binder 源码解析(ConfigurationProperties 全链路)
java·spring boot·后端·spring·binder
Victor3561 小时前
Redis(151)Redis的内存使用如何监控?
后端
Victor3561 小时前
Redis(150)Redis的性能瓶颈如何排查?
后端
豆浆whisky3 小时前
Go并发模式选择指南:找到最适合你项目的并发方案|Go语言进阶(19)
开发语言·后端·golang