当前位置: 首页 > news >正文

SQL调优专题笔记:打造你的数据库性能优化思维体系

1.认识索引

1.1索引是什么

索引是数据库为了加快数据查询而引入的一种数据结构.
它的核心是通过维护有序的数据引用,让数据库查找的时候可以像二分查找一样快
1️⃣ 如果被问“那为什么索引能加快查询?”
可以答:
因为索引结构是有序的(例如 B+ 树),数据库可以通过二分或层级查找快速定位数据,而不是逐行扫描。
2️⃣ 如果被问“那为什么索引会影响写入?”
可以答:
因为每次插入、删除或更新数据时,数据库必须同步更新索引结构,保持有序性。
3️⃣ 如果被问“索引一定能提升性能吗?”
可以答:
不一定。索引适合高频查询字段,但在小表或频繁更新的表上,反而可能降低整体性能。

1.2索引的划分

一般分为聚集索引和二级索引。聚集索引决定数据的物理存储顺序,通常是主键;二级索引独立于数据文件,存储键值和主键指针。
聚集索引与二级索引的区别是什么?
聚集索引与二级索引的主要区别在于数据存储的位置。

聚集索引:它的叶子节点直接存储实际的数据行。数据表的记录按照聚集索引的顺序进行存储,因此每个表只能有一个聚集索引,通常是主键。

二级索引:它的叶子节点存储的是指向聚集索引的指针(即主键),而不是实际的数据行。当查询时,二级索引首先返回主键,然后根据主键去聚集索引中查找实际的数据行。

1.3MySQL 中所有索引类型与语法

MySQL 支持 5 种主要类型的索引:

索引类型说明示例语法
普通索引(INDEX / KEY)最常见,无唯一性要求CREATE INDEX idx_name ON users(name);
唯一索引(UNIQUE INDEX)列值必须唯一,可为 NULLCREATE UNIQUE INDEX idx_email ON users(email);
主键索引(PRIMARY KEY)表中唯一且非空(自动创建)ALTER TABLE users ADD PRIMARY KEY (id);
全文索引(FULLTEXT INDEX)用于全文搜索(仅 CHAR/VARCHAR/TEXT)CREATE FULLTEXT INDEX idx_content ON articles(content);
空间索引(SPATIAL INDEX)用于几何数据(仅 MyISAM / InnoDB 支持部分)CREATE SPATIAL INDEX idx_location ON maps(geo_point);
CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name
ON table_name (column1 [ASC|DESC], column2 [ASC|DESC], ...);

或者用 ALTER TABLE 语句:

ALTER TABLE table_name
ADD [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name (column1, column2, ...);

1.4索引失效的总体规律

索引的核心作用是通过有序结构(B+树)来减少扫描行数。
但如果 SQL 写法、数据类型、或者查询条件破坏了这种“可预测性”,MySQL 优化器就可能放弃使用索引,改用全表扫描(type=ALL)。

编号原因示例说明与优化建议
1在索引列上进行函数或表达式计算WHERE YEAR(create_time)=2024MySQL 无法利用索引值比较,只能逐行计算。
✅ 改写为:WHERE create_time BETWEEN '2024-01-01' AND '2024-12-31'
2索引列进行了隐式类型转换WHERE id = '123' (id 为 INT)类型不同会导致索引失效。
✅ 保持类型一致,例如 WHERE id = 123
3在索引列上使用通配符前缀的 LIKELIKE '%abc'% 开头无法使用索引。
✅ 改为 LIKE 'abc%' 或用 全文索引 (FULLTEXT)
4使用 OR 连接不同字段的条件WHERE name='张三' OR age=20若其中一个字段无索引,则全表扫描。
✅ 改为 UNION ALL 或确保所有字段都有索引。
5索引字段参与运算或使用负号等操作WHERE id + 1 = 10运算使得优化器无法利用索引。
✅ 改为 WHERE id = 9
6不符合联合索引的最左前缀原则索引为 (a,b,c),条件为 WHERE b=1 AND c=2联合索引只会用到最左连续列。
✅ 改为 WHERE a=? AND b=?,或调整索引顺序。
7在 WHERE 中使用 IS NULLIS NOT NULLWHERE age IS NULLIS NULL 可用索引,IS NOT NULL 一般无法使用。
✅ 避免过多使用 IS NOT NULL
8在范围查询后再使用索引列WHERE a > 10 AND b = 5 (索引 a,b)a 是范围条件,b 的索引失效。
✅ 尽量把等值条件放在前面。
9使用 NOT、!=、<>、NOT IN 等WHERE age != 18索引跳跃性太大,优化器倾向全表扫描。
✅ 尽量改为正向条件或范围查询。
10使用 OR 包含不同表列时WHERE t1.id = 1 OR t2.name = 'Tom'无法在多个表索引上联合优化。
✅ 拆分查询或使用 UNION。
11字符串不加引号WHERE name = 123MySQL 自动转换导致索引失效。
✅ 文本值一定要加引号。
12数据分布极度不均genderstatus 这种区分度低的列即使有索引,MySQL 优化器可能认为全表扫描更快。
✅ 不为低选择性列建索引。
13使用 OR 且混合索引与非索引列WHERE name='Tom' OR score>90部分条件不能使用索引 → 全表扫描。
✅ 拆分为两条查询再 UNION ALL。
14ORDER BY 与索引顺序不一致ORDER BY b,a 而索引为 (a,b)会出现 Using filesort
✅ 与索引顺序保持一致。
15使用 SELECT * 并非覆盖索引SELECT * 会导致回表✅ 只查询索引列,形成覆盖索引

2.执行计划(EXPLAIN)

2.1认识执行计划(EXPLAIN)

EXPLAIN 是 MySQL 提供的分析工具,用来查看一条 SQL 语句的执行计划。
它展示了优化器如何执行查询,比如:

每个表的访问顺序;
使用了哪些索引;
连接类型(ALL、index、ref、const 等);
预估扫描的行数(rows);
以及可能的过滤条件(filtered)等。
通过 EXPLAIN,我们可以判断 SQL 是否走索引、是否存在全表扫描,从而优化查询性能。

2.2执行计划(EXPLAIN)中各个变量的含义

在这里插入图片描述
MySQL EXPLAIN 字段详细说明表

字段名含义常见取值/示例详细解释与优化提示
id查询的执行顺序标识1, 2, 3…SQL 中每个 SELECT 都会分配一个 id
数值越大,越先执行;同值表示并行执行。
建议: 通过 id 分析子查询执行顺序。
select_type查询类型SIMPLE、PRIMARY、SUBQUERY、DERIVED、UNION- SIMPLE:无子查询或 UNION;
- PRIMARY:最外层查询;
- SUBQUERY:SELECT 中包含的子查询;
- DERIVED:FROM 子句中的子查询(派生表);
- UNION:UNION 中的第二个及后续查询。
table当前访问的表students、orders 等表示当前执行计划中操作的表名或别名。
如果是临时表或派生表,这里会显示 <derivedX>
partitions表分区信息NULL、p0,p1如果表有分区,这里显示被访问的分区;否则为 NULL。
type连接类型(重要性能指标)const、eq_ref、ref、range、index、ALL性能排序(由优到劣):
system > const > eq_ref > ref > range > index > ALL
const:主键或唯一索引等值查询,只匹配一行。
ALL:全表扫描,需重点优化!
possible_keys可能使用的索引PRIMARY, idx_nameMySQL 认为可能可用的索引。可通过 EXPLAIN EXTENDED 查看更精确估计。
key实际使用的索引PRIMARY、idx_major显示真正被使用的索引名称。若为 NULL,说明没用上索引。
key_len索引长度(字节)4、10、767表示 MySQL 实际使用索引的字节数。
例如 INT=4 字节,VARCHAR(10)=10 字节。
ref与索引列比较的对象const、func、列名显示索引列与哪个值进行比较。const 表示常量值,如 WHERE id = 1
rows预估扫描的行数1、10、10000MySQL 预估要读取的行数(非实际值)。行数越少,查询越高效。
filtered过滤比例(百分比)100.00、10.00表示通过条件筛选后,预计保留的行数比例。
越接近 100 越好。
Extra额外信息Using where、Using index、Using temporary、Using filesort显示执行的额外操作:
Using where:使用 WHERE 条件过滤;
Using index:覆盖索引(不访问表);
Using temporary:使用临时表;
Using filesort:需要额外排序(通常需优化)。

常见 type 值性能对比表

type 值说明性能等级
system表中只有一行记录✅ 最优
const主键或唯一索引查单行✅ 很快
eq_ref唯一索引等值连接✅ 优秀
ref非唯一索引等值连接⚙️ 一般
range索引范围查询(BETWEEN, >, < 等)⚙️ 可接受
index全索引扫描❌ 较慢
ALL全表扫描🚫 最差,需优化

3.优化SELECT

使用SELECT语句查询数据库中的数据是最频繁的操作,优化查询是使用数据库的重中之重,在查
询数据时常用的条件查询、范围查询、表连接查询都可以进行优化,同样对于查询结果的处理,比如
排序、分组、去重、限制也都可以进行优化。还有一点,程序员编写SQL语句时,出于可读性考虑和
自身水平问题,提交到MySQL服务器的SQL风格各不相同,这样可能导致执行效率参差不齐,MySQL
内置的优化器可以帮助我们完成一部分优化操作,但了解具体的优化方法有助于写出高性能的SQL语
句,下面分别讨论不同场景下的查询优化方法。

3.1基础优化原则

原则说明示例
1. 查询字段精简避免 SELECT *,只取需要的列SELECT name, age FROM students
SELECT * FROM students
2. 尽量过滤数据量尽早在 SQL 中减少结果集使用 WHERE 限定范围
3. 控制结果集大小分页、限制输出LIMIT 100LIMIT 10 OFFSET 20

🔹 原则:取最少的数据、查最小的范围、用最合适的索引。

3.2 WHERE 条件优化

1.避免在索引列上使用函数或计算

-- ❌ 索引失效
SELECT * FROM users WHERE YEAR(create_time) = 2024;
-- ✅ 推荐
SELECT * FROM users WHERE create_time BETWEEN '2024-01-01' AND '2024-12-31';

2.避免隐式类型转换

-- ❌ id 是 INT,右边是字符串
WHERE id = '100';
-- ✅
WHERE id = 100;

3.避免使用 OR 连接不同字段

-- ❌ 索引失效
WHERE id = 1 OR name = 'Tom';
-- ✅ 改写
WHERE id = 1
UNION ALL
SELECT * FROM users WHERE name = 'Tom';

4.利用索引最左前缀原则

-- ❌ 索引失效
WHERE id = 1 OR name = 'Tom';
-- ✅ 改写
WHERE id = 1
UNION ALL
SELECT * FROM users WHERE name = 'Tom';

3.3索引使用优化

优化方式说明
单列索引为高频过滤字段建立索引
复合索引常用于多条件查询,遵循“最左前缀原则”
覆盖索引查询列完全被索引包含,避免回表
合理排序索引ORDER BY 字段建立联合索引
避免低选择性列建索引genderstatus 等字段不宜建索引

📘 示例:

CREATE INDEX idx_name_age ON students(name, age);
SELECT name, age FROM students WHERE name='张三' AND age=20;

➡️ 可使用覆盖索引,无需访问表数据。

3.4排序与分组优化(ORDER BY / GROUP BY)

1.ORDER BY 与索引顺序一致

-- 有联合索引 idx(a,b)
SELECT * FROM t ORDER BY a,b;    ✅ 可用索引  
SELECT * FROM t ORDER BY b,a;    ❌ 索引失效  

2.避免文件排序 (Using filesort)
若 EXPLAIN 的 Extra 出现 “Using filesort”,代表额外排序,可通过调整索引解决。

3.GROUP BY 优化

  • 若可能,使用索引字段分组;
  • 禁止在 GROUP BY 前进行函数操作。

3.5 JOIN 优化

1.确保关联字段类型一致、且有索引

SELECT * FROM orders o
JOIN users u ON o.user_id = u.id;

2.小表驱动大表(Nested Loop Join 原理)

SELECT * FROM small_table s
JOIN big_table b ON s.id = b.sid;

3.控制 JOIN 数量
超过 3 个以上 JOIN 通常性能下降,应考虑中间表或预处理。

3.6子查询优化

不推荐推荐
使用嵌套子查询改写为 JOIN 或 EXISTS

📘 示例:

-- ❌ 子查询效率低
SELECT name FROM student WHERE id IN (SELECT student_id FROM score);-- ✅ JOIN 优化
SELECT s.name FROM student s JOIN score sc ON s.id = sc.student_id;

3.7 分页优化

1.大偏移分页性能差

SELECT * FROM orders LIMIT 100000, 20; --

2.推荐基于索引定位分页

SELECT * FROM orders WHERE id > 上次最大ID LIMIT 20;

3.或使用覆盖索引 + 子查询

SELECT o.* 
FROM orders o
JOIN (SELECT id FROM orders ORDER BY id LIMIT 100000,20) t 
ON o.id = t.id;

3.7 IS NULL优化

1.普通写法:IS NULL 查询可使用索引 ✅

SELECT * FROM students WHERE age IS NULL;
  • 说明:

如果字段 age 上存在索引(如 INDEX idx_age(age)),
则 IS NULL 查询是可以使用索引的。
MySQL 的 B+Tree 索引会存储 NULL 值,并且将它们放在排序的最前端。
因此查询 IS NULL 时,可以快速定位这些记录。

  • 优化效果:

通过索引直接找到所有 age 为 NULL 的行,无需全表扫描。

2.IS NOT NULL 通常无法使用索引 ❌

SELECT * FROM students WHERE age IS NOT NULL;
  • 原因:

IS NOT NULL 会匹配大量记录(几乎整个表),
优化器会判断全表扫描比走索引更快,因此通常放弃索引。

  • 解决方案:

若 NULL 值比例较高,可尝试:

SELECT * FROM students FORCE INDEX(idx_age) WHERE age IS NOT NULL;

或通过默认值替代 NULL(如 age=0);

或添加辅助标志位字段(如 has_age TINYINT(1)),再对其建立索引。

查询语句索引使用原因建议
WHERE age IS NULL✅ 可能使用B+Tree 中保存 NULL 值可直接使用索引
WHERE age IS NOT NULL⚠️ 一般不使用范围过大,全表扫描更快用默认值或辅助字段优化

4.深入理解索引与查询优化

4.1 索引底层原理与结构

索引的核心结构是 B+Tree。
MySQL 之所以采用 B+Tree,是因为它能在磁盘上高效地实现范围查找和顺序扫描。
B+Tree 的特性:

  • 所有数据都存储在叶子节点,内部节点只存储键值和指针;
  • 叶子节点之间通过链表相连,便于范围查询;
  • 每个节点对应磁盘页,减少磁盘 I/O 次数;
  • 高度平衡(一般 2~4 层即可存储上百万行)。
    📘 示意图简述:
    根节点
    ├── 分支节点(索引项+指针)
    │ ├── 叶子节点(存放实际数据或主键引用)
    │ └── 叶子节点(顺序链接)
    👉 因为 B+Tree 节点是有序的,所以数据库可以像二分查找那样快速定位记录。

4.2 B+Tree 与 Hash 索引对比

特性B+Tree 索引Hash 索引
查询类型范围、排序、高效等值查询仅支持等值查询
是否有序✅ 有序❌ 无序
支持范围查询✅ 支持❌ 不支持
适用场景通用(大多数表)内存型表(Memory 引擎)
MySQL 支持InnoDB、MyISAM 等MEMORY 引擎

📘 结论:
大多数业务场景使用 B+Tree 索引,因为它在排序、范围查询上优势明显。
Hash 仅在非常特定的内存表中使用。

4.3 覆盖索引与回表

覆盖索引(Covering Index):
当查询的列 全部被索引覆盖 时,无需访问表数据,直接从索引中返回结果。
📘 示例:

CREATE INDEX idx_name_age ON students(name, age);-- ✅ 覆盖索引查询(不需要回表)
SELECT name, age FROM students WHERE name = '张三';-- ❌ 非覆盖索引(需要回表)
SELECT * FROM students WHERE name = '张三';

判断方式:

  • EXPLAIN 的 Extra 字段显示 Using index,说明是覆盖索引。

优势:

  • 避免二次 I/O;
  • 显著提升查询性能。

4.4 索引维护与监控

索引不是“建了就完事”,需要定期维护与监控。
✅ 查看表索引信息

SHOW INDEX FROM students;

✅ 检查 SQL 是否走索引

EXPLAIN SELECT * FROM students WHERE id=1;

✅ 分析索引命中率

SHOW STATUS LIKE 'Handler_read%';
指标含义
Handler_read_key通过索引读取的次数
Handler_read_rnd_next全表扫描的次数
👉 Handler_read_key 越高越好,Handler_read_rnd_next 越低越好。
✅ 重建索引统计信息
ANALYZE TABLE students;
OPTIMIZE TABLE students;

4.5 慢查询日志优化流程

开启慢查询日志:

SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 1;  -- 记录超过1秒的SQL

查看慢查询日志路径:

SHOW VARIABLES LIKE 'slow_query_log_file';

分析工具:

  • mysqldumpslow:官方工具,汇总慢查询;
  • pt-query-digest:Percona 提供,统计最耗时 SQL。

📘 优化流程建议:

  1. 开启慢查询日志;

  2. 找出执行最慢的 SQL;

  3. 使用 EXPLAIN 分析;

  4. 优化语句或添加索引;

  5. 重新验证执行时间。

4.6 索引选择性与优化器决策

索引选择性 = 不同值数量 ÷ 总行数。
选择性越高,索引越有效。

📘 示例:

  • gender 字段只有 “男/女” → 选择性低;
  • email 字段几乎唯一 → 选择性高。

优化器会根据选择性判断是否走索引。
当字段区分度过低时(例如 99% 都为同一个值),MySQL 可能选择 全表扫描

4.7 复合索引规则总结

索引定义查询条件是否用上索引说明
(a, b, c)WHERE a=1使用第一列
(a, b, c)WHERE a=1 AND b=2使用前两列
(a, b, c)WHERE b=2不满足最左前缀
(a, b, c)WHERE a>1 AND b=2⚠️范围查询后 b 列失效

口诀: 等值条件在前,范围条件在后;使用时遵守“最左前缀原则”。

4.8 查询优化流程图

SQL语句↓
EXPLAIN 分析执行计划↓
判断是否走索引 / 是否回表↓
优化 WHERE / JOIN / ORDER BY↓
调整或添加索引↓
验证性能差异(执行时间、rows、type)↓
记录慢查询日志,持续优化
http://www.dtcms.com/a/541156.html

相关文章:

  • AtCoder真题及详细题解 ABC425B: Find Permutation 2
  • 电子机箱网站建设报告上海百度做网站
  • web渗透知识总结
  • 盲盒小程序系统开发:助力品牌拓展新市场
  • Llama-2-7b 昇腾部署:六大场景性能基准核心指标拆解
  • Vue3.x核心技术与实战(八)
  • 批量吞吐量实测:Llama-2-7b 昇腾 NPU 六大场景数据报告
  • 网站建设涉及的法律易名中国域名门户网站
  • 企业网站托管的方案软件开发流程详细
  • 做推广适合哪些网站深圳办公室设计公司排名
  • 做网站设计怎么提升自己怎么搭建个人博客网站
  • 测试题-4
  • 莱西大型网站建设做宣传海报的网站
  • Coze套餐实现工作总结
  • 做新网站的swot分析怎样选择网站建设
  • Mantle Global Hackathon 2025:里程碑升级后的首场生态猎星行动!
  • 景观建设网站宁波网站推广多少钱一个
  • Spring JDBC高级操作全解析
  • Matlab混合编程技术学习教程——目录
  • 基于MATLAB的LBFGS优化算法实现
  • 【matlab】字符串数组 转 double
  • 技术速递|Playwright MCP 调试 Web 应用时,GitHub Copilot 生成断言脚本的实用方法
  • RTSP低延迟播放重构:SmartMediaKit如何让系统“看见即行动”
  • 技术文档搭建实战:基于PandaWiki的五步自动化方案
  • wordpress能做手机站么电商网站设计系列
  • 深入剖析SLAB分配器原理与优化实战
  • 建设安全备案登入那个网站wordpress文章微信公众号推送
  • 6.1.3.1 大数据方法论与实践指南-开源大数据离线调度平台
  • 技术支持 东莞网站建设石材seo智能优化系统
  • 南沙区建设局网站如何进行网站域名解析