MySql调优详解

一、索引

1.1 什么是索引?

快速定位数据的一种存储结构,比如文章目录;设计思想:以空间换时间。

1.2 索引的种类

常见的索引分类:

|--------|-------------------|--------------|-------------|----------------|
| 按数据结构分 | B+tree索引 | Hash索引 | Full-text索引 | |
| 物理存储 | 聚集索引 | 非聚集索引 | | |
| 字段特性 | 主键索引(PRIMARY KEY) | 唯一索引(UNIQUE) | 普通索引(INDEX) | 全文索引(FULLTEXT) |
| 字段个数 | 单列索引 | 联合索引(复合、组合) | | |

常见的索引数据结构和区别:

  • 二叉树、红黑树、B树、B+树
  • 区别:树的高度影响获取数据的性能(每一个树节点都是一次磁盘I/O)

1.3 B+tree索引

1.3.1 二叉树

特点:一个节点最多两个子节点,小的在左边,大的在右边

如果数据按顺序进入:

形成一个链表结构,树的高度很高,元素的查找效率等于链表查询O(n),数据检索效率低下。

1.3.2 红黑二叉树(平衡二叉树)

通过自旋平衡,减少树的高度。但是数据过多时,节点个数就越多,树的高度也会增高,影响查询效率。

1.3.3 B-树

特点:

B树的节点可以有多个子节点,多叉树

一个节点可以存储多个元素;

平衡多路查找树;

不适合范围查找
3阶B树

1.3.4 B+树

B+树结构图

对B-树的优化(为什么MySql用B+树):

  1. 所有的数据存储在叶子结点上(最下面一行),排序、分组、去重查询更简单
  2. 非叶子结点没有放数据,可以存更多的键值,树会更矮更胖,查询效率更快
  3. 每一个非叶子节点上都有对应的双向指针,范围查询、排序友好

如果一个表没有主键索引还会创建B+树吗?

复制代码
答案
↓
↓
↓
↓
↓
↓
↓
↓
↓
↓
↓
会的,InnoDB是MySQL的一种存储引擎,会为每个表创建一个主键索引。如果表没有一个明确的主键索引,InnoDB会使用一个隐藏的、自动生成的主键创建索引,使用的是B+树结构。

​

1.4 Hash索引

InonoDB不支持显式创建Hash索引,只支持自适应Hash索引;

Memory引擎支持Hash索引

Hash索引底层原理 类似与java的HashMap的原理

1.5 聚集索引与非聚集索引

按物理存储分类:InnoDB存储方式是聚集索引

聚集索引索引和数据存在一起 (叶子节点存整行数据

MyISAM是非聚集索引

非聚集索引索引和数据分开存 (叶子节点只存主键值

1.5.1 二级索引

非主键索引都是二级索引,也是非聚集索引

1.5.2 覆盖索引

已经在age索引中查到了id,节省了回表步骤,查询更快

这种索引列覆盖了查询字段就是覆盖索引

1.5.3 索引下推

索引下推 (Index Condition Pushdown, ICP),是MySQL5.6针对 扫描二级索引 的优化,用来在范围查询时减少回表次数,适用于MyISAM和InnoDB

查询SELECT * FROM user WHERE name LIKE '张%' AND age = 20;

1. 无索引下推
  1. 引擎按 name LIKE '张%' 找到 100 条索引记录(含主键)。
  2. 全部 100 次回表,读取整行数据。
  3. 返回 Server 层,再过滤 age = 20,只剩 10 条。
  • 浪费 90 次回表 I/O
2. 开启索引下推
  1. 引擎按 name LIKE '张%' 遍历索引。
  2. 在索引叶子节点直接判断 age = 20
  3. 只把符合条件的 10 条主键传给 Server。
  4. 仅 10 次回表,效率显著提升。

1.6 单列、组合索引

1.6.1 单列索引

单列索引 :只对一个字段建索引

sql 复制代码
CREATE INDEX idx_name ON user(name);

1.6.2 联合索引(复合索引)

多个字段按顺序组合成一个索引。

sql 复制代码
CREATE INDEX idx_name_age_sex ON user(name, age, sex);

name → age → sex 按顺序排序

核心规则:最左前缀原则

联合索引,必须从左往右数,连续不断才能用;跳过左边的列,右侧列全部失效;范围查询会截断右侧列。

例如索引 (a,b,c)

  • 能用到:where a=?
  • 能用到:where a=? and b=?
  • 能用到:where a=? and b=? and c=?
  • 用不到:where b=?
  • 用不到:where a=? and c=?(b 断了)

1.6.3 单列索引与联合索引的区别

对比维度 单列索引 (Single-Column Index) 联合索引 (Composite/Combined Index)
索引结构 仅对单个字段构建 B + 树索引 多个字段按顺序组合构建 B + 树索引
生效规则 仅该字段作为查询条件时生效 遵循最左前缀原则,从最左列开始的连续字段组合才能生效
多条件查询效率 多条件时 MySQL 通常仅能选择一个索引使用,无法同时利用多个单列索引过滤 可利用多字段连续过滤,一次索引扫描完成多条件筛选,效率远高于多个单列索引
回表次数 多条件查询时,仅用一个索引过滤,剩余条件需回表后筛选,回表次数多 多字段联合过滤,精准定位数据,回表次数大幅减少(覆盖索引可完全避免回表)
空间占用 每个索引是独立 B + 树,多个单列索引总空间占用大 一个索引树包含多列,总空间占用远小于多个单列索引
写入性能(增删改) 每新增 / 修改 / 删除数据,需更新所有相关单列索引,索引越多写入越慢 仅需更新一个联合索引,写入性能优于多个单列索引
维护成本 索引数量多,维护、优化、排查成本高 索引数量少,结构清晰,维护成本低
适用查询场景 1. 仅按单个字段高频查询2. 字段区分度极高(如手机号、身份证号)3. 多字段查询不固定,无法形成固定组合 1. 多字段固定组合高频查询2. 遵循最左前缀,可覆盖单个 / 多个连续字段的查询3. 排序、分组场景(ORDER BY/GROUP BY)
典型 SQL 示例 CREATE INDEX idx_name ON user(name);``SELECT * FROM user WHERE name = '张三'; CREATE INDEX idx_name_age_city ON user(name, age, city);``SELECT * FROM user WHERE name = '张三' AND age = 20 AND city = '北京';
核心优势 简单灵活,适配单字段查询,无需考虑字段顺序 多条件查询性能高,空间占用小,写入性能优,可实现覆盖索引
核心劣势 多条件查询性能差,空间浪费,写入性能差 需严格遵循最左前缀,字段顺序设计错误会导致索引失效

1.7 索引的优缺点及使用场景

1.7.1、优点

  1. 提高检索效率
  2. 降低排序成本,索引对应的字段有自动排序功能,默认升序

1.7.2 缺点

  1. 创建和维护索引 随数据量增加维护成本增加
  2. 占用额外磁盘空间,数据量越大,占用空间越大
  3. 降低写入性能(增删改):每插入 / 更新 / 删除数据,必须同步更新所有相关索引树,索引越多,写入越慢

1.7.3 索引的使用场景

适合场景:

  • 经常出现在查询条件中的字段,尤其是大表,必须加索引

不适合场景:

  • 更新非常频繁的字段
  • 区分度极低的字段:如性别(男 / 女)、状态(0/1),索引过滤性差,加了反而拖慢性能
  • 几乎不查询的字段:仅存储、不参与查询的字段,完全没必要加索引

二、优化

2.1、优化方法

2.2 通过Explain干预执行计划

1、Explain含义

EXPLAIN 是 MySQL 的执行计划分析工具 ,可以查看 MySQL 将要如何执行一条 SQL,可以帮助我们发现查询瓶颈,优化查询性能

2、Explain作用

核心作用

  • 看这条 SQL 走没走索引
  • 看是全表扫描 还是索引查找
  • 看有没有回表、文件排序、临时表等性能问题
  • 用来优化慢查询

3、Explain用法

例如:

sql 复制代码
Explain Extended select * from users;

结果:

执行结果关键字段(必懂)

字段 含义(重点)
id 查询执行顺序,越大越先执行
select_type 查询类型(简单查询 / 子查询 / 联合查询)
table 涉及哪张表
type 最重要:访问效率(从好到坏:system > const > eq_ref > ref > range > index > ALL)
possible_keys 可能用到的索引
key 实际用到的索引
key_len 索引使用长度
ref 与索引比较的列
rows 预计扫描行数
Extra 额外重要信息(Using index、Using filesort、Using temporary 等)

最关键的三个判断点

  1. type = ALL→ 全表扫描,性能最差,必须优化

  2. key = NULL→ 索引没生效,白建了

  3. Extra 出现这些要警惕

    • Using index:覆盖索引,很好
    • Using filesort:文件排序,性能差
    • Using temporary:用到临时表,性能差
    • Using index condition:索引下推,正常优化
3.1 id 列

查询中执行 SELECT 子句的顺序编号

  • id 越大,越先执行
  • id 相同,从上到下顺序执行
  • id 为 NULL,最后执行(一般是 UNION RESULT)
3.2 select_type 返回列详解
select_type 含义 出现场景 性能评价
SIMPLE 简单查询 不包含子查询、UNION 的普通 SELECT 最好,最常见
PRIMARY 主查询 包含子查询 / UNION 时,最外层的查询 正常
SUBQUERY 普通子查询 SELECT / WHERE 里的子查询,不依赖外层表 一般,可能被优化掉
DEPENDENT SUBQUERY 相关子查询 子查询依赖外层表的结果,需逐行执行 很差,性能灾难
DERIVED 衍生表 FROM 里的子查询,会生成临时表 一般,数据大就慢
UNCACHEABLE SUBQUERY 不可缓存子查询 子查询结果无法缓存,每行都要重算 极差
UNION UNION 中的第二个及以后查询 UNION 连接的多个查询 正常
UNION RESULT UNION 结果集 UNION 合并结果的临时表 正常
DEPENDENT UNION 相关 UNION UNION 里的查询依赖外层结果 差,和 dependent subquery 一样
MATERIALIZED 物化子查询 MySQL 把子查询结果存成临时表 比 dependent 好很多

1)SIMPLE

最简单的查询,没有子查询、没有 union。

复制代码
SELECT * FROM user WHERE id = 1;

2)PRIMARY

当 SQL 里有子查询或 UNION 时,最外层查询就是 PRIMARY。

3)SUBQUERY

WHERE / SELECT 里的子查询,不依赖外层表

复制代码
SELECT * FROM user
WHERE id = (SELECT id FROM user_info WHERE name = 'a');

4)DEPENDENT SUBQUERY(重点坑)

子查询用到了外层表的字段 ,必须循环执行,性能极差:

复制代码
SELECT * FROM user u
WHERE EXISTS (SELECT 1 FROM order o WHERE o.user_id = u.id);

外层查一行,子查询就要执行一次,数据多直接卡死。

5)DERIVED

FROM 括号里的子查询,会生成临时表

复制代码
SELECT * FROM (SELECT * FROM user WHERE age>18) t;

6)UNION & UNION RESULT

  • UNION:第二个及以后的查询

  • UNION RESULT:合并结果

    SELECT id FROM a
    UNION
    SELECT id FROM b;

7)MATERIALIZED

MySQL 5.6+ 对子查询的优化,把子查询结果物化成临时表,避免重复执行。比 DEPENDENT SUBQUERY 好很多。

3.3 type列

效率从高到低:system > const > eq_ref > ref > fulltext >ref_or_null > range > index > ALL ,一般保证到range,最好到ref

type 级别 含义(白话) 出现场景 性能评价
system 最优 系统表,只有 1 行数据 系统表、极少出现 顶级,几乎不用关心
const 极优 主键 / 唯一索引等值匹配,最多 1 行 where id=1(id 主键) 极快,理想状态
eq_ref 优秀 多表 join,关联字段是主键 / 唯一索引 join 关联主键 非常快,最优 join
ref 良好 普通索引等值匹配,可能多行 where name='xxx'(普通索引) 性能不错,常用
range 一般 索引范围查询 between、>、<、in、like 能接受,需注意优化
index 较差 遍历整个索引树 查索引全部,但不扫全表 比全表好,但依然慢
ALL 最差 全表扫描 无索引、索引失效 必须优化
3.4 key_len 列

MySQL 实际使用了索引的字节长度

MySQL 字段长度计算规则(必须知道)

1)常见字段占用字节

  • tinyint:1
  • int:4
  • bigint:8
  • char(n):n × 字符集字节(utf8mb4=4)
  • varchar(n) :n × 字符集字节 + 2 字节长度
  • date:3
  • datetime:8
  • timestamp:4

2)额外开销

  • 允许 NULL:+1 字节
  • 变长字段(varchar):+2 字节

例如:

sql 复制代码
explain
select * from orders where user_id = 1;

user_id 是int类型,占4个字节,所以key_len=4

3.5ref 列

当前查询中,与索引列做等值匹配的对象是什么(是常量?还是某个字段?还是函数?)。

常见取值

ref 值 含义 出现场景
const 常量等值匹配 where name = '张三'
字段名 (如 db.t.id 另一张表的字段关联匹配 JOIN 关联查询
func 函数 / 表达式匹配 索引失效前兆
NULL 没有等值匹配 范围查询、全表扫描、索引未使用

举例

1) ref = const(最常见、最健康)

复制代码
select * from user where name = '张三';
  • name 是索引
  • 常量比较
  • ref = const

2) ref = 表。字段(JOIN 关联)

复制代码
select * from user u
join order o on u.id = o.user_id;

3) ref = NULL

  • 范围查询:where id > 100
  • 模糊查询:where name like '张%'
  • 没走索引、全表扫描这些情况 ref 都是 NULL

4)ref = func(基本代表索引废了)

复制代码
select * from user where left(name,1) = '张';
  • 对索引列使用了函数
  • MySQL 识别为和函数结果比较
  • ref = func索引大概率失效

未完待续...

相关推荐
m0_588758482 小时前
CSS如何创建三角箭头图标_通过border透明技巧实现
jvm·数据库·python
九皇叔叔2 小时前
MySQL实操指南:复制表及数据复制全解析
android·数据库·mysql
一只大袋鼠2 小时前
MyBatis 特性(三):缓存、延迟加载、注解开发
java·数据库·笔记·sql·缓存·mybatis
m0_377618232 小时前
如何解决预检查网络失败_runcluvfy阶段报错忽略与修复
jvm·数据库·python
m0_515098422 小时前
如何配置Oracle分布式事务_两阶段提交与DB_DOMAIN参数
jvm·数据库·python
m0_684501982 小时前
SQL嵌套查询在ETL流程的应用_数据清洗逻辑
jvm·数据库·python
djjdjdjdjjdj2 小时前
c++ circle元编程如何使用
jvm·数据库·python
老毛肚2 小时前
Redis高级
java·数据库·redis
2401_871696522 小时前
如何解决Data Guard主库ORA-16038日志无法归档_强制日志传输报错排查
jvm·数据库·python