欢迎来到徐庆高(Tea)的个人博客网站
磨难很爱我,一度将我连根拔起。从惊慌失措到心力交瘁,我孤身一人,但并不孤独无依。依赖那些依赖我的人,信任那些信任我的人,帮助那些给予我帮助的人。如果我愿意,可以分裂成无数面镜子,让他们看见我,就像看见自己。察言观色和模仿学习是我的领域。像每个深受创伤的人那样,最终,我学会了随遇而安。
当前位置: 日志文章 > 详细内容

浅析MySQL动态查询条件导致索引失效问题优化

2025年07月15日 Mysql
文章从一名具有八年经验的 java 开发者视角出发,结合真实业务场景,深入浅出地剖析了 mysql 动态查询如何导致索引失效,并提供了 java 实战方案,包括 mybatis 动态 sql 编写及优

文章从一名具有八年经验的 java 开发者视角出发,结合真实业务场景,深入浅出地剖析了 mysql 动态查询如何导致索引失效,并提供了 java 实战方案,包括 mybatis 动态 sql 编写及优化技巧,并配套注释详尽的代码示例。

引言

那些年我们写过的“万能查询接口”,其实正在悄悄拖垮你的数据库

在很多 java 项目的后台管理系统中,我们常常需要为运营或业务人员提供“多条件组合查询”,比如:订单查询、用户搜索、日志筛选等。

于是你写下这样的 sql:

select * from orders 
where (user_id = #{userid} or #{userid} is null)
  and (status = #{status} or #{status} is null)
  and (create_time >= #{starttime} or #{starttime} is null)

看上去非常灵活,参数不传就忽略,传了就加上。但你知道吗?

这种写法,在大多数情况下会导致 sql 执行计划无法命中索引,导致全表扫描

在一次生产环境慢 sql 排查中,我就亲手“逮住”了这种写法导致的性能灾难。明明建了索引,查询却依然慢如蜗牛。究其原因,就是:动态条件拼接方式不当,破坏了索引优化器的预期路径

本文将结合真实业务场景,讲清楚:

  • 为什么动态查询条件会导致索引失效?
  • 如何使用 mybatis 的动态 sql 来构建安全且高效的查询?
  • 如何结合 java 工具类进行参数组装与优化?

一、业务场景还原:订单搜索接口

以一个电商系统为例,管理员后台需要筛选订单列表,支持以下条件组合:

  • 用户 id(userid)
  • 订单状态(status)
  • 下单时间(createtime)
  • 支付渠道(paytype)

这些条件用户可以任意组合查询,例如只查某个用户、或查某一时间段。

于是我们可能写出如下 sql:

select * from orders
where (user_id = #{userid} or #{userid} is null)
  and (status = #{status} or #{status} is null)
  and (create_time >= #{starttime} or #{starttime} is null);

虽然逻辑正确,业务能跑,但 mysql 查询优化器无法使用索引,因为:

  • 表达式中包含函数或 or 操作,导致无法精准判断是否可以走索引;
  • 查询条件不固定,执行计划不稳定;
  • mysql 不能对 or 中的部分条件单独使用索引

二、问题分析:or + 参数判断 = 索引失效

我们来看一个简化版本的 explain:

explain select * from orders 
where (user_id = 100 or 100 is null)

输出结果:

type: all
possible_keys: user_id_idx
key: null

说明即使 user_id 有索引,也不会被使用。

原因:

  • or 会让优化器放弃使用索引;
  • 参数判断 #{xxx} is null 是运行时决定,sql 编译时无法预测执行路径;
  • 导致 mysql 选择全表扫描type: all)。

三、优化方案:使用 mybatis 动态 sql 精确构建查询条件

优化目标

  • 只在参数不为空时拼接对应查询条件;
  • 避免使用 or + 参数判断;
  • 保证条件结构清晰,利于索引使用。

四、实战代码:mybatis 动态 sql 实现高性能动态查询

1. 定义查询参数类(dto)

public class orderqueryrequest {
    private long userid;
    private integer status;
    private localdatetime starttime;
    private localdatetime endtime;
    private integer paytype;

    // getters and setters
}

2. mapper 接口定义

public interface ordermapper {
    list<orderdo> queryorders(@param("param") orderqueryrequest param);
}

3. mapper xml 动态 sql 示例

使用 <if> 标签动态拼接查询字段,避免无谓的 or 条件。

<select id="queryorders" resulttype="com.example.domain.orderdo">
    select * from orders
    where 1=1
    <if test="param.userid != null">
        and user_id = #{param.userid}
    </if>
    <if test="param.status != null">
        and status = #{param.status}
    </if>
    <if test="param.starttime != null">
        and create_time >= #{param.starttime}
    </if>
    <if test="param.endtime != null">
        and create_time <= #{param.endtime}
    </if>
    <if test="param.paytype != null">
        and pay_type = #{param.paytype}
    </if>
    order by create_time desc
    limit 100
</select>

说明:

  • where 1=1 是常见的动态 sql 技巧,方便统一拼接 and
  • 只有在对应参数不为空时才拼接条件;
  • 避免 oris null 判断,mysql 执行计划更稳定;
  • 可配合索引如 (user_id, create_time) 提高性能。

五、进一步优化建议(高级)

为常用组合条件创建联合索引

如:

create index idx_user_time on orders(user_id, create_time);

让查询可以利用 覆盖索引,避免回表。

使用where+in或between替代不等式

如时间段查询用:

create_time between #{starttime} and #{endtime}

而不是 >= / <= 分开写。

使用查询缓存或 es 做异步查询(超大数据量)

对于千万级数据查询,建议将查询迁移到 elasticsearch 或 redis 缓存中,避免高并发直接打到 mysql。

六、总结

原始写法问题优化方式
(字段 = 参数 or 参数 is null)无法命中索引使用 mybatis <if> 精准拼接
or 多条件执行计划不稳定拆分多个 and 条件
参数全传执行计划多变控制参数组合,创建联合索引

最终目标是让每一条 sql 都在编译阶段就明确执行路径,最大化使用索引、最小化全表扫描。

七、建议

  • 不要盲目追求“万能查询接口”,要根据场景设计索引与 sql;
  • mybatis 提供了强大的动态 sql 能力,善用 <if><where><choose>
  • 建议对慢 sql 定期分析,善用 explainshow profile
  • 保持 sql 简洁、结构清晰,帮助优化器“读懂”你的意图。

八、结语

很多时候,性能问题并不是代码写得不对,而是写得“太灵活”。我们追求通用,却丢失了性能。作为有经验的开发者,我们要学会在“灵活”与“高效”之间找到平衡。

到此这篇关于浅析mysql动态查询条件导致索引失效问题优化的文章就介绍到这了,更多相关mysql动态查询优化内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!