在mysql中,子查询如果缺少索引支撑,往往会被执行成反复的全表扫描,尤其是关联子查询,外层每返回一行就要对内层查一次。通过合理地设计索引,可以显著减少扫描行数,从而降低响应时间。下面先看一个基础示例。

为什么子查询会慢
子查询分为非关联子查询和关联子查询。非关联子查询先执行一次,结果传给外层;关联子查询则依赖外层字段,可能被循环执行。如果子查询涉及的大表没有可用索引,mysql只能做全表扫描。
在where子句中使用索引加速
当子查询出现在where条件里,如in或exists,应对子查询的过滤列建立索引。例如有orders表和customers表,想查存在订单的客户:
-- 在 customers.id 上已有主键索引,orders.customer_id 建索引可加速 CREATE INDEX idx_orders_customer_id ON orders(customer_id); -- 使用 exists 的子查询 SELECT c.id, c.name FROM customers c WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.customer_id = c.id );
上面的exists子查询中,mysql可以利用idx_orders_customer_id快速定位每个客户的订单,不必全表扫描orders。
关联子查询的索引策略
关联子查询常见写法是内层引用外层列。对内部表的关联列建索引是最直接的方法。例如统计每个客户的订单数:
-- 确保 orders.customer_id 有索引 SELECT c.id, (SELECT COUNT(*) FROM orders o WHERE o.customer_id = c.id) AS order_cnt FROM customers c;
如果orders表在customer_id上没有索引,这个子查询会对每个客户做一次全表扫描,数据量大时非常慢。
用执行计划验证索引效果
使用explain观察子查询是否用到索引。重点看type列和key列:
| 字段 | 含义 |
|---|---|
| type | 访问类型,index或ref说明用了索引,ALL表示全表扫描 |
| key | 实际使用的索引名 |
| rows | 预估扫描行数,越小越好 |
EXPLAIN SELECT c.id FROM customers c WHERE EXISTS ( SELECT 1 FROM orders o WHERE o.customer_id = c.id );
将子查询改写为join
有时把子查询改写成join并配合索引,效率更稳定。仍以上面的例子:
SELECT DISTINCT c.id FROM customers c JOIN orders o ON o.customer_id = c.id;
只要orders.customer_id有索引,join同样能快速匹配。实际优化时,建议同时用explain对比子查询和join两种写法的成本。
注意事项
- 索引不是越多越好,写多读少的表应平衡索引维护成本。
- 对文本列建索引可考虑前缀索引,减少空间占用。
- 定期用analyze table更新统计信息,帮助优化器选对索引。
通过在子查询相关的过滤列和关联列上建立合适索引,并借助explain验证,就能在mysql中有效加速子查询。