问题描述
10点45分到11点10分,订单中心订单无法生产,主要表现为:1)用户支付,但app显示待支付,导致用户取消;2)订单已取消,但订单却正常配送了;3)订单无法接单拣货,无法抛单等;
事件级别
P0级别
解决过程描述
收到报警:
-
初步怀疑是数据超数据库长度,紧急排查原因日志,发现订单的操作流水表自增id已经超出了integer类型的最大值,溢出。
-
和dba快速讨论,制定紧急方案,采取创建新表+数据迁移的方案执行。
-
创建新表,订单操作流水自增id从1开始重新生成,观察新订单,订单能够正常流转,方案可行。紧接着把当天未完成的历史订单操作流水迁移到新表。
-
但是迁移历史数据后,没过多久又出现自增id溢出问题,排查发现刚才迁移的数据把流水id也迁移过去了,导致新id又溢出(是基于当前id最大值自增)。
-
基于刚才的方案,重新操作一遍,迁移历史数据时不迁移自增id,系统基本恢复正常。
-
观察新订单基本上全部恢复,紧接着处理因为刚才系统故障导致脏数据的问题。
根本原因
结合代码我们进行了彻底的问题分析,发现问题的根本原因是如下:由于数据库自增id定义的是bigint(20),而这里获取数据用的是integer类型,正好在今天,订单流水表自增id超出integer最大值,解析报错。
总结
将对组内所有系统进行全面排查,有类似问题尽早优化上线(本周完成),建议自增id数据库类型统一用bigint,而应用层面统一用long解析。避免类似问题再次发生。
问题一、主键自增int最大多少?
问题二、int(有符号)、int(无符号)区别在哪?
无符号 unsigned 表示设置的的数据为0或者正数; 有符号(默认)声明则可以是负数 - 创建表结构
CREATE TABLE `haha5` (
`id` int unsigned NOT NULL ,
`age` int(11) NOT NULL ,
PRIMARY KEY (`age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
我们来验证id是否可以设置为负数
可以看到 id声明了unsigned 无符号,我们传入的sql中 id为负数,执行成功了
查询结果发现:-111 自动转换为了0
通过age 主键字段可以看到 默认就是signed 字段可以为负数。
- 如果有AUTO_INCREMENT声明,则保障了id生成从0开始,所以在id默认为有符号的情况下,int最大值为2147483648
`id` int(10) NOT NULL AUTO_INCREMENT
问题三、int(1)和int(11)什么区别?
- int(1)和int(11)的区别,本质就在于 宽度的区别,比如 int(1) 代表宽度是1,int(11)代表宽度是11位,可以在1前面补0 ,比如int(11)可以设置为 0000000001
会影响int最大个数么?不会!
- 举例子:1和 0000000001的区别,也就是int(11)可以在前面补10个0,但是也遵循int的最大个数,如果声明的是无符号,则最大不能超过 4294967295,如果默认是有符号,则最大不能超过2147483648;
问题四、我们在平常建表的时候,自增id应该定义什么类型呢?
自增id定义为 bigint(一个bigint的存储大小为8字节,int是4字节) bigint 的大小是8个字节,一个字节 8 位,有符号的最大值就是 2 的 63 次方 -1。即 bigint 带符号的范围是 -9223372036854775808 到 9223372036854775807。无符号的范围是 0 到 18446744073709551615。
问题五、如果自增id定义的是int类型,超过了最大值 我们怎么处理?
三种解决方案自由选择:
-
预防,做好监控,不要等id溢出了再处理,最好是在风险发生之前,提前分库分表,毕竟数据量级如果那么大的话,本身就属于设计不合理,表的性能也好不到那里去
-
在不改变字段类型的情况下,可以将int类型字段声明为无符号,可以临时将id的最大值由2147483648扩充到4294967295,但是要注意!仅限于insert插入不再报错,查询的时候如果用到了id,并且定义为Integer类型的话 ,查询会报错!
alter table haha8 MODIFY id int(11) UNSIGNED NOT NULL AUTO_INCREMENT;
- 上述在执行DDL的时候,如果表里数据量特别大 也会遇到锁表,所以不是最好的方案,创建新表+数据迁移的方案执行还是相对比较迅速的.