← ClaudeAtlas

perf-apilisted

Use when optimizing 接口性能, N+1 SQL/RPC, 循环远程调用, Hikari 连接池, 批量写入, or 同步转异步. Do not use for GC (backend-jvm-prod), EXPLAIN (backend-mysql-index), or incident 排查顺序 (backend-observability).
1398281322-a11y/java-backend-guardrails · ★ 2 · API & Backend · score 73
Install: claude install-skill 1398281322-a11y/java-backend-guardrails
# 接口性能 ## When to Invoke 接口 RT 高、QPS 上不去、面试「怎么优化一个接口」「N+1」「连接池怎么设」「循环里调 Feign」。已经知道慢在 **业务代码路径**(多次 IO、池配错、同步多余活)。 ## When NOT 还没定位瓶颈 → `observability`(TraceId、P99、先测再改)。单条 SQL 慢 / EXPLAIN → `mysql-index`。Full GC / 堆 → `jvm-prod`。线程池无界 → `thread-pool`。Feign 超时数值 → `rpc-remote`。缓存击穿 → `cache-trap`。 ## 风险(面试考点) **先测后改。** 没有火焰图/Trace 就加缓存、加线程,是面试扣分项。 N+1:列表 20 条,循环里再查子表或调一次 Feign = 1+20 次网络。MyBatis `<collection select="...">` 默认就是 N+1。嵌套结果 JOIN 或 **两次查询 + 内存拼**(主列表 + `IN` 子表)次数固定。 循环 `insert` / `save`:MyBatis-Plus `saveBatch` 仍可能只是 JDBC batch;MySQL 要 `rewriteBatchedStatements=true` 才会合成多值 INSERT。`foreach` 一条超大 SQL 会撞 `max_allowed_packet`,按 200~1000 行切开。 线程池 200、Hikari **默认 10**:请求线程堵在 `getConnection`,报 `Connection is not available, request timed out`。Hikari 作者:**小池更好**,起点约 `(CPU核×2)+盘`(常 10~20),不是跟 Tomcat 线程数对齐。一个线程持有多连接时按 `Tn×(Cm-1)+1` 防死锁。 `maxLifetime` 必须比 MySQL `wait_timeout`(及中间 NAT/LB 超时)**至少短 30s**,否则池里是已死连接,表现为偶发 Communications link failure。 用户请求线程里做:发短信、算报表、写一堆日志 JSON、`SELECT *` 回 50 列、深 `offset` 翻页。用户不等的活丢 MQ。热路径日志不要 `toJson(huge)`。 ## 方案选型(轻量优先) | 症状 | 默认 | |------|------| | 循环查库 / 循环 Feign | 批量 `IN` / 批量 RPC,内存 Map 组装 | | 循环 insert | `saveBatch` + `rewriteBatchedStatements` + 分批 | | 获取连接超时 | 查泄漏(事务/连接未关);池不是先加到 100 | | 用户 RT 含非关键 IO | 同步只做成功路径,其余 MQ | | IN 列表过大 | 分批,不要上万 ID 一条 SQL | ```java List<Order> orders = orderMapper.selectPage(uid, cursor, 20); List<Long> ids = orders.stream().map(Order::getId).toList(); Map<Long, List<Item>> items = itemMapper.s