在数据库查询优化的过程中,索引扮演着至关重要的角色。合理使用索引不仅能大幅提升查询效率,还能降低数据库的负载。然而,不同类型的索引在不同场景下的表现可能存在较大差异,因此深入理解各类索引的特性以及 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 是必不可少的步骤,掌握它能让你在优化数据库查询时事半功倍。