知识全景图/ 软件工程与系统/ 数据库生产治理/ 如何设计数据库性能、慢查询与容量治理

如何设计数据库性能、慢查询与容量治理

不要先背 EXPLAIN 字段;先解释“为什么只查 50 行,却扫了 280 万行”

这一页只解决一个问题: 数据库设计怎样避免上线后慢到不可用。性能不是独立章节,它会反过来检查你的表、索引、查询、事务和读模型有没有设计错。
一、事故:本月账单从 200ms 变成 8s

Web 记账系统的“本月账单”只展示 50 条记录,但大租户打开页面要 8 秒。CPU 不高,数据库也没宕机。真正的问题是:SQL 返回少,不代表扫描少。

现象先别做什么先看什么
接口慢先加机器SQL 指纹、扫描行数、返回行数
CPU 不高以为没压力锁等待、连接等待、IO 等待
连接池满直接调大连接池连接为什么被长 SQL 或长事务占住
月底更慢只怪流量报表和导出是否在扫 OLTP 明细
二、建表和索引前只问 6 个性能问题
问题例子
最高频查询是什么按账本 + 月份倒序看 50 条账目
排序字段是什么occurred_at desc, id desc,不是随便按 created_at
分页会不会深翻避免 offset 深分页,改用游标
过滤条件是否进入索引账本、状态、时间范围、软删除字段要一起考虑
写入会被多少索引拖慢每个索引都增加插入、更新、回填和备份成本
报表是否应该直接打事实表年度统计、导出和大聚合要考虑读模型或异步任务
三、最小可信查询设计

先把最常用列表查询设计对。这个查询如果错,后面再谈容量治理都是补锅。

query: SELECT id, occurred_at, amount_minor, category_snapshot_id FROM ledger_entries WHERE ledger_id = ? AND status = 'posted' AND occurred_at >= ? AND occurred_at < ? AND deleted_at IS NULL ORDER BY occurred_at DESC, id DESC LIMIT 50; candidate index: (ledger_id, status, occurred_at DESC, id DESC)
吸收点: 索引不是字段清单,而是查询路径。先确定“谁过滤、谁排序、谁分页”,再谈索引。
四、慢查询常常是 4 个设计错误
错误后果改法
列表页 offset 深分页越翻越慢,还可能重复或漏用游标分页:occurred_at + id
报表实时扫明细月底拖垮在线记账月报读模型、预聚合、异步导出
事务里做慢计算锁持有时间变长,前台排队事务外计算,事务内只校验和写入
给每个查询都加索引写入变慢,索引膨胀索引要有用途、收益、成本和退场时间
不要把性能当调参: 很多慢不是参数错,而是查询入口、表结构、索引顺序、事务边界和读模型边界没设计。
五、用 6 个样本证明性能设计可信
样本通过标准
普通账本查本月 50 条命中组合索引,扫描行数接近返回行数
大账本查本月 50 条不因大租户历史数据变成全表扫描
新增账目写入索引数量没有把写入延迟推高到不可接受
月底月报生成不直接抢占前台 OLTP 资源
批量导出进入队列、限并发、可暂停,不长时间占连接
发布后计划变化能看到 SQL fingerprint、plan hash、扫描行数变化
六、下一步只按慢的原因下钻
如果慢在读模型没拆
如果慢在锁和长事务
如果慢在索引回填或 DDL
数据库迁移与在线演进里的执行风险。
如果慢要变成发布门禁
总结: 数据库性能设计先不谈工具。先把高频查询、过滤、排序、分页、索引成本、事务持锁和报表隔离讲清楚。只要“返回 50 行却扫描 280 万行”还解释不了,性能治理就还没开始。