










今天巡检系统时,发现一条耗时超过15s的慢SQL:
select count(*) as orderNum,
sum(amount) as totalAmt,
sum(case when order_status = 'SUCCESS' then amount end) as totalSuccessAmt,
sum(case when order_status = 'FAIL' then amount end) as totalFailAmt
from order
where enterprise_name = '海南XXXX有限公司'
and order_status = 'PROCESSING'
and create_time >= '2026-04-22 00:00:00'
and create_time < '2026-04-30 00:00:00';
这条语句来自交易订单查询页面。用户在页面筛选框中输入“企业名称”后,前端将这个值原样传给了后端,最终在拼接动态SQL时,enterprise_name 被直接用作查询条件。

隐患即刻暴露:
order 表是核心交易表,已有约 6000万 条记录,且每日新增约12万条。enterprise_name 字段上没有建立任何索引。enterprise_name 进行查询,数据库都不得不进行全表扫描,在数千万行数据中逐条比对,性能急剧下降。最不靠谱的方案是头疼医头,为 enterprise_name添加索引。(别笑~ 你或者曾经的你,在做着或做过类似的事情)
我与负责的开发者沟通后,我们采取了最直接的优化方案:转换查询维度。
order表中本就有 enterprise_id 字段且已建有索引,那么最优解就是将 enterprise_name 转换为对应的 enterprise_id 再进行查询。
原始动态SQL(MyBatis XML示例)如下:
<where>
<if test="query.enterpriseId != null">
and enterprise_id = #{query.enterpriseId}
</if>
<if test="query.enterpriseName != null and query.enterpriseName != ''">
and enterprise_name = #{query.enterpriseName}
</if>
... <!-- 其他条件 -->
</where>
与开发者沟通后,改造方案为:在业务逻辑层,根据传入的 enterpriseName 先查询出对应的 enterpriseId,然后通过enterprise_id 字段来查询(enterprise_id 有索引)。
开发者的修复方式是:直接移除了 XML 中 enterprise_name 的查询条件块。
<where>
<if test="query.enterpriseId != null">
and enterprise_id = #{query.enterpriseId} <!-- 仅使用ID查询 -->
</if>
... <!-- 其他条件 -->
</where>
立竿见影:查询速度从15s秒甚至超时下降到毫秒级,问题在当下被解决了。
问题虽然暂时解决,不过呢,站在工程层面,我更关注的是:我们如何保证,未来不会再有开发者在同一个地方摔倒?
AND enterprise_name = ...(甚至是AND enterprise_name LIKE ...) 的条件。enterprise_name 查询。我问这个开发者,他痛快地说:“那最好就是去掉这个enterprise_name字段。” 这无疑是最彻底的方案——大表应尽量避免存储可关联查询的冗余信息。然而,删除字段涉及广泛的代码梳理和影响范围评估,属于中长期重构目标,无法一蹴而就。
那么,在完成彻底的重构之前,我们如何在代码层面建立一道“防火墙”,短期内有效规避风险?
我的方案是:不隐藏,而是“标记”并“禁用它”。在代码中,将危险的查询条件注释掉,并附上无法被忽略的强烈警告。
将原来的直接删除,改为如下形式:
<where>
<if test="query.enterpriseId != null">
and enterprise_id = #{query.enterpriseId}
</if>
... 其他条件 ...
<!-- !!! 性能警告:order 表为大表(6000W+),enterprise_name 字段无索引,禁止直接使用该字段进行查询!!! -->
<!-- !!! 必须通过 enterprise_id 进行查询。如需使用名称,请在业务层先根据 name 查询出 id。!!! -->
<!--
<if test="query.enterpriseName != null and query.enterpriseName != ''">
and enterprise_name = #{query.enterpriseName}
</if>
-->
... 其他条件 ...
</where>
这个简单动作的工程价值:
enterprise_id),提供了修改范本。显然,这比约定规范或者互相周知,要更有效。毕竟,这东西并不能让团队人员都一一get。对于小规模团队或者需求研发高频迭代的团队,有妻如此。
解决一个线上慢SQL,是技术能力的体现;而思考如何防止它再次发生,则是工程素养的彰显。
“解决问题要彻底”,不仅意味着修复眼前的Bug,更意味着通过代码注释、流程规范、知识传承、技术工具等多重手段,在系统中建立起防止同类错误复发的“免疫机制”。
从给危险的代码块加上一个醒目的警告注释开始,我们就是在为团队积累资产,为系统的长期稳定运行铺设基石。这一个小小的举动,体现的正是对工程质量负责、对后来者负责的工匠精神。
当然,注释警告是有效的临时“路标”。从工程体系上,我们还可以努力做得更多,以追求更彻底的解决:
order.enterprise_name 的慢查询,能第一时间发现并拦截。此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。