一个未索引的列是如何搞垮我们的数据库的
一个缺失的索引,让原本 8 毫秒的 API 变成了 8 秒的噩梦。
在一次重大发布前,我们进行了压力测试。本地开发环境下,处理 100 行数据完全没问题。但在 staging 环境中,我们模拟了 500 名用户对 150 万行数据进行操作。
一切都崩溃了。
- API 响应时间从 45 毫秒飙升至 8,000 毫秒。
- 数据库 CPU 使用率达到 100%。
- 连接池耗尽。
- 请求超时。
罪魁祸首是一个简单的查询,它通过 user_id 和 status 来获取订单历史。
在 PostgreSQL 上运行 EXPLAIN ANALYZE 显示为全表扫描 (Sequential Scan)。由于 user_id 上没有索引,引擎在处理每个请求时都要读取全部 150 万行数据。
在 100 个并发请求下,数据库一次性扫描了 1.5 亿行数据。
修复只用了五分钟。我们使用 CREATE INDEX CONCURRENTLY 在 (user_id, status, created_at DESC) 上创建了一个复合索引,从而保证了表在创建索引期间仍处于在线状态。
结果:
- 查询时间从 8,150 毫秒降至 0.14 毫秒。
- API 延迟从 8 秒降至 12 毫秒。
- CPU 使用率从 100% 降至 8% 以下。
经验教训:
- 本地测试具有误导性;100 行数据无法代表百万级数据。
- 为外键建立索引——大多数 ORM 都会忽略这一点。
- 运行
EXPLAIN ANALYZE;数据库会直接指出问题所在。 - 在购买更多硬件之前,先优化查询。
不要先扩容服务器。先优化查询。
