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.需要定期优化

相关推荐
富士康质检员张全蛋13 小时前
JDBC 连接池
数据库
yangminlei13 小时前
集成Camunda到Spring Boot项目
数据库·oracle
ChineHe14 小时前
Redis数据类型篇002_详解Strings核心命令与存储结构
数据库·redis·缓存
清水白石00815 小时前
《从零到进阶:Pydantic v1 与 v2 的核心差异与零成本校验实现原理》
数据库·python
电商API&Tina15 小时前
京东 API 数据采集接口接入与行业分析
运维·服务器·网络·数据库·django·php
柠檬叶子C15 小时前
PostgreSQL 忘记 postgres 密码怎么办?(已解决)
数据库·postgresql
864记忆16 小时前
Qt创建连接注意事项
数据库·qt·nginx
小小bugbug16 小时前
mysql查询的原始返回顺序与limit分页优化
mysql·adb
毕设十刻16 小时前
基于Vue的迅读网上书城22f4d(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js