sql-optimization
Scannednpx machina-cli add skill chaterm/terminal-skills/sql-optimization --openclawSQL 优化与调优
概述
慢查询分析、执行计划、索引优化等通用 SQL 优化技能。
执行计划分析
MySQL EXPLAIN
-- 基础执行计划
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';
-- 详细执行计划
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';
-- JSON 格式
EXPLAIN FORMAT=JSON SELECT * FROM users WHERE email = 'test@example.com';
-- 关键字段解读
-- type: 访问类型 (system > const > eq_ref > ref > range > index > ALL)
-- key: 使用的索引
-- rows: 预估扫描行数
-- Extra: 额外信息 (Using index, Using filesort, Using temporary)
PostgreSQL EXPLAIN
-- 基础执行计划
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';
-- 实际执行
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';
-- 详细信息
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) SELECT * FROM users WHERE email = 'test@example.com';
-- 关键指标
-- Seq Scan: 全表扫描
-- Index Scan: 索引扫描
-- Bitmap Index Scan: 位图索引扫描
-- actual time: 实际执行时间
-- rows: 实际返回行数
索引优化
索引设计原则
-- 1. 选择性高的列优先
-- 选择性 = 不同值数量 / 总行数
SELECT COUNT(DISTINCT column) / COUNT(*) AS selectivity FROM table;
-- 2. 复合索引列顺序
-- 遵循最左前缀原则
-- 将选择性高的列放前面
CREATE INDEX idx_user ON users(status, created_at, name);
-- 3. 覆盖索引
-- 索引包含查询所需的所有列
CREATE INDEX idx_covering ON orders(user_id, status, amount);
SELECT user_id, status, amount FROM orders WHERE user_id = 1;
-- 4. 前缀索引(长字符串)
CREATE INDEX idx_email ON users(email(20));
索引使用检查
-- MySQL: 查看索引使用情况
SELECT * FROM sys.schema_index_statistics WHERE table_schema = 'mydb';
-- MySQL: 未使用的索引
SELECT * FROM sys.schema_unused_indexes WHERE object_schema = 'mydb';
-- PostgreSQL: 索引使用统计
SELECT indexrelname, idx_scan, idx_tup_read, idx_tup_fetch
FROM pg_stat_user_indexes
WHERE schemaname = 'public'
ORDER BY idx_scan;
索引失效场景
-- 1. 函数操作
-- 错误
SELECT * FROM users WHERE YEAR(created_at) = 2024;
-- 正确
SELECT * FROM users WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';
-- 2. 隐式类型转换
-- 错误 (phone 是 varchar)
SELECT * FROM users WHERE phone = 13800138000;
-- 正确
SELECT * FROM users WHERE phone = '13800138000';
-- 3. LIKE 前缀通配符
-- 错误
SELECT * FROM users WHERE name LIKE '%john%';
-- 正确
SELECT * FROM users WHERE name LIKE 'john%';
-- 4. OR 条件
-- 可能不走索引
SELECT * FROM users WHERE status = 1 OR name = 'john';
-- 改写为 UNION
SELECT * FROM users WHERE status = 1
UNION
SELECT * FROM users WHERE name = 'john';
-- 5. NOT IN / NOT EXISTS
-- 尽量避免,改用 LEFT JOIN
SELECT * FROM users WHERE id NOT IN (SELECT user_id FROM orders);
-- 改写
SELECT u.* FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.id IS NULL;
查询优化
SELECT 优化
-- 1. 只查询需要的列
-- 错误
SELECT * FROM users;
-- 正确
SELECT id, name, email FROM users;
-- 2. 避免 SELECT DISTINCT(考虑是否真的需要)
-- 检查是否有重复数据的根本原因
-- 3. 使用 LIMIT
SELECT * FROM logs ORDER BY created_at DESC LIMIT 100;
-- 4. 分页优化
-- 错误(大偏移量性能差)
SELECT * FROM users LIMIT 10000, 20;
-- 正确(使用游标分页)
SELECT * FROM users WHERE id > 10000 ORDER BY id LIMIT 20;
JOIN 优化
-- 1. 小表驱动大表
-- 确保 JOIN 顺序合理
-- 2. 确保 JOIN 列有索引
SELECT u.name, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id -- user_id 需要索引
WHERE u.status = 1;
-- 3. 避免过多 JOIN
-- 超过 3-4 个表的 JOIN 考虑拆分查询
-- 4. 使用 STRAIGHT_JOIN 强制顺序(MySQL)
SELECT STRAIGHT_JOIN u.name, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id;
子查询优化
-- 1. 将子查询改为 JOIN
-- 错误
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE amount > 100);
-- 正确
SELECT DISTINCT u.* FROM users u JOIN orders o ON u.id = o.user_id WHERE o.amount > 100;
-- 2. EXISTS 替代 IN(大数据集)
SELECT * FROM users u WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.amount > 100
);
慢查询分析
MySQL 慢查询
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
-- 查看配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
-- 分析慢查询日志
-- mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
PostgreSQL 慢查询
-- 配置 postgresql.conf
-- log_min_duration_statement = 1000 # 记录超过1秒的查询
-- 使用 pg_stat_statements
CREATE EXTENSION pg_stat_statements;
SELECT query, calls, total_time, mean_time, rows
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 10;
常见场景
场景 1:大表分页
-- 使用延迟关联
SELECT u.* FROM users u
JOIN (SELECT id FROM users ORDER BY created_at DESC LIMIT 10000, 20) t
ON u.id = t.id;
-- 使用游标分页
SELECT * FROM users
WHERE id > last_seen_id
ORDER BY id
LIMIT 20;
场景 2:批量更新
-- 分批更新,避免长事务
-- 每次更新 1000 条
UPDATE users SET status = 1 WHERE id BETWEEN 1 AND 1000;
UPDATE users SET status = 1 WHERE id BETWEEN 1001 AND 2000;
-- ...
-- 或使用存储过程循环
场景 3:统计查询优化
-- 使用汇总表
CREATE TABLE daily_stats (
date DATE PRIMARY KEY,
total_orders INT,
total_amount DECIMAL(10,2)
);
-- 定时任务更新汇总表
INSERT INTO daily_stats
SELECT DATE(created_at), COUNT(*), SUM(amount)
FROM orders
WHERE DATE(created_at) = CURDATE() - INTERVAL 1 DAY
GROUP BY DATE(created_at)
ON DUPLICATE KEY UPDATE
total_orders = VALUES(total_orders),
total_amount = VALUES(total_amount);
场景 4:锁优化
-- 减少锁范围
-- 错误:锁定整个表
SELECT * FROM users FOR UPDATE;
-- 正确:只锁定需要的行
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 使用乐观锁
UPDATE users SET balance = balance - 100, version = version + 1
WHERE id = 1 AND version = 5;
优化检查清单
| 检查项 | 说明 |
|---|---|
| 执行计划 | 是否全表扫描、是否使用索引 |
| 索引设计 | 选择性、覆盖索引、复合索引顺序 |
| 查询改写 | 避免 SELECT *、优化子查询 |
| 分页方式 | 大偏移量使用游标分页 |
| 批量操作 | 分批处理、避免长事务 |
| 锁粒度 | 减少锁范围、使用乐观锁 |
Source
git clone https://github.com/chaterm/terminal-skills/blob/main/database/sql-optimization/SKILL.mdView on GitHub Overview
这是一个面向 SQL 的优化与调优技能,覆盖慢查询分析、执行计划解读、以及索引设计与使用检查。你将学习如何在 MySQL 与 PostgreSQL 中诊断瓶颈、设计高效的索引,并通过实际场景将优化策略落地执行。
How This Skill Works
你在使用本技能时,首先针对目标查询获取并解读执行计划。对于 MySQL,使用 EXPLAIN、EXPLAIN ANALYZE 以及 EXPLAIN FORMAT=JSON,关注 type、key、rows 和 Extra 等字段。对于 PostgreSQL,使用 EXPLAIN、EXPLAIN ANALYZE,并可开启 ANALYZE、BUFFERS、FORMAT,以获取实际时间、缓冲区信息与行数。你将对比实际执行与估算的差异,判断是否存在全表扫描(Seq/ALL),索引扫描(Index/Range/Bitmap Index Scan)以及临时表或排序开销。随后,基于索引设计原则(高选择性、最左前缀、覆盖索引、前缀索引等)提出结构化的索引改造方案,并通过系统视图(如 MySQL 的 sys.schema_index_statistics、sys.schema_unused_indexes;PostgreSQL 的 pg_stat_user_indexes)评估现有索引的使用情况与命中率。最后,你会结合慢查询分析工具对查询节奏进行迭代优化,必要时进行查询改写、分页策略调整与锁粒度优化。
When to Use It
- 查询明显变慢或资源消耗异常时
- 对新建表/列或新增筛选条件后需要评估查询瓶颈时
- 进行跨表查询优化、联接顺序调整时
- 需要诊断并修复慢查询或高并发下的峰值负载时
- 计划对分页、批量更新或汇总统计进行性能改造时
Quick Start
- 1) 运行并收集目标查询的执行计划(MySQL: EXPLAIN / EXPLAIN ANALYZE / FORMAT=JSON;PostgreSQL: EXPLAIN / EXPLAIN ANALYZE,必要时开启 BUFFERS)。
- 2) 重点核对执行计划中的索引使用情况、是否存在全表扫描、Using temporary/Using filesort 等开销。
- 3) 基于高选择性与最左前缀原则设计或调整索引(覆盖索引优先、列顺序要素化)。
- 4) 重新执行查询并对比执行时间与行数,评估优化效果。
- 5) 如有慢查询,开启慢查询日志并结合 pg_stat_statements/sys 视图持续迭代优化。
Best Practices
- 避免 SELECT *,优先返回必要列以减少数据扫描
- 优先使用覆盖索引以避免回表
- 对复合索引遵循最左前缀原则,合理设置多列顺序
- 尽量避免函数运算、隐式类型转换和 LIKE 前缀以外的通配符
- 对大表分页尽量使用游标分页或主键范围分页,并在测试环境复现性能瓶颈
- 对慢查询进行分阶段优化,避免一次性改动引入新瓶颈
Example Use Cases
- 场景 A(MySQL):通过 EXPLAIN FORMAT=JSON 发现查询使用了 Using temporary 和 Using filesort,新增一个 cover 的复合索引 (status, created_at) 来覆盖筛选与排序,查询性能显著提升。
- 场景 B(PostgreSQL):利用 pg_stat_statements 识别高耗时查询,给 user_id 与 created_at 创建联合索引,结合 ANALYZE 后实际时间下降。
- 场景 C(大表分页):用游标分页替代大偏移量分页,或使用游标范围查询(WHERE id > last_id ORDER BY id LIMIT N),显著降低分页成本。
- 场景 D(批量更新):将大批量更新拆分为小批次执行,或用存储过程循环更新,避免长事务导致锁竞争与日志压力。
- 场景 E(汇总查询与覆盖索引):为汇总统计建立覆盖查询的索引,例如 orders(user_id, status, amount),并通过 SELECT user_id, status, amount FROM orders WHERE user_id=… 实现回表最小化。
Frequently Asked Questions
Related Skills
postgresql
chaterm/terminal-skills
PostgreSQL 数据库管理
mysql
chaterm/terminal-skills
MySQL 数据库管理与运维
tuning
chaterm/terminal-skills
--- name: tuning description: 系统调优 version: 1.0.0 author: terminal-skills tags: [performance, tuning, sysctl, kernel, optimization] --- # 系统调优 ## 概述 内核参数、文件系统、网络优化技能。 ## 内核参数调优 ### 内存管理 ```bash # /etc/sysctl.d/99-memory.conf # 减少交换倾向 vm.swappiness = 10 # 脏页刷新 vm.dirty_ratio = 20 vm.dirty_backg
Database Design Expert
martinholovsky/claude-skills-generator
Expert in database schema design with focus on normalization, indexing strategies, FTS optimization, and performance-oriented architecture for desktop applications
smart-sourcing
athola/claude-night-market
balancing accuracy with token efficiency.
python-performance
athola/claude-night-market
'Consult this skill for Python performance profiling and optimization.