性能优化实战:一次压测把接口从 3 秒优化到 80ms
双十一前夕压测,发现商品列表接口耗时 3 秒。以下是完整优化过程。
定位瓶颈
SQL
-- 先用 EXPLAIN ANALYZE 定位慢查询
EXPLAIN ANALYZE
SELECT p.*, c.name as category_name,
COUNT(r.id) as review_count
FROM products p
LEFT JOIN categories c ON p.category_id = c.id
LEFT JOIN reviews r ON r.product_id = p.id
WHERE p.status = 'active'
GROUP BY p.id, c.name
ORDER BY p.created_at DESC
LIMIT 20;结果:
CODE
Planning Time: 0.5ms
Execution Time: 2850ms ← 问题在这里优化 1:添加索引
SQL
-- 覆盖索引,避免回表
CREATE INDEX idx_products_list
ON products(status, created_at DESC)
INCLUDE (name, price, image_url, category_id);
-- 外键索引
CREATE INDEX idx_reviews_product
ON reviews(product_id);优化后:2850ms → 1200ms
优化 2:避免 COUNT 子查询
SQL
-- 不好的写法:每行都做子查询
SELECT p.*,
(SELECT COUNT(*) FROM reviews WHERE product_id = p.id) as review_count
FROM products p
-- 好的写法:用 LEFT JOIN + GROUP BY
SELECT p.*, COUNT(r.id) as review_count
FROM products p
LEFT JOIN reviews r ON r.product_id = p.id
GROUP BY p.id优化 3:加缓存
TYPESCRIPT
// Redis 缓存热门商品列表
async function getProducts(page: number) {
const cacheKey = `products:list:${page}`
const cached = await redis.get(cacheKey)
if (cached) return JSON.parse(cached)
const products = await db.query(/* ... */)
await redis.set(cacheKey, JSON.stringify(products), 'EX', 60)
return products
}优化后:1200ms → 80ms
优化 4:分页优化
SQL
-- 不用 OFFSET,用游标分页
SELECT * FROM products
WHERE created_at < $cursor
ORDER BY created_at DESC
LIMIT 20;优化结果
| 步骤 | 优化手段 | 耗时 |
|------|---------|------|
| 原始 | 无优化 | 3000ms |
| 第 1 步 | 添加索引 | 1200ms |
| 第 2 步 | 优化 SQL | 800ms |
| 第 3 步 | Redis 缓存 | 80ms |
| 第 4 步 | 游标分页 | 60ms |
核心原则
1. 先测量,再优化 — EXPLAIN ANALYZE 是第一步
2. 索引优先 — 90% 的性能问题可以用索引解决
3. 缓存是银弹 — 但不适合实时性要求高的场景
4. 不要过早优化 — 先保证正确,再优化性能
读者评论 2