微服务架构落地:慢 SQL 治理与连接池调优 —— 守住数据库的最后一道防线

大宗商品销采运储系统・六层架构连载(数据持久层)

上一篇我们通过冷热分离,把历史数据这个"包袱"甩掉了,让热库的查询重新跑得飞快。

但很多时候,拖垮数据库的并不是数据量有多大,而是某几个业务人员跑出的"烂 SQL"。

在大宗商品系统里,动辄几百个微服务、几千个接口,只要有一个接口写了全表扫描,或者发生了隐式类型转换,它就能在瞬间吃光数据库的 CPU,把连接池打满,导致其他几百个正常接口全部超时。

这就是典型的"一颗老鼠屎坏了一锅粥"。

架构做得再好,分库分表再牛,也顶不住一条烂 SQL 的降维打击。

一句话定调:

慢 SQL 治理不靠"事后救火",靠的是"事前监控、事中阻断、事后复盘"的闭环机制。


一、大宗商品系统里,慢 SQL 都是从哪来的?

排查了无数个线上故障,大宗商品的慢 SQL 重灾区永远集中在以下三个地方:

1. 深分页与全表扫描

  • 反面教材 :SELECT * FROM t_order WHERE customer_name LIKE '%西安XX%';

  • 死因 :前置 % 导致索引失效,千万级大表直接全表扫描。或者深分页 LIMIT 1000000, 10,导致 MySQL 扫描大量无用数据并频繁回表。

2. 隐式类型转换

  • 反面教材 :order_no 是 VARCHAR(32) 类型,但代码里传了数字类型 WHERE order_no = 12345678;

  • 死因:MySQL 会把字符串转成数字比较,直接导致 B+ 树索引失效,变成全表扫描。

3. 大表 JOIN 与复杂统计

  • 反面教材 :SELECT ... FROM t_order o LEFT JOIN t_order_detail d ON o.id = d.order_id LEFT JOIN t_customer c ... WHERE ... GROUP BY ... ORDER BY ...

  • 死因:多张大表 Join,如果在被驱动表上没有合适的索引,会产生极其可怕的笛卡尔积,直接吃满内存(Using temporary / Using filesort)。


二、事前监控:让慢 SQL 无处遁形

不能等业务投诉系统卡了再去抓,必须让慢 SQL 自动报警。

1. MySQL 慢查询日志(基础防线)

在 MySQL 配置中强制开启慢查询日志:

复制代码
[mysqld]# 开启慢查询日志slow_query_log = 1# 慢查询日志文件路径slow_query_log_file = /var/log/mysql/slow.log# 阈值:超过 1 秒记录(大宗商品核心交易建议设置为 0.5 秒)long_query_time = 1# 记录没有使用索引的 SQLlog_queries_not_using_indexes = 1# 记录管理语句(如 ALTER TABLE)log_slow_admin_statements = 1

2. Prometheus + MySQL Exporter(告警闭环)

结合公共中间件层的可观测体系,使用 mysqld_exporter 采集慢查询指标,配置 Alertmanager 告警规则:

复制代码
- alert: MySQLSlowQueriesHigh  expr: rate(mysql_global_status_slow_queries[5m]) > 5  for: 2m  labels:    severity: P1  annotations:    summary: "MySQL 慢查询突增(当前每秒 {{ $value }} 条)"

3. Druid 连接池监控(应用侧防线)

如果使用 Spring Boot + Druid,开启内置的监控面板,可以实时看到应用发出的 SQL 执行时间、并发数、执行计划。


三、事中诊断:用 EXPLAIN 一眼看穿死穴

抓到慢 SQL 后,第一件事就是加 EXPLAIN,看执行计划。

复制代码
EXPLAIN SELECT * FROM t_sale_order WHERE status = 1 AND customer_name = '西安XX贸易';

核心字段解读(避坑指南):

字段 警告标志 含义与解法
type ALL, index 表示全表扫描。必须优化到 ref(非唯一索引)、range(范围查询)或 eq_ref(主键/唯一索引)。
key NULL 表示没走索引。检查是否没建索引,或发生了索引失效(如前置%,隐式转换)。
rows 数值极大 预计扫描的行数。如果几百万行,绝对是灾难。
Extra Using filesort 表示需要额外排序。应通过建立合适的联合索引来消除排序。
Using temporary 表示使用了临时表(常见于 GROUP BY 或 DISTINCT)。极易撑爆内存。

四、事后复盘:SQL 优化的三板斧

第一斧:索引优化(最有效)

  • 联合索引的最左前缀原则 :如果建了 (status, create_time, customer_id) 索引,查询时条件必须按顺序带头使用 status。

  • 覆盖索引 :如果查询只需要主键和状态,尽量 SELECT id, status,避免 SELECT *,可以直接在索引树上取数据,无需"回表"查主键。

第二斧:SQL 改写

  • 禁止 SELECT *。

  • 避免在索引列上做函数计算 :WHERE DATE(create_time) = '2026-10-10' 会让索引失效。必须改写为 WHERE create_time >= '2026-10-10 00:00:00' AND create_time <= '2026-10-10 23:59:59'。

  • 小表驱动大表:在 Join 时,尽量用数据量小的表作为驱动表。

第三斧:业务妥协(架构兜底)

  • 如果实在优化不了(比如几十万行数据的实时多维度汇总),不要死磕 MySQL。利用我们在中间件层讲的 Elasticsearch 做异构检索,或者通过调度服务做 T+1 离线预计算。

五、终极防御:HikariCP 连接池调优

很多时候,数据库其实没死,只是几个慢 SQL 把应用端(Java)的连接池打满了,导致后续的所有线程都在 Connection Timeout 排队等待。

Spring Boot 默认使用 HikariCP,大宗商品高并发场景下的标准配置:

复制代码
spring:  datasource:    hikari:      minimum-idle: 10      maximum-pool-size: 50         # 核心公式:(CPU核心数 * 2) + 有效磁盘数      idle-timeout: 600000          # 空闲连接超时时间(10分钟)      max-lifetime: 1800000         # 连接最大存活时间(30分钟)      connection-timeout: 3000      # 获取连接超时时间(3秒,超时直接报错,防止拖死线程)      validation-timeout: 1000      # 连接有效性检测      connection-test-query: SELECT 1

⚠️ 连接池防雪崩核心机制

如果某接口发生慢 SQL,占满 50 个连接,后面的请求会在 3 秒后 connection-timeout 报错。

必须配合 Sentinel 做限流降级 :

针对已知会慢的报表接口,单独限制并发(比如只允许 5 个并发查库),防止它把整个连接池抢光。

复制代码
@SentinelResource(value = "exportOrderReport", blockHandler = "handleBlock")
public byte[] exportReport(ParamDTO dto) {
    // 大查询...
}

六、本篇小结

慢 SQL 治理是一场持久战。

记住四句话:

  1. 监控先行:开启慢查询日志,配好 Prometheus 告警。

  2. 诊断靠 Explain :关注 type、key 和 Extra,杜绝全表扫描。

  3. 优化三板斧:建联合索引、改写函数计算、消灭隐式转换。

  4. 连接池要限流:HikariCP 设好超时,Sentinel 兜底防雪崩。

下篇预告:AI 赋能 DBA:SQL 智能改写、索引推荐与故障根因分析。探讨如何利用大模型,把以前必须靠资深 DBA 人工看的慢 SQL 日志,变成自动化的优化建议?

相关推荐
m0_639906941 小时前
SpringBoot 实现大模型统一调度网关|对接 OpenAI、Claude 协议差异与适配方案
java·spring boot·spring
IvorySQL1 小时前
PostgreSQL 日报|子表 CHECK 约束恢复标记错误(11 月 2 日)
数据库·postgresql
顧棟1 小时前
OpenAPI 网关:鉴权 + 流量治理 一体化设计方案与实现
java·spring boot·设计模式
你看我名字2 小时前
Redis 高可用、持久化与故障切换:从缓存组件到云上基础设施
数据库·redis·缓存
真上帝的左手2 小时前
2. 编程语言-Java-Spring Boot-JAR
java·spring boot·jar
风翼靓崽2 小时前
centos7以mysql为例自建yum源
数据库·mysql·adb
码农阿豪2 小时前
MongoDB迁移到金仓数据库:商品属性继续灵活存,订单报表交给 SQL
数据库·sql·mongodb
秋饼2 小时前
Redis 全面解析:从数据结构到主从/哨兵/集群、分布式锁与面试高频考点,代码到原理一次讲透
数据库·redis·分布式·面试·后端开发