稀土掘金技术社区 04月09日 19:22
线上问题-我就加了个索引怎么就导致线上事故了
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文分享了后端程序员在处理线上数据库查询异常时的排查与解决过程。当一个上线五年的业务突然出现问题,导致系统崩溃时,作者通过分析SQL语句,发现问题出在MySQL的bit类型字段在索引查询时的隐式类型转换。文章详细介绍了排查过程、问题根源以及解决方案,并强调了避免隐式转换的重要性。

🚨 **问题现象:** 线上业务因SQL查询不到数据而异常,但直连数据库却有数据,且发版记录与该功能无关。

🔍 **排查过程:** 作者分析SQL语句,发现`status`字段为`bit(1)`类型,查询条件中`status = '1'`导致问题。

💡 **问题根源:** MySQL的bit类型字段在索引查询时,由于隐式类型转换规则,当`status = '1'`时,字符串'1'的ASCII码与bit(1)的二进制值不匹配,导致索引失效。

🛠️ **解决方案:** 紧急联系DBA,删除新增的索引,避免索引与隐式数据转换带来的问题。

⚠️ **总结与建议:** 避免隐式转换,因为MySQL隐式转换有许多默认规则,难以控制。同时,文章还提到了bit(1)类型在MySQL中的存储特性,以及与int类型的区别。

原创 后端程序员Aska 2025-04-01 08:30 重庆

关注更多AI编程资讯请去AI Coding专区:https://juejin.cn/aicoding



前言

不好啦❗ 天塌了❗ 系统崩了❗

「快看啊,一个上线 5 年的业务,今天发个版突然就崩了」

「生产问题群爆炸了」

我的心里活动:“太好了😀太好了😀终于给我碰上了,这个问题可很少发生啊,又积累血琳琳的生产一个问题”

不想看废话的直接看【解决过程和方案】 吧


排查过程

    ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line// 问题sql
    select * from w_location where location_type = 'SORTING_TEMPORARY' AND status = '1'


    问题就是这个 sql 在线上查不到数据从而导致这个业务异常抛出。 但是我直连线上数据库发现这个条件是有数据的

    然后查看发版记录,本次版本迭代改的跟这个功能毫无关系,甚至都没有发这个服务。真是奇了个怪,但是线上这里突然还是个必现的问题


    接下来开始研究这个线上 sql

      ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineCREATE TABLE `w_location` (  `id` bigint(19NOT NULL COMMENT '主键id',  `warehouse_id` bigint(19DEFAULT NULL COMMENT '仓库id',  `warehouse_code` varchar(20COLLATE utf8mb4_bin DEFAULT NULL COMMENT '仓库code',  `location_code` varchar(20COLLATE utf8mb4_bin DEFAULT NULL COMMENT '库位编码',  `tunnel_id` bigint(19DEFAULT NULL COMMENT '巷道id',  `entity_type` varchar(20COLLATE utf8mb4_bin DEFAULT NULL COMMENT '实体类型',  `high_low_type` varchar(20COLLATE utf8mb4_bin DEFAULT NULL COMMENT '高低类型',  `row_number` tinyint(4) unsigned DEFAULT NULL COMMENT '排',  `column_number` tinyint(4) unsigned DEFAULT NULL COMMENT '列',  `floor_number` tinyint(4) unsigned DEFAULT NULL COMMENT '层',  `grid` char(1COLLATE utf8mb4_bin DEFAULT NULL COMMENT '格',  `emp_loc` bit(1DEFAULT NULL COMMENT '是否空库位 0:不是空库位,1:是空库位',  `location_classify_id` bigint(19DEFAULT NULL COMMENT '库位分类id',  `status` bit(1DEFAULT NULL COMMENT '状态(1:生效,0:失效)',  `load_id` bigint(19DEFAULT NULL COMMENT '库位承载id',  `area_id` bigint(19DEFAULT NULL COMMENT '工作区id',  `zone_id` bigint(19DEFAULT NULL COMMENT '库区id',  `location_type` varchar(50COLLATE utf8mb4_bin DEFAULT NULL COMMENT '库位类型',  `picking_sequence` mediumint(7DEFAULT NULL COMMENT '拣货动线号',  `is_deleted` bit(1DEFAULT NULL COMMENT '是否删除',  `inventory_sequence` mediumint(7DEFAULT NULL COMMENT '盘点动线号',  `create_time` datetime DEFAULT NULL COMMENT '创建时间',  `create_user` varchar(50COLLATE utf8mb4_bin DEFAULT NULL COMMENT '创建人',  `update_time` datetime DEFAULT NULL COMMENT '更新时间',  `update_user` varchar(50COLLATE utf8mb4_bin DEFAULT NULL COMMENT '更新人',  `tenant_code` varchar(32COLLATE utf8mb4_bin NOT NULL COMMENT '租户编码',  `actual_temp_id` bigint(19DEFAULT NULL COMMENT '实际温层 ID',  `actual_temp_code` varchar(20COLLATE utf8mb4_bin DEFAULT NULL COMMENT '实际温层 CODE',  `create_nick_name` varchar(50COLLATE utf8mb4_bin DEFAULT NULL COMMENT '创建用户姓名',  `update_nick_name` varchar(50COLLATE utf8mb4_bin DEFAULT NULL COMMENT '更新用户姓名',  `production_line_id` bigint(19DEFAULT NULL COMMENT '生产线ID',  `production_line_name` varchar(100COLLATE utf8mb4_bin DEFAULT NULL COMMENT '生产线名称',  `instance_code` varchar(32COLLATE utf8mb4_bin DEFAULT NULL COMMENT '实例编码',  `empty_flag` varchar(1COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'Y=空库位,N=非空库位;供点检使用',  `picking_sequence_rearrangement` bigint(19DEFAULT NULL COMMENT '重排的拣货动线号',  `inventory_sequence_rearrangement` bigint(19DEFAULT NULL COMMENT '重排的盘点动线号',  `location_purpose` varchar(64COLLATE utf8mb4_bin DEFAULT NULL COMMENT '库位用途',  `casual_pick_loc` bit(1DEFAULT b'0' COMMENT '是否临时拣货位',  PRIMARY KEY (`id`USING BTREE,  KEY `idx_wh_location_type` (`warehouse_code`,`location_code`,`location_type`),  KEY `index_location_type_status` (`location_type`,`status`)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='库位';

      select * from w_location where location_type = 'SORTING_TEMPORARY' AND status = '1'



      这个 status 字段是 int 类型 怎么就参数还搞个字符串
      然后还有 KEY index_location_type_status (location_type,status) 这个索引

      这让我一下子就联想到之前学习 mysql 时的一个知识点, 索引 + 隐式数据转换会带来意想不到的问题

      「快速解决线上问题」

      紧急联系 DBA 把发版的新增的索引给干掉


      索引 + 隐式数据转换

      MySQL 中隐式转换详细查看官方文档相关的说明:

      dev.mysql.com/doc/refman/…[1]


      复现线上问题

        下面这三张图: 就可以很清晰的说明问题了: 索引 + 隐式数据转换会带来意想不到的问题


      索引 + 隐式数据转换 理论解析

      根据现象分析,根本原因是 MySQL 的 bit 类型字段在索引查询时的隐式类型转换规则导致的。以下是具体解释:

      问题核心原因:

      bit 类型存储特性:

      status 字段 bit(1) 实际存储的是二进制值: TRUE 存储为 b'1'(二进制值) FALSE 存储为 b'0'

      索引影响下的隐式转换:

        有索引时:

      status='1' → 需要将 bit 转为字符串比较(b'1' → "1") status=1 → 将 bit 转为整数比较(b'1' → 1)

        无索引时:

      两者都会做全表扫描,MySQL 统一进行类型转换

        这个 case 只有 bit(1) 的时候 才会出现 bug

      下面这俩 sql 你可以执行下 bit(1) 虽然存储的是 0 1 但他在 mysql 中 不是 int 类型

        下面的介绍更全面 来源网友


      总结

      存在索引的时候:status = '1':字符串'1'的 ASCII 码为 49,与 bit(1) 的 b'1'(二进制值 1)不匹配,导致索引无法命中。

      mysql 隐式转换的坑还有很多,并且隐式转化有很多默认规则,这个我们控制不来。 我们能做到的就是尽量避免隐式转换

      点击关注公众号,“技术干货” 及时达!

      阅读原文

      跳转微信打开

      Fish AI Reader

      Fish AI Reader

      AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

      FishAI

      FishAI

      鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

      联系邮箱 441953276@qq.com

      相关标签

      MySQL 数据库 线上问题 隐式转换 索引
      相关文章