使用 Codex 优化 MySQL、PostgreSQL 等数据库查询时,一个非常常见的动作就是:

给查询字段加索引。

例如发现:

SELECT *
FROM orders
WHERE user_id = ?
ORDER BY created_at DESC;

执行较慢,于是开始增加:

CREATE INDEX idx_orders_user_id
ON orders(user_id);

CREATE INDEX idx_orders_created_at
ON orders(created_at);

后来又发现其他查询慢,于是继续增加:

status
type
product_id
tenant_id
updated_at

最终一张表上挂了十几个甚至几十个索引。

查询可能暂时变快,但新的问题开始出现:

  • INSERT越来越慢;

  • UPDATE耗时明显上升;

  • 批量导入速度下降;

  • Migration建立索引耗时很久;

  • 数据库磁盘占用越来越大;

  • Buffer Pool压力上升;

  • 明明加了索引,SQL却仍然全表扫描;

  • Codex看到慢查询后继续建议增加新索引。

这时候真正需要问的已经不是:

还能加什么索引?

而是:

当前这些索引到底有没有被真正使用?


一、索引不是免费的

假设 orders 表只有数据,没有索引。

插入一条记录:

INSERT INTO orders (...) VALUES (...);

数据库主要负责写入数据页。

如果这张表还有:

idx_user
idx_status
idx_created
idx_product
idx_tenant

那么新增一条订单时,还需要同步维护这些索引结构。

可以简单理解成:

写1条业务数据
+
更新多个索引

索引越多:

INSERT
UPDATE
DELETE

的维护成本通常越高。

所以索引本质上是一种:

用额外存储与写入成本,换取部分读取性能。


二、为什么给每个字段单独建索引不一定有效?

假设查询:

SELECT id, total_amount
FROM orders
WHERE user_id = ?
AND status = 'paid'
ORDER BY created_at DESC
LIMIT 20;

项目中分别有:

INDEX(user_id)
INDEX(status)
INDEX(created_at)

不代表数据库一定能把三个索引完美组合起来。

很多情况下,一个更合理的联合索引可能是:

CREATE INDEX idx_orders_user_status_created
ON orders(
  user_id,
  status,
  created_at DESC
);

它更贴近真实查询方式:

先按user_id过滤
↓
再按status过滤
↓
再按created_at排序

因此索引应该围绕:

实际SQL访问模式

设计,而不是看到一个字段就给它单独加索引。


三、联合索引顺序为什么重要?

假设索引:

(user_id, status, created_at)

常见可利用查询包括:

WHERE user_id = ?

以及:

WHERE user_id = ?
AND status = ?

以及:

WHERE user_id = ?
AND status = ?
ORDER BY created_at DESC

但如果查询只有:

WHERE status = ?

这个联合索引未必能达到预期效果。

这也是常说的“最左前缀”思路之一。

所以不能只问:

这几个字段是不是都在索引里?

还要问:

SQL是按照什么顺序过滤这些字段的?


四、选择性太低的字段未必适合单独索引

例如订单状态只有:

pending
paid
cancelled
completed

整个表1000万条数据。

单独建立:

INDEX(status)

查询:

WHERE status = 'paid'

可能仍然返回数百万条记录。

这时索引选择性很低。

数据库可能判断:

走索引再回表

还不如:

直接扫描大量数据

所以它甚至可能不使用这个索引。

更适合结合其他高选择性字段:

tenant_id
user_id
时间范围

设计联合索引。


五、什么是覆盖索引?

假设接口只需要:

SELECT id, created_at
FROM orders
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT 20;

如果索引包含:

(user_id, created_at, id)

数据库可能直接从索引获得需要的数据。

不需要再回到主表读取完整记录。

可以简单理解为:

普通索引查询
→ 找索引
→ 找主表

覆盖索引
→ 找索引
→ 直接返回

对于高频列表接口,这可能明显降低随机 IO。

但同样不要为了实现覆盖索引,把几十个字段全部塞进索引。

索引本身也会快速膨胀。


六、SELECT * 会削弱很多索引优化空间

Codex 很容易生成:

SELECT *
FROM orders
WHERE user_id = ?;

但页面实际上只使用:

id
order_no
status
created_at

如果查询永远 SELECT *

  • 网络数据更多;

  • 数据库需要读取更多字段;

  • 覆盖索引更难实现;

  • ORM对象创建成本更高。

更推荐明确选择:

SELECT
  id,
  order_no,
  status,
  created_at
FROM orders
WHERE user_id = ?;

先减少数据量,再考虑索引。


七、索引字段顺序要看真实SQL

例如经常执行:

WHERE tenant_id = ?
AND status = ?
AND created_at >= ?
ORDER BY created_at DESC

可以评估:

tenant_id
status
created_at

这样的组合。

但如果另一个查询主要是:

WHERE user_id = ?
ORDER BY created_at DESC

可能需要另一套索引。

索引设计不是“找到一个万能联合索引”。

而是:

高频查询A
高频查询B
核心写入成本

之间寻找平衡。


八、索引太宽也会增加成本

例如建立:

(
 tenant_id,
 user_id,
 status,
 type,
 product_id,
 created_at,
 updated_at,
 order_no
)

虽然字段很多,但问题也很多:

  • 索引文件更大;

  • 缓存命中率下降;

  • 每次写入维护成本更高;

  • 比较操作增加;

  • 某些查询仍然无法有效使用。

不要用:

把可能查询的字段全部放进去。

这种方式设计联合索引。

联合索引应该服务于明确查询模式。


九、UPDATE为什么也会受到索引影响?

假设:

status

存在索引。

执行:

UPDATE orders
SET status = 'completed'
WHERE id = ?;

数据库不仅修改业务记录。

还需要更新与 status 相关的索引。

如果:

status
updated_at
type

都参与多个索引,频繁状态更新的订单表就会产生明显写放大。

因此频繁变动字段是否进入多个索引,需要特别谨慎。


十、索引越多,磁盘占用越大

假设主表:

50GB

再增加多个大型联合索引:

10GB
15GB
20GB
...

最终索引总大小甚至可能超过数据本身。

这会进一步影响:

  • 备份时间;

  • 恢复时间;

  • 数据复制;

  • Buffer Pool;

  • Migration;

  • 磁盘成本。

所以索引优化不能只看:

单条SQL快了多少

还应该看:

整个系统增加了多少长期成本。

十一、一定要看EXPLAIN

Codex建议创建索引后,不应该直接认为优化已经完成。

应该执行:

EXPLAIN
SELECT ...

重点看:

使用哪个索引?
预计扫描多少行?
是否出现全表扫描?
是否额外排序?
是否产生临时表?

如果创建:

idx_orders_status

但执行计划根本没有使用它,那么这条索引可能只是在增加写入负担。


十二、EXPLAIN ANALYZE更接近真实执行情况

数据库支持的情况下,还可以使用:

EXPLAIN ANALYZE

它可以帮助观察:

真实执行时间
实际扫描行数
实际循环次数

例如估算:

预计100行

实际:

扫描300万行

就说明数据库统计信息或查询结构可能存在问题。

注意在生产环境执行具有实际运行语义的分析命令时,应评估查询成本,避免直接对高风险写操作使用。


十三、索引存在不代表一定会被使用

例如:

WHERE DATE(created_at) = '2026-08-16'

虽然:

created_at

有索引,但对字段做函数计算可能影响普通索引的使用方式。

很多场景可以改成:

WHERE created_at >= '2026-08-16 00:00:00'
AND created_at < '2026-08-17 00:00:00'

类似问题还包括:

  • 隐式类型转换;

  • 前缀通配符;

  • 表达式计算;

  • 不匹配的排序;

  • OR条件。

所以慢查询不能简单通过“字段已经有索引”判断。


十四、LIKE查询也要看写法

例如:

WHERE name LIKE 'Codex%'

与:

WHERE name LIKE '%Codex%'

对普通 B-Tree 索引的利用能力通常差别很大。

如果产品需要:

全文搜索
模糊包含搜索
多字段关键词

可能应该考虑:

  • 全文索引;

  • Elasticsearch;

  • OpenSearch;

  • 专用搜索方案。

不要强行给普通数据库不断堆 B-Tree 索引。


十五、排序和分页必须一起考虑索引

例如:

SELECT id, title
FROM articles
WHERE category_id = ?
ORDER BY created_at DESC
LIMIT 20;

如果只有:

INDEX(category_id)

数据库过滤后仍然可能需要额外排序。

可以评估:

(category_id, created_at)

这样的索引。

尤其对于:

列表
分页
时间倒序

类接口,过滤和排序最好一起分析。


十六、不要看到Using filesort就条件反射加索引

某些排序的数据量非常小。

例如过滤后只剩:

20条

数据库内存排序可能非常快。

为这种查询再增加一个大型索引,可能得不偿失。

数据库优化不是:

执行计划里出现某个关键词
→ 必须消灭

而应该看:

真实耗时
扫描行数
调用频率
写入成本

综合判断。


十七、删除无效索引前先确认依赖

发现索引疑似无用时,也不要直接:

DROP INDEX ...

先检查:

  • 是否被低频关键任务使用;

  • 是否用于唯一约束;

  • 是否用于外键相关访问;

  • 是否在月底报表中使用;

  • 是否有不同查询依赖它。

“最近没看到使用”不一定等于永久没有价值。

最好结合:

索引使用统计
慢查询日志
业务查询清单

确认。


十八、重复索引很容易被Codex无意创建

例如已有:

INDEX(user_id)

后来增加:

INDEX(user_id, created_at)

在某些实际查询组合下,前一个单列索引可能已经变得冗余。

又或者项目存在:

idx_user_status
idx_user_status_created
idx_user

多轮自动优化后,很容易形成高度重叠的索引。

因此新增索引前应该先输出:

现有索引列表

判断是否已经存在可复用的前缀或类似索引。


十九、Migration中加索引也要评估生产风险

在小表:

CREATE INDEX ...

可能几秒完成。

但在上亿行大表上,可能:

  • 消耗大量IO;

  • 增加CPU;

  • 影响线上查询;

  • 占用大量临时空间;

  • 执行时间很长。

因此 Codex 生成 Migration 时应该同时说明:

目标表规模
预计索引大小
数据库是否支持在线创建
高峰期是否适合执行

不要把创建索引当成无风险配置修改。


二十、给慢查询建立Index Review流程

可以固定:

发现慢SQL
↓
记录真实SQL
↓
查看调用频率
↓
EXPLAIN / ANALYZE
↓
查看现有索引
↓
判断查询能否先改写
↓
再决定是否增加索引
↓
压测读写性能

这个顺序比:

SQL慢
↓
直接CREATE INDEX

更加可靠。


二十一、让Codex先输出索引审查报告

可以这样要求:

请先不要创建新索引。

分析当前SQL并输出:

1. WHERE条件;
2. JOIN条件;
3. ORDER BY;
4. 当前返回字段;
5. 当前已有索引;
6. 执行计划使用了哪个索引;
7. 预计扫描行数;
8. 是否存在重复或高度重叠索引;
9. 推荐索引会增加哪些写入成本。

只有解释清楚以后,再决定是否生成 Migration。


二十二、测试索引不能只测SELECT

索引上线以后应该同时测试:

查询

SELECT P50 / P95

有没有改善。

写入

INSERT耗时
UPDATE耗时

有没有明显恶化。

批量任务

导入10万条

耗时是否大幅上升。

存储

索引增加多少GB

不要只因为:

查询从80ms降到20ms

就认为索引一定值得保留。


二十三、把索引规则写进AGENTS.md

# Database Index规则

- 禁止看到慢查询就直接新增索引
- 新增索引前必须检查现有索引
- 联合索引必须根据真实WHERE与ORDER BY设计
- 低选择性字段禁止默认单独建索引
- 高频UPDATE字段进入索引前必须评估写放大
- 接口查询禁止默认SELECT *
- 索引优化必须查看EXPLAIN
- 新增索引后必须同时测试读写性能
- 重复与高度重叠索引必须定期审查
- 大表创建索引必须评估生产上线风险

这样 Codex 在处理数据库性能问题时,就不会把:

加索引

当成唯一答案。


二十四、Plus还是Pro?

如果主要使用 Codex 处理:

  • 单条慢SQL;

  • 普通联合索引;

  • 中小型数据库;

  • 简单 EXPLAIN;

Plus 通常已经能够覆盖大多数开发需求。

如果长期处理:

  • 大型生产数据库;

  • 数千万级数据表;

  • 大量慢查询;

  • 多模块 ORM 与原生 SQL;

  • 索引 Migration;

  • 长时间执行计划和压测分析;

可以根据实际开发强度评估 Pro。

对于大型数据库项目,更连续的代码、SQL、Migration 和测试分析会更重要。

但无论使用哪个方案,索引优化都应该遵循同一个原则:

索引是为真实查询模式服务的,而不是数量越多越好。

总结

Codex 给数据库不断增加索引后,为什么系统反而越来越慢?

因为每一条索引都需要数据库长期维护。

它可能提升:

SELECT

却同时拖慢:

INSERT
UPDATE
DELETE

并增加存储与缓存压力。

真正合理的索引优化应该结合:

真实SQL
联合索引
字段选择性
覆盖索引
执行计划
写入成本

一起判断。

不要只问:

这个字段有没有索引?

更应该问:

这个索引解决了哪条高频SQL?它真正被使用了吗?为了它,系统又付出了多少写入成本?

只有这三个问题都能回答,索引才算真正有价值。

CSDN文章描述

本文介绍 Codex 优化 MySQL、PostgreSQL 查询时常见的索引滥用问题,并通过联合索引、覆盖索引、字段选择性、EXPLAIN 和写放大分析,在查询性能与写入成本之间取得平衡。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐