在数据库查询优化的过程中,索引扮演着至关重要的角色。合理使用索引不仅能大幅提升查询效率,还能降低数据库的负载。然而,不同类型的索引在不同场景下的表现可能存在较大差异,因此深入理解各类索引的特性以及 EXPLAIN
关键字的查询分析能力尤为重要。
本篇文章将通过对比不同索引类型的性能、使用 EXPLAIN
分析 SQL 查询计划,并提供实际测试案例,帮助大家全面理解索引的应用和优化策略。
名称 | 版本 |
---|---|
MySQL | 8.0.41 |
Windows | Windows 10 专业版,22H2,19045.5487 |
SSD | 1TB |
memory | 64GB/3200 |
cpu | R7 4800H |
测试单表数据量 | 100W |
-- 创建测试数据库
CREATE DATABASE test_db2;
USE test_db2;
-- 创建 users 表
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
age INT NOT NULL,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 生成 100 万条测试数据
-- 启用事务加速插入
START TRANSACTION;
DELIMITER ;;
CREATE PROCEDURE generate_test_data()
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE batch_size INT DEFAULT 1000;
WHILE i <= 1000000 DO
INSERT INTO users (name, age, email)
VALUES (
CONCAT('User', i),
FLOOR(20 + (RAND() * 30)),
CONCAT('user', i, '@example.com')
);
-- 每 1000 条提交一次事务
IF i % batch_size = 0 THEN
COMMIT;
START TRANSACTION;
END IF;
SET i = i + 1;
END WHILE;
COMMIT;
END;;
DELIMITER ;
-- 执行存储过程
CALL generate_test_data();
索引是一种特殊的数据结构(如 B-Tree 或 Hash),用于加速数据库查询。索引的本质是额外的存储结构,它维护着一张有序的键值映射表,使得查询可以高效地通过索引访问目标数据,而无需进行全表扫描。
MySQL 提供了多种索引类型,以适应不同的查询需求。如下:
CREATE TABLE table_name[col_name data type][unique|fulltext][index|key][index_name](col_name[length])[asc|desc]
unique
或 fulltext
:分别表示唯一索引或全文索引。index
和 key
:这两个关键词作用相同,都用于指定创建索引。col_name
:指定要创建索引的列。index_name
:指定索引名称,默认为 col_name
。length
:仅对字符串字段有效,表示索引长度。asc
或 desc
:指定索引的升序或降序排列。CREATE INDEX idx_users_name ON users(name);
NULL
值。CREATE UNIQUE INDEX idx_users_email ON users(email);
NULL
值。CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100)
);
CREATE INDEX idx_users_name_age ON users(name, age);
MATCH() ... AGAINST()
进行搜索。CREATE FULLTEXT INDEX idx_users_description ON users(description);
在多个字段共同参与查询时,可以使用组合索引来提高性能,尤其是在 WHERE
子句中多个字段联合查询时。
CREATE INDEX idx_users_name_age ON users(name, age);
数据库使用过程中,索引可能会发生碎片化,因此需要定期进行优化。
OPTIMIZE TABLE users;
=
)有效。SELECT * FROM users WHERE name = 'John';
>
, <
, BETWEEN
, LIKE 'abc%'
)有效。SELECT * FROM users WHERE age BETWEEN 20 AND 30;
SELECT * FROM users ORDER BY name;
JOIN
操作中,索引有助于加速连接操作。SELECT * FROM users u JOIN orders o ON u.id = o.user_id;
WHERE
子句中的情况。SELECT * FROM users WHERE name = 'John' AND age > 20;
OR
操作符:当 WHERE
子句中包含 OR
时,索引可能失效,尤其是当 OR
左右两边的条件索引不同或包含非索引列时。SELECT * FROM users WHERE age = 25 OR name = 'John';
LIKE
使用通配符开头:当 LIKE
查询使用通配符(如 %abc
)开头时,索引失效。SELECT * FROM users WHERE name LIKE '%John';
IS NULL
和 IS NOT NULL
**:如果查询中使用 IS NULL
或 IS NOT NULL
,且列没有索引,索引会失效。SELECT * FROM users WHERE name IS NULL;
WHERE
子句中对列进行函数操作时,索引会失效。SELECT * FROM users WHERE YEAR(birth_date) = 1990;
SELECT * FROM users WHERE age = '30'; -- 假设 age 是整数类型
在 MySQL 中,EXPLAIN
是一个非常有用的关键字,用于分析 SQL 查询的执行计划。通过查看查询执行计划,开发人员可以了解 MySQL 如何执行某个 SQL 查询,进而帮助优化查询效率。无论是复杂的查询还是简单的 SELECT 语句,使用 EXPLAIN
都能帮助你分析执行情况并找出性能瓶颈。
执行计划是数据库在执行 SQL 查询时,选择的最优路径。它包括了数据库访问的表、扫描的行数、使用的索引等信息。了解执行计划能帮助我们优化查询性能。
EXPLAIN
的基本使用在 MySQL 中,EXPLAIN
用来查看某个 SELECT、DELETE、INSERT、UPDATE 或 REPLACE 语句的执行计划。最常见的使用方式是:
EXPLAIN SELECT * FROM users WHERE name = 'Sales';
这个语句将展示 MySQL 在执行 SELECT
查询时的执行计划,包括以下几个重要的信息。
EXPLAIN
输出的字段执行 EXPLAIN
后,会返回一张包含若干字段的表。以下是常见的字段及其含义:
SIMPLE
:简单的查询,不涉及 UNION 或子查询。PRIMARY
:最外层查询。UNION
:UNION 查询中的第二个或后续查询。SUBQUERY
:子查询。ALL
:全表扫描,效率最低。index
:索引扫描,扫描索引而非数据表。range
:范围扫描,查找索引中的某个范围。ref
:使用非唯一索引。eq_ref
:对每个查询行都进行唯一索引查找,通常出现在连接操作中。const
:常数查找,最优。Using where
,意味着 MySQL 通过 WHERE 子句进行了过滤。EXPLAIN
输出示例假设我们有一个简单的查询:
mysql> EXPLAIN SELECT * FROM users WHERE age = 51;
+----+-------------+-------+------------+------+---------------+---------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+---------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | users | NULL | ref | idx_users_age | idx_users_age | 4 | const | 1 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+---------------+---------+-------+------+----------+-------+
1 row in set (0.03 sec)
1
。SIMPLE
,表示这是一个简单的查询,没有涉及到联合查询或子查询。users
。NULL
,表示没有使用分区。ref
表示使用了索引来查找数据,并且是通过非唯一索引进行的查询。ref
类型的效率相对较高,但比 eq_ref
类型稍慢。其他常见类型包括 ALL
(全表扫描)和 range
(范围扫描)。idx_users_age
,表示可能会用到 idx_users_age
索引。NULL
。这里使用的是 idx_users_age
索引。4
,表示 idx_users_age
索引的长度为 4 字节。const
,表示 age = 51
是一个常量值,用来与 idx_users_age
索引匹配。1
,表示 MySQL 预计只需要扫描一行数据来匹配查询条件。100.00
表示没有过滤掉任何行,所有扫描的行都会被选中。NULL
,表示没有额外的优化信息。EXPLAIN
优化查询type
字段显示为 ALL
,说明查询进行了全表扫描,这通常是性能瓶颈的标志。此时可以考虑为查询字段添加索引,减少扫描的数据量。possible_keys
和 key
字段,确保查询使用了合适的索引。如果查询没有使用任何索引,可以考虑为查询字段添加索引。rows
字段显示了 MySQL 预计扫描的行数。较大的行数通常意味着查询效率较低,需要通过优化索引或修改查询来减少扫描的行数。Extra
字段中的提示信息(如 Using where
、Using index
、Using temporary
等)可以帮助你了解查询中的潜在性能问题。尤其是 Using temporary
或 Using filesort
,这通常表示查询可能存在排序或临时表的性能瓶颈。EXPLAIN ANALYZE
从 MySQL 8.0.18 版本开始,EXPLAIN
支持 ANALYZE
关键字,它不仅展示查询的执行计划,还会执行查询并给出实际执行时间。这可以帮助你更加准确地评估查询性能。
mysql> EXPLAIN ANALYZE SELECT * FROM users WHERE age = 51;
+-----------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-----------------------------------------------------------------------------------------------------------------------+
| -> Index lookup on users using idx_users_age (age=51) (cost=0.35 rows=1) (actual time=0.0296..0.0296 rows=0 loops=1)
|
+-----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.03 sec)
EXPLAIN
是一个强大的工具,可以帮助开发人员了解 SQL 查询的执行计划,从而发现性能瓶颈。通过合理使用索引、优化查询语句和调整数据库结构,能够有效提高数据库的查询效率。在日常开发和性能调优过程中,使用 EXPLAIN
是必不可少的步骤,掌握它能让你在优化数据库查询时事半功倍。