数据库性能调优
你的查询没问题。你的表结构也没问题。但你的生产数据库,在一个六百万行的表上还是能飙到 400 毫秒。大多数团队会先抓错的那根杠杆:加硬件、硬塞缓存、或者慌乱地重写查询。更便宜、更持久的修法藏在下面一层——表怎么组织、索引怎么建、查询到底怎么执行。这正是"在调优数据库"和"在瞎猜数据库"之间的差别,也是一项每个月都能真金白银省钱的技能。如果你还处于设计阶段,这里的纪律直接建立在扎实的数据库设计基础之上——调优工作大多是还设计决策的债;而让一个调优过的数据库保持调优的运维习惯(备份、监控、变更控制),则属于数据库管理的范围,本指南假定你已经在做这些。
你的应用感觉慢,十有八九是数据库
每一条慢查询都是线索,但团队常常靠猜而不是靠量。经典的失败场景:应用很慢,没人知道为什么,于是工程师扔一台更大的实例上去——每月多掏钱,而真正的问题(一条坏查询、一个缺失索引、一次全表扫描)还藏着。好消息是:只要你看对了证据,大多数性能问题修起来很便宜。这是一份像那个真的在凌晨两点被慢页面叫醒的人那样去调优数据库的实操指南,而不是一本把所有旋钮都讲一遍的教科书。

买硬件之前,先读执行计划
数据库工作中杠杆最大的一招,是读执行计划。它会精确告诉你引擎打算怎么跑你的查询:是全表扫、用索引、做嵌套循环连接,还是在排序一个巨大的中间结果。一条碰了 20 万行、只返回 12 行的查询,已经把它自己的秘密告诉你了。每套主流数据库都能给你解释查询——PostgreSQL 和 MySQL 用 EXPLAIN、SQLite 用 EXPLAIN QUERY PLAN、SQL Server 用 SET SHOWPLAN_XML ON。在调任何别的东西之前,先学着读它们,因为它们会直接指到成本在那里堆积的确切那一行。

索引是最划算的、可以"落袋为安"的赢
缺失索引造成最明显的变慢,而加上它通常只是五分钟的活。经验法则:给用在 WHERE、JOIN 和 ORDER BY 里的列建索引;偏好能匹配查询过滤方式的复合索引;扔掉你从不用的索引,因为每条索引都拖慢写入、吃存储。一条从全表扫描变成索引查找的查询,不换任何硬件就能快上 50 到 100 倍。想弄懂索引底层是怎么靠 B 树这类数据结构把查找变快的,可以从数据库索引基础入门。

重写慢查询,比瞎砸钱更有用
花钱上新硬件之前,先搜一遍常见的查询疑犯。别去 select 你根本用不到的列。别再用函数去过滤带索引的列(比如 WHERE YEAR(created_at) = 2026,它会让索引失效)。对本来可以在更早处过滤、从而能缩窄的大 JOIN 保持警惕。基于 offset 的分页在深分页时会变贵——keyset 分页在第 1000 页也依旧快。而 SELECT * 从一张宽表里拖走的数据,比任何人需要的几列多得多。这些模式的具体修法,可以对照着我们的SQL 数据库总览和查询优化清单来查漏补缺。

先测量,再调优:你的时间花在哪
如果闭着眼睛调优,你会优化错东西。先建立一个基线:带着计时而运行那条慢查询(最好再带执行计划),然后只改一件事、重跑、再对比。不要在一次里同时改表结构、索引和查询——那样你永远说不清到底是哪一处带来了收益。在非高峰时段批量工作,把数字记录下来,让几周之后的回归清晰可见。正是这种纪律,让一张慢看板从"反复着火的案例"变成"已经结案的案例"。

按预算优先看调优工具
监控工具从免费到企业级应有尽有,而只有你的规模真正撑得住时,才需要企业级定价。以下是一份给团队决定去哪花钱的现实对比。
| 平台/工具 | 核心特性 | 定价 |
|---|---|---|
| pg_stat_statements(PostgreSQL) | 跟踪查询执行统计、识别成本最高的查询、零额外成本 | 免费(内置) |
| MySQL Performance Schema | 内置的查询、锁与等待插桩 | 免费(内置) |
| EXPLAIN / 执行计划 | 展示单条查询的访问路径与成本 | 免费 |
| pgBadger | 从 Postgres 日志生成丰富的 HTML 报告的日志分析工具 | 免费(开源) |
| pgAdmin / DBeaver | 带内置 explain 与性能工具的 GUI 客户端 | 免费 |
| Datadog 数据库监控 | 托管看板、异常告警、规模化查询解释 | $9/主机/月 起(付费) |
如果你要从调优走向"选哪个引擎适合我的负载",我们关于 SQL 数据库的对比介绍了 MySQL、PostgreSQL、SQL Server 等,让你睁着眼睛做选择。
在扩容数据库之前,先用缓存
缓存常常是比单独调数据库更便宜的那根杠杆,因为它从源头移除了重复劳动。重读的页面百万次地命中同样的行,正是完美候选:把结果集放进 Redis 或 Memcached,配一个合理的过期时间和 miss 时的预热。数据库于是只服务原来的一小部分流量,CPU、内存和连接压力一次全部缓解。经验法则:先缓存那些昂贵、稳定、重读的数据;别把动态的个人数据放进缓存,或者激进地让它失效。
连接池与配置陷阱
两个看不见的问题会带来惊人的变慢。第一个是连接抖动:每个请求都新开一条数据库连接很贵,而一个复用少量连接的连接池能去掉这笔成本。第二个是错误的默认设置——共享缓冲区太小、max_connections 太低导致排队、或者事务隔离级别配错造成锁竞争。在买任何东西之前,先抬高池子、把显而易见的 memory 设置调对,你会看到延迟的下降比加一台实例还明显。
从设计阶段就为速度而设计
表结构一开始就合理,调优就容易得多。在该去重的地方做规范化,但给热点读路径刻意做反规范化。给用在 join 里的外键建索引。让主键小而聚簇。用真实约束把数据弄干净,这样优化器才能依赖 NOT NULL 和唯一性保证。这也是好基础会持续回报的原因——我们的数据库设计原则讲了规范化和表结构思维,能在问题发生之前就阻止它们。
什么时候才真的该花钱
在你修好了查询、加好了索引、引进了缓存、把连接池调对之后,会到达一个点,负载真的超出了当前这台机器。那才是考虑更大实例、为重读扩展加只读副本、或者要一个替你处理运维的托管服务的时刻。关键在顺序:先免费调优,再付费。反着来的团队,是花钱让云厂商去修他们自己查询造成的问题,月账单就成了一种对未检查代码征收的税。
常见问题
怎么找出数据库里哪些查询慢?
开启查询日志或用内置统计。PostgreSQL 上打开 pg_stat_statements,按总耗时或平均耗时排序,就能看到最贵的查询。MySQL 的慢查询日志会捕获超过你设定阈值的查询。然后拿 EXPLAIN 跑每个疑犯,看引擎在哪里做了昂贵的扫描或排序。
先加内存还是先加索引?
永远先看索引和执行计划。如果一条本应走索引的查询在做了全表扫描,加内存只是让那个浪费的扫描变快一点,并不会移走浪费。先加索引并确认计划变了,只有在负载仍真地把机器吃满的时候,才去考虑内存或硬件。
什么时候该从调优转向扩扩容?
当慢查询和表结构已经很干净、缓存已就位、单实例在正常并发下仍长时间高 CPU 或高内存时。那意味着你确实撑过了这台机器。扩容之前,先为重读流量加只读副本,它更便宜且无中断。把分库分表留给很大、写很重的负载,而且要只在副本不再够用之后。
学数据库调优最快的方式是什么?
拿一棵真实或合成的表,故意写一条慢查询、生成执行计划、加一个索引并观察计划的变化——这样来个十几次,模式就刻进脑子了。配合扎实的基础会更有帮助;先熟悉怎么读懂数据,会让调优更像侦探工作而不是做数学。