1. SQL 执行流程
了解SQL语句的执行流程有助于进行性能优化。以下是MySQL处理SQL查询的完整流程:
flowchart TD
A[SQL语句] --> B[语法解析器]
B --> C[语义检查]
C --> D{查询缓存?}
D -->|命中| E[直接返回缓存结果]
D -->|未命中| F[查询优化器]
F --> G[生成执行计划]
G --> H[成本评估]
H --> I[选择最优计划]
I --> J[执行器]
J --> K[存储引擎]
K --> L[读取数据]
L --> M[返回结果]
M --> N[写入查询缓存]
E --> O[返回客户端]
N --> O
style A fill:#fff3e0,stroke:#e65100,stroke-width:2px
style F fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
style I fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
style O fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
各组件说明:
| 组件 |
功能 |
| 语法解析器 |
将SQL解析为语法树,检查语法错误 |
| 语义检查 |
检查表、列是否存在,权限是否足够 |
| 查询缓存 |
缓存查询结果,避免重复执行 |
| 查询优化器 |
生成多种执行计划,选择最优方案 |
| 执行器 |
调用存储引擎执行计划 |
| 存储引擎 |
实际读取/写入数据 (InnoDB/MyISAM) |
2. AI 查询优化器
-- 启用 AI 查询优化器
SET GLOBAL optimizer_switch = 'ai_optimization=on';
-- 使用 AI 优化提示
SELECT /*+ AI_OPTIMIZE */ * FROM users WHERE age > 25;
-- 分析查询计划(AI 增强版)
EXPLAIN ANALYZE SELECT * FROM users WHERE age > 25;
-- 查看 AI 优化建议
SELECT * FROM sys.ai_query_recommendations
WHERE query_hash = MD5('YOUR_QUERY');
-- 自动应用优化建议
CALL sys.apply_ai_optimizations('YOUR_QUERY_HASH');
2. 查询缓存优化
-- 启用查询缓存(MySQL 9.x 智能缓存)
SET GLOBAL query_cache_type = ON;
SET GLOBAL query_cache_size = 67108864; -- 64MB
-- 监控缓存命中率
SELECT * FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;
-- 查看缓存统计
SHOW STATUS LIKE 'Qcache%';
3. SQL 语句优化技巧(点击展开)
3.1 避免全表扫描
-- ❌ 全表扫描(效率低)
SELECT * FROM users WHERE age != 25;
SELECT * FROM users WHERE age NOT IN (20, 30);
-- ✅ 使用索引(效率高)
SELECT * FROM users WHERE age = 25;
-- ❌ 前导通配符(无法使用索引)
SELECT * FROM users WHERE name LIKE '%zhang%';
-- ✅ 后置通配符(可以使用索引)
SELECT * FROM users WHERE name LIKE 'zhang%';
-- ✅ 使用覆盖索引
SELECT name, age FROM users WHERE name LIKE 'zhang%';
3.2 优化 LIMIT 分页
-- ❌ 低效的分页(深度分页问题)
SELECT * FROM orders ORDER BY id LIMIT 100000, 10;
-- MySQL 会先扫描前 100010 条记录,然后丢弃前 100000 条
-- ✅ 优化方案1:基于主键的范围查询
SELECT * FROM orders
WHERE id > 100000
ORDER BY id
LIMIT 10;
-- ✅ 优化方案2:延迟关联
SELECT o.*
FROM orders o
INNER JOIN (
SELECT id FROM orders
ORDER BY id
LIMIT 100000, 10
) t ON o.id = t.id;
-- ✅ 优化方案3:记录上一页最后一条 ID
SELECT * FROM orders
WHERE id > 100000
ORDER BY id
LIMIT 10;
-- 假设上一页最后一条是 id=100000
3.3 避免 SELECT *
-- ❌ SELECT * 会读取所有列,增加 IO
SELECT * FROM orders WHERE customer_id = 100;
-- ✅ 只查询需要的列
SELECT order_no, amount, created_at
FROM orders WHERE customer_id = 100;
-- ✅ 使用覆盖索引时,效果更明显
CREATE INDEX idx_cover ON orders(customer_id, order_no, amount);
SELECT order_no, amount FROM orders WHERE customer_id = 100;
-- 完全在索引中完成,不需要回表
3.4 合理使用 JOIN
-- ❌ 大表 JOIN 小表(效率低)
SELECT o.*, c.name
FROM orders o -- 大表
LEFT JOIN customers c ON o.customer_id = c.id;
-- ✅ 使用小表驱动大表(EXPLAIN 中 type 最好)
SELECT o.*, c.name
FROM customers c -- 小表
LEFT JOIN orders o ON o.customer_id = c.id;
-- 确保 JOIN 条件有索引
CREATE INDEX idx_order_customer ON orders(customer_id);
CREATE INDEX idx_customer_id ON customers(id);
-- 避免多表 JOIN,适当拆分为多个查询
3.5 批量操作优化
-- ❌ 逐条插入(效率低)
INSERT INTO users (name) VALUES ('a');
INSERT INTO users (name) VALUES ('b');
INSERT INTO users (name) VALUES ('c');
-- ✅ 批量插入(效率高)
INSERT INTO users (name) VALUES
('a'), ('b'), ('c'), ('d'), ('e');
-- 批量插入建议每次 1000-5000 条
-- 禁用索引加速批量插入
ALTER TABLE users DISABLE KEYS;
INSERT INTO users (name) VALUES ...; -- 批量插入
ALTER TABLE users ENABLE KEYS;
-- 使用 LOAD DATA INFILE(最快)
LOAD DATA INFILE '/path/to/file.csv'
INTO TABLE users
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n'
(name);
4. 常用配置优化
4.1 缓冲区配置
-- InnoDB 缓冲池大小(建议设置为可用内存的 60-80%)
SET GLOBAL innodb_buffer_pool_size = 4294967296; -- 4GB
-- 缓冲池实例数量(多核CPU建议设置多个实例)
SET GLOBAL innodb_buffer_pool_instances = 4;
-- 日志文件大小
SET GLOBAL innodb_log_file_size = 1073741824; -- 1GB
-- 刷新日志频率(平衡性能和数据安全)
SET GLOBAL innodb_flush_log_at_trx_commit = 2;
-- 1: 每次事务刷新(最安全,默认)
-- 2: 每秒刷新(性能好,崩溃可能丢失1秒数据)
-- 0: 不立即刷新(最快,崩溃可能丢失大量数据)
4.2 连接配置
-- 最大连接数
SET GLOBAL max_connections = 500;
-- 连接超时时间
SET GLOBAL wait_timeout = 600; -- 10分钟
SET GLOBAL interactive_timeout = 600;
-- 线程缓存大小
SET GLOBAL thread_cache_size = 50;
-- 查询缓存大小
SET GLOBAL query_cache_size = 671>; -- 64MB
SET GLOBAL query_cache_limit = 1048576; -- 单个查询最大1MB
4.3 临时表和排序缓冲
-- 临时表大小
SET GLOBAL tmp_table_size = 134217728; -- 128MB
SET GLOBAL max_heap_table_size = 134217728;
-- 排序缓冲大小
SET GLOBAL sort_buffer_size = 4194304; -- 4MB
-- 读取缓冲大小
SET GLOBAL read_buffer_size = 2097152; -- 2MB
SET GLOBAL read_rnd_buffer_size = 4194304; -- 4MB
-- 查看配置
SHOW VARIABLES LIKE '%buffer%';
SHOW VARIABLES LIKE '%size%';
5. 慢查询分析
5.1 开启慢查询日志
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
SET GLOBAL long_query_time = 1; -- 超过1秒记录
SET GLOBAL log_queries_not_using_indexes = 'ON';
-- 查看慢查询
SELECT * FROM mysql.slow_log
ORDER BY start_time DESC
LIMIT 10;
-- 使用 mysqldumpslow 工具分析
-- mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
-- -s: 排序方式 (t=时间, c=次数)
-- -t: 显示前N条
5.2 使用 EXPLAIN 分析
-- EXPLAIN 分析查询计划
EXPLAIN SELECT * FROM users WHERE age > 25;
-- 关键字段解读:
-- type: 连接类型
-- system > const > eq_ref > ref > range > index > ALL
-- const/eq_ref/ref/range 都是可接受的
-- ALL 表示全表扫描,需要优化
-- key: 实际使用的索引
-- rows: 预计扫描的行数(越小越好)
-- Extra: 额外信息
-- Using index: 使用覆盖索引
-- Using filesort: 需要额外排序
-- Using temporary: 使用临时表
-- EXPLAIN ANALYZE(实际执行并分析)
EXPLAIN ANALYZE SELECT * FROM users WHERE age > 25;
6. 表结构优化
6.1 选择合适的数据类型
-- ❌ 使用过大的数据类型
CREATE TABLE t1 (
id INT, -- 4字节
phone VARCHAR(20)
);
-- ✅ 使用合适的数据类型
CREATE TABLE t2 (
id INT UNSIGNED, -- 无符号,支持更大范围
status TINYINT, -- 1字节,范围 -128~127
age TINYINT UNSIGNED, -- 0-255
views BIGINT UNSIGNED, -- 大访问量
price DECIMAL(10,2) -- 精确价格
);
-- 使用 ENUM 代替字符串
CREATE TABLE orders (
status ENUM('pending', 'processing', 'completed', 'cancelled')
);
-- 使用 NOT NULL
-- NULL 需要额外空间存储,且影响索引效率
6.2 垂直拆分和水平拆分
-- 垂直拆分:将大表拆分为多个小表
-- 原始表:users (id, name, email, avatar, bio, ...) 字段很多
-- 拆分后:
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
email VARCHAR(100)
);
CREATE TABLE user_profiles (
user_id INT PRIMARY KEY,
avatar VARCHAR(255),
bio TEXT,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- 水平拆分:按数据范围或哈希分表
-- 按月份分表(适用于日志、订单等)
CREATE TABLE orders_202401 (
LIKE orders
);
CREATE TABLE orders_202402 (
LIKE orders
);
-- 使用分区表(MySQL 原生支持)
CREATE TABLE orders_partitioned (
id INT,
created_at DATE,
amount DECIMAL(10,2)
) PARTITION BY RANGE (YEAR(created_at)) (
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION pmax VALUES LESS THAN MAXVALUE
);
7. 读写分离与负载均衡
sequenceDiagram
participant App as 应用服务器
participant Master as 主库 (Master)
participant Slave1 as 从库 (Slave1)
participant Slave2 as 从库 (Slave2)
App->>Master: 1. INSERT/UPDATE/DELETE
Master->>Master: 写入binlog
Master-->>App: 返回成功
par 异步复制
Master->>Slave1: 2. 传输binlog
Master->>Slave2: 2. 传输binlog
end
Slave1->>Slave1: 3. 重放binlog
Slave2->>Slave2: 3. 重放binlog
Note over App,Master: 读写分离: 写操作→主库, 读操作→从库
App->>Slave1: 4. SELECT查询
Slave1-->>App: 返回查询结果
App->>Slave2: 5. SELECT查询
Slave2-->>App: 返回查询结果
主从复制原理:
| 步骤 |
主库操作 |
从库操作 |
| 1. 日志记录 |
将写操作记录到 binlog |
- |
| 2. 日志传输 |
将 binlog 发送到从库 |
接收 binlog |
| 3. 日志重放 |
- |
执行 binlog 中的操作 |
| 4. 同步完成 |
- |
更新 relay log 位置 |
7.1 主从复制配置
-- 主库配置
[mysqld]
server-id = 1
log-bin = mysql-bin
binlog-do-db = myapp
-- 从库配置
[mysqld]
server-id = 2
relay-log = relay-bin
read-only = 1
-- 从库设置
CHANGE MASTER TO
MASTER_HOST='master_host',
MASTER_USER='repl_user',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=154;
START SLAVE;
SHOW SLAVE STATUS\G
7.2 连接池配置
-- 推荐使用连接池(推荐 HikariCP、Druid)
-- 连接池配置建议:
-- 最小空闲连接数
minimum-idle = 5
-- 最大连接数
maximum-pool-size = 20
-- 空闲连接超时(毫秒)
idle-timeout = 300000
-- 连接最大生命周期(毫秒)
max-lifetime = 1800000
-- 连接超时(毫秒)
connection-timeout = 30000
8. B+ 树角度优化建议
从 B+ 树的物理存储特性出发,深入理解索引底层原理,可以制定更有效的优化策略。
8.1 B+ 树高度与 IO 优化
flowchart LR
subgraph 理想情况 [理想情况:3 层 B+ 树]
R1[根节点] --> L1[中间层]
R1 --> L2[中间层]
L1 --> D1[叶子层]
L1 --> D2[叶子层]
L2 --> D3[叶子层]
L2 --> D4[叶子层]
end
subgraph 优化后 [优化目标]
T1["IO 次数 ≤ 3"]
T2["查询延迟 < 10ms"]
end
理想情况 --> 优化后
style 理想情况 fill:#e3f2fd,stroke:#1565c0
style 优化后 fill:#c8e6c9,stroke:#2e7d32
| 指标 |
计算公式 |
优化建议 |
| 非叶子节点容量 |
16KB / 12 字节 ≈ 1365 键/页 |
主键越短,每页存储键越多 |
| 2 层树最大容量 |
1365 行 |
小表无需担心索引深度 |
| 3 层树最大容量 |
1365² ≈ 186 万行 |
大多数表保持 3 层即可 |
| 4 层树最大容量 |
1365³ ≈ 25 亿行 |
超大表考虑分库分表 |
-- 优化策略 1:缩短主键长度
-- ❌ 长主键导致树高度增加
CREATE TABLE users_bad (
id VARCHAR(36) PRIMARY KEY, -- UUID,36 字节
name VARCHAR(100)
);
-- 每页存储键数:16KB / 36 字节 ≈ 455 个
-- 3 层树容量:455² ≈ 20 万行(容量大幅下降)
-- ✅ 短主键优化
CREATE TABLE users_good (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, -- 8 字节
name VARCHAR(100)
);
-- 每页存储键数:16KB / 8 字节 ≈ 2048 个
-- 3 层树容量:2048² ≈ 419 万行
-- 查看表的 B+ 树高度估算
SELECT
table_name,
table_rows,
round(data_length / 16384, 0) AS data_pages,
round(index_length / 16384, 0) AS index_pages,
CASE
WHEN table_rows < 1365 THEN '2 层'
WHEN table_rows < 1870000 THEN '3 层'
ELSE '4 层+'
END AS estimated_height
FROM information_schema.tables
WHERE table_schema = 'your_database'
ORDER BY table_rows DESC;
8.2 页分裂与写入优化
flowchart TD
A[插入新记录] --> B{目标页已满?}
B -->|否 | C[直接插入
IO: 1 次]
B -->|是 | D[页分裂]
D --> E[分配新页]
E --> F[移动 50% 数据]
F --> G[更新父节点指针]
G --> H[插入新记录]
H --> I[IO: 3-5 次]
style C fill:#c8e6c9,stroke:#2e7d32
style I fill:#ffcdd2,stroke:#c62828
style D fill:#fff3e0,stroke:#e65100
-- 页分裂性能影响:
-- - 直接插入:1 次 IO(写 1 个页)
-- - 页分裂:3-5 次 IO(分配新页 + 移动数据 + 更新指针)
-- - 连锁分裂:可能向上传播到根节点
-- 优化策略 2:使用自增主键,避免随机插入
-- ❌ 随机主键导致频繁页分裂
CREATE TABLE orders_bad (
id VARCHAR(36) PRIMARY KEY, -- UUID 随机值
amount DECIMAL(10, 2)
);
-- 插入顺序:uuid_abc, uuid_xyz, uuid_def ...
-- 每次插入可能触发页分裂
-- ✅ 自增主键顺序插入
CREATE TABLE orders_good (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
amount DECIMAL(10, 2)
);
-- 插入顺序:1, 2, 3, 4, 5...
-- 顺序写入,几乎不触发页分裂
-- 必须使用 UUID 时的优化方案
CREATE TABLE orders_uuid (
id BINARY(16) PRIMARY KEY, -- 压缩 UUID
amount DECIMAL(10, 2),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_time (created_at) -- 时间索引用于范围查询
);
-- 使用 UUID_TO_BIN(uuid, 1) 优化时间局部性
INSERT INTO orders_uuid (id, amount)
VALUES (UUID_TO_BIN(UUID(), 1), 100.00);
8.3 数据页利用率优化
| 场景 |
页利用率 |
IO 效率 |
优化方案 |
| 顺序插入 |
90-100% |
高 |
保持现状 |
| 随机插入 |
50-70% |
中 |
批量插入,定期 OPTIMIZE |
| 大量删除 |
30-50% |
低 |
OPTIMIZE TABLE 重建 |
| 频繁更新 |
60-80% |
中 |
使用行格式压缩 |
-- 优化策略 3:提高数据页利用率
-- 1. 批量插入,减少页分裂
-- ❌ 单条插入,每批 1000 条
INSERT INTO orders (amount) VALUES (100); -- 执行 1000 次
-- ✅ 批量插入,每批 1000 条
INSERT INTO orders (amount)
VALUES (100), (100), ..., (100); -- 1000 个值
-- 2. 定期优化表,整理碎片
OPTIMIZE TABLE orders;
-- 重建表,回收空间,提高页利用率
-- 3. 监控页利用率
SELECT
table_name,
round(data_length / 1024 / 1024, 2) AS data_mb,
round(data_free / 1024 / 1024, 2) AS free_mb,
round((data_free / (data_length + data_free)) * 100, 2) AS fragmentation_pct
FROM information_schema.tables
WHERE table_schema = 'your_database'
ORDER BY fragmentation_pct DESC;
-- 碎片率 > 20% 时建议优化
8.4 覆盖索引与叶子节点优化
flowchart TD
subgraph 回表查询 [回表查询:4 次 IO]
A1[查询] --> A2[非聚簇索引查找]
A2 --> A3[获取主键]
A3 --> A4[聚簇索引回表]
A4 --> A5[返回数据]
end
subgraph 覆盖索引 [覆盖索引:2 次 IO]
B1[查询] --> B2[覆盖索引查找]
B2 --> B3[直接返回]
end
style 回表查询 fill:#ffebee,stroke:#c62828
style 覆盖索引 fill:#e8f5e9,stroke:#2e7d32
-- 优化策略 4:使用覆盖索引减少回表
-- 场景:查询用户订单统计
-- ❌ 需要回表
SELECT customer_id, amount, created_at
FROM orders WHERE customer_id = 100;
-- 执行过程:
-- 1. 在 idx_customer 中找到 customer_id=100 的记录
-- 2. 获取主键 id
-- 3. 回表到聚簇索引获取 amount, created_at
-- 假设命中 100 条记录,需要回表 100 次
-- ✅ 覆盖索引,无需回表
CREATE INDEX idx_cover ON orders(customer_id, amount, created_at);
SELECT customer_id, amount, created_at
FROM orders WHERE customer_id = 100;
-- 执行过程:
-- 1. 在 idx_cover 中直接获取所有数据
-- 2. 无需回表,IO 减少 50-90%
-- 覆盖索引设计原则:
-- 1. WHERE 条件列放在最左
-- 2. SELECT 列包含在索引中
-- 3. 考虑索引大小与收益比
-- 验证覆盖索引
EXPLAIN SELECT customer_id, amount FROM orders WHERE customer_id = 100;
-- Extra: Using index(表示使用覆盖索引)
8.5 B+ 树优化最佳实践总结
| 优化方向 |
核心策略 |
预期收益 |
| 降低树高度 |
缩短主键长度,使用 INT/BIGINT |
IO 减少 25-50% |
| 减少页分裂 |
自增主键,顺序插入 |
写入性能提升 3-5 倍 |
| 提高页利用率 |
批量操作,定期 OPTIMIZE |
空间节省 30-50% |
| 减少回表 |
覆盖索引,延迟关联 |
查询性能提升 5-10 倍 |
| 预分配空间 |
设置合适的初始 extent 大小 |
减少分配开销 |
9. 并发控制
-- 查看当前锁情况
SELECT * FROM performance_schema.data_locks;
-- 设置事务隔离级别(支持新的隔离级别)
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 乐观锁(改进的版本控制)
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 1 AND version = 5;
-- 查看行锁等待
SELECT * FROM performance_schema.events_waits_current
WHERE EVENT_NAME LIKE '%lock%';
10. 性能监控
-- 实时性能监控
SELECT * FROM sys.statements_with_runtimes_in_95th_percentile;
-- 查看慢查询(AI 分析)
SELECT * FROM sys.statements_with_temp_tables
ORDER BY tmp_tables DESC;
-- 表空间使用情况
SELECT * FROM information_schema.innodb_tablespaces;
-- AI 性能诊断报告
CALL sys.generate_performance_report('your_database');