Codex写数据库索引为什么越加越慢?别让“优化查询”变成写入性能灾难
使用 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 和写放大分析,在查询性能与写入成本之间取得平衡。
更多推荐




所有评论(0)