一个未索引的列是如何搞垮我们的数据库的

一个缺失的索引,让原本 8 毫秒的 API 变成了 8 秒的噩梦。

在一次重大发布前,我们进行了压力测试。本地开发环境下,处理 100 行数据完全没问题。但在 staging 环境中,我们模拟了 500 名用户对 150 万行数据进行操作。

一切都崩溃了。

  • API 响应时间从 45 毫秒飙升至 8,000 毫秒。
  • 数据库 CPU 使用率达到 100%。
  • 连接池耗尽。
  • 请求超时。

罪魁祸首是一个简单的查询,它通过 user_idstatus 来获取订单历史。

在 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;数据库会直接指出问题所在。
  • 在购买更多硬件之前,先优化查询。

不要先扩容服务器。先优化查询。

来源:https://dev.to/mia_keller_ffd2584c046ecb/how-an-unindexed-column-silently-killed-our-database-under-load-and-the-5-minute-fix-m32