MySQL 之SQL 执行规则及索引详解

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言

在面试或者日常开发中有关sql 知识是必须要掌握的,现总结下mysql 中一些基本知识,为后面的sql 优化做一些知识准备,该篇文章主要讲了sql中关键字的执行顺序,如何编写一个好sql及 索引基础知识的整理,有关索引的学习非常重要。


一、SQL 各关键字执行顺序

1.基本sql查询顺序

执行顺序从 上到下 依次执行

关键字 解释含义
FROM 从指定表/视图 加载数据
ON 表字段间的连接条件
JOIN 表之间的连接操作
WHERE 过滤基础表/结果集 中的数据
GROUP BY 对筛选后的结果集进行分组
HAVING 对分组后的结果集再进行过滤
SELECT(包括计算表达式) 选择返回的列
DISTINCT 去除列的重复数据
ORDER BY 对结果集进行排序
LIMIT / OFFSET 限制或偏移要返回的数据量

1.下面是相关示例的sql,理解执行顺序

sql 复制代码
SELECT DISTINCT 
    d.name AS department,
    COUNT(*) AS emp_count,
    AVG(e.salary) AS avg_salary
FROM employees e
JOIN departments d ON e.dept_id = d.id
WHERE e.hire_date > '2020-01-01'
  AND d.location = '上海'
GROUP BY d.id, d.name
HAVING AVG(e.salary) > 8000
   AND COUNT(*) >= 3
ORDER BY avg_salary DESC
LIMIT 5;

分阶段执执行演示:
1.1 数据准备阶段

sql 复制代码
-- 1. FROM 和 JOIN:确定数据源和连接
FROM employees e
JOIN departments d ON e.dept_id = d.id

-- 执行流程:
-- a. 执行笛卡尔积(如果没有ON条件)
-- b. 应用ON条件过滤连接结果
-- c. 如果是外连接,添加NULL行

1.2 数据过滤阶段

sql 复制代码
-- 2. WHERE:行级过滤
WHERE e.salary > 5000 
  AND e.status = 'active'
  AND d.location = '北京'

-- ⚠️ 注意:WHERE 在 GROUP BY 之前执行
-- 所以不能在这里使用聚合函数
-- ❌ WHERE SUM(salary) > 10000  -- 错误!

1.3 分组聚合阶段

sql 复制代码
-- 3. GROUP BY:分组
GROUP BY e.dept_id, d.name

-- 4. HAVING:分组后过滤
HAVING AVG(e.salary) > 10000 
   AND COUNT(*) > 5

-- WHERE vs HAVING 的区别:
-- WHERE: 分组前过滤,不能使用聚合函数
-- HAVING: 分组后过滤,可以使用聚合函数

1.4 结果处理阶段

sql 复制代码
-- 5. SELECT:选择列并计算
SELECT 
    d.name AS dept_name,
    COUNT(*) AS emp_count,
    AVG(e.salary) AS avg_salary,
    AVG(e.salary) * 1.1 AS new_salary  -- 可以计算表达式

-- 6. DISTINCT:去重
SELECT DISTINCT dept_name, emp_count

-- 重要:DISTINCT 在 SELECT 之后执行
-- 所以可以对计算后的结果去重

1.5 排序和限制阶段

sql 复制代码
-- 7. ORDER BY:排序
ORDER BY avg_salary DESC, 
         emp_count ASC

-- 8. LIMIT:限制行数
LIMIT 10 OFFSET 20

2.相关子查询

每次都执行

sql 复制代码
-- 主查询的每一行都会执行子查询
SELECT e.name,
       e.salary,
       (SELECT AVG(salary) 
        FROM employees 
        WHERE dept_id = e.dept_id) AS dept_avg
FROM employees e;

-- 执行顺序:
-- 1. FROM employees e (获取一行)
-- 2. 执行子查询(使用当前行的dept_id), 
			-- 相当于 e.dept_id 当成一个参数传递到子查询sql中
-- 3. SELECT 选择列
-- 4. 重复第一步到第三步直到所有行处理完

3.非相关子查询

执行一次

sql 复制代码
-- 子查询先执行,结果作为常量
SELECT name, salary
FROM employees
WHERE dept_id IN (
    SELECT id 
    FROM departments 
    WHERE location = '北京'  -- 先执行
);

-- 执行顺序:
-- 1. 执行子查询:SELECT id FROM departments...
-- 2. 主查询WHERE:dept_id IN (子查询结果)
-- 3. 主查询其他部分

4.FROM 中的子查询(派生表)

在mysql5.7版本中派生表中不会给字段添加索引 比如 t.dept_id 没有索引,派生表内部的where 条件会走索引,通过派生表连接其他表,查询时不会索引,派生表数据会全表扫描。

sql 复制代码
SELECT d.name, t.avg_salary
FROM departments d
JOIN (
    SELECT dept_id, AVG(salary) AS avg_salary
    FROM employees
    GROUP BY dept_id
) t ON d.id = t.dept_id;  -- 先执行子查询

-- 执行顺序:
-- 1. 执行派生表子查询(先分组计算)
-- 2. FROM + JOIN(连接派生表和departments)

对于派生表:派生表通常是性能瓶颈,应该尽量避免或者优化,如果可能,尽量将查询重写为join形式或者保证 派生表的结果集尽量小,或者对于派生表写成视图,如果项目需要,做成物化视图也是一个很好优化方案。

二、SQL中各索引整理

1.什么是索引(B+Tree)

**索引是数据库中帮助快速查找数据的数据结构,这个数据结构就是B+tree:**

就像书籍的目录:

1.没有目录:需要一页页的翻找(全表扫描)

2.有目录:直接翻到对应页码(快速定位)

3.如果一张表中,有多个索引,那么每一个索引就像书的一种目录编排方式。

4.组合索引是多级排序的目录,有严格的顺序规则

5.索引不包含完整数据,只包含索引字段的值和指向数据的指针

6.索引越多,查询时可选的方式越多,但维护成本也越高

eg:如一张user 表中有3个字段(id,name,age),其中id 是主键索引 并且只有三条数据 (1,"张三",20) ,(2,"李四",21) ,(3,"王五",22) ,那么他完整的B+tree 结构图是:

sql 复制代码
              ┌─────────────────┐
              │   根节点         │
              │ 指针|2|指针      │
              └─────┬─────┬─────┘
                    │     │
            ┌───────┘     └───────┐
            ↓                     ↓
    ┌───────────────┐     ┌───────────────┐
    │  叶子节点1     │────→│  叶子节点2     │
    │ id=1,张三,20  │     │ id=3,王五,22  │
    │ id=2,李四,21  │     └───────────────┘
    └───────────────┘
    
说明:
1. 根节点存储指针和键值2(代表id 为2 的数据:这里mysql 取),表示:
   - 第一个指针(如hash值0xa):指向id<=2的数据
   - 键值2:分界点(这里取2 保证了树的平衡,和数据分散的合理性,方便查询)
   - 第二个指针(如hash值0xb):指向id>2的数据
2. 叶子节点存储实际数据(在实际B+tree结构中 是不会存具体的数据)
3. 叶子节点之间有双向链表连接(这里简化为单向)

2.按照数据结构分类

索引名称 特点 相关示例sql
B+Tree 索引(默认索引类型) 1.InnoDB 和 MyISAM 的默认索引类型。 2.平衡树结构,适合范围查询和排序。 3.叶子节点存储实际数据(InnoDB)或数据指针(MyISAM)支持全键值、键值范围和键前缀查找 CREATE INDEX idx_name ON table_name (column_name); 或者 alter table table_name add index idx_name (column_name);
哈希索引 1.只有 MEMORY 和 NDB 引擎显式支持 2.精确查找速度快 O(1) 3.不支持范围查询、排序和模糊查询 4.存在哈希冲突问题 CREATE TABLE hash_table (id INT(1), INDEX USING HASH(id)) ENGINE=MEMORY;
全文索引(FULLTEXT) 1.用于文本内容的全文搜索。 2.支持 MATCH AGAINST 语法。 3.只有 InnoDB(5.6+)和 MyISAM 支持 4.有最小词长限制 CREATE FULLTEXT INDEX idx_name ON table_name (column_name);
空间索引(SPATIAL) 1.用于地理空间数据。 2.遵循 R-Tree 数据结构。 3.支持 GIS 函数 CREATE SPATIAL INDEX idx_name ON table_name (column_name);

3.按照功能特性分类

索引名称 特点 相关示例sql
主键索引(PRIMARY KEY) 1.唯一且非空。 2.一个表只能有一个。 3.InnoDB 中作为聚集索引 无
唯一索引(UNIQUE) 1.保证列值唯一性 2.允许 NULL 值(但只能有一个 NULL) 3.用于业务唯一性约束 无
普通索引(INDEX) 1.最基本的索引类型。 2.允许重复值和 NULL。 3.提高查询性能 无
组合索引(复合索引) 1.多列组成的索引。 2.遵循最左前缀原则。 3.可以减少单列索引数量 无

4.两个特殊索引类型

索引名称 特点 相关示例sql
前缀索引 1.对文本列前 N 个字符建立索引。 2.节省存储空间。 3.可能降低选择性 无
覆盖索引 1.索引包含查询所需的所有字段。 2.无需回表查询. 3.显著提升性能,但是增加索引维护成本 无

5.组合索引和覆盖索引的区别

组合索引是"武器",覆盖索引是"使用武器的技巧"。一把好武器(组合索引)加上正确的使用技巧(覆盖索引),才能发挥最大威力,这里的覆盖索引全称应该称为 覆盖索引查询。

sql 复制代码
-- 不是一个索引类型,而是一种使用方式
-- 当查询所需的所有列都包含在索引中时
SELECT name, age FROM users WHERE name = '张三';
-- 如果存在 idx_name_age (name, age),这就是覆盖索引查询
-- 知道了覆盖索引,也就知道了平常写查询不要用select * 这样,会加大回表的概率

组合索引和覆盖索引的核心区别对比:

维度 组合索引 覆盖索引
本质 索引类型(数据结构) 索引使用方式(查询优化)
物理存在 ✅ 是实际创建的索引 ❌ 不是独立的索引类型
创建方式 CREATE INDEX idx ON table(a,b,c) 无法直接创建,是查询使用索引的方式
包含列数 可以是2列或更多 可以是单列索引,也可以是组合索引
主要目的 支持多列查询条件 避免回表,提升查询性能
能否回表 可能回表,也可能不回表 一定不回表(查询时不需要再访问主键/数据行)

回表:当使用索引查找数据时,如果索引中没有包含查询所需的所有列,就需要根据索引中的指针(主键值)回到主键索引(或数据行)中获取完整数据的过程。

总结

了解完上面的sql执行顺序及索引的相关知识后,后面的sql 优化在表设计基本合理的情况下,都是对索引使用的深理解,为此我们也总结了索引的几点性能影响。
1.索引的性能影响:
优点:

1.大幅提高查询速度

2.加速表连接

3.减少排序和分组时间

4.保证数据唯一性

缺点:

1.占用额外存储空间

2.降低插入、更新、删除速度

3.维护成本高(因此对于建组合索引、单个索引需要根据业务去权衡)

4.需要定期优化

相关推荐
gb42152875 小时前
向量数据库多模态特性
数据库
泡海椒6 小时前
数据占比展示:jquick-pdf饼图动态数据渲染PDF教程
数据库·oracle·pdf
大连好光景6 小时前
向量数据库的工作流程详解
数据库·milvus
catchadmin6 小时前
用 Jev 与 Laravel AI SDK 检测垃圾邮件和自动回复
数据库·人工智能·laravel
吠品7 小时前
CSS图片模糊过渡:一个transition加filter就够
java·数据库·notepad++
梦想不只是梦与想8 小时前
对象存储服务器vs数据库
数据库·对象存储·图片存储
Ivanqhz9 小时前
正余弦位置编码(Sinusoidal Positional Encoding)
java·linux·数据库
Ivanqhz9 小时前
层归一化、残差、前馈网络与激活函数简述
服务器·数据库·人工智能·深度学习·算法
Java后端的Ai之路9 小时前
Python进阶探索23 - Python中的Time与Datetime模块
开发语言·数据库·python·datetime·time
通信瓦工10 小时前
利用浊度和电导率测量确定乙二醇基流体的质量
网络·数据库·ai