三、数据库层面:优化数据交互的「最后一公里」
数据库是接口性能的「重灾区」—— 超过 60% 的接口响应慢问题可追溯至低效的数据交互。优化需从「SQL 执行效率」「索引设计」「连接管理」三个维度突破。
- SQL 优化:让查询「少走弯路」
核心原则:减少无效数据扫描,让数据库「只做必要的工作」。
常见问题与优化:
-
** 避免 SELECT ***:
问题:返回冗余字段,增加数据传输量,无法利用覆盖索引。
优化:明确指定需要的字段,如SELECT id, name, create_time FROM user WHERE status = 1
。 -
通过 EXPLAIN 分析 SQL:
关键指标:- type:查询类型(从优到差:system > const > eq_ref > ref > range > index > all),all 表示全表扫描(需优化);
- key:实际使用的索引(为 NULL 表示未走索引);
- rows:预估扫描的行数(越小越好)。
-
拆分复杂多表关联:
-- 低效:复杂多表关联 SELECT o.id, o.amount, u.name, p.title FROM order o JOIN user u ON o.user_id = u.id JOIN product p ON o.product_id = p.id WHERE o.status = 1 AND o.create_time > '2023-01-01';
优化为:
- 先查订单表:
SELECT id, user_id, product_id, amount FROM order WHERE status = 1 AND create_time > '2023-01-01'
; - 提取 user_id 和 product_id,批量查用户表和商品表;
- 在应用层用代码合并结果(如用 Map 缓存)。
- 先查订单表:
-
批量操作替代循环单条操作:
INSERT INTO order (user_id, amount) VALUES (1, 100), (2, 200), ..., (1000, 50); -- 批量插入1000条
- 索引设计:平衡查询与写入效率
索引是「加速查询的利器」,但并非越多越好 —— 过多索引会导致写入操作变慢(需维护索引结构)。
索引设计原则:
-
针对 WHERE、ORDER BY、JOIN 字段建索引:
如WHERE status = 1 AND create_time > '2023-01-01'
可建(status, create_time)
联合索引。 -
遵循最左前缀原则:
联合索引(a, b, c)
可匹配:a = ?
(最左前缀);a = ? AND b = ?
;a = ? AND b = ? AND c = ?
;
但无法匹配b = ?
或b = ? AND c = ?
。
-
避免冗余索引:
已存在联合索引(a, b)
时,单独的a
索引是冗余的;索引(a, b)
和(a, b, c)
中,前者是后者的前缀,若后者存在,前者可删除(除非需利用前者的覆盖索引)。
- 连接池优化:避免「连接争抢」
数据库连接是「稀缺资源」,配置不当会导致连接数不足(请求等待超时)或连接闲置(资源浪费)。
以 HikariCP 为例,核心参数配置:
参数 | 作用 | 推荐配置 |
---|---|---|
minimumIdle | 最小空闲连接数 | 核心业务并发量的 1/3(如并发 30 则设为 10) |
maximumPoolSize | 最大连接数 | 50-80(不超过数据库允许的最大连接数) |
connectionTimeout | 获取连接的超时时间 | 3-5 秒 |
idleTimeout | 空闲连接超时时间 | 300 秒(5 分钟) |
maxLifetime | 连接最大生命周期 | 1800 秒(30 分钟,小于数据库 wait_timeout) |
监控连接池状态:
通过 Spring Boot Actuator 暴露 HikariCP 指标:
management:
endpoints:
web:
exposure:
include: hikaricp
访问http://localhost:8080/actuator/hikaricp
查看连接使用率、等待队列长度等,若等待队列持续增长,需调大 maximumPoolSize。
四、框架与中间件:让「配置」适配业务
- 缓存优化:减轻数据库压力
缓存是「抗高并发的利器」,但需解决「穿透、击穿、雪崩」三大问题。
-
缓存穿透:查询不存在的数据,缓存失效,直击数据库。
解决方案:- 缓存空值(设置短期过期时间);
- 布隆过滤器:提前过滤不存在的 key,如用 Redis 的 BF.ADD 和 BF.EXISTS 命令。
-
缓存击穿:热点数据过期瞬间,大量请求直击数据库。
解决方案:- 热点数据永不过期,后台定时任务异步更新;
- 互斥锁:用 Redis 的 SETNX 加锁,只允许一个线程查数据库,其他线程等待重试。
-
缓存雪崩:大量缓存同时过期,请求全部直击数据库。
解决方案:- 过期时间加随机值(如 30 分钟 ±5 分钟),避免同时过期;
- 多级缓存:本地缓存(如 Caffeine)+ 分布式缓存(如 Redis),设置不同过期时间。
- 线程池调优:提升并发处理能力
线程池是「并发处理的核心」,合理配置可最大化利用 CPU 和 IO 资源。
-
Tomcat 线程池(处理 HTTP 请求):
server: tomcat: threads: max: 200 # 最大线程数(默认200) min-spare: 10 # 核心线程数(默认10) accept-count: 100 # 等待队列长度(默认100) connection-timeout: 30000 # 连接超时时间(默认60000ms)
配置原则:
- IO 密集型任务:max-threads 设为 CPU 核心数 ×2+1;
- CPU 密集型任务:max-threads 设为 CPU 核心数 + 1。
-
@Async 线程池(处理异步任务):
需自定义线程池(避免默认线程池无上限导致 OOM):@Configuration @EnableAsync public class AsyncConfig { @Bean(name = "asyncExecutor") public Executor asyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); // 核心线程数 executor.setMaxPoolSize(20); // 最大线程数 executor.setQueueCapacity(1000); // 队列容量 executor.setKeepAliveSeconds(60); // 线程空闲时间 executor.setThreadNamePrefix("async-task-"); // 线程名前缀 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略 executor.initialize(); return executor; } }