图 1:本文封面,五层阶梯就是排查的行进路线。
先说明属性:这是一篇经典题解析,题目出自本地历史面试资料,与任何公司的当前招聘无关。「数据库优化的思路」是开放题里的经典,也是区分「背过答案」和「干过活」的试金石:背答案的人会罗列手段,干过活的人会给顺序。本文的主线就是这个顺序——自下而上五层,能用下一层解决就不上一层,因为越往上动得越狠、代价越大。
第一层:慢查询发现——先找到元凶,再谈优化。
一切优化的前提是知道谁在慢。MySQL 的慢查询日志就是干这个的:执行时间超过 long_query_time 阈值(默认 10 秒,生产一般调低到几百毫秒)且扫描行数达标的语句会被记录下来。两个细节值得记住:慢查询日志默认是关闭的,线上排查第一步是确认它开着;日志里每条语句带执行时间、锁等待时间、扫描行数、返回行数等字段,用官方自带的 mysqldumpslow 可以按次数与耗时聚合排序,「执行次数 × 单次耗时」最高的那几条就是元凶。还可以打开 log_queries_not_using_indexes,把没用索引的查询也抓出来。
第二层:SQL 层——写法问题最便宜,先改。
拿到元凶 SQL,先用执行计划(EXPLAIN)看它走了什么访问路径:全表扫描还是索引、用了哪个索引、扫描行数估算是多少。常见的写法问题包括 SELECT 星号取回根本不需要的列、对索引列套函数导致索引失效、深分页(LIMIT 十万 offset)越翻越慢。这一层的改动不动表结构、不动架构,成本最低,收益却往往立竿见影。
第三层:索引层——为高频条件设计,而不是见列就建。
SQL 写法没问题还慢,轮到索引。为高频查询条件建联合索引,顺序按区分度与最左前缀匹配设计;能用覆盖索引就让查询不回表。但索引不是免费的:每个索引都是一棵要维护的 B+ 树,写入时同步更新,索引越多写放大越严重——给一张写多读少的表堆十个索引,是把读的快换成写的慢。
第四层:表结构层——动 schema 要慎重。
字段类型收紧(能用 INT 不用 VARCHAR)、冷热数据分离(历史数据归档到冷库)、大表按业务维度拆分,都属于这一层。改动涉及数据迁移,风险与成本高,所以排在索引之后。
第五层:架构层——读写分离、缓存、分库分表。
单机优化到头了才考虑架构手术:读多写少上加缓存挡读、读写分离分担主库压力;单机容量到顶再分库分表。这一层引入分布式复杂度——缓存一致性、跨库查询、分布式事务,每一个都是新的问题域。上来就喊分库分表的答案,等于还没查血就开刀。
用 SQL 把第一层落到手上。
以 MySQL 8.4 为例,开启与分析慢查询的标准动作如下:
-- 运行时开启慢查询日志并下调阈值(默认 10 秒太长)
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 0.5;
-- 把没用索引的查询也记入日志
SET GLOBAL log_queries_not_using_indexes = 1;
-- 拿到元凶 SQL 后看执行计划:关注 type(访问类型)、key(实际走的索引)、rows(估算扫描行数)
EXPLAIN SELECT order_id, amount
FROM orders
WHERE user_id = 10086 AND status = 'PAID'
ORDER BY created_at DESC
LIMIT 20;
执行计划里 type 出现 ALL 意味着全表扫描,rows 估算几十万而实际只取 20 行,这就是索引层的活儿了。慢日志文件用 mysqldumpslow 汇总:mysqldumpslow -s t -t 10 按总耗时排出前十个元凶。
图 2:五层排查顺序与「先量化基线」的操作纪律。
可能的追问与易错点。
「每层怎么验证有效?」固定一个指标——元凶 SQL 的响应时间与扫描行数——动手前记录基线,改完用同一指标复测,没有复测的优化等于没做。易错点三个:上来就加机器或分库分表,跳过便宜的四层;索引见列就建,写放大反噬写入性能;凭感觉判断瓶颈,不开慢查询日志直接动手。
回到面试现场,建议的答题结构:先给「自下而上五层」的顺序总纲,再逐层各讲两三个代表手段,最后强调基线量化与复测纪律——顺序感就是这道题的分水岭。这套顺序对 AI 应用的特征库与会话库同样适用:它们进入中后期最常见的瓶颈就是慢查询,先开日志定位再谈扩容,能省下一大笔冤枉钱。你排查过的最棘手的数据库变慢,最后卡在哪一层?欢迎分享。