欢迎光临
我们一直在努力

MySQL 性能调优面试复习:Explain、索引、慢查询、缓存和架构优化

文章目录

    • 前言
    • 一、先用 EXPLAIN 定位问题
      • 1. possible_keys 和 key 的区别
      • 2. key_len 看联合索引用得深不深
      • 3. rows 看扫描量大不大
    • 二、重点看 type:扫描方式决定效率
      • 1. ALL:全表扫描
      • 2. index:全索引扫描
      • 3. range:索引范围扫描
      • 4. ref:非唯一索引等值查询
      • 5. eq_ref:唯一索引关联查询
      • 6. const:主键或唯一索引查一行
    • 三、Extra 里三个高频信号
      • 1. Using filesort:额外排序
      • 2. Using temporary:使用临时表
      • 3. Using index:覆盖索引
    • 四、索引优化:不要只会说“加索引”
      • 1. 哪些字段适合建索引
      • 2. 哪些字段不适合建索引
      • 3. 联合索引要遵循最左匹配原则
      • 4. 联合索引字段顺序怎么排
    • 五、常见索引失效场景
      • 1. 左模糊查询
      • 2. 对索引列使用函数
      • 3. 对索引列做表达式计算
      • 4. 隐式类型转换
      • 5. OR 使用不当
      • 6. 范围查询后的字段
    • 六、如果 MySQL 选错索引怎么办
      • 1. USE INDEX
      • 2. FORCE INDEX
      • 3. IGNORE INDEX
    • 七、查询优化:少查、少回表、少 JOIN
      • 1. 避免 SELECT *
      • 2. 尽量使用覆盖索引
      • 3. JOIN 要小结果集驱动大结果集
      • 4. 被驱动表关联字段要有索引
      • 5. 高频 JOIN 可以考虑冗余字段
    • 八、分页优化:重点解决深分页
      • 1. 基于位置分页
      • 2. 先查 id 再回表
    • 九、表结构优化:大表和宽表要拆
      • 1. 垂直拆分宽表
      • 2. 冷热数据分离
      • 3. 分库分表
    • 十、缓存优化:读多写少优先考虑
      • 1. 缓存穿透
      • 2. 缓存击穿
      • 3. 缓存雪崩
    • 十一、架构优化:读写分离和分库分表
      • 1. 主从复制
      • 2. 读写分离
      • 3. 分库分表解决什么
    • 十二、慢查询和锁等待怎么定位
      • 1. 开启慢查询日志
      • 2. 慢查询日志看什么
      • 3. 用工具分析慢日志
      • 4. 当前正在执行什么 SQL
      • 5. 什么是 MDL 锁
      • 6. 查 InnoDB 事务和锁等待
      • 7. 常见锁等待原因
      • 8. 死锁怎么排查
      • 9. 面试怎么回答慢查询定位
    • 十三、面试回答模板
    • 十四、复习速记版

前言

这篇文章系统梳理 MySQL 查询慢的排查和优化方法,读完你可以用一套清晰思路回答性能调优类面试题。

面试里问 MySQL 性能优化,最怕答成散点。

比如只说“加索引”。

这不够。

真正靠谱的回答,应该从 定位问题 开始,再到 索引优化、SQL 优化、分页优化、表结构优化、缓存和架构优化。

核心目标就一句话:

让 MySQL 少扫描、少回表、少排序、少建临时表、少做无效 IO。


一、先用 EXPLAIN 定位问题

查询慢,不要上来就加索引。

第一步应该是看执行计划。

常用命令:

EXPLAIN SELECT * FROM user WHERE age = 18;

EXPLAIN 可以告诉你 MySQL 准备怎么执行这条 SQL。

重点看这些字段:

字段含义重点
possible_keys 可能使用的索引 优化器发现的候选索引
key 实际使用的索引 为 NULL 说明没用索引
key_len 实际使用的索引长度 常用于判断联合索引用到几列
rows 预估扫描行数 越大越危险
type 访问类型 判断查询效率的关键
Extra 额外信息 看排序、临时表、覆盖索引

1. possible_keys 和 key 的区别

possible_keys 表示 MySQL 认为可能用得上的索引。

key 表示最终真正用到的索引。

比如:

possible_keys: idx_age
key: NULL
type: ALL

这说明:

  • idx_age 理论上可用;
  • 但优化器最终没用;
  • 查询还是全表扫描。

原因可能是数据量小、索引区分度低、回表成本高,或者统计信息不准确。

2. key_len 看联合索引用得深不深

假设有联合索引:

CREATE INDEX idx_name_age_city ON user(name, age, city);

如果 SQL 是:

SELECT * FROM user WHERE name = 'Tom' AND age = 18;

key_len 可以帮你判断索引用到了 name,还是用到了 name + age。

它不是越长越好。

它的价值是判断 联合索引是否被充分使用。

3. rows 看扫描量大不大

rows 是优化器预估要扫描的行数。

比如:

type: ALL
rows: 1000000

这基本就是危险信号。

它说明 MySQL 可能要扫描 100 万行。

但 rows 是估算值。

如果统计信息不准,可以执行:

ANALYZE TABLE user;

让 MySQL 重新统计。


二、重点看 type:扫描方式决定效率

type 表示 MySQL 找数据的方式。

常见类型从差到好大致是:

ALL < index < range < ref < eq_ref < const

1. ALL:全表扫描

ALL 是最差的情况。

它表示 MySQL 要扫描整张表。

type: ALL
key: NULL

这种情况通常要重点优化。

2. index:全索引扫描

index 是全索引扫描。

它比全表扫描稍好一点,因为扫的是索引树。

但本质还是从头扫到尾。

数据量大时也很慢。

3. range:索引范围扫描

常见于:

WHERE age > 18
WHERE price BETWEEN 10 AND 80
WHERE id IN (1, 2, 3)

从 range 开始,索引作用就比较明显了。

4. ref:非唯一索引等值查询

比如:

SELECT * FROM user WHERE age = 18;

如果 age 是普通索引,可能出现:

type: ref

因为 age = 18 可能匹配多行。

5. eq_ref:唯一索引关联查询

常见于多表 JOIN。

比如订单表关联用户表:

SELECT *
FROM orders o
JOIN user u ON o.user_id = u.id;

如果 u.id 是主键或唯一索引,可能出现 eq_ref。

6. const:主键或唯一索引查一行

比如:

SELECT * FROM user WHERE id = 1;

id 是主键,结果最多一条。

这种查询非常快。


三、Extra 里三个高频信号

Extra 是执行计划里的补充信息。

面试里最常问这三个:

Using filesort
Using temporary
Using index

1. Using filesort:额外排序

Using filesort 表示 MySQL 不能直接利用索引顺序完成排序,只能自己额外排序。

注意,filesort 不一定真的写磁盘文件。

它可能在内存里排。

数据太大时才会落盘。

比如:

SELECT * FROM user ORDER BY age;

如果 age 没有索引,MySQL 可能会:

扫描数据
取出结果
额外排序
返回结果

这就可能出现 Using filesort。

优化方式是让排序字段走索引:

CREATE INDEX idx_age ON user(age);

2. Using temporary:使用临时表

Using temporary 表示 MySQL 创建了内部临时表保存中间结果。

常见于:

  • GROUP BY
  • ORDER BY
  • DISTINCT
  • UNION

比如:

SELECT age, COUNT(*)
FROM user
GROUP BY age;

如果不能利用索引完成分组,MySQL 可能会把中间结果放进临时表。

临时表为什么慢?

因为 MySQL 要额外写入中间结果,再读取、排序或分组。数据量大时还可能落盘。

内部临时表可以有索引。

但通常由 MySQL 自动决定。

你优化的方向不是手动控制临时表,而是让原 SQL 能直接利用索引完成分组或排序。

3. Using index:覆盖索引

Using index 是好信号。

它表示查询需要的数据只从索引里就能拿到,不需要回表。

比如有索引:

CREATE INDEX idx_name_age ON user(name, age);

执行:

SELECT age FROM user WHERE name = 'Tom';

name 用于过滤。

age 用于返回。

两个字段都在索引里。

这就是覆盖索引。


四、索引优化:不要只会说“加索引”

索引的本质是减少扫描范围。

但索引不是越多越好。

它会占空间,也会降低写入速度。

1. 哪些字段适合建索引

常见适合建索引的字段:

  • 经常出现在 WHERE 里的字段;
  • 经常用于 ORDER BY 的字段;
  • 经常用于 GROUP BY 的字段;
  • 多表 JOIN 的关联字段;
  • 区分度高的字段。

比如:

SELECT * FROM orders WHERE user_id = 1001;

可以考虑:

CREATE INDEX idx_user_id ON orders(user_id);

2. 哪些字段不适合建索引

这些字段不一定适合:

  • 区分度很低的字段,比如性别、状态;
  • 很少用于查询条件的字段;
  • 经常更新的字段;
  • 小表上的普通字段;
  • 大文本字段的完整索引。

比如 gender 只有男、女。

建索引后筛选度很低。

优化器可能仍然选择全表扫描。

3. 联合索引要遵循最左匹配原则

假设有联合索引:

CREATE INDEX idx_a_b_c ON t(a, b, c);

可以命中索引的情况:

WHERE a = 1
WHERE a = 1 AND b = 2
WHERE a = 1 AND b = 2 AND c = 3

不太能充分命中的情况:

WHERE b = 2
WHERE c = 3
WHERE b = 2 AND c = 3

因为缺少最左边的 a。

4. 联合索引字段顺序怎么排

常见原则:

  • 等值查询字段放前面;
  • 区分度高的字段优先;
  • 高频查询字段优先;
  • 排序、分组字段尽量接在后面。

比如高频 SQL 是:

SELECT *
FROM orders
WHERE user_id = 1001 AND status = 1
ORDER BY create_time DESC;

可以考虑:

CREATE INDEX idx_user_status_time
ON orders(user_id, status, create_time);

这样既能过滤,也能帮助排序。


五、常见索引失效场景

很多慢 SQL 不是没索引。

而是索引用不上。

1. 左模糊查询

WHERE name LIKE '%Tom'
WHERE name LIKE '%Tom%'

这种通常无法走普通 B+Tree 索引。

因为索引是从左到右有序的。

如果你不知道开头是什么,MySQL 很难按索引定位。

可以优化成右模糊:

WHERE name LIKE 'Tom%'

2. 对索引列使用函数

WHERE DATE(create_time) = '2026-06-08'

这样会让索引列参与函数计算。

可以改成范围查询:

WHERE create_time >= '2026-06-08 00:00:00'
AND create_time < '2026-06-09 00:00:00'

3. 对索引列做表达式计算

WHERE age + 1 = 19

可以改成:

WHERE age = 18

4. 隐式类型转换

如果 phone 是字符串类型,却这样写:

WHERE phone = 13800138000

可能发生隐式类型转换。

应该写成:

WHERE phone = '13800138000'

5. OR 使用不当

WHERE name = 'Tom' OR age = 18

如果 name 有索引,但 age 没索引,可能导致索引效果变差。

可以考虑给两个字段都建合适索引,或者拆成 UNION。

6. 范围查询后的字段

联合索引中,范围查询后面的字段通常不能继续用于精确定位。

比如索引:

CREATE INDEX idx_a_b_c ON t(a, b, c);

SQL:

WHERE a = 1 AND b > 10 AND c = 3

这里 a 可以用。

b 可以做范围扫描。

c 通常不能继续用于缩小索引扫描范围。


六、如果 MySQL 选错索引怎么办

有时 EXPLAIN 会发现优化器选了不合适的索引。

不要第一反应就强制索引。

先看三个问题:

  • 统计信息是否不准;
  • 索引区分度是否太低;
  • SQL 是否让索引失效。
  • 可以先更新统计信息:

    ANALYZE TABLE products;

    如果确认优化器确实选错了,可以使用索引提示。

    1. USE INDEX

    建议优化器考虑某个索引:

    SELECT *
    FROM products USE INDEX (idx_buyprice)
    WHERE buyPrice BETWEEN 10 AND 80;

    2. FORCE INDEX

    强制 MySQL 尽量使用某个索引:

    EXPLAIN SELECT productName, buyPrice
    FROM products FORCE INDEX (idx_buyprice)
    WHERE buyPrice BETWEEN 10 AND 80
    ORDER BY buyPrice;

    如果生效,可能看到:

    key: idx_buyprice
    type: range

    3. IGNORE INDEX

    让优化器忽略某个索引:

    SELECT *
    FROM products IGNORE INDEX (idx_name)
    WHERE buyPrice BETWEEN 10 AND 80;

    但这些都属于干预手段。

    更推荐先从索引设计和 SQL 写法上解决。


    七、查询优化:少查、少回表、少 JOIN

    1. 避免 SELECT *

    SELECT * 的问题很直接。

    它会查出整行数据。

    带来四类成本:

    • 磁盘 IO 更多;
    • 网络传输更多;
    • 应用反序列化更多;
    • 更容易破坏覆盖索引。

    比如你只需要:

    SELECT id, name FROM user WHERE id = 1;

    就不要写:

    SELECT * FROM user WHERE id = 1;

    2. 尽量使用覆盖索引

    普通二级索引查询流程:

    查二级索引
    拿到主键 id
    回表查完整行
    返回结果

    覆盖索引查询流程:

    查二级索引
    直接拿到需要字段
    返回结果

    少了回表,自然更快。

    3. JOIN 要小结果集驱动大结果集

    JOIN 可以理解成嵌套循环。

    如果小表有 100 行,大表有 100 万行。

    理想方式是:

    先查小结果集 100 行
    每行去大表按索引查

    如果反过来:

    先扫大表 100 万行
    每行去小表匹配

    成本就高很多。

    更准确地说,不是物理小表驱动大表。

    而是 过滤后的小结果集驱动大结果集。

    4. 被驱动表关联字段要有索引

    比如:

    SELECT *
    FROM orders o
    JOIN user u ON o.user_id = u.id;

    如果 u.id 是主键,匹配很快。

    如果被驱动表关联字段没索引,可能每次匹配都扫描一遍表。

    这会非常慢。

    5. 高频 JOIN 可以考虑冗余字段

    比如订单列表每次都要展示用户名。

    原设计:

    orders(id, user_id, amount)
    user(id, name)

    每次都 JOIN:

    SELECT o.id, o.amount, u.name
    FROM orders o
    JOIN user u ON o.user_id = u.id;

    可以在订单表冗余:

    orders(id, user_id, user_name, amount)

    查询就变成:

    SELECT id, amount, user_name FROM orders;

    代价是数据冗余和一致性维护。

    适合读多写少、字段变化少的场景。

    订单商品名、下单价格这类历史快照字段,也很适合冗余。


    八、分页优化:重点解决深分页

    普通分页:

    SELECT * FROM tb_sku LIMIT 200000, 10;

    它不是直接跳到第 200000 行。

    MySQL 通常要先扫描前 200010 行,再丢掉前 200000 行。

    这就是深分页慢的原因。

    1. 基于位置分页

    如果主键自增,可以改成:

    SELECT *
    FROM tb_sku
    WHERE id > 200000
    ORDER BY id
    LIMIT 10;

    这样 MySQL 可以从索引位置继续往后查。

    效率高很多。

    但它适合连续翻页,不适合随机跳页。

    2. 先查 id 再回表

    如果必须深分页,可以先用覆盖索引查 id:

    SELECT id
    FROM tb_sku
    ORDER BY create_time
    LIMIT 200000, 10;

    再根据 id 回表查详情。

    也可以写成 JOIN:

    SELECT s.*
    FROM tb_sku s
    JOIN (
    SELECT id
    FROM tb_sku
    ORDER BY create_time
    LIMIT 200000, 10
    ) t ON s.id = t.id;

    这样至少避免一开始就搬运完整行数据。


    九、表结构优化:大表和宽表要拆

    如果单表数据达到千万级,查询压力可能明显增加。

    但不要一上来就分库分表。

    先看索引和 SQL 是否还能优化。

    1. 垂直拆分宽表

    如果一张表字段很多,可以按访问频率拆。

    比如:

    user(id, name, phone, avatar, description, remark, ext_json)

    可以拆成:

    user(id, name, phone)
    user_profile(user_id, avatar, description, remark, ext_json)

    高频字段放主表。

    低频大字段放扩展表。

    这样可以减少单行大小,提高缓存命中率。

    2. 冷热数据分离

    订单表常见做法是按时间冷热分离。

    比如:

    • 最近 3 个月订单放热表;
    • 历史订单放冷表;
    • 普通查询只查热表。

    这样可以降低高频查询的数据规模。

    3. 分库分表

    当单库或单表成为瓶颈时,再考虑分库分表。

    常见方式:

    方式解决问题
    垂直分库 按业务拆库,降低单库压力
    垂直分表 拆宽表,减少单行体积
    水平分库 数据分散到多个库
    水平分表 大表拆成多个小表

    分库分表会引入复杂度。

    比如:

    • 分布式事务;
    • 跨库 JOIN;
    • 全局唯一 ID;
    • 分页排序;
    • 扩容迁移。

    所以它是后手,不是起手式。


    十、缓存优化:读多写少优先考虑

    热点数据可以放 Redis。

    缓存的目标是减少数据库压力。

    常见读流程是旁路缓存:

    应用先查缓存
    缓存命中,直接返回
    缓存未命中,查询数据库
    数据库返回后,写入缓存

    写流程常见做法:

    先更新数据库
    再删除缓存

    为什么不是先删缓存再更新数据库?

    因为并发下可能出现旧数据回写缓存。

    更推荐:

    先保证数据库成功,再删除缓存,让下一次读取重新加载新数据。

    缓存也不是银弹。

    要考虑几个问题:

    1. 缓存穿透

    查询的数据根本不存在。

    请求每次都打到数据库。

    解决方式:

    • 缓存空值;
    • 使用布隆过滤器。

    2. 缓存击穿

    某个热点 key 过期。

    大量请求同时打到数据库。

    解决方式:

    • 热点 key 不设置短过期;
    • 加互斥锁;
    • 提前异步刷新。

    3. 缓存雪崩

    大量 key 同时过期。

    数据库瞬间被打爆。

    解决方式:

    • 过期时间加随机值;
    • 多级缓存;
    • 限流降级。

    十一、架构优化:读写分离和分库分表

    当 SQL 和索引已经优化过,但压力仍然大,就要看架构。

    1. 主从复制

    MySQL 主从复制大致流程:

    主库写入数据
    主库记录 binlog
    从库拉取 binlog
    从库回放日志
    从库得到相同数据

    2. 读写分离

    读写分离的思路:

    写请求 -> 主库
    读请求 -> 从库

    这样可以提高读并发。

    但要注意主从延迟。

    比如用户刚下单,马上查订单。

    如果读从库,可能查不到。

    这类强一致场景可以强制读主库。

    3. 分库分表解决什么

    分库主要解决单库资源瓶颈。

    分表主要解决单表数据量过大。

    简单理解:

    单库扛不住 -> 分库
    单表太大 -> 分表
    字段太多 -> 垂直拆表
    读压力大 -> 读写分离 + 缓存


    十二、慢查询和锁等待怎么定位

    慢查询不一定都是索引问题。

    有时 SQL 本身不慢,是被锁卡住了。

    面试里可以把它拆成两类:

    执行慢:SQL 扫描多、排序多、回表多
    等待慢:SQL 在等锁、等 IO、等连接

    1. 开启慢查询日志

    慢查询日志用来记录执行时间超过阈值的 SQL。

    常见参数:

    SHOW VARIABLES LIKE 'slow_query_log';
    SHOW VARIABLES LIKE 'long_query_time';

    临时开启可以这样:

    SET GLOBAL slow_query_log = ON;
    SET GLOBAL long_query_time = 1;

    long_query_time = 1 表示超过 1 秒的 SQL 会被记录。

    生产环境一般要结合业务情况设置。

    不是越小越好。

    太小会产生大量日志。

    2. 慢查询日志看什么

    慢查询日志里常看:

    • Query_time:SQL 总耗时;
    • Lock_time:等待锁的时间;
    • Rows_sent:返回行数;
    • Rows_examined:扫描行数;
    • SQL 原文。

    如果 Rows_examined 很大,说明扫描多。

    重点查索引和 SQL 写法。

    如果 Lock_time 很大,说明可能被锁阻塞。

    重点查事务和锁等待。

    3. 用工具分析慢日志

    慢查询日志很多时,不适合手动看。

    可以用:

    mysqldumpslow -s t -t 10 slow.log

    含义是按总耗时排序,取前 10 条。

    也可以用 pt-query-digest。

    它能聚合相似 SQL,看总耗时、平均耗时、执行次数和扫描行数。

    面试里说出这个工具,会显得更接近真实排查。

    4. 当前正在执行什么 SQL

    如果线上突然变慢,可以看当前连接:

    SHOW FULL PROCESSLIST;

    重点看:

    • Command 是否是 Query;
    • Time 是否很长;
    • State 是否在等锁;
    • Info 里具体 SQL 是什么。

    常见状态包括:

    Sending data
    Waiting for table metadata lock
    Locked
    Creating sort index
    Copying to tmp table

    其中 Waiting for table metadata lock 很典型。

    它表示在等元数据锁,也就是 MDL 锁。

    5. 什么是 MDL 锁

    MDL 是 metadata lock,元数据锁。

    它保护表结构不被并发破坏。

    比如一个事务正在查询表。

    另一个线程执行:

    ALTER TABLE user ADD COLUMN age INT;

    这个 DDL 可能会等待 MDL 锁。

    更麻烦的是,后续新的查询也可能排队。

    于是一个 DDL 就拖慢一堆 SQL。

    所以线上执行 DDL 要特别小心。

    6. 查 InnoDB 事务和锁等待

    可以看 InnoDB 当前状态:

    SHOW ENGINE INNODB STATUS\\G

    重点看:

    • LATEST DETECTED DEADLOCK:最近死锁信息;
    • TRANSACTIONS:当前事务;
    • 是否有 lock wait;
    • 哪个事务持有锁;
    • 哪个事务在等待锁。

    MySQL 8.0 也可以查系统表:

    SELECT * FROM performance_schema.data_locks;
    SELECT * FROM performance_schema.data_lock_waits;

    这些表可以看到谁持有锁、谁在等锁。

    7. 常见锁等待原因

    慢查询里的锁等待,常见原因有:

    • 大事务长时间不提交;
    • 更新没有命中索引,导致锁范围变大;
    • 间隙锁导致插入被阻塞;
    • DDL 等 MDL 锁;
    • 事务里夹杂慢接口调用;
    • 批量更新一次改太多数据。

    比如:

    UPDATE user SET status = 1 WHERE phone = '13800138000';

    如果 phone 没索引,MySQL 可能扫描大量记录。

    在事务隔离级别和执行条件影响下,锁范围可能明显扩大。

    所以更新、删除语句的条件字段也要有索引。

    8. 死锁怎么排查

    死锁不是“数据库坏了”。

    它是两个事务互相等对方释放锁。

    例如:

    事务 A:锁住 id = 1,再等 id = 2
    事务 B:锁住 id = 2,再等 id = 1

    就会形成死锁。

    InnoDB 会自动检测死锁,并回滚其中一个事务。

    排查方式:

    SHOW ENGINE INNODB STATUS\\G

    看 LATEST DETECTED DEADLOCK。

    优化方式:

    • 固定访问顺序;
    • 缩短事务时间;
    • 减少一次事务影响的数据量;
    • 更新条件走索引;
    • 避免用户交互或远程调用放在事务里。

    9. 面试怎么回答慢查询定位

    可以这样说:

    我会先看慢查询日志,
    通过 Query_time、Lock_time、Rows_examined 判断是执行慢还是等锁慢。

    如果 Rows_examined 很大,
    就用 EXPLAIN 看执行计划,
    重点看 type、key、rows、Extra,
    再优化索引和 SQL。

    如果 Lock_time 很大,
    就看 SHOW FULL PROCESSLIST,
    确认当前 SQL 是否在 Waiting for lock 或 metadata lock。

    然后用 SHOW ENGINE INNODB STATUS,
    或者 performance_schema.data_locks、data_lock_waits,
    定位哪个事务持有锁,哪个事务在等待。

    如果是死锁,
    看 LATEST DETECTED DEADLOCK,
    再从事务访问顺序、索引、事务范围几个方向优化。

    慢查询排查不是只看 SQL,也要看它是不是在等锁。执行慢看 EXPLAIN,等待慢看锁和事务。


    十三、面试回答模板

    如果面试官问:

    一张表查询很慢,你有哪些解决方案?

    可以这样回答:

    我会先用慢查询日志定位慢 SQL,
    再用 EXPLAIN 分析执行计划。

    重点看 type、key、rows、Extra。
    如果是全表扫描,就看是否缺索引或索引失效。
    如果 rows 很大,就考虑减少扫描范围。
    如果有 Using filesort 或 Using temporary,
    就看 ORDER BY、GROUP BY 是否能通过索引优化。

    然后从几个层面处理:
    第一,优化索引,比如 WHERE、JOIN、ORDER BY、GROUP BY 字段建合适索引,
    多个字段高频组合查询就建联合索引,并遵循最左匹配原则。

    第二,避免索引失效,
    比如不要左模糊、不要对索引列使用函数或表达式,
    避免隐式类型转换,注意 OR 条件。

    第三,优化 SQL,
    避免 SELECT *,只查必要字段,尽量使用覆盖索引减少回表。
    JOIN 时让小结果集驱动大结果集,
    并保证被驱动表关联字段有索引。
    高频 JOIN 可以通过冗余字段减少关联。

    第四,优化分页,
    深分页不要直接 LIMIT 大 offset,
    可以改成基于 id 的位置分页,
    或者先查 id 再回表。

    第五,优化表结构,
    大表可以考虑冷热分离、水平拆表,
    宽表可以考虑垂直拆分。
    但拆表不是第一选择,要先看 SQL 和索引。

    第六,引入缓存,
    热点数据可以放 Redis,
    读请求使用旁路缓存,
    写请求可以先更新数据库再删除缓存,
    同时处理缓存穿透、击穿和雪崩。

    如果单机数据库压力仍然大,
    再考虑主从复制、读写分离、分库分表等架构方案。

    最后可以补一句:

    MySQL 调优不是只会加索引,而是先定位瓶颈,再分层优化。核心是减少扫描行数、减少回表、减少排序和临时表,并降低数据库整体压力。


    十四、复习速记版

    最后给你一版背诵用口诀:

    先定位:慢日志 + EXPLAIN
    看计划:type、key、rows、Extra
    查索引:缺不缺、准不准、失没失效
    改 SQL:少查列、少回表、少 JOIN
    治分页:避开深 offset
    拆结构:大表拆、宽表拆、冷热分
    加缓存:热点进 Redis
    上架构:主从、读写分离、分库分表

    再记住这几个判断:

    key 为 NULL -> 没用索引
    rows 很大 -> 扫描多
    type = ALL -> 全表扫描
    Using filesort -> 额外排序
    Using temporary -> 用了临时表
    Using index -> 覆盖索引,好事


    赞(0)
    未经允许不得转载:171主机测评 » MySQL 性能调优面试复习:Explain、索引、慢查询、缓存和架构优化
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址