Skip to content

MySQL高级

MySQL 高级阶段不是学习更多零散名词,而是把执行计划、索引、事务、锁、MVCC、慢 SQL 排查串成完整分析能力。

你最终要能回答这些问题:

  1. 一条 SQL 为什么慢。
  2. 为什么有索引但没用。
  3. 为什么接口偶尔卡住但 SQL 本身不慢。
  4. 为什么两个事务会互相等待甚至死锁。
  5. 为什么长事务会影响数据库。
  6. 如何优化而不是盲目加索引。

知识结构

mermaid
flowchart TD
    A["SQL 执行链路"] --> B["执行计划 EXPLAIN"]
    B --> C["索引设计"]
    C --> D["事务隔离"]
    D --> E["MVCC 快照读"]
    D --> F["锁和当前读"]
    E --> G["慢 SQL 和并发排查"]
    F --> G
    G --> H["容量治理和架构演进"]

高级阶段的核心不是背“优化口诀”,而是建立排查顺序。

一条慢 SQL 可能慢在哪里

mermaid
flowchart TD
    A["接口变慢"] --> B{"慢在哪里"}
    B -- "SQL 扫描多" --> C["索引 / 执行计划问题"]
    B -- "等待锁" --> D["事务和锁问题"]
    B -- "返回太多" --> E["分页 / 查询范围问题"]
    B -- "排序分组重" --> F["filesort / temporary"]
    B -- "数据库外部" --> G["网络 / 应用处理 / 连接池"]

不要一看到接口慢就认为“数据库没索引”。慢可能来自:

类型典型现象排查方向
扫描行数大EXPLAIN rows 很大索引、条件、返回范围
排序重Using filesort联合索引是否支持排序
临时表Using temporarygroup by、distinct、复杂排序
回表多命中二级索引但查大量列覆盖索引、减少返回字段
锁等待SQL 单独跑快,并发时慢innodb_trxdata_lock_waits
连接池耗尽数据库 QPS 不高但应用卡应用连接池、慢事务

慢 SQL 排查流程

mermaid
flowchart TD
    A["拿到慢 SQL"] --> B["确认业务目标和返回行数"]
    B --> C["执行 EXPLAIN"]
    C --> D["看 type / key / rows / Extra"]
    D --> E{"是否扫描过多"}
    E -- "是" --> F["检查索引和 SQL 写法"]
    E -- "否" --> G{"是否锁等待或事务问题"}
    G -- "是" --> H["查 innodb_trx / data_lock_waits"]
    G -- "否" --> I["检查排序、临时表、网络和应用处理"]
    F --> J["改 SQL 或索引后复测"]
    H --> J
    I --> J

排查时要保留证据:

  1. 原 SQL。
  2. 原执行计划。
  3. 表数据量和索引结构。
  4. 优化前后耗时。
  5. 优化前后执行计划变化。

没有这些证据,优化就很容易变成“改了但不知道是否真的有效”。

执行计划重点

EXPLAIN 要重点看:

字段问题
typeALLindexrangeref 还是 const
key实际用了哪个索引
rows预估扫描多少行
filtered过滤比例是否合理
Extra是否 filesort、temporary、Using index

示例:

sql
explain
select id, amount
from orders
where user_id = 1001
  and created_at >= '2026-01-01'
order by created_at desc
limit 20;

适合索引:

sql
alter table orders
add index idx_user_created(user_id, created_at);

为什么这样设计:

  1. user_id 是等值条件,适合作为联合索引前缀。
  2. created_at 既用于范围过滤,又用于排序。
  3. 如果索引顺序反过来 (created_at, user_id),按时间范围可能扫描更多用户的数据。

索引优化不是盲目加索引

mermaid
flowchart TD
    A["发现慢 SQL"] --> B["分析 where / order by / group by"]
    B --> C["设计候选联合索引"]
    C --> D["EXPLAIN 验证"]
    D --> E{"收益是否明显"}
    E -- "是" --> F["上线并观察慢日志"]
    E -- "否" --> G["改写 SQL 或调整业务查询方式"]

不要做:

sql
-- 看到字段就建单列索引
alter table orders add index idx_user_id(user_id);
alter table orders add index idx_status(status);
alter table orders add index idx_created_at(created_at);

更推荐根据 SQL 设计:

sql
-- 用户订单列表
alter table orders add index idx_user_created(user_id, created_at);

-- 后台按状态看最近订单
alter table orders add index idx_status_created(status, created_at);

索引过多的后果:

  1. 写入、更新、删除变慢。
  2. 磁盘占用增加。
  3. Buffer Pool 被更多索引页占用。
  4. 优化器选择计划更复杂。

事务和锁高级理解

事务和锁要分开理解:

概念主要解决
事务一组操作的原子性、一致性、隔离性、持久性
并发写入和当前读的冲突控制
MVCC普通读的一致性和读写并发
mermaid
flowchart TD
    A["并发访问同一数据"] --> B["普通 select"]
    A --> C["update / delete / for update"]
    B --> D["MVCC 快照读"]
    C --> E["行锁 / 间隙锁 / 临键锁"]
    D --> F["减少读写阻塞"]
    E --> G["保证修改顺序正确"]

线上排查时,要区分两类慢:

慢的类型特征处理
扫描慢单独执行也慢,rows优化索引、SQL、返回范围
等锁慢单独执行快,并发时慢查锁等待、事务持有时间、更新顺序

长事务问题

长事务是 MySQL 线上常见风险。

mermaid
flowchart TD
    A["事务开启后长时间不提交"] --> B["锁持有时间变长"]
    A --> C["Read View 长期存在"]
    B --> D["其他事务等待"]
    C --> E["undo log 无法及时清理"]
    D --> F["接口变慢或超时"]
    E --> F

常见来源:

  1. 事务里调用第三方接口。
  2. 批量处理一次更新太多行。
  3. 手工执行 begin 后忘记提交。
  4. 程序异常路径没有提交或回滚。
  5. 大查询在事务中执行太久。

排查:

sql
select trx_id,
       trx_state,
       trx_started,
       trx_mysql_thread_id,
       trx_query
from information_schema.innodb_trx;

死锁处理

死锁不是完全不能出现,但必须能定位和恢复。

mermaid
sequenceDiagram
    participant A as "事务 A"
    participant B as "事务 B"
    A->>A: "锁订单 1"
    B->>B: "锁订单 2"
    A->>B: "等待订单 2"
    B->>A: "等待订单 1"

查看最近死锁:

sql
show engine innodb status\G

业务改进:

  1. 同类资源按固定顺序加锁。
  2. 更新条件必须命中索引。
  3. 缩短事务时间。
  4. 批量更新分批处理。
  5. 捕获死锁异常后有限重试。

Java 重试示例:

java
for (int i = 0; i < 3; i++) {
    try {
        orderService.pay(orderId);
        break;
    } catch (DeadlockLoserDataAccessException ex) {
        if (i == 2) {
            throw ex;
        }
    }
}

重试只能用于幂等或可安全重试的业务。支付、扣库存等场景必须先设计幂等。

慢 SQL Demo

准备订单表:

sql
create table orders (
  id bigint primary key auto_increment,
  user_id bigint not null,
  status tinyint not null,
  created_at datetime not null,
  amount decimal(10, 2) not null,
  key idx_user_created (user_id, created_at)
) engine = InnoDB default charset = utf8mb4;

较好的查询:

sql
explain
select id, amount
from orders
where user_id = 1001
  and created_at >= '2026-01-01'
order by created_at desc
limit 20;

常见错误示例:

sql
-- 对索引列使用函数,可能导致索引效果变差
select *
from orders
where date(created_at) = '2026-01-01';

-- 更推荐写成范围查询
select *
from orders
where created_at >= '2026-01-01 00:00:00'
  and created_at < '2026-01-02 00:00:00';

另一个错误:

sql
-- 返回字段过多,可能产生大量回表
select *
from orders
where user_id = 1001
order by created_at desc
limit 20;

如果列表页只需要 idamountcreated_at,就不要查所有字段。

优化优先级

优先级动作原因
1缩小查询范围少查数据永远是最有效优化
2设计合理联合索引减少扫描和排序
3减少返回字段降低回表、网络和序列化成本
4控制事务范围降低锁等待和 undo 压力
5分页和归档防止大表无限增长
6缓存或架构改造在数据库优化到位后再考虑

不要把缓存当成第一选择。缓存可以降低读压力,但不能修复错误的表结构、错误的事务边界和不合理的索引。

生产建议

  1. 开启慢查询日志,记录慢 SQL。
  2. 所有核心查询上线前都要 EXPLAIN
  3. 大表 DDL 要评估风险,避免业务高峰执行。
  4. 事务内不要远程调用。
  5. 高频更新必须命中索引。
  6. 关键业务要有幂等设计,方便死锁和超时后重试。
  7. 定期清理无用索引和归档冷数据。
  8. 监控连接数、QPS、慢 SQL、锁等待、Buffer Pool 命中率。

小结

MySQL 高级能力可以总结成一条链路:

mermaid
flowchart TD
    A["会写 SQL"] --> B["能看执行计划"]
    B --> C["能解释索引为什么生效或失效"]
    C --> D["能区分扫描慢和锁等待"]
    D --> E["能设计短事务和安全重试"]
    E --> F["能结合业务做长期治理"]

真正的数据库优化不是背技巧,而是用证据分析:执行计划、扫描行数、索引结构、事务状态、锁等待、业务访问模式都要能对上。