这是一个简单的数据生产导入的故事,原本故事情节应该是这样的:数据整理 -->测试验证 -->生产发布 -->生产验证,然后就是各回各家,所以这本来应该是一个平淡的故事,然而实际却变成了如下情节:数据整理 -->测试验证 -->生产发布 -->生产验证 -->校验失败 (预期数据未导入)--> 问题排查 -->解决问题 -->生产发布 -->生产验证 -->校验问题(大部分数据是正确的,少部分数据不正确)-->问题排查(当时未能排查出原因,但能判断出异常与生产原有的几条异常数据有关)-->异常数据删除 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 INidentityNoVARCHAR(20),
- 6 INuNameVARCHAR(15),
- 7 INcAmountLONG
- 8 )
- 9 label_at_start:
- 10 BEGIN
- 11
- 12 SELECT @uid:=idFROM`user`
- 13 WHEREidentity_no=identityNoANDNAME=uName;
- 14
- 15 IF @uid IS NULL THEN
- 16 selectidentityNo,uName,0 ret;
- 17 LEAVE label_at_start;
- 18 END IF;
- 19 updateaccountsetbalance=balance+cAmountwhereuid=@uid;
- 20 selectidentityNo,uName,1 ret;
- 21 END label_at_start$$
- 22DELIMITER ;
首先就是 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
这种隐式声明参数的方式居然会在整个对话期间内都有效,所以还是老老实实改成如下显式声明才能测试正确
- declareu_idlong;
- select`id`intou_idFROM`user`
最终完整修改后的存储过程应该如下
- DROP PROCEDURE IF EXISTS `usp_SadEvent`;
- DELIMITER $$
- CREATE PROCEDURE `usp_SadEvent`
- (
- INidentityNoVARCHAR(20),
- INuNameVARCHAR(15)character set utf8,
- INcAmountLONG
- )
- label_at_start:
- BEGIN
- declareu_idlong;
- select`id`intou_idFROM`user`
- WHEREidentity_no=identityNoANDNAME=uName;
- IFu_idIS NULL THEN
- selectidentityNo,uName,0 ret;
- LEAVE label_at_start;
- END IF;
- updateaccountsetbalance=balance+cAmountwhereuid=u_id;
- selectidentityNo,uName,1 ret;
- END label_at_start$$
- DELIMITER ;
哎,事后描述问题似乎很简单,实际排查这些问题还是挺坑的,哎……
来源: http://www.cnblogs.com/benxiaohai-microcosm/p/6845047.html