在数据库查询里,字段筛选直接决定了需要扫描多少数据、能否命中索引以及返回结果的体积。很多慢查询并不是因为数据量天然大,而是筛选逻辑写得让优化器无从下手。要把筛选优化吃透,得从存储、索引、执行计划几个层面顺一遍。

一、字段筛选的底层执行逻辑
一条带筛选的SQL提交后,优化器会先解析语义,再估算每种执行路径的成本。筛选条件如果直接作用在索引列上,存储引擎可以在B+树里做范围或等值定位,只读取符合条件的页;如果条件包在函数里或发生了隐式类型转换,引擎往往只能把数据读到内存再逐行判断,这就是常说的索引失效。
除此之外,字段筛选还和列裁剪有关。即使WHERE用得再好,如果SELECT列出了所有字段,尤其是大文本或JSON列,网络传输和临时表构建都会拖慢整体响应。因此优化筛选不能只盯WHERE,还要看“取哪些列”和“什么时候取”。
1.1 引擎层与计算层筛选
以MySQL为例,部分条件下推(condition pushdown)能让存储引擎先筛掉数据,减少向Server层递交的行数。像分区表的分区裁剪、联合索引的最左前缀匹配,都属于引擎层筛选。如果写了WHERE YEAR(create_time) = 2023,函数阻断了下推,引擎只能全量返回再计算。
理解这一点后,改写方向就清晰了:把字段上的操作挪到常量侧,或改用范围条件,让引擎能用索引本身的有序性完成筛选。下面代码展示两种写法差异。
-- 不推荐:字段套函数,索引失效 SELECT id, name FROM orders WHERE YEAR(paid_at) = 2023; -- 推荐:范围筛选,可用paid_at索引 SELECT id, name FROM orders WHERE paid_at >= '2023-01-01' AND paid_at < '2024-01-01';
二、索引与字段选择的协同优化
建立联合索引时,把高频筛选字段放在前面,可以同时服务多个查询。比如业务常按user_id加status筛选,索引(user_id, status)就能覆盖这两列的组合条件。但要注意,如果筛选里出现status却没带user_id,最左前缀规则会导致只能用到部分索引或全表扫。
另一个重点是覆盖索引。当SELECT的列刚好都在索引树上,引擎无需回表,性能提升明显。这要求我们在设计索引时,适当包含查询所需的非筛选列,也就是“索引覆盖”思路。当然索引也不是越多越好,写操作的成本会随索引增加而上扬。
2.1 用EXISTS替代IN减少字段比对
在子查询筛选场景,IN会先物化子查询全部结果再做匹配,数据量大时很吃内存;EXISTS则是逐行判断是否存在,配合索引往往更轻量。尤其是外部表大、内部表有索引时,差异显著。
下面示例里,我们只关心有没有关联记录,不需要子查询的具体字段,用EXISTS语义更贴切,优化器也容易选到半连接策略。
-- 不推荐:IN展开子查询字段 SELECT u.id, u.nick FROM users u WHERE u.id IN (SELECT order_user_id FROM orders WHERE amount > 1000); -- 推荐:EXISTS半连接 SELECT u.id, u.nick FROM users u WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.order_user_id = u.id AND o.amount > 1000 );
三、常见筛选误区与改写方案
第一大类误区是前置通配符模糊查询,例如LIKE '%手机',因为 leftmost match 被破坏,B+树无法定位,只能全表扫。如果业务允许,改成LIKE '手机%'就能走索引,或者引入倒排索引、全文检索组件。
第二大类是隐式转换,比如字段是字符串类型,WHERE却用数字比较,数据库会转字段类型,同样让索引失效。写SQL时应保持类型一致,用引号包住字符串常量。
3.1 减少SELECT星号带来的隐性成本
不少人图省事写SELECT *,但在字段筛选优化里,这会让列裁剪失效,所有列都被读取和传输。如果表 later 增加了大字段,老接口性能会莫名劣化。明确列出所需字段,既缩小结果集,也方便建立覆盖索引。
| 写法 | 扫描方式 | 回表 | 适用场景 |
|---|---|---|---|
| SELECT * | 可能全列读 | 需要 | 临时排查 |
| SELECT 必要列 | 索引覆盖可能 | 不需要 | 线上接口 |
四、系统化掌握的操作清单
拿到慢查询,先看执行计划里type和Extra,确认是不是全表扫或用了filesort。接着检查WHERE里字段是否带函数、是否类型不匹配、是否用了前置模糊匹配。然后评估SELECT列能否缩减,能否用联合索引覆盖。
最后把改写后的SQL在测试环境跑一遍,对比前后耗时和扫描行数。形成“看计划、查条件、缩列、调索引”的闭环,字段筛选优化就能从零散经验变成可复用的系统性方法。
-- 查看执行计划示例 EXPLAIN SELECT id, status FROM orders WHERE user_id = 123 AND status = 'paid' ORDER BY create_time DESC LIMIT 20;