0Pricing
Coding Interview Prep · 课时

按计算值筛选

了解对列使用函数为何会导致索引失效,以及面试官如何考查这一点

按计算值筛选 是 CoddyKit 上的免费 Coding Interview Prep 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Coding Interview Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Coding Interview Prep 课程共包含 4 节课。

为什么这个问题能区分不同水平

这个问题听起来很简单:这个查询是正确的,但为什么很慢?通常,答案是 WHERE 子句在带索引的列上套用了函数。这会使谓词变得不可搜索:优化器无法再使用索引,只能扫描每一行。

本课将解释可搜索性,展示面试官希望看到的改写方式,并说明计算后的筛选条件实际应该放在哪里。

用一个定义理解可搜索性

可搜索(表示搜索参数可用)是指谓词可以使用索引,直接定位匹配的行。一个经验规则是:索引列必须在比较运算符的一侧直接出现,不能埋在函数或表达式中。

  • 可搜索:col = 5、col > 100、col LIKE 'abc%'
  • 不可搜索:FUNC(col) = 5、col + 1 > 100

对列使用函数的反模式

这里的目标是筛选 2024 年下单的订单。在列上套用 YEAR() 会迫使数据库引擎在比较之前为每一行计算年份,因此 order_date 上的索引就无法发挥作用。

查询会返回正确结果,但会扫描整张表。对于大表来说,这可能意味着从几毫秒增加到几分钟。

-- non-sargable: function on the indexed column
SELECT *
FROM orders
WHERE YEAR(order_date) = 2024;

改写为范围条件

解决方法是让 order_date 直接出现,并将条件表达为半开范围。这样,order_date 上的索引就可以直接定位到 2024 年的起点,并在 2025 年停止。

结果相同,但使用的是索引范围扫描,而不是全表扫描。这种范围改写是面试中最常考的可搜索性修复方法。

-- sargable: column stays bare
SELECT *
FROM orders
WHERE order_date >= '2024-01-01'
  AND order_date <  '2025-01-01';

对列进行算术运算

算术运算中也隐藏着同样的问题。WHERE salary + bonus > 100000 或 WHERE price * 0.9 < 50 都会对列进行计算,从而阻止使用索引。

请尽可能把计算移到常量一侧:将 price * 0.9 < 50 改写为 price < 50 / 0.9。字面量只需计算一次,同时让 price 保持直接出现并可使用索引。

-- before: math on the column (non-sargable)
WHERE price * 0.9 < 50
-- after: math on the constant (sargable)
WHERE price < 50 / 0.9

不区分大小写的搜索变体

相对于 email 上的普通索引,WHERE LOWER(email) = 'a@b.com' 是不可搜索的,因为必须先将每一行的电子邮件地址转换为小写。

生产环境中有两种修复方式:存储一份经过规范化并转换为小写的副本,然后为其建立索引;或者在 LOWER(email) 上创建函数索引,直接为表达式建立索引。提到函数索引这一选项,能够体现您具有实际项目经验。

-- functional index makes the expression sargable
CREATE INDEX idx_email_lower ON users (LOWER(email));
SELECT * FROM users WHERE LOWER(email) = 'a@b.com';

确实需要计算时

有时筛选条件确实依赖某个无法改写为范围的计算值,例如按比率筛选。不过,您仍然不能在 WHERE 中引用 SELECT 别名,因为 WHERE 会在 SELECT 列表之前求值。

因此,您可以在 WHERE 中重复该表达式,或者将查询包在子查询或 CTE 中,再在外层查询中筛选计算列。

SELECT *
FROM (
  SELECT *, revenue / NULLIF(visits, 0) AS rev_per_visit
  FROM stats
) t
WHERE t.rev_per_visit > 2.5;

聚合条件放在 HAVING,而不是 WHERE

聚合计算完全不能放在 WHERE 中,因为 WHERE 会在分组之前筛选单独的行。WHERE SUM(amount) > 1000 会产生错误。

聚合筛选条件应放在 HAVING 中,因为它会在 GROUP BY 之后执行。了解哪个子句可以使用该计算结果,本身就是一个常见的执行顺序问题。

SELECT customer_id, SUM(amount) AS total
FROM orders
GROUP BY customer_id
HAVING SUM(amount) > 1000;

面试官如何考察这一点

面试官会展示一个在列上使用函数的慢查询,并要求您在不改变结果的情况下让它变快。您的处理方式是:

  • 识别出对列使用函数属于不可搜索条件
  • 改写查询,让列直接出现(使用范围条件或将计算移到常量一侧)
  • 如果无法改写,则提出函数索引或存储的计算列

提到使用 EXPLAIN 确认执行计划已从顺序扫描变为索引扫描,就能让答案更加完整。

权衡意识

请保持客观:索引和函数索引可以加快读取,但会降低写入速度并占用存储空间。对于很小的表,全表扫描完全没有问题,添加索引反而是浪费精力。

更成熟的回答应当视情况而定:如果该列对应的数据量很大,并且经常以这种方式进行筛选,就让谓词变得可搜索,或添加函数索引;否则就保持现状。面试中,具体情境比教条更重要。

函数索引让计算表达式可使用索引

有时您确实必须根据转换后的值进行筛选,例如进行不区分大小写的匹配。与其放弃使用索引,不如针对筛选时使用的确切表达式创建表达式(函数)索引。

  • 这样一来,即使列被函数包裹,优化器仍可使用该索引。
  • 索引表达式必须与谓词表达式完全一致。
-- index the expression you filter on
CREATE INDEX idx_users_lower_email ON users (lower(email));

-- now this predicate stays sargable
SELECT * FROM users WHERE lower(email) = 'amy@example.com';

快速检查

找出优化器可以使用索引的谓词。

回顾

要点:

  • 当索引列直接出现,而不是位于函数或算术运算中时,谓词就是可搜索的
  • 将 YEAR(col) = 2024 改写为半开范围;把计算移到常量一侧
  • 对于无法避免的表达式,使用函数索引或存储的计算列
  • 不能在 WHERE 中使用 SELECT 别名;聚合条件应放在 HAVING 中

经典问题是慢查询;经典修复方式是让列直接出现。

常见问题解答

「按计算值筛选」课时是免费的吗?

是的 — 「按计算值筛选」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Coding Interview Prep 课程的其余内容,请升级到 CoddyKit PRO。 Coding Interview Prep 课程共包含 4 节课。

「按计算值筛选」这节课中我会学到什么?

了解对列使用函数为何会导致索引失效,以及面试官如何考查这一点 你通过在浏览器中直接运行的动手代码来练习 Coding Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Coding Interview Prep 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Coding Interview Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「按计算值筛选」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Coding Interview Prep 课中编写并运行代码吗?

能。每节 Coding Interview Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. AND/OR 优先级与括号
  2. BETWEEN、IN 与包含边界
  3. LIKE、通配符与转义
  4. 按计算值筛选
← 返回 Coding Interview Prep