news 2026/6/1 13:43:11

【MySQL】SQL 调优

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【MySQL】SQL 调优

文章目录

  • SQL 调优
    • 压测工具
    • 执行计划 Explain
    • 关键参数讲解
      • select_type
      • key_len
      • ref
      • rows
      • filtered
      • possible_keys 和 key 关系
    • Type 列详解
      • 对于 ref ref_or_null
      • const
    • Extra 列

文章讲解思路:先讲解 SQL 调优依靠的字段和 sql 指令,然后 MySQL 优化文章讲解 MySQL 怎么完成的优化、自己如何利用这些完成自己的 sql 优化和表优化索引优化

SQL 调优

压测工具

mysqlslap-uroot-p123--concurrency=100 --iterations=1 --create-schema="topic01" --engine="innodb" --number-of-queries=10000 --query "select id from topic01 where id = 1";

执行计划 Explain

对于 select delete replace update 的 sql 语句查看执行情况。(并不会执行 sql,只是分析并返回结果)

列名说明
idselect 标识符(SELECT 的执行顺序编号,一个 sql 可能多个 select)
select_typeselect 类型(表示这个 select 是子查询还是最外层还是最简单的 select,主要是为了标注 select 在 sql 的位置)
table表名字(如果是中间结果表会有 deriverdN 或 unionM,N 标明)
partitions查询的分区(只对分区表有效)
type查询的方式(主要优化的字段)
possible_keyswhere 筛选时可能用到的索引
key实际选择的索引
key_len索引长度,判断复合索引使用了前多少列
ref与索引比较的列的属性
rows估算要检查的行数
filtered按条件筛选行的百分比,有多少比例行能满足 where 条件,越大说明过滤的效果越好
Extra附加信息

关键参数讲解

select_type

key_len

查询中使用的索引字节数长度,可以用来判断复合索引使用了前几列。

key_len 越小越说明在索引树上查找导致的 IO 操作越少,索引效率越高。不过前提是保障你对于磁盘数据不变,

ref

查询中与索引比较的列或常量。

**值 **含义
const使用常量(直接写的值,如 = ‘test@example’)
NULL没有引用任何列(可能是函数计算或全索引扫描)
表名.列名使用另一张表的列(JOIN 操作)
func使用了函数/表达式的结果
-- 场景1:使用常量EXPLAINSELECT*FROMusersWHEREemail='test@example.com';|key|ref||-----|-----||idx_email|const|-- 场景2:使用函数(索引失效)EXPLAINSELECT*FROMusersWHEREUPPER(email)='TEST@EXAMPLE.COM';|key|ref||-----|-----||NULL|NULL|-- 函数导致索引无法使用-- 场景3:JOIN 操作EXPLAINSELECTu.*FROMusers uJOINorders oONu.id=o.user_id;|table|key|ref||-------|-----|-----||u|PRIMARY|NULL|-- 主键扫描|o|idx_user_id|u.id|-- 使用了 users 表的 id 列

rows

MySQL 优化器预估要检查的物理行数。rows 越小越好

filtered

预估符合条件的行数占扫描行数的百分比。

比如:

  1. EXPLAIN SELECT * FROM orders WHERE status = 1;
  • 优化器知道 status=1 有 6000 行
  • 因为 status 有索引,精准定位到 6000 行
  • 所以 filtered = 100%(全部符合)
  1. EXPLAIN SELECT * FROM orders WHERE user_id > 5000;

扫描 10000 行(没有索引),预计 50% 符合条件,那么 filtered 就是 50

  1. 代码如下:其中 status 是 index,别的都不是
EXPLAINSELECT*FROMordersWHEREstatus=1ANDuser_id>5000ANDcreated_at>'2024-01-01';
  • 先用 idx_status 定位到 status=1 的 6000 行(rows=6000)
  • 然后在 6000 行中过滤其他条件
  • 预计只有 10% 满足所有条件
  • 预计返回行数 = 6000 × 10% = 600 行

possible_keys 和 key 关系

:::info
会不会出现 possible_type = null,但是key不为空的情况?

:::

有的,比如:EXPLAIN SELECT id, name FROM orders;其中 name 是表的唯一键索引

因为没有使用 where,所以 possible_type 为 NULL ,但是 mysql 优化器发现全表扫描太慢,会选择遍历索引树、

还有其他情况,比如:使用索引的排序结果,其中因为没有 where 也会导师 possible_keys 为 NULL

Type 列详解

性能从好到坏:system const eq_ref ref fulltext ref_or_null index_merge unique_subquery index_subquery range index ALL

type 列类型场景
systemMyIsam 引擎下,且表只有一行数据
const使用常量对非空唯一键或主键进行筛选
eq_ref用于多表连接,表关联条件是主键索引或者非空唯一键
ref通过非唯一索引的 “等值匹配”(针对非 NULL 值)查找数据,返回所有匹配该值的行。
ref_or_null优化器在一次索引扫描中,同时匹配 “等值条件的非 NULL 值” 和 “NULL 值”,避免分两次查询。列 = 某个非NULL值OR列 IS NULL
index_merge使用多个索引,or 两边都是单独索引,然后对结果集合并。此时 key_len 返回最长索引长度
unique_subquery子查询返回外层表的唯一索引或主键索引,比如<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0);">value in (select primary_key from signal where expr)</font>
index_subquery子查询返回普通索引
range使用比较运算符或者 is NULL is not NULL like in 对索引列进行范围查询,对于 NULL 相关,优化器会判断使用 range 和 ref 哪个效率更高做出抉择
index遍历索引树查询,比如排序,或者 like %s,因为不知道 like 前缀就只能遍历索引树了
ALL全表扫描,不用索引

对于 ref ref_or_null

-- 创建测试表:索引列允许 NULLCREATETABLEt1_with_null(idINTPRIMARYKEY,emailVARCHAR(100),-- 允许 NULLINDEXidx_email(email));-- 创建测试表:索引列不允许 NULLCREATETABLEt2_not_null(idINTPRIMARYKEY,emailVARCHAR(100)NOTNULL,-- 不允许 NULLINDEXidx_email(email));-- 插入测试数据INSERTINTOt1_with_null(id,email)VALUES(1,'a@test.com'),(2,'b@test.com'),(3,NULL);INSERTINTOt2_not_null(id,email)VALUES(1,'a@test.com'),(2,'b@test.com');-- 测试 1:精确匹配-- 索引允许 NULLEXPLAINSELECT*FROMt1_with_nullWHEREemail='a@test.com';-- 索引不允许 NULLEXPLAINSELECT*FROMt2_not_nullWHEREemail='a@test.com';-- 测试 2:IS NULL 查询-- 索引允许 NULLEXPLAINSELECT*FROMt1_with_nullWHEREemailISNULL;-- 索引不允许 NULL where后面筛选恒不成立,相当于啥也没有EXPLAINSELECT*FROMt2_not_nullWHEREemailISNULL;-- 测试 3:IS NOT NULL 查询-- 索引允许 NULL 范围查找EXPLAINSELECT*FROMt1_with_nullWHEREemailISNOTNULL;-- 索引不允许 NULLEXPLAINSELECT*FROMt2_not_nullWHEREemailISNOTNULL;-- 测试 4:OR ... IS NULL-- 索引允许 NULLEXPLAINSELECT*FROMt1_with_nullWHEREemail='a@test.com'ORemailISNULL;-- 索引不允许 NULLEXPLAINSELECT*FROMt2_not_nullWHEREemail='a@test.com'ORemailISNULL;(4queries)|----|-------------|--------------|------------|------|---------------|-----------|---------|-------|------|----------|-------------||id|select_type|table|partitions|type|possible_keys|key|key_len|ref|rows|filtered|Extra||----|-------------|--------------|------------|------|---------------|-----------|---------|-------|------|----------|-------------||1|SIMPLE|t1_with_null|[null]|ref|idx_email|idx_email|303|const|1|100|Usingindex||----|-------------|-------------|------------|------|---------------|-----------|---------|-------|------|----------|-------------||id|select_type|table|partitions|type|possible_keys|key|key_len|ref|rows|filtered|Extra||----|-------------|-------------|------------|------|---------------|-----------|---------|-------|------|----------|-------------||1|SIMPLE|t2_not_null|[null]|ref|idx_email|idx_email|302|const|1|100|Usingindex||----|-------------|--------------|------------|------|---------------|-----------|---------|-------|------|----------|--------------------------||id|select_type|table|partitions|type|possible_keys|key|key_len|ref|rows|filtered|Extra||----|-------------|--------------|------------|------|---------------|-----------|---------|-------|------|----------|--------------------------||1|SIMPLE|t1_with_null|[null]|ref|idx_email|idx_email|303|const|1|100|Usingwhere;Usingindex||----|-------------|--------|------------|--------|---------------|--------|---------|--------|--------|----------|------------------||id|select_type|table|partitions|type|possible_keys|key|key_len|ref|rows|filtered|Extra||----|-------------|--------|------------|--------|---------------|--------|---------|--------|--------|----------|------------------||1|SIMPLE|[null]|[null]|[null]|[null]|[null]|[null]|[null]|[null]|[null]|ImpossibleWHERE||----|-------------|--------------|------------|-------|---------------|-----------|---------|--------|------|----------|--------------------------||id|select_type|table|partitions|type|possible_keys|key|key_len|ref|rows|filtered|Extra||----|-------------|--------------|------------|-------|---------------|-----------|---------|--------|------|----------|--------------------------||1|SIMPLE|t1_with_null|[null]|range|idx_email|idx_email|303|[null]|2|100|Usingwhere;Usingindex||----|-------------|-------------|------------|-------|---------------|-----------|---------|--------|------|----------|-------------||id|select_type|table|partitions|type|possible_keys|key|key_len|ref|rows|filtered|Extra||----|-------------|-------------|------------|-------|---------------|-----------|---------|--------|------|----------|-------------||1|SIMPLE|t2_not_null|[null]|index|[null]|idx_email|302|[null]|2|100|Usingindex||----|-------------|--------------|------------|-------------|---------------|-----------|---------|-------|------|----------|--------------------------||id|select_type|table|partitions|type|possible_keys|key|key_len|ref|rows|filtered|Extra||----|-------------|--------------|------------|-------------|---------------|-----------|---------|-------|------|----------|--------------------------||1|SIMPLE|t1_with_null|[null]|ref_or_null|idx_email|idx_email|303|const|2|100|Usingwhere;Usingindex||----|-------------|-------------|------------|------|---------------|-----------|---------|-------|------|----------|-------------||id|select_type|table|partitions|type|possible_keys|key|key_len|ref|rows|filtered|Extra||----|-------------|-------------|------------|------|---------------|-----------|---------|-------|------|----------|-------------||1|SIMPLE|t2_not_null|[null]|ref|idx_email|idx_email|302|const|1|100|Usingindex|

其中,我原本不理解这个结果

:::info
where筛选条件恒为真,那么全表不应该更快么?

经过计算,认为扫描整个索引的成本更低,因为毕竟也不用回表。我们如果让复合索引不完全包含*就可以发现使用 ALL 了

:::

const

Extra 列

如果出现 Using filesort 和 Using temporary,将会严重影响效率,一个是使用文件排序,一个是把数据放入内存,使用临时表排序。当在内存排序发现空间不足时,就只能申请临时文件,此时临时表排序就会变成文件排序,IO 更多

属性效果
Using temporary使用非索引列进行分组,会用临时表下排序,优化时可以对分组的列加索引
Using filesort对非索引列排序,优化时可以对排序的列加索引
Using where使用非索引列检索数据
Using index使用索引检索数据,发生索引覆盖,高效查询
NULL发生回表查询
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/28 21:35:24

VMD-SE-BiLSTM+Transformer多变量时序预测,MATLAB代码

一、研究背景 该模型针对复杂非线性时间序列预测问题&#xff0c;特别是具有多尺度、非平稳特性的时序数据。传统单一模型难以同时捕捉时序数据中的低频趋势和高频波动特征&#xff0c;因此采用分解-重构-混合建模 的策略&#xff0c;结合信号处理与深度学习技术提升预测精度。…

作者头像 李华
网站建设 2026/5/28 14:56:22

局域网中两台win电脑传输文件

文章目录1.方案一&#xff1a;Python 一行命令 HTTP 服务 (最接近 Linux 体验)1. 在发送方电脑 A 上操作2. 在接收方电脑 B 上操作2.方案二&#xff1a;Windows 共享文件夹 (适合频繁传输)3. Linux电脑向Win电脑传输文件总结✨✨✨学习的道路很枯燥&#xff0c;希望我们能并肩走…

作者头像 李华
网站建设 2026/5/28 13:38:25

Flink运行架构深度解析:从核心组件到实战提交

一、Flink运行架构概述Flink作为一个分布式流式计算引擎&#xff0c;其运行架构主要围绕 JobManager 和 TaskManager 两大核心组件展开。1. JobManager&#xff08;Master&#xff09;负责协调分布式任务的执行&#xff0c;包括任务调度、资源申请、检查点协调和故障恢复等。一…

作者头像 李华
网站建设 2026/5/30 21:11:24

如何选择高安全性CDN服务?2026年五大厂商深度横评指南

在数字化时代&#xff0c;CDN 作为业务内容分发的核心基础设施&#xff0c;其安全性直接决定了企业数据传输与业务运营的稳定性&#xff0c;选择一家高安全性的 CDN 服务公司成为企业数字化布局的关键。本文从合规资质、传输加密、访问控制、运维与服务四大核心维度&#xff0c…

作者头像 李华
网站建设 2026/5/28 14:56:28

数位差与数值和的构造

求解代码public static void main(String[] args) throws IOException {BufferedReader br new BufferedReader(new InputStreamReader(System.in));StringTokenizer in new StringTokenizer(br.readLine());PrintWriter out new PrintWriter(new OutputStreamWriter(System…

作者头像 李华