库存 SQL 忽快忽慢,别先怪索引:把 WHERE 函数冲突拆成可复现实验
同一条库存查询有时走索引、有时全表扫描常被归因于“优化器不稳定”。更常见的原因是WHERE中的类型转换、日期函数或字符串函数改变了谓词形态再叠加参数类型、数据分布和统计信息最终产生不同计划。本文用可搜索条件、隐式转换和区间边界三条线给出从采样、复现、改写到回归的完整诊断方法。先冻结现场不要立刻加索引慢查询出现时先保存 SQL 文本、绑定参数和值的类型、执行时间、返回行数、数据库版本、表结构、索引、统计信息时间和执行计划。只有 SQL 文本没有参数通常无法复现库存编码传字符串还是数字就可能让转换发生在完全不同的一侧。同时区分“数据库执行慢”和“应用看起来慢”。连接池等待、网络传输、锁等待、结果集过大和应用反序列化都可能占用时间。使用数据库侧的实际执行统计确认扫描行数、过滤行数、等待事件和耗时再决定是否进入 SQL 优化。不要在生产上立即创建多个试验索引。新索引会增加写入成本、锁风险和统计变化还会改变现场。先在具有相近数据分布的环境中复现或使用数据库支持的不可见索引、计划分析能力进行低风险验证。可搜索条件为什么重要B-Tree 索引按列值的有序形式组织。谓词若直接约束索引列优化器容易把它转成一个连续范围例如sku ?或created_at ? AND created_at ?。若对列先执行函数数据库往往需要逐行计算后才能判断普通索引中的原始顺序就难以利用。典型写法是WHEREDATE(created_at)2026-08-05更可搜索的改写是半开区间WHEREcreated_at2026-08-05 00:00:00ANDcreated_at2026-08-06 00:00:00半开区间避免猜测时间精度也能覆盖带小数秒的值。但应用必须明确数据库时区和业务时区。若库存日按上海时间计算、字段却保存 UTC需要先在应用侧换算边界而不是在列上逐行做时区转换。两个函数“打架”的本质可能是类型方向假设sku_code是字符列查询却把参数绑定成整数。数据库可能把列转换为数字再比较导致索引无法按原字符顺序定位00123、123和包含字母的编码还可能产生意外等价或转换警告。正确方向通常是让参数匹配列类型而不是给列套CASTWHEREsku_codeCAST(?ASCHAR)更稳妥的是驱动层一开始就按字符串绑定并让字符集与排序规则匹配。SQL 中显式转换参数可以用于排查但长期方案应修正接口契约。列、参数、临时表和关联键的类型要一起检查只改一处可能让转换移动到 JOIN 的另一侧。字符串清洗函数也常见例如TRIM(sku_code)、LOWER(warehouse_code)。如果数据应当规范优先在写入时校验并回填历史脏数据如果业务确实按表达式查询再评估数据库版本支持的函数索引或生成列。不要用表达式索引掩盖不清晰的数据定义。用最小数据集证明是哪一个条件把复杂查询缩成目标表和两个可疑谓词准备四组数据常见值、稀有值、边界时间、类型异常值。分别执行原查询、只保留条件 A、只保留条件 B、两个条件改写后的版本并保存实际执行计划。每组比较访问方式、使用索引、估算行数、实际行数、扫描行数和耗时。若估算与实际差距巨大问题可能还包括统计信息或列相关性若单个条件都能走索引组合后退化则检查复合索引顺序、选择性和返回比例。缓存会影响“忽快忽慢”。第一次读取要从存储加载页后续可能命中缓冲池。比较计划时不要只跑一次也不要通过清空生产缓存制造公平。记录冷暖状态多次交替执行候选 SQL关注扫描量和计划稳定性而非只看某一次毫秒数。锁等待同样会伪装成查询退化。库存表通常写入频繁慢样本若等待事务锁即使执行计划完全正常也不应通过加索引解决。把等待时间与 CPU 执行时间分开保存。计划变化不一定是优化器“随机”参数值不同返回比例可能相差几个数量级。某个仓库占全表一半另一个仓库只有几十行优化器选择不同计划可能合理。准备语义相同但选择性不同的参数集检查系统是否使用参数化计划、是否复用首次编译结果以及当前统计直方图能否表达倾斜。统计信息过旧会让估算失真但更新统计并非万能。更新前记录旧计划与数据变化更新后验证写入压力和其他关键查询。不要因为一条 SQL 变快就忽略共享索引与统计变化对其他查询的影响。如果需要借助模型整理计划或生成改写候选应先删除业务数据、账号和真实 SQL 常量通过本地适配层限制上传范围。haerapi.com可以作为待评估的 API 中转候选之一但模型建议只能进入实验清单不能直接在生产执行建索引、改类型或更新统计。改写之后必须做语义回归SQL 更快但结果变化是最昂贵的“优化”。日期改为半开区间后验证零点、月底、闰日、夏令时和小数秒字符串类型修正后验证前导零、空值、空串、大小写和尾部空格库存条件还要验证负库存、冻结量和多仓合并规则。建立一份固定回归集保存输入参数、预期主键集合和聚合结果。性能测试记录 P50、P95、扫描行数和计划摘要正确性测试比较结果集合。两类测试都通过才允许进入灰度。上线采用单一可回退变更。先改参数类型或谓词再观察不要同时改 SQL、加索引、升级驱动和更新统计否则发生回归时无法定位。灰度期间监控该查询的调用量、错误率、延迟分位数、扫描行数与数据库负载。一张排查清单收尾遇到 WHERE 中函数导致的疑似索引问题按顺序问函数作用在列还是参数两边类型和字符集是否一致日期边界和时区是否明确谓词能否改成等值或半开范围返回比例是否适合索引估算与实际是否接近慢样本是否包含锁等待不同参数是否需要不同计划改写是否通过边界语义回归。这些问题比“有没有索引”更接近根因。索引是数据访问结构不是让任意表达式自动变快的开关。结语查询忽快忽慢时最可靠的方法是保存参数化现场把两个函数拆成独立变量用实际计划证明扫描发生在哪里。先让列保持可搜索、让参数匹配类型、让时间使用明确区间再处理统计与索引。这样得到的不只是一次提速而是一条能够解释、复现和回退的优化结论。