前言

接口变慢是最常见的线上故障之一。面试官通常不会只问“怎么优化 SQL”,而是会追问:用户反馈接口慢,你怎么判断慢在哪里?如何证明是数据库慢、缓存慢、线程池满、网络慢,还是下游服务慢?生产上怎么止血?

典型现象

  • 单个接口 RT 从 200ms 上升到 3s 以上。
  • P99 延迟升高,但平均耗时变化不明显。
  • 只有部分用户、部分租户、部分区域慢。
  • 应用 CPU 不高,但请求大量超时。
  • 数据库慢查询增加,或者连接池等待时间变长。

排查接口慢不能只看一次请求,要同时看平均值、P95、P99、错误率、吞吐量和依赖调用耗时。

排查总链路

flowchart TD
    A[用户反馈接口慢] --> B[确认范围]
    B --> C[查看网关和APM]
    C --> D[拆分接口耗时]
    D --> E[应用内部逻辑]
    D --> F[数据库]
    D --> G[缓存]
    D --> H[MQ或第三方接口]
    E --> I[线程池/锁/CPU]
    F --> J[执行计划/索引/锁等待]
    G --> K[热Key/大Key/网络抖动]
    H --> L[超时/重试/熔断]
    J --> M[止血与长期优化]
    K --> M
    L --> M

第一步:确认慢的范围

先回答几个问题:

  • 是所有接口慢,还是某个接口慢?
  • 是所有实例慢,还是某台机器慢?
  • 是所有用户慢,还是某类参数慢?
  • 是刚发布后慢,还是流量突然升高后慢?
  • 是读接口慢,还是写接口慢?

范围判断决定排查方向。所有接口都慢,优先看机器资源、网关、线程池、数据库连接池、下游公共依赖。单接口慢,优先看代码逻辑、SQL、缓存和参数分布。

第二步:用链路追踪拆耗时

生产环境通常会接入 SkyWalking、Pinpoint、Zipkin、Jaeger、CAT 或 OpenTelemetry。看链路时重点关注:

  • 网关耗时。
  • Controller 到 Service 的业务耗时。
  • SQL 执行耗时。
  • Redis 调用耗时。
  • HTTP/RPC 下游调用耗时。
  • 重试次数和异常分支。

如果链路显示 SQL 只用了 30ms,但接口耗时 3s,就不能把问题甩给数据库。可能是应用内循环调用、远程接口慢、线程池排队、锁等待或序列化耗时。

第三步:检查应用日志

接口日志建议带上:

  • traceId。
  • userId 或 tenantId。
  • 请求参数摘要。
  • 各阶段耗时。
  • 异常码和下游错误码。

生产中不要只打印入口和出口日志。复杂接口要记录关键阶段耗时,比如参数校验、权限校验、查主表、查明细、查缓存、调用第三方、组装返回值。

常见日志结论:

  • 日志入口很快,出口很慢:应用内部或下游慢。
  • 入口日志都很少:请求可能卡在网关、负载均衡或连接层。
  • 同一 traceId 多次重试:重试策略可能放大故障。
  • 某类参数特别慢:索引选择、数据倾斜或业务分支异常。

第四步:慢 SQL 定位

数据库方向重点看:

  • 慢查询日志。
  • SQL 执行计划。
  • 索引命中情况。
  • 返回行数和扫描行数。
  • 锁等待。
  • 临时表和文件排序。
  • 分页是否过深。

MySQL 常用排查点:

1
2
3
EXPLAIN SELECT * FROM order_info WHERE user_id = ? ORDER BY create_time DESC LIMIT 20;
SHOW FULL PROCESSLIST;
SHOW ENGINE INNODB STATUS;

执行计划重点看 typekeyrowsExtra。如果出现全表扫描、Using filesort、Using temporary,就要结合数据量判断是否需要联合索引、覆盖索引或改写 SQL。

常见慢 SQL 根因

根因 表现 处理方式
索引缺失 扫描行数大 增加合适索引
索引失效 有索引但未命中 避免函数、隐式转换、前置模糊匹配
分页过深 limit offset 很大 游标分页、搜索后翻页
返回字段过多 网络和反序列化慢 只查必要字段
大事务 锁等待和 undo 增长 拆小事务
数据倾斜 少数用户数据极多 分库分表、冷热分离、异步汇总

第五步:检查缓存

缓存慢不一定是 Redis 本身慢,也可能是应用访问模式有问题:

  • 缓存未命中率突然升高。
  • 热 Key 被集中访问。
  • 大 Key 读写导致网络和序列化耗时。
  • 缓存穿透导致数据库压力上升。
  • Redis 连接池耗尽。

如果接口依赖多个 Redis Key,要注意是否存在循环调用。例如列表 100 条数据,每条都查一次 Redis 和一次 DB,这种 N+1 调用在压测低流量时不明显,线上数据量上来后会放大。

第六步:检查线程池和连接池

很多接口变慢不是业务逻辑慢,而是在排队。

需要关注:

  • Tomcat 工作线程是否打满。
  • 业务线程池队列是否堆积。
  • 数据库连接池 active、idle、wait。
  • Redis 连接池等待时间。
  • HTTP 客户端连接池是否耗尽。

典型链路是:慢 SQL 增多 -> DB 连接被长期占用 -> 请求线程阻塞等待连接 -> Tomcat 线程打满 -> 所有接口变慢。

第七步:第三方依赖和重试

支付、短信、物流、文件服务等第三方接口经常是接口慢的根因。排查时要看:

  • 调用超时时间是否过长。
  • 是否同步调用了非核心依赖。
  • 是否有多次重试。
  • 是否缺少熔断和降级。
  • 是否把第三方失败拖进主事务。

生产上,非核心能力尽量异步化。比如下单成功后的短信通知、站内信、积分发放,可以放到 MQ 消费,不要阻塞主链路。

止血方案

  • 打开降级开关,关闭非核心查询或第三方调用。
  • 临时扩容应用实例和数据库只读实例。
  • 对慢接口增加限流,保护核心链路。
  • 对热点数据增加本地缓存或预计算。
  • 回滚最近发布版本。
  • 针对慢 SQL 加临时索引,但要评估建索引对线上写入的影响。

长期优化

  • 接口分层打点,沉淀统一耗时日志。
  • 接入链路追踪和指标监控。
  • 建立慢 SQL 审核机制。
  • 核心接口做压测和容量基线。
  • 对高频查询做缓存、预聚合或读模型。
  • 对第三方依赖设置合理超时、隔离和熔断。

高频面试题

用户说接口慢,你第一步做什么?

先确认范围和影响面:哪个接口、哪个时间段、哪些用户、哪些实例、错误率和 P99 是否升高。然后通过链路追踪拆分耗时,定位慢在网关、应用、数据库、缓存还是下游。

慢 SQL 怎么排查?

先看慢查询日志和执行计划,再看索引命中、扫描行数、排序方式、锁等待和返回数据量。不能只凭 SQL 文本判断,要结合表数据量、参数分布和执行计划。

接口慢但数据库不慢怎么办?

继续看应用内部耗时、线程池排队、锁竞争、Redis 调用、第三方接口、序列化和网络。数据库不慢只说明一个依赖没问题,不能说明接口本身没问题。

总结

接口变慢排查的核心是拆链路、看数据、定范围。面试回答要体现生产思维:先止血,再定位,再修复,最后沉淀监控和容量基线。