轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 架构设计与模式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • JavaScript
  • TypeScript
  • Node.js
  • Vue.js
  • 前端工程化
  • 浏览器与Web API
  • 架构设计与模式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 架构设计与模式

    • 高可用-分布式基础之CAP理论
    • 高可用-服务容错与降级策略
    • 高可用-故障检测与自动恢复
    • 高可用-混沌工程实践
    • 高可用-分布式事务实战
    • 高可用-多活与容灾架构设计
    • 高性能-缓存架构设计
    • 高性能-性能优化方法论
    • 高性能-异步处理与消息队列
    • 高性能-数据库性能优化
      • 一、引言:数据库性能优化的系统性思考
      • 二、数据库性能问题的根源分析
        • 2.1 性能问题的常见表现
        • 2.2 性能瓶颈的分层模型
        • 2.3 80/20法则在数据库优化中的应用
      • 三、数据库性能优化的通用方法论
        • 3.1 性能优化的四个阶段
        • 阶段一:建立性能基线
        • 阶段二:定位性能瓶颈
        • 阶段三:制定优化方案
        • 阶段四:验证优化效果
      • 四、应用层优化:从源头减少数据库压力
        • 4.1 避免N+1查询问题
        • 4.2 合理控制事务边界
        • 4.3 连接池管理
        • 4.4 批量操作
      • 五、查询层优化:让SQL跑得更快
        • 5.1 索引优化策略
        • 5.1.1 何时需要添加索引
        • 5.1.2 索引设计原则
        • 5.1.3 索引失效的常见场景
        • 5.2 查询改写技巧
        • 5.2.1 避免SELECT *
        • 5.2.2 分页查询优化
        • 5.2.3 子查询优化
        • 5.3 执行计划分析
      • 六、存储层优化:合理的表结构设计
        • 6.1 数据类型选择
        • 6.2 表结构设计原则
        • 6.2.1 垂直分表
        • 6.2.2 水平分表
        • 6.3 分区表
      • 七、架构层优化:分布式数据库方案
        • 7.1 读写分离
        • 7.2 分库分表
        • 7.3 NewSQL数据库
      • 八、缓存策略:减少数据库访问
        • 8.1 缓存在数据库优化中的定位
        • 8.2 缓存与数据库的协同设计
        • 8.3 需要关注的核心问题
      • 九、容量规划与监控
        • 9.1 容量评估
        • 9.2 监控指标体系
      • 十、总结:构建高性能数据库的心智模型
  • 代码质量管理

  • 基础

  • 操作系统

  • 计算机网络

  • AI

  • 编程范式

  • 安全

  • 中间件

  • 心得

  • 架构
  • 架构设计与模式
轩辕李
2025-04-17
目录

高性能-数据库性能优化

# 一、引言:数据库性能优化的系统性思考

在大多数应用系统中,数据库往往是第一个遭遇性能瓶颈的组件。

当业务量增长、数据规模扩大、并发请求增多时,数据库的问题会被急剧放大:

  • 响应时间变长:原本几十毫秒的查询变成了几秒甚至超时
  • CPU飙高:数据库服务器CPU使用率常年在80%以上
  • 慢查询堆积:慢查询日志每天增长数GB,但不知从何下手
  • 连接池耗尽:高峰期频繁出现"Too many connections"错误
  • 主从延迟:从库延迟越来越大,读写分离失效

很多团队在数据库优化上的典型问题:

  • 只会加索引:遇到慢查询就加索引,结果索引越来越多,写入越来越慢
  • 只会分库分表:一上来就讨论分库分表,忽视了更简单有效的优化手段
  • 缺乏监控:没有基线数据,优化全凭感觉,不知道效果如何
  • 忽视容量规划:等到数据库撑不住了才想起来扩容

本文的目标是提供一套系统性的数据库性能优化方法论:

  • 从架构视角看待数据库性能问题,而不是局限于SQL优化
  • 涵盖从单机优化到分布式架构的完整路径
  • 重点讲解通用方法论,适用于MySQL、PostgreSQL等主流数据库
  • 强调监控、度量、容量规划等工程化能力

注意:本文侧重架构层面的性能优化方法论。关于MySQL、PostgreSQL的具体特性和使用细节,请参考本博客的相关文章:理解MySQL、从MySQL到PostgreSQL。


# 二、数据库性能问题的根源分析

# 2.1 性能问题的常见表现

从现象入手,先识别问题的表现形式:

现象 可能原因 影响范围
查询慢 缺少索引、索引失效、锁等待 单个查询或少数查询
CPU高 全表扫描、复杂计算、高并发 整个数据库实例
IO高 大量磁盘读写、缓冲池不足 整个数据库实例
连接数打满 慢查询堵塞、连接泄漏 整个应用系统
主从延迟 写入过载、大事务、网络问题 读写分离架构
死锁频繁 事务设计不当、锁顺序混乱 涉及事务的业务

# 2.2 性能瓶颈的分层模型

数据库性能问题通常可以分为以下几个层次:

应用层     ← SQL设计、事务边界、连接管理
  ↓
查询层     ← 索引设计、查询优化、执行计划
  ↓
存储层     ← 表结构设计、数据类型、分区策略
  ↓
引擎层     ← 缓冲池、日志、锁机制
  ↓
硬件层     ← CPU、内存、磁盘IO、网络

优化原则:自上而下逐层排查,优先解决上层问题。

  • 应用层的问题往往收益最大、风险最小
  • 硬件层的升级成本高、效果有限

# 2.3 80/20法则在数据库优化中的应用

根据经验统计:

  • 80%的数据库性能问题由20%的慢查询引起
  • 80%的查询压力集中在20%的热点数据上
  • 80%的优化效果来自20%的关键改进

启示:优化要抓住主要矛盾,先解决TOP问题。


# 三、数据库性能优化的通用方法论

# 3.1 性能优化的四个阶段

# 阶段一:建立性能基线

在优化之前,必须先量化当前状态:

关键指标:

  • QPS/TPS:每秒查询数/事务数
  • 响应时间:平均响应时间、P95、P99
  • 连接数:当前连接数、最大连接数
  • 慢查询率:慢查询数量/总查询数
  • 资源使用率:CPU、内存、磁盘IO、网络

如何建立基线:

-- MySQL查看当前QPS
SHOW GLOBAL STATUS LIKE 'Questions';
SHOW GLOBAL STATUS LIKE 'Uptime';
-- QPS = (Questions2 - Questions1) / (Uptime2 - Uptime1)

-- 查看慢查询统计
SHOW GLOBAL STATUS LIKE 'Slow_queries';

-- 查看连接数
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW VARIABLES LIKE 'max_connections';

监控工具推荐:

  • Prometheus + Grafana:开源监控方案
  • Percona Monitoring and Management (PMM):专为MySQL设计
  • pgAdmin/pg_stat_statements:PostgreSQL监控

# 阶段二:定位性能瓶颈

自上而下的排查路径:

  1. 应用层排查

    • 是否有N+1查询问题?
    • 事务是否过长?
    • 连接是否及时释放?
  2. SQL层排查

    • 哪些SQL最慢?执行频率如何?
    • 执行计划是否合理?
    • 是否存在全表扫描?
  3. 系统层排查

    • CPU、内存、磁盘IO是否成为瓶颈?
    • 缓冲池命中率如何?
    • 是否存在锁等待?

排查工具:

-- MySQL慢查询日志分析
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

-- 查看当前正在执行的查询
SHOW FULL PROCESSLIST;

-- 查看锁等待情况
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;

-- 查看缓冲池状态
SHOW ENGINE INNODB STATUS;

# 阶段三:制定优化方案

优化手段的优先级:

优先级 优化方向 收益 风险 成本
P0 修复慢SQL 高 低 低
P1 增加索引 高 中 低
P2 优化表结构 中 中 中
P3 引入缓存 高 中 中
P4 读写分离 中 中 中
P5 分库分表 高 高 高
P6 更换数据库 高 极高 极高

建议:先用低成本手段解决80%的问题,再考虑高成本方案。

# 阶段四:验证优化效果

A/B测试:

  • 在灰度环境或流量的一部分上验证优化效果
  • 对比优化前后的关键指标

持续观测:

  • 优化后持续观察1-2周,确保没有引入新问题
  • 关注CPU、内存、磁盘IO等资源使用趋势

# 四、应用层优化:从源头减少数据库压力

# 4.1 避免N+1查询问题

问题描述:

// 反例:N+1查询
List<Order> orders = orderRepository.findAll(); // 1次查询
for (Order order : orders) {
    User user = userRepository.findById(order.getUserId()); // N次查询
    order.setUser(user);
}

上述代码会产生1 + N次数据库查询,当N很大时性能急剧下降。

优化方案:

// 正例:批量查询
List<Order> orders = orderRepository.findAll();
Set<Long> userIds = orders.stream()
    .map(Order::getUserId)
    .collect(Collectors.toSet());

Map<Long, User> userMap = userRepository.findByIdIn(userIds).stream()
    .collect(Collectors.toMap(User::getId, Function.identity()));

orders.forEach(order -> order.setUser(userMap.get(order.getUserId())));

或使用ORM的关联查询:

// 使用JOIN一次性获取
@Query("SELECT o FROM Order o LEFT JOIN FETCH o.user WHERE o.id IN :ids")
List<Order> findOrdersWithUsers(@Param("ids") List<Long> ids);

# 4.2 合理控制事务边界

问题:事务范围过大,持有锁时间过长。

// 反例:事务边界过大
@Transactional
public void processOrder(Order order) {
    orderRepository.save(order);           // 需要事务
    inventoryService.deduct(order);        // 需要事务
    
    emailService.sendEmail(order);         // 不需要事务,但很耗时
    logService.recordLog(order);           // 不需要事务
}

优化方案:

// 正例:缩小事务边界
public void processOrder(Order order) {
    // 事务内只处理核心业务
    transactionTemplate.execute(status -> {
        orderRepository.save(order);
        inventoryService.deduct(order);
        return null;
    });
    
    // 异步处理非核心操作
    asyncExecutor.execute(() -> {
        emailService.sendEmail(order);
        logService.recordLog(order);
    });
}

# 4.3 连接池管理

关键配置:

// HikariCP配置示例
hikari.maximum-pool-size=20          // 最大连接数
hikari.minimum-idle=5                // 最小空闲连接数
hikari.connection-timeout=30000      // 连接超时时间(ms)
hikari.idle-timeout=600000           // 空闲连接超时时间(ms)
hikari.max-lifetime=1800000          // 连接最大存活时间(ms)

连接数计算公式:

连接数 = ((核心数 * 2) + 有效磁盘数)

注意事项:

  • 连接池不是越大越好,过多连接会增加数据库压力
  • 要根据应用实例数量合理分配连接数
  • 监控连接池使用率,及时发现连接泄漏

# 4.4 批量操作

单条插入 vs 批量插入:

// 反例:逐条插入
for (User user : users) {
    userRepository.save(user);  // 每次都是一个事务
}

// 正例:批量插入
userRepository.saveAll(users);  // 一个事务,批量提交

批量更新:

-- 反例:多次单条更新
UPDATE users SET status = 1 WHERE id = 1;
UPDATE users SET status = 1 WHERE id = 2;
-- ... 1000次

-- 正例:批量更新
UPDATE users SET status = 1 WHERE id IN (1,2,3,...,1000);

注意:批量操作也要控制批次大小,建议每批不超过1000条。


# 五、查询层优化:让SQL跑得更快

# 5.1 索引优化策略

# 5.1.1 何时需要添加索引

典型场景:

  • WHERE子句中的过滤条件
  • JOIN连接条件
  • ORDER BY排序字段
  • GROUP BY分组字段

不适合添加索引的场景:

  • 数据量很小的表(几百条记录)
  • 频繁更新的字段
  • 区分度很低的字段(如性别、状态等只有2-3个值)

# 5.1.2 索引设计原则

联合索引的最左前缀原则:

-- 创建联合索引
CREATE INDEX idx_user_age_city ON users(age, city);

-- 可以使用索引的查询
SELECT * FROM users WHERE age = 25 AND city = '北京';  -- ✓
SELECT * FROM users WHERE age = 25;                    -- ✓

-- 无法使用索引的查询
SELECT * FROM users WHERE city = '北京';               -- ✗

覆盖索引:

-- 创建覆盖索引
CREATE INDEX idx_user_name_age ON users(name, age);

-- 查询只涉及索引列,无需回表
SELECT name, age FROM users WHERE name = 'Alice';  -- 使用覆盖索引

# 5.1.3 索引失效的常见场景

使用函数或表达式:

-- 反例:索引失效
SELECT * FROM users WHERE YEAR(created_at) = 2024;

-- 正例:索引生效
SELECT * FROM users WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';

隐式类型转换:

-- 反例:id是BIGINT,但用字符串查询
SELECT * FROM users WHERE id = '12345';  -- 索引失效

-- 正例:类型匹配
SELECT * FROM users WHERE id = 12345;

LIKE以通配符开头:

-- 反例:索引失效
SELECT * FROM users WHERE name LIKE '%Alice%';

-- 正例:索引生效
SELECT * FROM users WHERE name LIKE 'Alice%';

# 5.2 查询改写技巧

# 5.2.1 避免SELECT *

-- 反例:查询所有字段
SELECT * FROM orders WHERE user_id = 123;

-- 正例:只查询需要的字段
SELECT id, order_no, amount, created_at FROM orders WHERE user_id = 123;

好处:

  • 减少网络传输
  • 可能使用覆盖索引
  • 避免大字段拖慢查询

# 5.2.2 分页查询优化

深分页问题:

-- 反例:深分页性能差
SELECT * FROM orders ORDER BY id LIMIT 1000000, 20;
-- MySQL需要扫描1000020行,然后丢弃前1000000行

优化方案一:基于上次查询结果:

-- 正例:记录上次查询的最后一个ID
SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 20;

优化方案二:延迟关联:

-- 先查询ID,再关联获取完整数据
SELECT o.* FROM orders o
INNER JOIN (
    SELECT id FROM orders ORDER BY id LIMIT 1000000, 20
) t ON o.id = t.id;

# 5.2.3 子查询优化

-- 反例:IN子查询可能性能差
SELECT * FROM orders WHERE user_id IN (
    SELECT id FROM users WHERE city = '北京'
);

-- 正例:改写为JOIN
SELECT o.* FROM orders o
INNER JOIN users u ON o.user_id = u.id
WHERE u.city = '北京';

# 5.3 执行计划分析

使用EXPLAIN查看执行计划:

EXPLAIN SELECT * FROM orders WHERE user_id = 123;

关键字段解读:

字段 说明 理想值
type 访问类型 const > eq_ref > ref > range
possible_keys 可能使用的索引 -
key 实际使用的索引 非NULL
rows 扫描行数 越少越好
Extra 额外信息 Using index(覆盖索引)

警惕的信号:

  • type = ALL:全表扫描
  • Extra = Using filesort:文件排序,性能差
  • Extra = Using temporary:使用临时表

# 六、存储层优化:合理的表结构设计

# 6.1 数据类型选择

原则:选择最小但够用的数据类型。

-- 反例:数据类型过大
user_id BIGINT           -- 最多存储 2^63-1,通常INT就够
status VARCHAR(255)      -- 状态只有几个值,用TINYINT或ENUM更好
amount DOUBLE            -- 金额用DECIMAL避免精度问题

-- 正例:合理的数据类型
user_id INT UNSIGNED     -- 最多42亿,足够大多数场景
status TINYINT           -- 用0,1,2表示状态
amount DECIMAL(10,2)     -- 精确到分

# 6.2 表结构设计原则

# 6.2.1 垂直分表

场景:表字段很多,但常用字段只有少数几个。

-- 反例:一张大表
CREATE TABLE users (
    id INT PRIMARY KEY,
    username VARCHAR(50),
    email VARCHAR(100),
    -- 常用字段
    avatar TEXT,
    bio TEXT,
    preferences JSON
    -- 不常用的大字段
);

-- 正例:拆分为两张表
CREATE TABLE users (
    id INT PRIMARY KEY,
    username VARCHAR(50),
    email VARCHAR(100)
);

CREATE TABLE user_profiles (
    user_id INT PRIMARY KEY,
    avatar TEXT,
    bio TEXT,
    preferences JSON,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

# 6.2.2 水平分表

场景:单表数据量过大(通常超过千万级)。

分表策略:

按时间分表:

CREATE TABLE orders_2024_01 (...);
CREATE TABLE orders_2024_02 (...);
CREATE TABLE orders_2024_03 (...);

按哈希分表:

-- user_id % 10 = 0 存入 users_0
-- user_id % 10 = 1 存入 users_1
CREATE TABLE users_0 (...);
CREATE TABLE users_1 (...);
-- ...
CREATE TABLE users_9 (...);

注意事项:

  • 分表会增加应用复杂度,需要分表中间件或应用层路由
  • 跨表查询、聚合统计会变得困难
  • 全局唯一ID生成需要特殊处理

# 6.3 分区表

分区 vs 分表:

  • 分区:逻辑上一张表,物理上分散存储,对应用透明
  • 分表:物理上多张表,应用需要感知

范围分区:

CREATE TABLE orders (
    id BIGINT,
    order_date DATE,
    amount DECIMAL(10,2)
)
PARTITION BY RANGE (YEAR(order_date)) (
    PARTITION p2022 VALUES LESS THAN (2023),
    PARTITION p2023 VALUES LESS THAN (2024),
    PARTITION p2024 VALUES LESS THAN (2025),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

哈希分区:

CREATE TABLE users (
    id INT,
    username VARCHAR(50)
)
PARTITION BY HASH(id)
PARTITIONS 10;

分区的优势:

  • 查询时可以只扫描相关分区,提升性能
  • 可以快速删除整个分区的数据
  • 对应用完全透明

# 七、架构层优化:分布式数据库方案

# 7.1 读写分离

架构图:

应用
  ↓
 [读写路由]
  ↙     ↘
主库    从库集群
(写)    (读)

实现方式:

方式一:应用层路由:

@Service
public class UserService {
    
    @Autowired
    @Qualifier("masterDataSource")
    private DataSource masterDataSource;
    
    @Autowired
    @Qualifier("slaveDataSource")
    private DataSource slaveDataSource;
    
    // 写操作使用主库
    public void createUser(User user) {
        JdbcTemplate master = new JdbcTemplate(masterDataSource);
        master.update("INSERT INTO users ...", user);
    }
    
    // 读操作使用从库
    public User getUser(Long id) {
        JdbcTemplate slave = new JdbcTemplate(slaveDataSource);
        return slave.queryForObject("SELECT * FROM users WHERE id = ?", id);
    }
}

方式二:中间件路由:

  • MyCat:MySQL中间件
  • ShardingSphere:支持MySQL、PostgreSQL等
  • ProxySQL:MySQL代理层

主从延迟问题:

// 写后立即读可能读不到最新数据
userService.createUser(user);
User latest = userService.getUser(user.getId());  // 可能为null

// 解决方案:写后短时间内强制读主库
@Transactional
public void createAndNotify(User user) {
    userService.createUser(user);
    // 同一事务内读主库
    User created = userService.getUserFromMaster(user.getId());
    notificationService.send(created);
}

# 7.2 分库分表

垂直分库:按业务模块拆分。

原库:db_all
  ↓
用户库:db_user
订单库:db_order
商品库:db_product

水平分库:按数据维度拆分。

db_user_0  (user_id % 4 = 0)
db_user_1  (user_id % 4 = 1)
db_user_2  (user_id % 4 = 2)
db_user_3  (user_id % 4 = 3)

分库分表的挑战:

问题 解决方案
分布式事务 Seata、两阶段提交、最终一致性
跨库JOIN 应用层聚合、宽表冗余
全局唯一ID 雪花算法、数据库号段模式
数据迁移 双写、数据同步工具

分库分表中间件:

  • ShardingSphere-JDBC:轻量级,应用层嵌入
  • ShardingSphere-Proxy:独立部署,对应用透明
  • MyCat:独立部署,功能丰富

# 7.3 NewSQL数据库

当分库分表变得复杂难以维护时,可以考虑NewSQL:

产品 特点 适用场景
TiDB MySQL兼容,自动分片 海量数据OLTP
CockroachDB PostgreSQL兼容,全球分布 多地域部署
YugabyteDB PostgreSQL兼容,云原生 云环境

优势:

  • 原生支持水平扩展
  • 提供分布式事务
  • 对应用透明,兼容SQL标准

劣势:

  • 学习曲线陡峭
  • 运维复杂度较高
  • 单点性能不如传统数据库

# 八、缓存策略:减少数据库访问

引入缓存是减轻数据库压力最有效的手段之一。合理的缓存架构可以拦截80-90%的读请求,将数据库查询从10-100ms降低到1-5ms。

# 8.1 缓存在数据库优化中的定位

缓存的核心价值:

读请求流量
    ↓
本地缓存 (拦截50-70%)
    ↓
分布式缓存 (拦截20-40%)
    ↓
数据库 (只处理5-10%)

关键指标:

  • 缓存命中率:命中次数/总请求数,目标应达到90%以上
  • 缓存穿透率:直达数据库的请求比例,应控制在10%以内
  • 响应时间对比:
    • 本地缓存: < 1ms
    • Redis缓存: 1-5ms
    • 数据库查询: 10-100ms

# 8.2 缓存与数据库的协同设计

缓存优先场景:

  • 读多写少的数据(如用户资料、商品信息)
  • 计算成本高的结果(如统计数据、推荐结果)
  • 访问频繁的热点数据(如首页、排行榜)

直接访问数据库场景:

  • 强一致性要求(如库存扣减、金融交易)
  • 实时性要求高(如实时库存查询)
  • 访问模式分散,缓存命中率低

简单示例:

public User getUser(Long id) {
    // 1. 先查缓存
    User user = cache.get("user:" + id);
    if (user != null) {
        return user;
    }
    
    // 2. 缓存未命中,查数据库
    user = userRepository.findById(id);
    
    // 3. 写入缓存
    if (user != null) {
        cache.put("user:" + id, user, 3600);
    }
    
    return user;
}

# 8.3 需要关注的核心问题

在数据库性能优化中引入缓存时,需要重点考虑:

数据一致性:

  • 数据库更新后,如何保证缓存及时更新?
  • 采用"删除缓存"还是"更新缓存"策略?
  • 如何处理并发更新导致的不一致?

缓存穿透:

  • 大量查询不存在的数据,缓存无法拦截
  • 可能导致数据库压力骤增

缓存击穿:

  • 热点数据过期瞬间,大量请求打到数据库
  • 造成数据库瞬时压力过大

缓存雪崩:

  • 大量缓存同时失效,数据库承受全量流量
  • 可能导致数据库崩溃

容量规划:

  • 缓存容量如何设计?
  • 数据过期策略如何制定?
  • 如何监控缓存状态?

深入学习:关于缓存的详细设计、多级缓存架构、一致性方案、常见问题及解决方案,请参考本博客的专题文章:高性能-缓存架构设计。该文章深入讲解了缓存的读写策略、多级缓存协同、缓存穿透/击穿/雪崩的完整解决方案、缓存Key设计规范等内容。


# 九、容量规划与监控

# 9.1 容量评估

评估公式:

单表容量 = 单条记录大小 × 记录数
单库容量 = Σ(所有表容量)
磁盘容量需求 = 单库容量 × (1 + 索引比例) × (1 + 增长系数)

示例:

假设订单表:
- 单条记录:500字节
- 日均新增:100万条
- 保留时长:3年

存储需求 = 500字节 × 100万 × 365 × 3
         = 547.5 GB (数据)
         + 200 GB (索引,约占36%)
         + 150 GB (增长缓冲,20%)
         ≈ 900 GB

# 9.2 监控指标体系

核心指标:

指标类别 具体指标 告警阈值
性能 QPS、TPS、响应时间 P99 > 100ms
资源 CPU、内存、磁盘IO CPU > 80%
连接 活跃连接数、等待连接数 连接数 > 最大值80%
慢查询 慢查询数量、慢查询率 慢查询率 > 5%
主从 主从延迟、复制状态 延迟 > 1秒

监控工具:

  • Prometheus + Grafana
  • 云厂商监控(阿里云RDS、AWS RDS)
  • Percona Monitoring (PMM)
  • Datadog、New Relic

# 十、总结:构建高性能数据库的心智模型

数据库性能优化是一个系统工程,需要从多个层面协同推进。

核心要点回顾:

  1. 性能优化有章法:建立基线 → 定位瓶颈 → 制定方案 → 验证效果
  2. 优先级很重要:应用层 > 查询层 > 存储层 > 架构层 > 硬件层
  3. 监控是基础:没有监控的优化都是在赌运气
  4. 避免过度设计:不要一上来就分库分表,80%的问题可以通过简单手段解决
  5. 索引是双刃剑:合理使用提升性能,滥用反而拖慢写入
  6. 容量规划先行:等到出问题再扩容,代价会大得多

实践建议:

  • 建立性能监控体系:先有数据,再谈优化
  • 定期review慢查询:每周分析TOP10慢查询,逐个优化
  • 做好压测和容量规划:提前发现瓶颈,从容应对流量增长
  • 文档化优化经验:沉淀优化案例,形成团队知识库
  • 保持技术敏感度:关注NewSQL、时序数据库等新技术,在合适时机引入

进阶学习资源:

  • 本博客相关文章:理解MySQL、从MySQL到PostgreSQL
  • 书籍推荐:《高性能MySQL》、《PostgreSQL修炼之道》
  • 官方文档:MySQL、PostgreSQL官方性能优化指南

当你和你的团队长期坚持这样做,你们会发现:

  • 数据库不再是系统的瓶颈,而是稳定的基石
  • 遇到性能问题时能够快速定位和解决
  • 系统可以从容应对业务增长和流量高峰
  • 团队整体的工程能力和问题解决能力显著提升

祝你变得更强!

编辑 (opens new window)
#性能优化#高性能#数据库#架构设计
上次更新: 2025/12/17
高性能-异步处理与消息队列
代码质量管理之规范篇

← 高性能-异步处理与消息队列 代码质量管理之规范篇→

最近更新
01
AI编程时代的一些心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code 最佳实践(个人版)
08-01
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式