mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
3073 字
8 分钟
数据库性能优化:从慢查询到系统调优
2024-08-10

线上接口 P99 延迟从 50ms 飙到 2s,数据库 CPU 打满,连接池耗尽——这是每个后端工程师都会遇到的噩梦。面对性能问题,最常见的反应是”加索引""加缓存""加机器”,但往往治标不治本:索引加了还是慢,缓存加了穿透更严重,机器加了发现瓶颈在锁。

索引原理中,理解了索引如何加速查询;在查询处理与优化中,我们读懂了优化器如何选择执行计划;在MySQL 深入Redis 深入中,掌握了具体数据库的实现细节。本章将把这些知识串联起来,建立一套系统性的性能优化方法论——从定位问题到解决问题,从 SQL 层到系统层,从应急响应到长效治理。

一、性能优化方法论#

1.1 性能优化金字塔#

性能优化不是随机尝试,而是有层次的系统性工程。越底层的优化收益越大、成本越低,越上层的优化越精细、越需要领域知识:

graph BT APP["应用层优化<br/>缓存策略 / 批量操作 / 异步化"] APP --> SQL["SQL 与索引优化<br/>慢查询 / 执行计划 / 索引设计"] SQL --> CONFIG["配置与参数调优<br/>连接池 / 缓冲池 / 日志策略"] CONFIG --> ARCH["架构层优化<br/>读写分离 / 分库分表 / 数据库选型"] style ARCH fill:#e3f2fd,stroke:#1565c0,stroke-width:2px style CONFIG fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px style SQL fill:#fff3e0,stroke:#e65100,stroke-width:2px style APP fill:#fce4ec,stroke:#c62828,stroke-width:2px
层级优化方向典型收益实施成本
架构层读写分离、分库分表、换数据库10x~100x高(涉及架构变更)
配置层参数调优、连接池、缓冲池2x~5x低(改配置即可)
SQL 层慢查询优化、索引设计5x~50x中(需理解业务)
应用层缓存、批量、异步3x~20x中(需改代码)
Tip

优化顺序应该是自底向上:先确保架构合理,再调配置,再优化 SQL,最后在应用层做精细化。一个架构不合理的系统,SQL 再怎么优化也无法突破天花板。

1.2 量化驱动优化#

性能优化的第一步不是改代码,而是建立基线、量化问题

没有量化 → 凭感觉优化 → 可能优化了不重要的路径
有了量化 → 精准定位瓶颈 → 每次优化都有据可循

量化优化的核心流程:

  1. 建立基线:记录当前 P50/P95/P99 延迟、QPS、资源利用率
  2. 定位瓶颈:通过监控和日志找到耗时最长的环节
  3. 假设验证:提出优化假设,实施后对比基线数据
  4. 回归测试:确保优化没有引入新问题
Warning

切忌”盲目优化”。Donald Knuth 的名言”过早优化是万恶之源”在数据库领域同样适用——在量化数据证明某个环节是瓶颈之前,不要投入精力优化它。

二、慢查询分析#

慢查询是数据库性能问题的最直接表现。系统性地捕获、分析和优化慢查询,是性能调优的第一步。

2.1 MySQL 慢查询日志#

MySQL 提供了内置的慢查询日志功能,记录执行时间超过阈值的 SQL:

-- 启用慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1; -- 超过 1 秒的查询
SET GLOBAL log_queries_not_using_indexes = ON; -- 记录未使用索引的查询
SET GLOBAL min_examined_row_limit = 100; -- 至少扫描 100 行才记录
-- 查看慢查询日志位置
SHOW VARIABLES LIKE 'slow_query_log_file';

慢查询日志输出示例:

22.123456Z
# User@Host: appuser[appuser] @ web-server [10.0.1.5]
# Query_time: 3.521400 Lock_time: 0.000120 Rows_sent: 1 Rows_examined: 2847293
SELECT * FROM orders WHERE status = 'pending' AND created_at > '2026-04-01' ORDER BY amount DESC LIMIT 10;

关键指标解读:

指标含义优化方向
Query_time查询总耗时关注是否可减少扫描行数
Lock_time等锁耗时关注是否存在锁竞争
Rows_examined扫描行数与 Rows_sent 的比值越大,优化空间越大
Rows_sent返回行数是否返回了过多不需要的数据

2.2 PostgreSQL pg_stat_statements#

PostgreSQL 的 pg_stat_statements 扩展提供了更结构化的查询统计:

-- 启用扩展
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- 查找最耗时的 Top 10 查询
SELECT query,
calls,
round(total_exec_time::numeric, 2) AS total_ms,
round(mean_exec_time::numeric, 2) AS avg_ms,
round((100 * total_exec_time / SUM(total_exec_time) OVER ())::numeric, 2) AS pct_total
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

2.3 pt-query-digest 分析工具#

Percona Toolkit 的 pt-query-digest 是分析 MySQL 慢查询日志的利器:

# 基本用法:分析慢查询日志
pt-query-digest /var/lib/mysql/mysql-slow.log
# 输出按总耗时排序的查询指纹
# Rank Query ID Response time Calls R/Call V/M
# ==== ================== ============== ====== ======= ====
# 1 0x3F8E1A2B4C5D6E7F 1200.1234 62.5% 3421 0.3508 0.01
# 2 0x7A8B9C0D1E2F3A4B 450.5678 23.5% 1205 0.3736 0.02
# 只分析最近 1 小时的慢查询
pt-query-digest --since '1h' /var/lib/mysql/mysql-slow.log
# 将结果保存到数据库
pt-query-digest --review h=localhost,D=percona,t=query_review \
/var/lib/mysql/mysql-slow.log

2.4 优化案例#

案例一:缺少索引导致全表扫描

-- 问题:扫描 280 万行,返回 10 行
SELECT * FROM orders
WHERE status = 'pending' AND created_at > '2026-04-01'
ORDER BY amount DESC LIMIT 10;
-- Query_time: 3.52s Rows_examined: 2847293 Rows_sent: 10
-- 分析:EXPLAIN 显示 type=ALL(全表扫描)
EXPLAIN SELECT * FROM orders WHERE status = 'pending' AND created_at > '2026-04-01';
-- 优化:添加联合索引(参考[索引原理](./03-索引原理.md)的最左前缀原则)
CREATE INDEX idx_status_created_amount ON orders(status, created_at, amount);
-- 优化后:索引范围扫描 + 覆盖索引
-- Query_time: 0.003s Rows_examined: 156 Rows_sent: 10

案例二:索引失效——隐式类型转换

-- 问题:phone 字段是 VARCHAR,但查询用了数字
SELECT * FROM users WHERE phone = 13800138000;
-- MySQL 对 phone 列做了隐式转换,导致索引失效
-- Query_time: 2.1s Rows_examined: 1500000
-- 优化:使用字符串常量
SELECT * FROM users WHERE phone = '13800138000';
-- Query_time: 0.002s Rows_examined: 1

案例三:N+1 查询问题

-- 问题:循环中执行单条查询,1000 次查询
-- 应用层伪代码:
-- for order in orders: # 1000 条
-- SELECT * FROM users WHERE id = order.user_id;
-- 优化:批量查询
SELECT * FROM users WHERE id IN (1, 2, 3, ..., 1000);
-- 更优:JOIN 查询
SELECT o.*, u.name, u.email
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = 'pending';
Note

索引失效的常见原因远不止隐式类型转换。在索引原理中详细分析了函数调用、OR 条件、LIKE 前缀通配符、最左前缀违反等场景,此处不再赘述。

三、连接池优化#

3.1 为什么需要连接池#

每次建立数据库连接都需要:TCP 三次握手 → SSL 协商 → 身份认证 → 会话初始化,整个过程可能耗时 10~50ms。如果每个请求都新建连接,高并发下连接建立本身就会成为瓶颈。

sequenceDiagram participant App as 应用程序 participant Pool as 连接池 participant DB as 数据库 Note over App,DB: 无连接池:每次请求新建连接 App->>DB: TCP 连接(10-50ms) App->>DB: 认证与会话初始化 App->>DB: 执行 SQL DB-->>App: 返回结果 App->>DB: 关闭连接 Note over App,DB: 有连接池:复用已有连接 App->>Pool: 获取连接(<1ms) Pool-->>App: 返回空闲连接 App->>DB: 执行 SQL(复用连接) DB-->>App: 返回结果 App->>Pool: 归还连接
维度无连接池有连接池
连接建立开销每次请求 10~50ms首次建立,后续 <1ms
并发连接数不可控,可能打满可控,由池大小限制
连接生命周期短连接,频繁创建/销毁长连接,复用
数据库压力高(频繁认证)低(连接复用)

3.2 HikariCP 配置#

HikariCP 是 Java 生态中性能最高的连接池,Spring Boot 2.x+ 默认使用:

application.yml
spring:
datasource:
hikari:
# 核心配置
maximum-pool-size: 20 # 最大连接数
minimum-idle: 5 # 最小空闲连接数
connection-timeout: 30000 # 获取连接超时(ms)
idle-timeout: 600000 # 空闲连接超时(ms)
max-lifetime: 1800000 # 连接最大存活时间(ms)
# 泄漏检测
leak-detection-threshold: 60000 # 连接泄漏检测阈值(ms)
# 连接验证
connection-test-query: SELECT 1 # 连接有效性检查(MySQL)
validation-timeout: 5000 # 验证超时(ms)

3.3 PgBouncer 配置#

PgBouncer 是 PostgreSQL 的高性能连接池,支持三种池化模式:

; pgbouncer.ini
[databases]
mydb = host=127.0.0.1 port=5432 dbname=mydb
[pgbouncer]
; 池化模式选择
pool_mode = transaction ; session / transaction / statement
; 连接数配置
max_client_conn = 1000 ; 最大客户端连接数
default_pool_size = 25 ; 每个数据库/用户对的默认池大小
min_pool_size = 5 ; 最小池大小
reserve_pool_size = 5 ; 预留池大小(突发流量)
reserve_pool_timeout = 3 ; 等待预留连接超时(秒)
; 超时配置
server_idle_timeout = 600 ; 服务端空闲连接超时(秒)
client_idle_timeout = 0 ; 客户端空闲超时(0=不超时)
query_timeout = 30 ; 查询超时(秒)
query_wait_timeout = 120 ; 等待服务端连接超时(秒)
; 日志
log_connections = 0
log_disconnections = 0
stats_period = 60

三种池化模式对比:

模式连接释放时机适用场景事务支持
session客户端断开需要会话状态(SET、PREPARE、临时表)完整
transaction事务结束大多数 Web 应用(推荐)完整
statement语句执行完无事务的简单查询不支持

3.4 连接数计算#

连接数不是越多越好。过多的连接会导致上下文切换开销增大、锁竞争加剧。经验公式:

最优连接数 = CPU 核心数 × (1 + 等待时间 / 计算时间)
示例:
- 8 核 CPU
- 查询计算时间 5ms,I/O 等待时间 45ms
- 最优连接数 = 8 × (1 + 45/5) = 80
Warning

上述公式是起点而非终点。实际连接数还需要根据连接池监控数据(活跃连接比、等待线程数)动态调整。如果活跃连接长期接近池大小上限,说明池太小;如果大部分连接空闲,说明池太大。

四、缓存策略#

缓存是应用层性能优化的核心手段。在Redis 深入中了解了 Redis 的内部实现,这里聚焦缓存策略的设计与实战。

4.1 缓存模式#

graph TB subgraph CacheAside["Cache-Aside(旁路缓存)"] CA_R["读请求"] --> CA_C{缓存命中?} CA_C -->|是| CA_RET["返回缓存数据"] CA_C -->|否| CA_DB["查询数据库"] CA_DB --> CA_WRITE["写入缓存"] CA_WRITE --> CA_RET2["返回数据"] end subgraph WriteThrough["Write-Through(写穿透)"] WT_W["写请求"] --> WT_C["更新缓存"] WT_C --> WT_DB["缓存同步写数据库"] end subgraph WriteBehind["Write-Behind(写回)"] WB_W["写请求"] --> WB_C["更新缓存"] WB_C --> WB_ASYNC["异步批量写数据库"] end style CacheAside fill:#e3f2fd,stroke:#1565c0 style WriteThrough fill:#e8f5e9,stroke:#2e7d32 style WriteBehind fill:#fff3e0,stroke:#e65100

三种缓存模式对比:

模式读路径写路径一致性性能适用场景
Cache-Aside先读缓存,miss 读 DB 后回填先更新 DB,再删缓存最终一致读快、写快通用场景(最常用)
Write-Through先读缓存,miss 读 DB 后回填先更新缓存,缓存同步写 DB强一致读快、写慢一致性要求高
Write-Behind先读缓存,miss 读 DB 后回填先更新缓存,异步批量写 DB最终一致读快、写最快写入密集、允许丢失

4.2 缓存三大问题#

缓存穿透:查询不存在的数据,缓存永远 miss,请求直达数据库。

# 解决方案一:布隆过滤器(推荐)
# 在缓存层前加一层布隆过滤器,不存在的 key 直接拦截
import pybloom_live
bloom = pybloom_live.ScalableBloomFilter(initial_capacity=1000000)
# 启动时加载所有合法 key
for user_id in db.query("SELECT id FROM users"):
bloom.add(f"user:{user_id}")
def get_user(user_id):
key = f"user:{user_id}"
if key not in bloom: # 布隆过滤器说不存在,直接返回
return None
return cache_aside_get(key) # 可能存在,走正常缓存流程
# 解决方案二:缓存空值(短 TTL)
def get_user_with_null_cache(user_id):
key = f"user:{user_id}"
data = redis.get(key)
if data is not None:
return None if data == "NULL" else deserialize(data)
user = db.query("SELECT * FROM users WHERE id = %s", user_id)
redis.setex(key, 300 if user is None else 3600,
"NULL" if user is None else serialize(user))
return user

缓存击穿:热点 key 过期瞬间,大量并发请求同时穿透到数据库。

# 解决方案:互斥锁(只允许一个请求回源)
import redis
r = redis.Redis()
def get_user_with_mutex(user_id):
key = f"user:{user_id}"
lock_key = f"lock:{key}"
data = r.get(key)
if data is not None:
return deserialize(data)
acquired = r.set(lock_key, "1", nx=True, ex=10) # 互斥锁,10 秒超时
if acquired:
try:
user = db.query("SELECT * FROM users WHERE id = %s", user_id)
r.setex(key, 3600, serialize(user))
return user
finally:
r.delete(lock_key)
else:
time.sleep(0.1)
return get_user_with_mutex(user_id) # 等待并重试

缓存雪崩:大量 key 同时过期,或缓存节点宕机,请求全部打到数据库。

# 解决方案一:随机过期时间
def set_with_jitter(key, value, base_ttl=3600, jitter_range=300):
jitter = random.randint(-jitter_range, jitter_range)
r.setex(key, base_ttl + jitter, value)
# 解决方案二:多级缓存(本地缓存 + Redis)
from cachetools import TTLCache
local_cache = TTLCache(maxsize=10000, ttl=60) # 本地缓存 60 秒
def get_user_multi_level(user_id):
key = f"user:{user_id}"
if key in local_cache: return local_cache[key] # L1
data = r.get(key)
if data is not None:
result = deserialize(data)
local_cache[key] = result
return result # L2
user = db.query("SELECT * FROM users WHERE id = %s", user_id)
r.setex(key, 3600, serialize(user))
local_cache[key] = user
return user # L3

三大问题对比总结:

问题触发条件核心危害解决方案
穿透查询不存在的数据恶意请求打垮 DB布隆过滤器 / 缓存空值
击穿热点 key 过期瞬时并发压垮 DB互斥锁 / 永不过期+异步刷新
雪崩大量 key 同时过期DB 瞬时负载飙升随机 TTL / 多级缓存 / 熔断降级

4.3 Redis 缓存实战#

结合Redis 深入中的数据结构知识,选择合适的缓存数据结构:

# String:简单 KV 缓存
SET user:1001 '{"name":"张三","age":28}' EX 3600
# Hash:对象缓存(比 String 更节省内存,支持部分读取)
HSET user:1001 name "张三" age 28 city "北京"
HGET user:1001 name
# ZSet:排行榜缓存(利用跳表的有序特性)
ZADD leaderboard 9500 "player:A" 8800 "player:B" 9200 "player:C"
ZREVRANGE leaderboard 0 9 WITHSCORES # Top 10
# Bitmap:用户签到(极致节省内存)
SETBIT sign:uid:1001:202604 20 1 # 4 月 20 日签到
BITCOUNT sign:uid:1001:202604 # 本月签到次数

五、参数调优#

数据库默认参数是通用场景的保守配置,针对具体负载调优参数可以获得显著性能提升。

5.1 MySQL 关键参数#

# my.cnf — MySQL 8.0 性能调优配置
[mysqld]
# ===== InnoDB 缓冲池 =====
# 核心参数:缓存数据和索引的内存区域
# 建议设为物理内存的 60%~80%(独占服务器)
innodb_buffer_pool_size = 8G
# 缓冲池实例数(减少锁竞争)
# 建议:每个实例 1G 以上
innodb_buffer_pool_instances = 8
# ===== I/O 能力 =====
# 每秒后台刷新的页数(SSD 建议 10000+,HDD 建议 200)
innodb_io_capacity = 10000
innodb_io_capacity_max = 20000
# 刷新邻接页(SSD 关闭,HDD 开启)
innodb_flush_neighbors = 0
# ===== 日志与持久化 =====
# binlog 刷盘策略
# 0=依赖 OS 刷盘 1=每次提交刷盘(最安全) N=每 N 次提交刷盘
sync_binlog = 1
# redo log 刷盘策略
# 1=每次提交刷盘(最安全) 2=每次提交写 OS 缓存,每秒刷盘
innodb_flush_log_at_trx_commit = 1
# ===== 并发与连接 =====
innodb_thread_concurrency = 0 # 0=不限制(推荐,InnoDB 自管理)
max_connections = 500
thread_cache_size = 100

MySQL 关键参数速查表:

参数默认值推荐值影响
innodb_buffer_pool_size128M物理内存 60%~80%最重要参数,直接影响缓存命中率
innodb_io_capacity200SSD: 10000+后台刷新速度,影响脏页刷盘
sync_binlog11(安全)/ 100(性能)binlog 持久性 vs 性能
innodb_flush_log_at_trx_commit11(安全)/ 2(折中)redo log 持久性 vs 性能
innodb_flush_neighbors1SSD: 0 / HDD: 1顺序写优化,SSD 无需
Note

innodb_flush_log_at_trx_commit = 2sync_binlog = 100 可以显著提升写入性能,但在操作系统崩溃时可能丢失 1 秒数据。在MySQL 深入中详细分析了 InnoDB 的 Doublewrite 和 Redo Log 机制,理解这些机制有助于做出正确的权衡。

5.2 PostgreSQL 关键参数#

# postgresql.conf — PostgreSQL 16 性能调优配置
# ===== 共享缓冲区 =====
# 数据库共享内存,缓存数据页
# 建议设为物理内存的 25%(不超过 40%)
shared_buffers = 4GB
# ===== 查询规划器缓存估计 =====
# 规划器假设可用于缓存的内存(不实际分配)
# 建议设为物理内存的 50%~75%
effective_cache_size = 12GB
# ===== 排序与哈希操作 =====
# 每个操作的最大内存(按连接分配,注意总内存)
work_mem = 64MB
# 维护操作内存(VACUUM、CREATE INDEX)
maintenance_work_mem = 1GB
# ===== WAL 配置 =====
# WAL 写入策略
# fsync = on(安全)/ off(危险但快)
fsync = on
synchronous_commit = on # on(安全)/ off(性能)
wal_buffers = 64MB
# ===== 检查点与自动清理 =====
max_wal_size = 4GB # WAL 最大大小
autovacuum = on
autovacuum_max_workers = 4

PostgreSQL 关键参数速查表:

参数默认值推荐值影响
shared_buffers128MB物理内存 25%数据页缓存,直接影响 I/O
effective_cache_size4GB物理内存 50%~75%影响规划器决策(不实际分配)
work_mem4MB32~256MB排序/哈希内存,影响磁盘排序
maintenance_work_mem64MB1GB+VACUUM/CREATE INDEX 速度
max_wal_size1GB2~8GBWAL 回收阈值,影响检查点频率

5.3 Linux 内核参数#

数据库性能不仅取决于数据库配置,还受操作系统参数影响:

/etc/sysctl.d/99-database.conf
# ===== 虚拟内存 =====
# 降低 swappiness,减少交换(数据库推荐 1~10)
vm.swappiness = 1
# 脏页刷新策略
vm.dirty_background_ratio = 5 # 后台刷新阈值(%)
vm.dirty_ratio = 10 # 强制刷新阈值(%)
# ===== 网络优化 =====
# TCP 连接队列
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65535
# TCP 优化
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_tw_reuse = 1
# ===== 文件描述符 =====
fs.file-max = 1000000
# 应用生效
sysctl -p /etc/sysctl.d/99-database.conf

MySQL vs PostgreSQL 参数调优对比:

调优维度MySQLPostgreSQL
数据缓存innodb_buffer_pool_size(独占)shared_buffers(共享内存)
规划器提示无直接等价effective_cache_size
排序内存sort_buffer_size(按连接)work_mem(按操作)
WAL 策略innodb_flush_log_at_trx_commitsynchronous_commit + fsync
后台清理InnoDB 自管理autovacuum 系列参数
I/O 能力innodb_io_capacityeffective_io_concurrency

六、监控体系#

没有监控的优化是盲目的。建立完善的监控体系,才能量化问题、验证优化效果、及时发现问题。

6.1 指标分类#

数据库监控指标遵循 USE 方法(Utilization / Saturation / Errors)和 RED 方法(Rate / Errors / Duration):

graph LR subgraph USE["USE 方法 — 资源视角"] U["利用率 Utilization<br/>CPU/内存/磁盘使用率"] S["饱和度 Saturation<br/>连接等待/IO 队列/锁等待"] E1["错误 Errors<br/>连接失败/查询错误/复制中断"] end subgraph RED["RED 方法 — 请求视角"] R["速率 Rate<br/>QPS/TPS/连接数"] E2["错误 Errors<br/>慢查询/超时/死锁"] D["延迟 Duration<br/>P50/P95/P99 延迟"] end style USE fill:#e3f2fd,stroke:#1565c0 style RED fill:#fce4ec,stroke:#c62828

核心监控指标分类:

类别指标告警阈值建议
延迟查询 P99 延迟> 500ms(Warning),> 2s(Critical)
延迟连接获取延迟> 100ms
吞吐QPS / TPS下降 30%(Warning)
吞吐慢查询数量> 10/min(Warning)
错误查询错误率> 1%(Warning),> 5%(Critical)
错误死锁频率> 5/min
饱和活跃连接数 / 最大连接数> 80%
饱和缓冲池命中率< 95%(Warning)
饱和磁盘 I/O 利用率> 80%

6.2 Prometheus + Grafana 监控#

监控部署(Docker Compose):

# docker-compose.yml — 添加 Exporter
services:
mysql-exporter:
image: prom/mysqld-exporter
environment:
DATA_SOURCE_NAME: "exporter:password@(mysql:3306)/"
ports: ["9104:9104"]
postgres-exporter:
image: prometheuscommunity/postgres-exporter
environment:
DATA_SOURCE_NAME: "postgresql://exporter:password@postgres:5432/postgres?sslmode=disable"
ports: ["9187:9187"]

关键 Grafana 面板指标

# MySQL 缓冲池命中率
rate(mysql_global_status_buffer_pool_read_requests[5m])
/ (rate(mysql_global_status_buffer_pool_read_requests[5m])
+ rate(mysql_global_status_buffer_pool_reads[5m]))
# MySQL 活跃连接数
mysql_global_status_threads_running
# PostgreSQL 缓存命中率
rate(pg_stat_database_blks_hit{datname="mydb"}[5m])
/ (rate(pg_stat_database_blks_hit{datname="mydb"}[5m])
+ rate(pg_stat_database_blks_read{datname="mydb"}[5m]))

6.3 告警规则#

# alert_rules.yml — 数据库告警规则
groups:
- name: database_alerts
rules:
# 慢查询激增
- alert: HighSlowQueryRate
expr: rate(mysql_global_status_slow_queries[5m]) > 10
for: 5m
labels:
severity: warning
annotations:
summary: "MySQL 慢查询激增"
description: "慢查询速率 {{ $value }} 次/秒,持续 5 分钟"
# 连接数接近上限
- alert: ConnectionPoolExhaustion
expr: mysql_global_status_threads_running / mysql_global_variables_max_connections > 0.8
for: 3m
labels:
severity: critical
annotations:
summary: "MySQL 连接池即将耗尽"
description: "活跃连接占比 {{ $value | humanizePercentage }}"
# PostgreSQL 复制延迟
- alert: PostgreSQLReplicationLag
expr: pg_replication_lag > 30
for: 5m
labels:
severity: critical
annotations:
summary: "PostgreSQL 复制延迟超过 30 秒"
description: "当前延迟 {{ $value }} 秒"
Tip

告警不是越多越好。过多的告警会导致”告警疲劳”——工程师开始忽略所有告警。遵循以下原则:每个告警都必须可操作(收到后知道该做什么),优先级明确(Warning vs Critical),避免重复告警。

七、总结#

数据库性能优化是一个从定位到解决的系统工程,核心要点回顾:

graph LR A["量化基线"] --> B["定位瓶颈"] B --> C["分层优化"] C --> D["验证效果"] D --> E["持续监控"] A -.->|"监控数据"| E E -.->|"新问题"| B style A fill:#e3f2fd,stroke:#1565c0 style B fill:#e8f5e9,stroke:#2e7d32 style C fill:#fff3e0,stroke:#e65100 style D fill:#fce4ec,stroke:#c62828 style E fill:#f3e5f5,stroke:#6a1b9a

方法论层面:遵循优化金字塔——架构→配置→SQL→应用,自底向上逐层优化;量化驱动,用数据说话,避免盲目优化。

慢查询层面:善用慢查询日志和 pt-query-digest 定位问题 SQL;通过 EXPLAIN 分析执行计划(详见查询处理与优化);关注 Rows_examined / Rows_sent 比值,比值越大优化空间越大。

连接池层面:连接复用是高并发的基础;HikariCP 和 PgBouncer 是 Java 和 PostgreSQL 生态的最佳选择;连接数不是越多越好,按公式计算并动态调整。

缓存层面:Cache-Aside 是最通用的模式;穿透/击穿/雪崩各有对策——布隆过滤器、互斥锁、随机 TTL;选择合适的 Redis 数据结构(详见Redis 深入)。

参数调优层面innodb_buffer_pool_sizeshared_buffers 是 MySQL 和 PostgreSQL 最重要的参数;持久性与性能的权衡需要根据业务需求决定;不要忽视 Linux 内核参数的影响。

监控层面:USE + RED 方法论覆盖资源与请求两个维度;Prometheus + Grafana 是事实标准;告警必须可操作、有优先级、避免疲劳。

性能优化没有银弹,但有方法论。掌握这套方法论,你就能在面对性能问题时不再慌张,而是有条不紊地定位、分析、解决。

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

数据库性能优化:从慢查询到系统调优
https://blog.souloss.com/posts/database/database-performance-optimization/
作者
Souloss
发布于
2024-08-10
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时