当前位置: 首页 > news >正文

一次MySQL隐式类型转化导致的bug分析

目录

  • 问题描述
  • 第一次修复
  • 又遇到了问题
  • 问题解析
  • 经验教训
    • 1. 不要拼接查询参数
    • 2. 使用合适的数据类型,尽量少的冗余列,尽可能使用数据库约束检查

问题描述

我们有一个定时任务,每天从数据中间库sales销售表抽取统计数据,写入业务统计表。中间库sales表有amt销售数量,dept营销部,date1日期等字段,日期是varchar类型,格式是4位年加上2位月,如202603,每天抽取本月和上个月的数据统计数据(因为数据更新有延迟,所以要更新上个月的),更新统计表。语句如下:

selectsum(amt),deptfromsaleswheredate1="202512"groupbydept

统计表是这么设计的:

classSalesStatistic{// 格式是yyyyMMStringdate1;Integeryear;Integermonth;Doubleamt;Stringdept;}

同步逻辑如下:

voidsyncDataV1(intyear,intmonth){// 结果形如202512Stringdate=year+StringUtils.leftPad(""+month,2,"0");Stringsql=""" select sum(amt) as amt, dept from sales where date1 = "${date}" group by dept """.replace("${date}",date);// key是deptMap<String,SalesStatistic>newDataMap=newHashMap<>();List<Map<String,?>>midDataList=runSql(sql);for(Map<String,?>item:midDataList){SalesStatisticnewData=newSalesStatistic();newData.amt=(Double)item.get("amt");newData.dept=(String)item.get("dept");newData.date1=date;newData.year=Integer.parseInt(date.substring(0,4));newData.month=Integer.parseInt(date.substring(4));newDataMap.put(newData.dept,newData);}Map<String,SalesStatistic>oldDataMap=getOldData(date);// 统计表旧数据// ...// 对比两个map,更新数据,代码略}

还有很多个类似的任务。运行了几个月没发生问题。中间库原来使用的是oracle。后来2025年12月,中间库因信创要求更换成国产数据库了AnalyticDB,因为不是我们负责运维,也没必要了解太多,总之就是用MySQL驱动就能连接,语法和MySQL类似。换中间库后,所有字段名称,表名称都变了,有些字段含义,格式也变了,于是我们只能手动改同步任务代码。对于sales表,新表date1格式改成了yyyy-MM即中间多了一个"-"符号。修改后的代码变成下面的样子:

voidsyncDataV2(intyear,intmonth){// mod1 改了这行 date 形如 2025-12Stringdate=year+"-"+StringUtils.leftPad(""+month,2,"0");Stringsql=""" select sum(amt) as amt, dept from sales // mod2 这里因为某些原因改了,忘记加引号 where date1 = ${date} group by dept """.replace("${date}",date);// key是deptMap<String,SalesStatistic>newDataMap=newHashMap<>();List<Map<String,?>>midDataList=runSql(sql);for(Map<String,?>item:midDataList){SalesStatisticnewData=newSalesStatistic();newData.amt=(Double)item.get("amt");newData.dept=(String)item.get("dept");// mod3 这里改了newData.date1=date.replace("-","");newData.year=Integer.parseInt(date.substring(0,4));// mod4 这里忘记改了newData.month=Integer.parseInt(date.substring(4));newDataMap.put(newData.dept,newData);}Map<String,SalesStatistic>oldDataMap=getOldData(date);// 统计表旧数据// ...// 对比两个map,更新数据}

因为要改的太多(几十个同步任务),时间又紧急,加上没有测试环境,修改部署到生产后看到运行没报错,数据大致正常就行。修改后运行了syncDataV2函数没有报错,数据也没有异常,当时想着改完了。

第一次修复

26年1月1日,接到电话说有几条负一月的数据,我立刻打开电脑远程连接查看数据,发现确实有几条异常数据,year字段是2026,month字段是-1,date1字段是202601。想着应该是改中间库同步代码时漏掉了什么。查看syncDataV2发现是mod4那里忘记改了,date原来是yyyyMM格式的,原来截取逻辑是第4位,现在改成了yyyy-MM,再从第四位截取就变成-MM,负的月份了,于是我把这一行改成了newData.month = Integer.parseInt(date.substring(5));,心想应该改对了把,就把错误数据删除,重新运行了一次修改后的程序。

又遇到了问题

运行后,见到月份正常了,就回复说已经修复了。没过多久,电话又打来了,说数据误差很大!我突然想到刚才没改之前,25年12月也运行过syncDataV2没有出现过负月份,数据也大致正常,这究竟是怎么一回事呢?

我连接中间库运行sql查询语句:

selectsum(amt)asamt,deptfromsales// 这里我记得加上引号了wheredate1="2025-12"groupbydept

发现和统计表有点误差(syncDataV2是月底改的),差别不是很大,难道是有新数据导致的误差,但还是不能解释为什么syncDataV2没有出现负月份,只有一种可能了,java代码查询到的中间表数据是空的?这样就不会修改统计表的值了,怪不得有一点误差,但2025年12月中间表数据为什么会是空,2026年1月又有值呢?难道是拼接语句有问题?我又打印出java执行的sql语句,才发现是忘记加引号了。现在的问题就是,为什么下面这条sql语句:

selectsum(amt)asamt,deptfromsaleswheredate1=2025-12groupbydept

没有数据,而这一条sql语句:

selectsum(amt)asamt,deptfromsaleswheredate1=2026-01groupbydept

有数据,但数据不正常?

问题解析

2025-12会被解释为减法运算,结果是2024(数值)。
同样2026-01的结果是2025(数值)。
中间库MySQL(AnalyticDB)date1字段是字符串类型(值如2025-12),当它和数值2025做等值对比时,数据库会触发隐式类型转换:
MySQL对字符串转数值的规则是:从字符串开头截取连续的数字,直到遇到非数字字符为止;
因此date1字段的2025-12会被转换为数值2025(截取到-前的2025)。
因此

selectsum(amt)asamt,deptfromsaleswheredate1=2026-01groupbydept

这条语句实际查询的是2025年的所有数据,而

selectsum(amt)asamt,deptfromsaleswheredate1=2025-12groupbydept

这条语句实际查询的是2025-12=2013年的所有数据,而中间库的数据是从2024年开始的。所有问题都能解释清楚了,破案。

经验教训

针对这次事故,我总结出以下经验教训:

1. 不要拼接查询参数

这个其实是老生常谈的问题了,不拼接查询参数,用占位符参数的好处一是可以防止sql注入(虽然我这里的参数都是自己生成的,不会存在sql注入),二是可以预防这类引号问题,不用手动去加引号,我这里的代码里日期类型其实是都是对的,就是忘加了引号。

2. 使用合适的数据类型,尽量少的冗余列,尽可能使用数据库约束检查

如果月份字段如果用了unsigned int或者check校验,就算应用层出错,还有数据库帮忙兜底,负月份也不会插入;表设计的不合理,有三个日期字段,改一处需要修改其他两处。

http://www.jsqmd.com/news/440115/

相关文章:

  • 合规无忧!2026软件测试服务商推荐排行 资质过硬/案例齐全 - 极欧测评
  • Ollama不使用 GPU 问题解决——基于 inter arc 显卡
  • 2026交通减速带优质厂家推荐指南高性价比售后无忧 - 优质品牌商家
  • 超越Linux之后:OpenClaw登顶GitHub,但开发者真正需要怎样的AI编程工具?
  • 2026乐山优质养老院推荐榜:乐山失能养老机构、乐山失能养老院、乐山市区的养老机构哪家好选择指南 - 优质品牌商家
  • Java开发效率提升200%的AI工具,我每天都在用
  • Win11家庭版系统升级到Win11专业版系统
  • 权威省心!2026软件测试服务商推荐排行 资质齐全/适配广泛 - 极欧测评
  • 2026年 防水维修服务商推荐榜单:室内外墙渗水、厂房漏水检测与翻新维修,专业团队高效解决您的漏水难题! - 品牌企业推荐师(官方)
  • 2026自动化植物工厂优质厂家推荐指南:消毒灭菌组培室、温湿度调控组培室、环境可控植物工厂、短期贮藏种子资源库选择指南 - 优质品牌商家
  • 北京十大优秀离婚律师团队2025最新 - 资讯焦点
  • 《MAUI 嵌入式 Web 架构实战》序II
  • 2026年热门的定制高低温一体机工厂推荐:全密闭防爆高低温一体机厂家实力哪家强 - 品牌宣传支持者
  • 2026年国内主要有哪些安全的电暖桌定制厂家和安全的电暖桌厂家? - 睿易优选
  • 2026年评价高的三节同步托底轨工厂推荐:液压同步托底轨实力厂家如何选 - 品牌宣传支持者
  • 专业靠谱!2026软件测试服务商推荐排行 权威资质/高效交付 - 极欧测评
  • 2026最新公布北京十大离婚律师权威排名 - 资讯焦点
  • 家长必读!2026太原高考复读学校权威排名及择校指南 - 华Sir1
  • Selenium多线程自动化工程实践
  • 2026年评价高的梳兔毛绒公司推荐:泡泡兔毛绒/提花兔毛绒/氨纶兔毛绒正规生产厂家推荐 - 品牌宣传支持者
  • 抖音“分家”硬刚美团:从内容帝国到工具大佬的生存转型!
  • 2026年 防水修缮企业推荐排行榜:室内/外墙/厂房/卫生间/结构防水技术施工,专业可靠的全方位防水解决方案! - 品牌企业推荐师(官方)
  • 2026华北地区减速带安装优质服务商推荐榜:小区道路划线、橡胶减速带、橡胶减速带厂家、汽车减速带、消防通道划线选择指南 - 优质品牌商家
  • 2026年徐州新房毛坯装修供货厂家top5推荐 - 2026年企业推荐榜
  • 2026全国宠物寻找服务专业机构推荐榜:国内宠物托运/宠物寻找专业店铺/宠物寻找服务公司/宠物托运服务公司/选择指南 - 优质品牌商家
  • 拒绝“魔术数字”:C++ 枚举(Enum)的工程化进阶指南
  • QuickDesk:AI 原生远程桌面工具,开源免费解锁智能协作新体验
  • Pycharm使用SVN同步代码数据教程
  • 【chacha20poly1305】Rust `chacha20poly1305` 库详解
  • OpenClaw+GraphRAG实战:让AI具备长期记忆与复杂逻辑推理