🗄️ 数据库调优

数据库通常是整个系统链路的终极瓶颈——应用可以水平扩展,但数据库的扩展要复杂得多。 本指南涵盖执行计划分析、索引优化、连接池调优、读写分离架构及慢查询治理五大核心领域。

1. 执行计划分析(EXPLAIN)

执行计划是SQL调优的核心工具。通过 EXPLAIN 了解优化器的决策过程, 判断是否走索引、扫描行数、连接方式等关键信息。

📊 EXPLAIN 关键字段速查(MySQL)

字段含义理想值危险信号
idSELECT 序号多个不同id的复杂子查询
select_type查询类型SIMPLEDEPENDENT SUBQUERY、UNCACHEABLE SUBQUERY
type访问类型(最重要)const > eq_ref > ref > rangeALL(全表扫描)、index(全索引扫描)
possible_keys可能使用的索引不为NULLNULL(无可用索引)
key实际使用的索引不为NULLNULL(未使用索引)
key_len使用索引的长度越短越好过长(复合索引使用不全)
rows预估扫描行数越小越好数万/数十万(扫描过多)
Extra额外信息Using index(覆盖索引)Using filesort、Using temporary
⚠️ type 字段等级排序(优→劣) system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
目标:至少达到 range 级别,尽量避免 ALL 和 index。

1.1 执行计划实战示例

🔍 慢SQL分析实例

-- 原始慢SQL
SELECT o.*, u.name FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.create_time > '2024-01-01' AND o.status = 'PAID'
ORDER BY o.create_time DESC LIMIT 20;

-- EXPLAIN 结果分析
+----+------+---------------+------+---------+------+-------+-----------------------------+
| id | type | possible_keys | key  | key_len | rows  | Extra                           |
+----+------+---------------+------+---------+-------+-----------------------------+
|  1 | ALL  | NULL          | NULL | NULL    | 500万 | Using where; Using filesort    |
|  1 | ref  | idx_user_id   | idx  | 8       | 1     | Using where                     |
+----+------+---------------+------+---------+-------+-----------------------------+

-- 问题:orders 表全表扫描 500万行 + filesort
-- 解决:创建复合索引
ALTER TABLE orders ADD INDEX idx_status_time (status, create_time);

-- 优化后 type=range, rows<100

2. 索引优化策略

2.1 索引设计原则

📐 索引设计十大原则

  1. 最左前缀原则:复合索引从最左列开始匹配,WHERE a=? AND b=? 可用 INDEX(a,b),但 WHERE b=? 不行。
  2. 高选择性优先:区分度高的列放前面。区分度 = COUNT(DISTINCT col)/COUNT(*),越接近1越好。
  3. 覆盖索引:查询列全部在索引中时,无需回表(Extra: Using index),性能提升显著。
  4. 避免冗余索引INDEX(a) + INDEX(a,b) 中前者是冗余的,MySQL优化器可以只用后者。
  5. 避免在索引列上使用函数WHERE DATE(create_time) = '2024-01-01' 不走索引,应写为 WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02'
  6. LIKE 前缀匹配LIKE 'abc%' 走索引,LIKE '%abc' 不走索引。
  7. 隐式类型转换:字符串列用数字查询(WHERE phone=13800138000)不走索引,须加引号。
  8. OR 条件:每个OR分支都需要有索引,否则全表扫描。建议改用 UNION ALL。
  9. 不要过度索引:索引占用空间、拖慢写入。一个表的索引数建议控制在 5 个以内
  10. 定期维护ANALYZE TABLE 更新统计信息,OPTIMIZE TABLE 整理碎片。

2.2 常见SQL性能问题速查

🩺 SQL 性能问题排查表

问题类型典型症状根因EXPLAIN信号解决方案
全表扫描大表查询慢、CPU高无可用索引type=ALL, rows=大数创建合适索引
回表过多索引扫描快但查询慢非覆盖索引+大量回表type=index/range, Extra无Using index覆盖索引或分页优化
索引失效有索引但不走函数/类型转换/ORpossible_keys有值但key=NULL改写SQL消除函数/转换
filesort排序慢、temp表写磁盘ORDER BY列无索引Extra=Using filesort创建排序索引
临时表GROUP BY/DISTINCT慢分组列无索引Extra=Using temporary创建分组索引
深分页OFFSET很大时变慢扫描+丢弃大量行rows随OFFSET线性增长游标分页/延迟关联
锁等待写入阻塞、超时长事务/间隙锁缩短事务、优化索引减少锁范围
Join顺序不当多表Join极慢驱动表太大rows乘积巨大STRAIGHT_JOIN指定驱动表
子查询低效IN子查询非常慢DEPENDENT SUBQUERYselect_type=DEPENDENT SUBQUERY改写为JOIN

3. 连接池配置调优

数据库连接是昂贵的资源,连接池的合理配置直接决定了系统的并发能力。 注意:连接数不是越大越好——每个连接都会在MySQL服务端占用线程和内存。

🔌 连接池参数最佳实践

参数DruidHikariCP推荐值调优策略
最大连接数maxActivemaximumPoolSizeCPU核数*2+磁盘数公式:connections = ((core_count * 2) + effective_spindle_count)
最小空闲连接minIdleminimumIdle同最大连接数HikariCP建议与最大值相同,避免突发创建连接
连接超时maxWaitconnectionTimeout1~3s不可设置过大,避免请求堆积
空闲超时minEvictableIdleTimeMillisidleTimeout10min小于MySQL的 wait_timeout(默认8h)
连接存活时间maxAge / timeBetweenEvictionRunsMillismaxLifetime30min小于MySQL的 wait_timeout 且小于基础设施连接超时
连接验证validationQueryconnectionTestQuerySELECT 1HikariCP默认用JDBC4的isValid,无需配置
泄漏检测removeAbandonedleakDetectionThreshold60s超过此时间未归还则告警/强制回收
💡 连接数瓶颈排查
  • MySQL 端:SHOW PROCESSLISTSHOW STATUS LIKE 'Threads_connected'
  • 应用端:HikariCP 的 /actuator/prometheus 暴露 hikaricp_connections_active
  • 典型症状:应用日志出现 Connection is not available, request timed out after Xms

4. 读写分离架构

当单库成为瓶颈时,读写分离是最常用的水平扩展手段。核心思路是将读流量分流到从库, 主库专注于写入操作。

🏗️ 读写分离架构方案对比

方案实现方式优点缺点适用场景
客户端路由 ShardingSphere-JDBC、MyBatis插件 无额外网络开销、配置简单 语言绑定、升级需重启应用 中小规模、Java技术栈
中间件代理 ProxySQL、MySQL Router、ShardingSphere-Proxy 对应用透明、多语言支持 增加一跳网络延迟、运维成本高 大规模、异构技术栈
DB Mesh Sidecar 模式 云原生、治理能力强大 架构复杂、学习成本高 Service Mesh 环境

4.1 读写分离关键问题

⚠️ 主从延迟处理策略

读写分离最大的挑战是主从复制延迟(通常几百ms到几秒)。写后立刻读可能读到旧数据。

  • 强制读主库:关键业务(如支付后查订单状态)写操作后指定时间段内读主库
  • 延迟阈值路由:从库延迟超过N秒后,自动摘除该从库
  • 等GTID:写操作获取GTID,读操作等待从库追上该GTID
  • 最终一致性容忍:对于列表页、推荐等场景,接受短暂不一致

5. 慢查询治理体系

5.1 慢查询治理流程

📋 慢查询治理四阶段

阶段目标手段工具
1. 发现 被动发现 + 主动监控 慢查询日志(slow_query_log)、APM告警 slow_query_log、pt-query-digest、Prometheus
2. 分析 定位根因、评估影响 EXPLAIN分析、索引使用率、锁等待 EXPLAIN、SHOW PROFILE、sys.schema_*
3. 优化 SQL改写、索引调整、架构变更 加索引、拆分大查询、去除非必要JOIN 数据库管理工具、代码Review
4. 防护 上线前拦截、容量规划 SQL审核平台、索引自动推荐、压测基线 Yearning、Archery、SOP

5.2 慢查询日志配置

⚙️ MySQL 慢查询配置最佳实践

-- 开启慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';

-- 设置阈值(超过1秒计入慢查询)
SET GLOBAL long_query_time = 1;

-- 记录未使用索引的查询(开发环境开启,生产慎用)
SET GLOBAL log_queries_not_using_indexes = OFF;

-- 记录管理语句
SET GLOBAL log_slow_admin_statements = ON;

-- 慢查询分析工具
-- pt-query-digest /var/log/mysql/slow.log > slow_report.txt

-- 查询Top N慢查询
SELECT * FROM mysql.slow_log ORDER BY query_time DESC LIMIT 10;
📌 数据库调优黄金法则
  1. 80%的性能问题来自索引不当——先优化SQL和索引,再考虑硬件升级
  2. 不要在生产环境直接执行 ALTER TABLE——使用 pt-online-schema-change 或 gh-ost
  3. 连接池大小不是越大越好——MySQL建议单实例总连接数控制在2000以内
  4. 读写分离不是银弹——先确认读流量是否真的占大多数,再看主从延迟是否可接受