SQL 性能优化:出现 sql 比较慢怎么办?

文章目录

  • 一、优化器成本分析
    • [1.1 成本分析步骤](#1.1 成本分析步骤)
    • [1.2 SQL优化](#1.2 SQL优化)
    • [1.3 常用数据类型](#1.3 常用数据类型)
  • [二、EXPLAIN 执行计划](#二、EXPLAIN 执行计划)
    • [2.1 执行计划字段说明](#2.1 执行计划字段说明)
      • [关于 type(访问类型)](#关于 type(访问类型))
      • [Extra 常见信息解析](#Extra 常见信息解析)
      • select_type(查询类型)
    • [2.2 优化器追踪](#2.2 优化器追踪)
  • 三、慢日志查询
    • [3.1 配置与开启](#3.1 配置与开启)
    • [3.2 分析慢日志(mysqldumpslow)](#3.2 分析慢日志(mysqldumpslow))
    • [3.3 SHOW PROFILE(分析 SQL 耗时)](#3.3 SHOW PROFILE(分析 SQL 耗时))
    • [3.4 SHOW PROCESSLIST(查看线上 SQL)](#3.4 SHOW PROCESSLIST(查看线上 SQL))
    • [3.5 统计信息维护](#3.5 统计信息维护)
  • [四、出现 sql 比较慢怎么办?🌟](#四、出现 sql 比较慢怎么办?🌟)

一、优化器成本分析

优化器(Optimizer) 的核心任务是为 SQL 语句选择最优的执行路径

MySQL 优化器主要针对 IO 和 CPU 计算语句的成本;可能不会按照分析的原理来执行语句

  • I/O 成本:数据页的读取次数;
  • CPU 成本:数据行过滤、排序、比较等操作的开销。

1.1 成本分析步骤

  • 识别可用索引:首先判断当前 SQL 语句中涉及哪些列,并列出所有可能使用的索引;
  • 计算全表扫描代价:估算读取整张表所需的 I/O 与 CPU 成本;
  • 计算各索引执行代价:分析每个可用索引下的扫描行数、过滤比例等;
  • 选择最优方案:综合对比后,选择成本最低的访问路径。

1.2 SQL优化

SQL优化:MySQL :: MySQL 5.7 Reference Manual :: 8 Optimization

在 MySQL 优化中,数据类型的选择非常关键 。合适的数据类型不仅能减少存储空间,还能降低内存和 I/O 消耗,从而提升查询性能。

一个通用原则是:

在满足业务需求的前提下,数据类型越小越好、越简单越好。

例如:

如果字段值只在 0~1000 之间,可以选择 SMALLINT 而不是 INT

如果只需要存储"是/否",可以使用 TINYINT(1) 而不是 CHAR(1)

1.3 常用数据类型

类型类别 数据类型 说明 存储大小 使用建议
数值类型 TINYINT 小整数,范围 -128~127 或 0~255(无符号) 1 字节 状态码、布尔标志(如性别、是否启用)
SMALLINT 较小整数,范围 -32768~32767 2 字节 年龄、分数、数量较小的计数
INT / INTEGER 常用整数,范围约 -21亿~21亿 4 字节 一般计数、主键 ID
BIGINT 大整数,范围约 ±9×10¹⁸ 8 字节 大范围计数、雪花 ID、金额分单位存储
DECIMAL(M,D) 定点数,精确小数,如货币 M+2 字节 金额、精度要求高的计算
FLOAT 单精度浮点数(约 7 位有效数字) 4 字节 科学计算中非精确值
DOUBLE 双精度浮点数(约 15 位有效数字) 8 字节 浮点精度要求更高时使用
字符类型 CHAR(n) 定长字符串(不足补空格) n 字节 长度固定的数据,如性别、身份证号
VARCHAR(n) 变长字符串 实际长度 + 1~2 字节 一般文本,如用户名、标题
TEXT 大文本(最大 65,535 字节) 实际长度 + 2 字节 文章、描述类字段
MEDIUMTEXT 中等文本(最大 16 MB) 实际长度 + 3 字节 博客正文、大段说明
LONGTEXT 超大文本(最大 4 GB) 实际长度 + 4 字节 日志、文档存储
日期时间类型 DATE 日期(YYYY-MM-DD) 3 字节 出生日期、注册日期
DATETIME 日期+时间(YYYY-MM-DD HH:MM:SS) 8 字节 一般时间记录
TIMESTAMP 自动记录时间(随时区变化) 4 字节 创建或更新时间(推荐)
布尔类型 BOOLEAN / BOOL 逻辑值,本质为 TINYINT(1) 1 字节 真假判断字段(如 is_active)
二进制类型 BLOB 二进制数据(最大 65 KB) 实际长度 + 2 字节 小型文件、图片
MEDIUMBLOB 中等二进制(最大 16 MB) 实际长度 + 3 字节 大文件、音视频片段
LONGBLOB 超大二进制(最大 4 GB) 实际长度 + 4 字节 压缩包、大文件存储

二、EXPLAIN 执行计划

EXPLAIN 是 MySQL 提供的执行计划分析工具,用于查看 SQL 的实际执行路径

它能告诉我们优化器选择了哪个索引、扫描了多少行、使用了哪些算法,从而指导后续优化。

语法:EXPLAIN SELECT ...;

2.1 执行计划字段说明

字段名 说明
id 查询序列号,决定执行顺序。id 越大,优先级越高。相同 id 从上到下执行。
select_type 查询类型(如 SIMPLEPRIMARYUNIONSUBQUERY 等)。
table 当前访问的表或临时结果集。可能是物理表、派生表(derivedN)、UNION 结果等。
type 数据访问类型(访问效率指标,见下文)。
possible_keys 查询中可能使用的索引(候选集)。
key 实际使用的索引(如果为 NULL,则未使用索引)。
key_len 实际使用索引的字节长度(越短越好,代表索引利用率高)。
ref 索引与常量或其他字段的比较关系。
rows MySQL 估算的扫描行数(越小越好)。
filtered 预估过滤比例(百分比,越高越好)。
Extra 额外执行信息,如 Using indexUsing filesortUsing temporary 等。

关于 type(访问类型)

访问类型表示 MySQL 从表中取数据的方式,性能由高到低如下:

sql 复制代码
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
类型 说明
ALL 全表扫描;若数据量大,需优化。
index 全索引扫描(效率优于 ALL)。典型场景:① 查询覆盖索引(所需数据可直接从索引获取);② 用索引排序(避免数据重排)。
range 利用索引做范围查询(避免全索引扫描)。适用操作符:=、<>、>、>=、<、<=、IS NULL、BETWEEN、LIKE、IN()。
index_subquery 利用索引关联子查询(无需全表扫描)。
unique_subquery 类似 index_subquery,但基于唯一索引
index_merge 查询需多个索引组合使用。
ref_or_null 字段需"关联条件 + NULL 值匹配"时,优化器选择的访问方式。
ref 使用非唯一性索引查找数据。
eq_ref 使用唯一性索引(如主键、唯一索引)查找数据。
const 表至多有1条匹配行(查询效率极高)。
system 表只有1行记录(如系统表),是 const 的特例。

Extra 常见信息解析

  • Using index:使用了覆盖索引 (无需回表,性能好)。
    • 同时有 using where,表示索引用于"键值查找";
    • using where,表示索引仅用于"数据读取"(非条件查找)。
  • Using where:使用 WHERE 条件过滤数据。
  • Using filesort:使用文件排序 ,需要额外排序(未命中索引排序,性能较差)。
  • Using temporary:使用临时表保存中间结果(如复杂 GROUP BY)。
  • Using index condition:启用了索引下推(ICP) 优化,减少回表次数。
  • Using join buffer:使用连接缓存优化多表 JOIN
  • impossible whereWHERE 条件恒为 false,返回空结果。

select_type(查询类型)

主要用来分辨查询的类型,是普通查询还是联合查询还是子查询

类型 含义
SIMPLE 简单查询(没有联合查询和子查询)
PRIMARY 最外层 SELECT
UNION 若第二个 SELECT 出现在 UNION 之后,则被标记为 UNION
DEPENDENT UNION UNION 或 UNION ALL 联合而成的结果会受外部表影响
UNION RESULT 从 UNION 表获取结果的 SELECT
SUBQUERY 在 SELECT 或者 WHERE 列表中包含子查询
DEPENDENT SUBQUERY 子查询要受到外部表查询的影响
DERIVED FROM 子句中出现的子查询,也叫做派生表
UNCACHEABLE SUBQUERY 表示使用子查询的结果不能被缓存

2.2 优化器追踪

优化器根据解析树可能会生成多个执行计划,然后选择最优的的执行计划

通过 optimizer_trace,我们可以查看 MySQL 的决策过程,了解它为何选择某种执行路径。

sql 复制代码
SHOW VARIABLES LIKE 'optimizer_trace';
-- 启用优化器的追踪
SET optimizer_trace='enabled=on';
-- 执行一条查询语句
SELECT * FROM user WHERE id<100; -- 执行查询
SELECT * FROM information_schema.optimizer_trace; -- 查看结果
-- 用完关闭
SET optimizer_trace="enabled=off";
SHOW VARIABLES LIKE 'optimizer_trace';

优化器追踪是定位"为什么不走索引"的关键工具,可以直接看到优化器放弃某索引的原因,如"estimated cost too high"。


三、慢日志查询

在生产环境中,SQL 慢的原因通常有两类:

  1. 执行计划不理想(索引未命中、数据倾斜等);
  2. SQL 逻辑不合理(条件复杂、数据量过大等)。

MySQL 提供了完整的慢查询分析手段来辅助定位。

3.1 配置与开启

sql 复制代码
-- 查看配置
SHOW GLOBAL VARIABLES LIKE 'slow_query%';
SHOW GLOBAL VARIABLES LIKE 'long_query%';

-- 临时开启
SET GLOBAL slow_query_log = ON; -- on 开启 off 关闭
SET GLOBAL long_query_time = 4; -- 单位秒;默认10s;此时设置为4s

或者修改配置(my.cnf 或 my.ini)

conf 复制代码
-- 永久开启(配置文件)
[mysqld]
slow_query_log = ON
long_query_time = 4
slow_query_log_file = D:/mysql/mysql57-slow.log

3.2 分析慢日志(mysqldumpslow)

用于统计和筛选慢查询日志中的典型语句:

bash 复制代码
# 查询最近10条包含"select"的慢SQL,按时间排序
mysqldumpslow -s t -t 10 -g 'select' /data/mysql/slow.log

3.3 SHOW PROFILE(分析 SQL 耗时)

SHOW PROFILE 用于查看 SQL 执行的时间细分(解析、优化、执行阶段)。

sql 复制代码
SELECT @@profiling; -- 查看是否开启(默认0)
SET profiling = 1; -- 开启
SELECT * FROM user WHERE name LIKE 'Gu%'; -- 执行SQL
show profiles; -- 查看所有profiles(获query_id)
show profile for query 10; -- 查看query_id=10的耗时
SET profiling = 0; -- 关闭

3.4 SHOW PROCESSLIST(查看线上 SQL)

查看连接线程;可以查看此时线上运行的 SQL 语句; 如果要查看完整的 SQL 语句: SHOW FULL PROCESSLIST ; 然后优化该语句;

sql 复制代码
SHOW PROCESSLIST; -- 简要信息(SQL可能截断)
SHOW FULL PROCESSLIST; -- 完整SQL
  • 核心关注:Time(执行时间,秒)、Info(SQL 语句),定位长时间运行的 SQL。

3.5 统计信息维护

sql 复制代码
-- 查看索引统计信息
SHOW INDEX FROM student;

-- 立即更新 Cardinality 值
ANALYZE TABLE student;

-- 调整 Change Buffer 大小
SELECT @@innodb_change_buffer_max_size; -- 默认25(1/4缓冲池)
SET GLOBAL innodb_change_buffer_max_size=30;

-- 自适应 Hash 索引
SELECT @@innodb_adaptive_hash_index;
SET GLOBAL innodb_adaptive_hash_index=1; -- 默认开启

注意: 在非高峰时间段,对数据库中几张核心表做 ANALYZE TABLE 操作,这能使优化器和索引更好的工作。

四、出现 sql 比较慢怎么办?🌟

当遇到SQL执行较慢的情况,按"定位问题、分析、优化"的思路处理:

  • 首先是定位阶段:
    • 通过 show processlist 实时查看运行中的 SQL,重点关注执行时间长的语句;
    • 再开启 慢查询日志,并结合工具如 mysqldumpslow 或 pt-query-digest 对日志分析,快速锁定高频或高耗时的慢查询语句。
  • 定位到问题后进入分析阶段:
    • 通过 explainexplain analyze 查看SQL执行计划,重点关注 type、key、rows、Extra 等字段是否合理,比如是否走了索引、是否存在全表扫描
  • 然后针对性优化:
    • 一是检查索引 合理性,确保wheregroup byorder by子句涉及的字段都有合适的索引,避免全表扫描或索引失效;
    • 二是优化SQL语句 本身,比如把innot in替换成更高效的联合查询,同时尽量减少多表联合查询的次数;结构层面优化字段类型、拆分大表或大字段,必要时做分区或分库。

补充:

另外在实际工作中,不要直接存储age字段,而是存储用户的生日(年月日),这样既能灵活计算实时年龄,又能避免后续因年龄变化带来的数据维护成本,最终提升SQL的执行效率。

相关推荐
熊小猿3 小时前
如何在 Spring Boot 项目中使用 @Slf4j 注解结合 Logback 进行系统日志管理
java·开发语言·spring boot
Francek Chen3 小时前
【IoTDB】时序数据库选型迷茫?Apache IoTDB 为何成工业场景优选?
大数据·数据库·apache·时序数据库·iotdb
OKkankan4 小时前
模板的进阶
开发语言·数据结构·c++·算法
啊吧怪不啊吧4 小时前
SQL之表的增删
服务器·数据库·sql·1024程序员节
鼓掌MVP4 小时前
Rust Web实战:构建高性能并发工具的艺术
开发语言·前端·rust·异步编程·内存安全·actix-web·高性能web服务
想不明白的过度思考者4 小时前
MySQL 8.0.x 全平台安装指南:Windows、CentOS、Ubuntu 详细步骤与问题解决
windows·mysql·centos
盒马盒马4 小时前
Rust:函数与控制流
开发语言·网络·rust
豐儀麟阁贵4 小时前
5.4静态变量和静态方法
java·开发语言
weixin_307779134 小时前
Linux 下 Docker 与 ClickHouse 的安装配置及 MySQL 数据同步指南
linux·数据库·mysql·clickhouse·运维开发