当前位置: 首页 > news >正文

慢查询优化

文章目录

    • 一、什么是慢查询
      • 1.1 定义
      • 1.2 慢查询的影响
      • 1.3 性能基准
    • 二、如何发现慢查询
      • 2.1 方法1:慢查询日志(推荐)
        • 开启慢查询日志
        • 分析慢查询日志
      • 2.2 方法2:performance_schema(MySQL 5.6+)
      • 2.3 方法3:sys系统库(MySQL 5.7+)
      • 2.4 方法4:SHOW PROCESSLIST
      • 2.5 方法5:应用层监控
        • 方式1:日志记录
        • 方式2:APM工具
        • 方式3:数据库中间件
    • 三、慢查询的常见原因
      • 3.1 索引问题(80%的慢查询原因)
        • 1. 没有索引
        • 2. 索引失效
        • 3. 索引选择不当
      • 3.2 SQL写法问题
        • 1. SELECT *
        • 2. 深度分页
        • 3. 大批量操作
      • 3.3 数据量问题
        • 1. 单表数据量过大
        • 2. 数据分布不均
      • 3.4 锁和并发问题
        • 1. 锁等待
        • 2. 长事务
      • 3.5 JOIN问题
        • 1. 大表JOIN
        • 2. JOIN列没有索引
      • 3.6 服务器资源问题
        • 1. CPU瓶颈
        • 2. 内存不足
        • 3. 磁盘IO瓶颈
    • 四、系统的排查流程
      • 4.1 五步排查法
      • 4.2 详细排查步骤
        • Step 1:获取慢SQL
        • Step 2:EXPLAIN分析
        • Step 3:定位问题
        • Step 4:制定方案
        • Step 5:验证效果
    • 五、优化方法详解
      • 5.1 索引优化
        • 优化1:创建合适的索引
        • 优化2:联合索引顺序
        • 优化3:删除冗余索引
      • 5.2 SQL改写优化
        • 优化1:避免SELECT *
        • 优化2:深度分页优化
        • 优化3:IN优化
        • 优化4:OR改写为UNION
        • 优化5:子查询改写为JOIN
      • 5.3 表结构优化
        • 优化1:选择合适的数据类型
        • 优化2:字段拆分
        • 优化3:表分区
      • 5.4 查询优化
        • 优化1:避免全表扫描
        • 优化2:使用LIMIT限制结果
        • 优化3:批量操作
      • 5.5 缓存优化
        • 优化1:应用层缓存
        • 优化2:查询缓存(Query Cache)
      • 5.6 读写分离和分库分表
        • 优化1:主从复制+读写分离
        • 优化2:分库分表
    • 六、实战优化案例
      • 6.1 案例1:电商订单查询优化
        • 问题描述
        • 原始SQL
        • 排查过程
      • 6.2 案例2:报表统计优化
        • 问题描述
        • 原始SQL
        • 排查过程
        • 优化方案
      • 6.3 案例3:JOIN查询优化
        • 问题描述
        • 原始SQL
        • EXPLAIN分析
        • 优化方案
    • 七、监控与预防
      • 7.1 慢查询监控
        • 1. 数据库层监控
        • 2. 应用层监控
        • 3. 监控平台
      • 7.2 SQL审核
        • 上线前审核
        • SQL审核工具
      • 7.3 数据库规范
        • 索引规范
        • SQL规范
        • 表设计规范
    • 八、优化效果评估
      • 8.1 性能指标
      • 8.2 对比测试
    • 九、常见问题FAQ
      • Q1:为什么添加了索引还是慢?
      • Q2:数据量不大为什么还慢?
      • Q3:如何选择优化优先级?
      • Q4:索引是不是越多越好?
      • Q5:什么时候需要分表?
    • 总结
      • 慢查询优化核心思路
      • 优化方法速查
      • 最佳实践


一、什么是慢查询

1.1 定义

慢查询(Slow Query) 是指执行时间超过指定阈值的SQL语句。

判断标准

-- 查看慢查询阈值(默认10秒)
SHOW VARIABLES LIKE 'long_query_time';-- 临时设置为1秒
SET GLOBAL long_query_time = 1;-- 永久配置(my.cnf)
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1  # 记录未使用索引的查询

1.2 慢查询的影响

影响维度具体表现
用户体验页面响应慢,操作卡顿,超时报错
系统性能CPU飙升,内存占用高,磁盘IO繁忙
数据库压力连接池耗尽,锁等待,主从延迟
业务影响订单失败,支付超时,用户流失
成本增加需要扩容硬件,增加服务器

1.3 性能基准

合理的查询时间

⭐⭐⭐⭐⭐ < 10ms    极佳(简单主键查询)
⭐⭐⭐⭐   < 50ms    优秀(索引查询)
⭐⭐⭐     < 100ms   良好(复杂查询)
⭐⭐       < 500ms   可接受(报表查询)
⭐         < 1s      需优化
❌         > 1s      严重问题

二、如何发现慢查询

2.1 方法1:慢查询日志(推荐)

开启慢查询日志
-- 1. 查看慢查询日志配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';-- 2. 临时开启(重启失效)
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = ON;-- 3. 永久配置(my.cnf或my.ini)
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1
min_examined_row_limit = 100  # 至少扫描100行才记录
分析慢查询日志
# 使用mysqldumpslow工具分析
# 按查询时间排序,显示前10条
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log# 按查询次数排序
mysqldumpslow -s c -t 10 /var/log/mysql/slow.log# 按平均查询时间排序
mysqldumpslow -s at -t 10 /var/log/mysql/slow.log# 按锁定时间排序
mysqldumpslow -s l -t 10 /var/log/mysql/slow.log# 过滤特定数据库
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log | grep 'database_name'

慢查询日志示例

# Time: 2024-01-15T10:30:45.123456Z
# User@Host: root[root] @ localhost []  Id: 12345
# Query_time: 5.234567  Lock_time: 0.000123 Rows_sent: 100  Rows_examined: 1000000
SET timestamp=1705318245;
SELECT * FROM orders WHERE DATE(create_time) = '2024-01-15';

字段解读

  • Query_time: 5.234567 - 查询耗时5.23秒
  • Lock_time: 0.000123 - 锁等待0.12毫秒
  • Rows_sent: 100 - 返回100行
  • Rows_examined: 1000000 - 扫描100万行(关键指标!

2.2 方法2:performance_schema(MySQL 5.6+)

-- 1. 开启performance_schema(需重启)
[mysqld]
performance_schema = ON-- 2. 查询最慢的SQL(按总执行时间)
SELECT DIGEST_TEXT AS query,COUNT_STAR AS exec_count,AVG_TIMER_WAIT / 1000000000000 AS avg_time_sec,SUM_TIMER_WAIT / 1000000000000 AS total_time_sec,MIN_TIMER_WAIT / 1000000000000 AS min_time_sec,MAX_TIMER_WAIT / 1000000000000 AS max_time_sec
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;-- 3. 查询平均时间最长的SQL
SELECT DIGEST_TEXT AS query,COUNT_STAR AS exec_count,AVG_TIMER_WAIT / 1000000000000 AS avg_time_sec
FROM performance_schema.events_statements_summary_by_digest
ORDER BY AVG_TIMER_WAIT DESC
LIMIT 10;-- 4. 查询当前正在执行的SQL
SELECT t.PROCESSLIST_ID,t.PROCESSLIST_USER,t.PROCESSLIST_HOST,t.PROCESSLIST_DB,t.PROCESSLIST_COMMAND,t.PROCESSLIST_TIME,t.PROCESSLIST_STATE,t.PROCESSLIST_INFO
FROM performance_schema.threads t
WHERE t.PROCESSLIST_COMMAND != 'Sleep'
ORDER BY t.PROCESSLIST_TIME DESC;

2.3 方法3:sys系统库(MySQL 5.7+)

-- 1. 查询执行时间最长的SQL
SELECT * FROM sys.statement_analysis
ORDER BY avg_latency DESC
LIMIT 10;-- 2. 查询全表扫描的SQL
SELECT * FROM sys.statements_with_full_table_scans
ORDER BY total_latency DESC
LIMIT 10;-- 3. 查询临时表使用情况
SELECT * FROM sys.statements_with_temp_tables
ORDER BY total_latency DESC
LIMIT 10;-- 4. 查询排序操作
SELECT * FROM sys.statements_with_sorting
ORDER BY total_latency DESC
LIMIT 10;-- 5. 查询未使用索引的SQL
SELECT query,exec_count,sys.format_time(total_latency) AS total_latency,rows_sent,rows_examined,rows_sent_avg,rows_examined_avg
FROM sys.statements_with_full_table_scans
WHERE db = 'your_database'
ORDER BY total_latency DESC
LIMIT 10;

2.4 方法4:SHOW PROCESSLIST

-- 实时查看正在执行的查询
SHOW FULL PROCESSLIST;-- 查找执行时间超过5秒的查询
SELECT id,user,host,db,command,time,state,info
FROM information_schema.processlist
WHERE time > 5
AND command != 'Sleep'
ORDER BY time DESC;-- 杀掉慢查询
KILL QUERY 12345;  -- 12345是进程ID

2.5 方法5:应用层监控

方式1:日志记录
// Java示例
long startTime = System.currentTimeMillis();
try {// 执行SQLList<User> users = userMapper.selectByAge(20);
} finally {long endTime = System.currentTimeMillis();long duration = endTime - startTime;if (duration > 1000) {  // 超过1秒记录log.warn("慢查询警告: 耗时{}ms, SQL: {}", duration, sql);}
}
方式2:APM工具
  • SkyWalking:链路追踪
  • Pinpoint:性能监控
  • Arthas:在线诊断
  • CAT(美团):实时监控
方式3:数据库中间件
  • ShardingSphere:SQL审计
  • MyCAT:慢查询统计
  • ProxySQL:查询路由和监控

三、慢查询的常见原因

3.1 索引问题(80%的慢查询原因)

1. 没有索引
-- ❌ 全表扫描
SELECT * FROM users WHERE email = 'test@example.com';-- ✅ 添加索引
CREATE INDEX idx_email ON users(email);
2. 索引失效
-- ❌ 函数导致索引失效
SELECT * FROM orders WHERE YEAR(create_time) = 2024;-- ✅ 改写SQL
SELECT * FROM orders 
WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01';-- 详见:数据库索引失效场景详解.md
3. 索引选择不当
-- 优化器选错索引
-- 使用 FORCE INDEX 强制指定
SELECT * FROM orders FORCE INDEX(idx_create_time)
WHERE create_time > '2024-01-01';

3.2 SQL写法问题

1. SELECT *
-- ❌ 查询所有列
SELECT * FROM orders WHERE user_id = 123;-- ✅ 只查询需要的列
SELECT id, order_no, amount FROM orders WHERE user_id = 123;
2. 深度分页
-- ❌ 偏移量过大
SELECT * FROM orders ORDER BY id LIMIT 1000000, 10;-- ✅ 使用子查询优化
SELECT * FROM orders 
WHERE id >= (SELECT id FROM orders ORDER BY id LIMIT 1000000, 1)
LIMIT 10;-- ✅ 使用游标分页
SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 10;
3. 大批量操作
-- ❌ 一次插入10万条
INSERT INTO logs VALUES (1,...), (2,...), ... (100000,...);-- ✅ 分批插入(每次1000条)
INSERT INTO logs VALUES (1,...), ... (1000,...);
-- 循环100次

3.3 数据量问题

1. 单表数据量过大
建议:
- 单表 < 500万行:正常使用
- 500万 ~ 2000万:需优化索引和查询
- > 2000万:考虑分表

解决方案

-- 水平分表(按时间)
CREATE TABLE orders_2024_01 LIKE orders;
CREATE TABLE orders_2024_02 LIKE orders;-- 水平分表(按用户ID)
CREATE TABLE orders_0 LIKE orders;  -- user_id % 10 = 0
CREATE TABLE orders_1 LIKE orders;  -- user_id % 10 = 1
2. 数据分布不均
-- 查询倾斜数据
SELECT status, COUNT(*) AS cnt
FROM orders
GROUP BY status;-- 结果:
-- status=0: 1000万(95%)
-- status=1: 50万(5%)-- 查询status=0会很慢(即使有索引)

3.4 锁和并发问题

1. 锁等待
-- 查看锁等待
SELECT * FROM sys.innodb_lock_waits;-- 查看当前锁信息
SELECT * FROM performance_schema.data_locks;-- 查看锁等待超时时间
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';  -- 默认50秒
2. 长事务
-- 查找长事务(执行超过60秒)
SELECT * FROM information_schema.innodb_trx
WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60;-- 查看事务详情
SELECT trx_id,trx_state,trx_started,trx_requested_lock_id,trx_wait_started,trx_weight,trx_mysql_thread_id,trx_query
FROM information_schema.innodb_trx;

3.5 JOIN问题

1. 大表JOIN
-- ❌ 两个大表直接JOIN
SELECT *
FROM orders o  -- 1000万
INNER JOIN order_details od ON o.id = od.order_id  -- 5000万
WHERE o.user_id = 123;-- ✅ 先过滤再JOIN
SELECT *
FROM (SELECT * FROM orders WHERE user_id = 123) o
INNER JOIN order_details od ON o.id = od.order_id;
2. JOIN列没有索引
-- ❌ 关联列无索引
SELECT * FROM orders o
INNER JOIN users u ON o.user_email = u.email;-- ✅ 添加索引
CREATE INDEX idx_email ON users(email);
CREATE INDEX idx_user_email ON orders(user_email);

3.6 服务器资源问题

1. CPU瓶颈
# 查看CPU使用率
top
# 或
vmstat 1# 查看MySQL进程CPU占用
ps aux | grep mysql
2. 内存不足
-- 查看内存配置
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';  -- InnoDB缓冲池
SHOW VARIABLES LIKE 'key_buffer_size';          -- MyISAM缓冲-- 查看缓存命中率
SHOW STATUS LIKE 'Innodb_buffer_pool_read%';
3. 磁盘IO瓶颈
# 查看磁盘IO
iostat -x 1# 关键指标:
# %util: 磁盘利用率(>80%表示繁忙)
# await: 平均等待时间(>10ms需关注)

四、系统的排查流程

4.1 五步排查法

Step 1: 发现慢查询↓
Step 2: 分析执行计划(EXPLAIN)↓
Step 3: 定位具体原因↓
Step 4: 制定优化方案↓
Step 5: 验证优化效果

4.2 详细排查步骤

Step 1:获取慢SQL
-- 从慢查询日志获取
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log-- 或从performance_schema获取
SELECT DIGEST_TEXT AS query,COUNT_STAR AS exec_count,AVG_TIMER_WAIT / 1000000000000 AS avg_time_sec,SUM_TIMER_WAIT / 1000000000000 AS total_time_sec
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;

Step 2:EXPLAIN分析
-- 执行EXPLAIN
EXPLAIN SELECT * FROM orders WHERE DATE(create_time) = '2024-01-15';-- 关键指标:
-- 1. type: ALL → 全表扫描(需优化)
-- 2. key: NULL → 未使用索引
-- 3. rows: 1000000 → 扫描100万行
-- 4. Extra: Using filesort → 需要排序优化

详细分析见:EXPLAIN执行计划详解.md


Step 3:定位问题

检查清单

检查项检查方法问题表现
索引SHOW INDEX FROM tablekey=NULL, type=ALL
表结构SHOW CREATE TABLE table字段类型、字符集
数据量SELECT COUNT(*) FROM table单表过大
数据分布SELECT col, COUNT(*) GROUP BY col数据倾斜
锁等待SHOW ENGINE INNODB STATUSLock wait timeout
连接数SHOW PROCESSLISTToo many connections
服务器资源top, iostat, freeCPU/内存/IO瓶颈

Step 4:制定方案

决策树

问题类型?
├─ 索引问题
│  ├─ 无索引 → 创建索引
│  ├─ 索引失效 → 改写SQL
│  └─ 索引选择错误 → 优化索引或强制索引
│
├─ SQL写法
│  ├─ SELECT * → 只查需要的列
│  ├─ 深度分页 → 改用游标分页
│  └─ 大批量操作 → 分批处理
│
├─ 数据量
│  ├─ 单表过大 → 分表
│  └─ 返回结果过多 → 添加LIMIT
│
├─ JOIN问题
│  ├─ 大表JOIN → 先过滤再JOIN
│  └─ JOIN列无索引 → 添加索引
│
└─ 服务器资源├─ CPU瓶颈 → 优化SQL或扩容├─ 内存不足 → 调整缓冲池或扩容└─ IO瓶颈 → 使用SSD或优化查询

Step 5:验证效果
-- 1. 执行优化后的SQL,记录耗时
SET profiling = 1;
SELECT ...;  -- 优化后的SQL
SHOW PROFILES;-- 2. 对比EXPLAIN结果
EXPLAIN 优化前的SQL;
EXPLAIN 优化后的SQL;-- 3. 压力测试
-- 使用sysbench或mysqlslap测试-- 4. 监控线上效果
-- 观察慢查询日志、QPS、响应时间

五、优化方法详解

5.1 索引优化

优化1:创建合适的索引
-- 场景:WHERE条件列
CREATE INDEX idx_user_id ON orders(user_id);-- 场景:ORDER BY排序列
CREATE INDEX idx_create_time ON orders(create_time);-- 场景:JOIN关联列
CREATE INDEX idx_order_id ON order_details(order_id);-- 场景:GROUP BY分组列
CREATE INDEX idx_status ON orders(status);-- 场景:覆盖索引(包含查询的所有列)
CREATE INDEX idx_user_order_amount ON orders(user_id, order_no, amount);
优化2:联合索引顺序
-- 高频查询
SELECT * FROM orders 
WHERE status = 1 AND user_id = 123 AND create_time > '2024-01-01';-- ❌ 错误顺序(范围查询在前)
CREATE INDEX idx_wrong ON orders(create_time, status, user_id);-- ✅ 正确顺序(等值查询在前,范围查询在后)
CREATE INDEX idx_right ON orders(status, user_id, create_time);

排序原则

  1. 等值查询(=) > 范围查询(>、<、BETWEEN)
  2. 高频查询列 > 低频查询列
  3. 高区分度列 > 低区分度列
优化3:删除冗余索引
-- 查看表的所有索引
SHOW INDEX FROM orders;-- 冗余情况
CREATE INDEX idx_a ON orders(user_id);
CREATE INDEX idx_ab ON orders(user_id, status);
-- idx_a 是冗余的,可以删除-- 删除冗余索引
ALTER TABLE orders DROP INDEX idx_a;-- 使用sys库查找冗余索引(MySQL 5.7+)
SELECT * FROM sys.schema_redundant_indexes;

5.2 SQL改写优化

优化1:避免SELECT *
-- ❌ 查询所有列
SELECT * FROM orders WHERE user_id = 123;-- ✅ 只查询需要的列
SELECT id, order_no, amount, status FROM orders WHERE user_id = 123;-- 优势:
-- 1. 减少网络传输
-- 2. 可能使用覆盖索引
-- 3. 减少内存占用
优化2:深度分页优化
-- ❌ 偏移量过大(扫描+跳过100万行)
SELECT * FROM orders ORDER BY id LIMIT 1000000, 10;-- ✅ 方案1:使用子查询(延迟关联)
SELECT o.*
FROM orders o
INNER JOIN (SELECT id FROM orders ORDER BY id LIMIT 1000000, 10
) t ON o.id = t.id;-- ✅ 方案2:使用游标(记录上次位置)
SELECT * FROM orders 
WHERE id > 1000000  -- 上次查询的最后一条记录ID
ORDER BY id 
LIMIT 10;-- ✅ 方案3:使用ES等搜索引擎
-- 数据同步到Elasticsearch,使用scroll API分页
优化3:IN优化
-- ❌ IN中元素过多(>1000)
SELECT * FROM orders WHERE user_id IN (1, 2, 3, ..., 10000);-- ✅ 方案1:分批查询
SELECT * FROM orders WHERE user_id IN (1, 2, ..., 1000);
SELECT * FROM orders WHERE user_id IN (1001, 1002, ..., 2000);-- ✅ 方案2:使用临时表
CREATE TEMPORARY TABLE tmp_users (user_id INT);
INSERT INTO tmp_users VALUES (1), (2), ..., (10000);
SELECT o.* FROM orders o
INNER JOIN tmp_users t ON o.user_id = t.user_id;
优化4:OR改写为UNION
-- ❌ OR可能导致索引失效
SELECT * FROM orders WHERE status = 1 OR amount > 1000;-- ✅ 改写为UNION ALL
SELECT * FROM orders WHERE status = 1
UNION ALL
SELECT * FROM orders WHERE amount > 1000 AND status != 1;
优化5:子查询改写为JOIN
-- ❌ 关联子查询(N+1问题)
SELECT u.id,u.name,(SELECT COUNT(*) FROM orders WHERE user_id = u.id) AS order_count
FROM users u;-- ✅ 改写为JOIN
SELECT u.id,u.name,COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name;

5.3 表结构优化

优化1:选择合适的数据类型
-- ❌ 使用过大的类型
CREATE TABLE users (id BIGINT,              -- INT就够用age TINYINT UNSIGNED,   -- ✅ 正确status VARCHAR(255),    -- 只需要CHAR(1)amount DECIMAL(20,2)    -- DECIMAL(10,2)就够
);-- ✅ 优化后
CREATE TABLE users (id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,age TINYINT UNSIGNED,status CHAR(1),amount DECIMAL(10,2)
);-- 优势:
-- 1. 减少存储空间
-- 2. 提升索引效率
-- 3. 减少内存占用
优化2:字段拆分
-- ❌ 大字段和常用字段混在一起
CREATE TABLE articles (id INT PRIMARY KEY,title VARCHAR(200),author VARCHAR(50),content TEXT,         -- 大字段,但不常查询view_count INT
);-- ✅ 垂直拆分
CREATE TABLE articles (id INT PRIMARY KEY,title VARCHAR(200),author VARCHAR(50),view_count INT
);CREATE TABLE article_contents (article_id INT PRIMARY KEY,content TEXT,FOREIGN KEY (article_id) REFERENCES articles(id)
);-- 优势:减少IO,提升常用查询性能
优化3:表分区
-- 按时间分区(适合历史数据查询)
CREATE TABLE orders (id INT,user_id INT,amount DECIMAL(10,2),create_time DATETIME
)
PARTITION BY RANGE (YEAR(create_time)) (PARTITION p2022 VALUES LESS THAN (2023),PARTITION p2023 VALUES LESS THAN (2024),PARTITION p2024 VALUES LESS THAN (2025),PARTITION p_future VALUES LESS THAN MAXVALUE
);-- 查询时只扫描特定分区
SELECT * FROM orders WHERE create_time >= '2024-01-01';
-- 只扫描p2024分区

5.4 查询优化

优化1:避免全表扫描
-- ❌ 没有WHERE条件
SELECT * FROM orders;-- ✅ 添加WHERE和LIMIT
SELECT * FROM orders WHERE status = 1 LIMIT 1000;
优化2:使用LIMIT限制结果
-- ❌ 返回全部结果
SELECT * FROM orders WHERE status = 1;-- ✅ 限制返回数量
SELECT * FROM orders WHERE status = 1 LIMIT 100;-- ✅ EXISTS只需判断存在性
SELECT EXISTS(SELECT 1 FROM orders WHERE user_id = 123) AS has_order;
优化3:批量操作
-- ❌ 逐条插入
INSERT INTO logs (user_id, action) VALUES (1, 'login');
INSERT INTO logs (user_id, action) VALUES (2, 'logout');
-- 执行1000次...-- ✅ 批量插入
INSERT INTO logs (user_id, action) VALUES
(1, 'login'),
(2, 'logout'),
...
(1000, 'view');-- ✅ 批量更新
UPDATE orders SET status = 1 WHERE id IN (1, 2, 3, ..., 1000);

5.5 缓存优化

优化1:应用层缓存
// Redis缓存示例
public User getUserById(Long id) {// 1. 先查缓存String cacheKey = "user:" + id;User user = redisTemplate.opsForValue().get(cacheKey);if (user != null) {return user;  // 缓存命中}// 2. 缓存未命中,查数据库user = userMapper.selectById(id);// 3. 写入缓存if (user != null) {redisTemplate.opsForValue().set(cacheKey, user, 1, TimeUnit.HOURS);}return user;
}
优化2:查询缓存(Query Cache)
-- 注意:MySQL 8.0已移除查询缓存-- MySQL 5.7及以下
-- 开启查询缓存
SET GLOBAL query_cache_type = ON;
SET GLOBAL query_cache_size = 67108864;  -- 64MB-- 查看缓存状态
SHOW VARIABLES LIKE 'query_cache%';
SHOW STATUS LIKE 'Qcache%';

5.6 读写分离和分库分表

优化1:主从复制+读写分离
// 使用动态数据源
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {String value() default "master";
}// 写操作走主库
@DataSource("master")
public void createOrder(Order order) {orderMapper.insert(order);
}// 读操作走从库
@DataSource("slave")
public Order getOrderById(Long id) {return orderMapper.selectById(id);
}
优化2:分库分表
// 使用ShardingSphere实现分库分表
// 按user_id取模分表
spring.shardingsphere.rules.sharding.tables.orders.actual-data-nodes=ds0.orders_$->{0..9}
spring.shardingsphere.rules.sharding.tables.orders.table-strategy.standard.sharding-column=user_id
spring.shardingsphere.rules.sharding.tables.orders.table-strategy.standard.sharding-algorithm-name=orders-inlinespring.shardingsphere.rules.sharding.sharding-algorithms.orders-inline.type=INLINE
spring.shardingsphere.rules.sharding.sharding-algorithms.orders-inline.props.algorithm-expression=orders_$->{user_id % 10}

六、实战优化案例

6.1 案例1:电商订单查询优化

问题描述
业务场景:用户查看订单列表
问题:订单表1000万数据,查询超时
SQL执行时间:8秒
原始SQL
SELECT * FROM orders 
WHERE user_id = 12345 
ORDER BY create_time DESC 
LIMIT 10;
排查过程

Step 1:EXPLAIN分析

EXPLAIN SELECT * FROM orders 
WHERE user_id = 12345 
ORDER BY create_time DESC 
LIMIT 10;

结果

+----+-------------+--------+------+---------------+------+---------+------+----------+-----------------------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows     | Extra                       |
+----+-------------+--------+------+---------------+------+---------+------+----------+-----------------------------+
|  1 | SIMPLE      | orders | ALL  | NULL          | NULL | NULL    | NULL | 10000000 | Using where; Using filesort |
+----+-------------+--------+------+---------------+------+---------+------+----------+-----------------------------+

问题分析

  1. type = ALL:全表扫描
  2. key = NULL:未使用索引
  3. rows = 10000000:扫描全表
  4. Extra = Using filesort:需要排序

Step 2:优化方案

-- 1. 创建联合索引(user_id + create_time)
CREATE INDEX idx_user_create ON orders(user_id, create_time);-- 2. 优化SQL(只查询需要的列)
SELECT id, order_no, amount, status, create_time
FROM orders 
WHERE user_id = 12345 
ORDER BY create_time DESC 
LIMIT 10;

Step 3:验证优化效果

EXPLAIN SELECT id, order_no, amount, status, create_time
FROM orders 
WHERE user_id = 12345 
ORDER BY create_time DESC 
LIMIT 10;

优化后结果

+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
| id | select_type | table  | type | possible_keys    | key              | key_len | ref   | rows | Extra       |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+
|  1 | SIMPLE      | orders | ref  | idx_user_create  | idx_user_create  | 4       | const |  100 | Using where |
+----+-------------+--------+------+------------------+------------------+---------+-------+------+-------------+

优化效果

  • type = ref:使用索引
  • key = idx_user_create:使用了新索引
  • rows = 100:只扫描100行(从1000万降到100)
  • ✅ 无 Using filesort:利用索引排序
  • ⏱️ 执行时间:从8秒降到0.01秒(提升800倍)

6.2 案例2:报表统计优化

问题描述
业务场景:后台订单统计
问题:统计查询超时
SQL执行时间:25秒
原始SQL
SELECT DATE(create_time) AS date,COUNT(*) AS order_count,SUM(amount) AS total_amount,AVG(amount) AS avg_amount
FROM orders
WHERE create_time >= '2024-01-01' AND create_time < '2024-02-01'
GROUP BY DATE(create_time)
ORDER BY date;
排查过程

EXPLAIN分析

+----+-------------+--------+------+---------------+------+---------+------+----------+----------------------------------------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows     | Extra                                        |
+----+-------------+--------+------+---------------+------+---------+------+----------+----------------------------------------------+
|  1 | SIMPLE      | orders | ALL  | NULL          | NULL | NULL    | NULL | 10000000 | Using where; Using temporary; Using filesort |
+----+-------------+--------+------+---------------+------+---------+------+----------+----------------------------------------------+

问题

  1. type = ALL:全表扫描
  2. Extra = Using temporary:使用临时表
  3. Extra = Using filesort:需要排序
  4. ❌ GROUP BY使用了函数DATE()
优化方案

方案1:添加冗余字段

-- 1. 添加日期字段
ALTER TABLE orders ADD COLUMN order_date DATE;-- 2. 创建索引
CREATE INDEX idx_order_date ON orders(order_date);-- 3. 更新历史数据
UPDATE orders SET order_date = DATE(create_time);-- 4. 应用层写入时自动填充
INSERT INTO orders (user_id, amount, create_time, order_date) 
VALUES (123, 100.00, NOW(), CURDATE());-- 5. 优化后的SQL
SELECT order_date AS date,COUNT(*) AS order_count,SUM(amount) AS total_amount,AVG(amount) AS avg_amount
FROM orders
WHERE order_date >= '2024-01-01' AND order_date < '2024-02-01'
GROUP BY order_date
ORDER BY order_date;

方案2:汇总表(更优)

-- 1. 创建日统计表
CREATE TABLE order_daily_stats (stat_date DATE PRIMARY KEY,order_count INT,total_amount DECIMAL(15,2),avg_amount DECIMAL(10,2),update_time DATETIME
);-- 2. 定时任务汇总数据(凌晨执行)
INSERT INTO order_daily_stats (stat_date, order_count, total_amount, avg_amount, update_time)
SELECT DATE(create_time),COUNT(*),SUM(amount),AVG(amount),NOW()
FROM orders
WHERE DATE(create_time) = CURDATE() - INTERVAL 1 DAY
ON DUPLICATE KEY UPDATEorder_count = VALUES(order_count),total_amount = VALUES(total_amount),avg_amount = VALUES(avg_amount),update_time = VALUES(update_time);-- 3. 查询汇总表(毫秒级)
SELECT * FROM order_daily_stats
WHERE stat_date >= '2024-01-01' AND stat_date < '2024-02-01'
ORDER BY stat_date;

优化效果

  • ⏱️ 执行时间:从25秒降到0.005秒(提升5000倍)
  • 💾 空间成本:增加一个汇总表(31行/月)
  • 🎯 适用场景:历史数据统计、报表查询

6.3 案例3:JOIN查询优化

问题描述
业务场景:用户订单详情
问题:多表关联查询慢
SQL执行时间:12秒
原始SQL
SELECT u.name,o.order_no,o.amount,p.product_name,od.quantity
FROM users u
INNER JOIN orders o ON u.id = o.user_id
INNER JOIN order_details od ON o.id = od.order_id
INNER JOIN products p ON od.product_id = p.id
WHERE u.status = 1
AND o.create_time >= '2024-01-01';
EXPLAIN分析
+----+-------------+-------+------+---------------+------+---------+------+---------+-----------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows    | Extra                       |
+----+-------------+-------+------+---------------+------+---------+------+---------+-----------------------------+
|  1 | SIMPLE      | u     | ALL  | NULL          | NULL | NULL    | NULL | 1000000 | Using where                 |
|  1 | SIMPLE      | o     | ALL  | NULL          | NULL | NULL    | NULL | 5000000 | Using where; Using join...  |
|  1 | SIMPLE      | od    | ALL  | NULL          | NULL | NULL    | NULL | 10000000| Using where; Using join...  |
|  1 | SIMPLE      | p     | ALL  | NULL          | NULL | NULL    | NULL | 100000  | Using where; Using join...  |
+----+-------------+-------+------+---------------+------+---------+------+---------+-----------------------------+

问题

  • 4个表都是全表扫描
  • 所有JOIN列都没有索引
优化方案
-- 1. 添加索引
CREATE INDEX idx_status ON users(status);
CREATE INDEX idx_user_create ON orders(user_id, create_time);
CREATE INDEX idx_order_id ON order_details(order_id);
CREATE INDEX idx_product_id ON order_details(product_id);-- 2. 优化SQL(先过滤再JOIN)
SELECT u.name,o.order_no,o.amount,p.product_name,od.quantity
FROM (SELECT id, name FROM users WHERE status = 1
) u
INNER JOIN (SELECT id, user_id, order_no, amount FROM orders WHERE create_time >= '2024-01-01'
) o ON u.id = o.user_id
INNER JOIN order_details od ON o.id = od.order_id
INNER JOIN products p ON od.product_id = p.id;

优化后EXPLAIN

+----+-------------+-------+--------+------------------+------------------+---------+-----------+-------+-------------+
| id | select_type | table | type   | possible_keys    | key              | key_len | ref       | rows  | Extra       |
+----+-------------+-------+--------+------------------+------------------+---------+-----------+-------+-------------+
|  1 | SIMPLE      | u     | ref    | idx_status       | idx_status       | 1       | const     | 10000 | Using where |
|  1 | SIMPLE      | o     | ref    | idx_user_create  | idx_user_create  | 4       | u.id      |     5 | Using where |
|  1 | SIMPLE      | od    | ref    | idx_order_id     | idx_order_id     | 4       | o.id      |     3 | NULL        |
|  1 | SIMPLE      | p     | eq_ref | PRIMARY          | PRIMARY          | 4       | od.product_id|  1 | NULL        |
+----+-------------+-------+--------+------------------+------------------+---------+-----------+-------+-------------+

优化效果

  • ⏱️ 执行时间:从12秒降到0.05秒(提升240倍)

七、监控与预防

7.1 慢查询监控

1. 数据库层监控
-- 定期检查慢查询
SELECT DIGEST_TEXT AS query,COUNT_STAR AS exec_count,AVG_TIMER_WAIT / 1000000000000 AS avg_time_sec,MAX_TIMER_WAIT / 1000000000000 AS max_time_sec
FROM performance_schema.events_statements_summary_by_digest
WHERE AVG_TIMER_WAIT > 1000000000000  -- 超过1秒
ORDER BY AVG_TIMER_WAIT DESC
LIMIT 20;
2. 应用层监控
// AOP拦截慢SQL
@Aspect
@Component
public class SlowSqlAspect {@Around("execution(* com.example.mapper..*(..))")public Object around(ProceedingJoinPoint pjp) throws Throwable {long start = System.currentTimeMillis();try {return pjp.proceed();} finally {long duration = System.currentTimeMillis() - start;if (duration > 1000) {  // 超过1秒log.warn("慢SQL告警: {}ms, 方法: {}", duration, pjp.getSignature());// 发送告警alertService.sendSlowSqlAlert(pjp.getSignature(), duration);}}}
}
3. 监控平台
  • Prometheus + Grafana:可视化监控
  • Zabbix:告警通知
  • 阿里云RDS:自带慢查询分析
  • 腾讯云CDB:SQL审计

7.2 SQL审核

上线前审核
-- 使用EXPLAIN预审SQL
EXPLAIN SELECT ...;-- 审核标准:
-- 1. type不能是ALL
-- 2. key不能是NULL
-- 3. rows不能超过10万
-- 4. Extra不能有Using temporary
SQL审核工具
  • Yearning:开源SQL审核平台
  • Archery:SQL上线审核系统
  • Soar:SQL优化和改写工具

7.3 数据库规范

索引规范
1. 单表索引数量不超过5个
2. 联合索引字段数不超过5个
3. 索引字段总长度不超过767字节
4. 必须为JOIN字段创建索引
5. 禁止在低区分度字段建索引(如性别)
SQL规范
1. 禁止SELECT *
2. 禁止在WHERE子句使用函数
3. 禁止隐式类型转换
4. 禁止LIKE '%keyword%'
5. 禁止大批量操作(单次超过1000条分批)
6. 禁止深度分页(OFFSET超过10000)
表设计规范
1. 单表字段数不超过30个
2. 单表数据量不超过2000万
3. 必须有主键
4. 字符字段必须指定字符集
5. 禁止使用TEXT、BLOB(必要时拆分)

八、优化效果评估

8.1 性能指标

指标优化前优化后提升
响应时间8秒0.01秒800倍
扫描行数1000万10010万倍
CPU使用率80%20%降低75%
QPS101000100倍
并发数5050010倍

8.2 对比测试

-- 开启profiling
SET profiling = 1;-- 执行优化前SQL
SELECT ...;  -- Query 1-- 执行优化后SQL
SELECT ...;  -- Query 2-- 查看对比
SHOW PROFILES;-- 详细分析
SHOW PROFILE FOR QUERY 1;
SHOW PROFILE FOR QUERY 2;

九、常见问题FAQ

Q1:为什么添加了索引还是慢?

可能原因

  1. 索引失效(函数、类型转换、LIKE ‘%xx’)
  2. 优化器没有选择索引(数据量太小或太大)
  3. 回表开销大(可以用覆盖索引)
  4. 锁等待

排查方法

EXPLAIN SELECT ...;
-- 查看key是否为NULL
-- 查看type是否为ALL

Q2:数据量不大为什么还慢?

可能原因

  1. 没有WHERE条件(全表扫描)
  2. 锁等待(长事务、表锁)
  3. 硬件问题(磁盘IO、CPU)
  4. 网络延迟

Q3:如何选择优化优先级?

优先级排序

1. 紧急慢查询(影响线上业务)
2. 高频慢查询(执行次数多)
3. 低频慢查询(偶尔执行)
4. 报表查询(可离线处理)

Q4:索引是不是越多越好?

不是!索引的代价

  • 占用存储空间
  • 降低写入性能
  • 增加维护成本

建议

  • 单表索引不超过5个
  • 定期删除未使用的索引

Q5:什么时候需要分表?

建议阈值

  • 单表 < 500万:正常使用
  • 500万 ~ 2000万:优化索引和查询
  • 2000万:考虑分表

分表策略

  • 垂直分表:按字段拆分
  • 水平分表:按数据范围拆分

总结

慢查询优化核心思路

1. 发现问题:慢查询日志 + 监控
2. 分析问题:EXPLAIN + 执行计划
3. 定位原因:索引 / SQL / 数据量 / 锁
4. 制定方案:索引优化 / SQL改写 / 分表
5. 验证效果:性能对比 + 压力测试
6. 持续监控:告警 + 定期review

优化方法速查

问题类型优化方法优先级
无索引创建索引⭐⭐⭐⭐⭐
索引失效改写SQL⭐⭐⭐⭐⭐
全表扫描添加WHERE+索引⭐⭐⭐⭐⭐
**SELECT ***只查需要的列⭐⭐⭐⭐
深度分页游标分页⭐⭐⭐⭐
大表JOIN先过滤再JOIN⭐⭐⭐⭐
单表过大分表⭐⭐⭐
锁等待优化事务⭐⭐⭐⭐

最佳实践

  1. 开发阶段:所有SQL必须EXPLAIN分析
  2. 测试阶段:压力测试,发现潜在问题
  3. 上线阶段:SQL审核,慢查询告警
  4. 运维阶段:定期review,持续优化

http://www.dtcms.com/a/512337.html

相关文章:

  • 什么大型网站用python做的杭州科技公司排名
  • 四个字网站 域名莱芜金点子信息港最新招聘
  • 【算法笔记】暴力递归尝试
  • 一次学会二分法——力扣278.第一个错误的版本
  • 数据结构——二十七、十字链表与邻接多重链表(王道408)
  • 网站公司做的网站被攻击苏州网络推广
  • 网站权重能带来什么作用灰大设计导航网
  • i.MX6ULL Linux内核启动流程深度解析
  • Browser-Use 打造可操作浏览器的 AI 智能体
  • php网站开发入门到精通教程好玩的游戏网页
  • 代码仓库码云(gitee)配置环境记录
  • 织梦网站模板陶瓷广州建设行业网站
  • 面试(六)——Java IO 流
  • 怎么做视频网站教程php彩票网站建设教程
  • 大模型(Large Language Model, LLM)——什么是大模型,大模型的基本原理、架构、流程
  • 长春网站建设排名怎样用自己电脑做网站
  • 基于 Redis 的基数统计:高效的大规模去重与计数
  • 机械外贸网站站长网站工具
  • 广州企业建站素材安徽禹尧工程建设有限公司网站
  • MySQL if函数
  • Promise.all怎么用
  • 成都网站建设开发价玉环哪里有做网站
  • 01)mysql数据误删恢复相关-mysql5.7 开启 binlog、设置binlog 保留时间
  • 电力电子技术 第五章——非连续导电模式
  • Django 项目 .gitignore 模板
  • MySQL 中文排序(拼音排序)不生效问题全解析
  • 建站网络公司云南网站备案难吗
  • 深度学习(8)- PyTorch 数据处理与加载
  • JAVA:Spring Boot 集成 Jackson 实现高效 JSON 处理
  • 深度学习之YOLO系列YOLOv4