首页 > 代码库 > 原本预期1小时的发布,为何最终发布加校验实际花费时间5小时

原本预期1小时的发布,为何最终发布加校验实际花费时间5小时

这是一个简单的数据生产导入的故事,原本故事情节应该是这样的:数据整理-->测试验证-->生产发布-->生产验证,然后就是各回各家,所以这本来应该是一个平淡的故事,然而实际却变成了如下情节:数据整理-->测试验证-->生产发布-->生产验证-->校验失败(预期数据未导入)-->问题排查-->解决问题-->生产发布-->生产验证-->校验问题(大部分数据是正确的,少部分数据不正确)-->问题排查(当时未能排查出原因,但能判断出异常与生产原有的几条异常数据有关)-->异常数据删除sql编写-->测试校验-->生产发布-->生产校验-->重新导入删除部分数据(异常数据这次直接排除,没包括在导入范围)-->部分异常数据请示领导修正-->修正Sql准备-->测试校验-->生产发布-->修正数据对应数据导入-->生产校验!

你以为到这里就结束了?NO NO NO,故事怎么可能就这么结束,因为这批数据导入有对应的其它业务,还需要执行该部分业务,最终确认后才能各回各家,结果发现,坑爹的数据库数据是修正了,但因为程序采用了Redis,异常数据还在Redis中,所以还要在Redis中删除该部分异常数据,还好程序部分对此有处理,直接删除没导致程序功能异常,至此本次发布才算结束,但此时也已经是凌晨0点了,这真是一个悲剧的故事……

首先需要介绍下本次导入的猪脚,一个预先写好,且已经发布至生产的存储过程,另外该猪脚所在场景是MySql,其大致代码精简后如下

 1 DROP PROCEDURE IF EXISTS `usp_SadEvent`;
 2 DELIMITER $$
 3 CREATE PROCEDURE `usp_SadEvent`
 4 (
 5 IN identityNo VARCHAR(20),
 6 IN uName VARCHAR(15),
 7 IN cAmount LONG
 8 )
 9 label_at_start:
10 BEGIN
11 
12 SELECT @uid := id FROM `user`
13 WHERE identity_no=identityNo AND NAME=uName;
14 
15 IF @uid IS NULL THEN
16     select identityNo,uName,0 ret;
17   LEAVE label_at_start;
18 END IF;
19    update account set balance=balance+cAmount where uid=@uid;
20     select identityNo,uName,1 ret;
21 END label_at_start$$
22 DELIMITER ;

首先就是what the fuck的执行失败问题,调用该存储过程结果居然都是返回ret=0失败!!

技术分享

还好当初写存储过程时,考虑到了结果查询,比较容易就发现为什么返回的uName是乱码?碰上这种问题,直觉就是数据库编码有问题,一查果然如此技术分享

问题查出来了,怎么修正呢,正常剧情当然是改数据库默认编码为utf8了,但改编码后Mysql必须要重启,生产环境是你想重启就能重启的吗?好吧,还好mysql存储过程支持指定编码,修改存储过程,在uName部分指定编码集是utf8(补充虽然数据库默认字符集是latin1,但实际里面的表在创建时都默认指定utf8了,所以导致一直没发现生产环境居然有默认编码集问题)

IN uName VARCHAR(15) character set utf8,

改完后执行批量调用存储过程的sql,大致如下

call usp_SadEvent(123131231313123132,张三,3000);#数据库有
call usp_SadEvent(123454566778899999,李四,5000);#数据库无李四,或数据库里叫李斯

这里特别标明第一条数据库里是有对应数据,第二条在数据库中是查不到用户数据的,执行结果居然发现第二条“李四”的金额,被加到了“张三”身上,这又是什么鬼!

其实问题就出在mysql的默认声明参数上,只要在上面的调用语句下面再加一句

call usp_SadEvent(123131231313123132,张三,3000);#数据库有
call usp_SadEvent(123454566778899999,李四,5000);#数据库无
select @uid;

执行结果很明显的就告诉你@uid是有值的,而且值为张三的uid,好吧,没想到Mysql中

SELECT @uid := id 

这种隐式声明参数的方式居然会在整个对话期间内都有效,所以还是老老实实改成如下显式声明才能测试正确

declare u_id long;
select `id` into u_id FROM `user`

最终完整修改后的存储过程应该如下

DROP PROCEDURE IF EXISTS `usp_SadEvent`;
DELIMITER $$
CREATE PROCEDURE `usp_SadEvent`
(
IN identityNo VARCHAR(20),
IN uName VARCHAR(15) character set utf8,
IN cAmount LONG
)
label_at_start:
BEGIN
declare u_id long;
select `id` into u_id FROM `user`
WHERE identity_no=identityNo AND NAME=uName;

IF u_id IS NULL THEN
    select identityNo,uName,0 ret;
  LEAVE label_at_start;
END IF;
   update account set balance=balance+cAmount where uid=u_id;
    select identityNo,uName,1 ret;
END label_at_start$$
DELIMITER ;

哎,事后描述问题似乎很简单,实际排查这些问题还是挺坑的,哎……

原本预期1小时的发布,为何最终发布加校验实际花费时间5小时