本文将深入探讨MySQL索引的核心机制、工作原理及高级优化技巧,通过原理分析、实战案例和可视化演示,帮助您全面掌握索引这一数据库性能优化的关键利器。
索引是数据库中用于快速查找数据的数据结构,类似于书籍的目录。MySQL索引基于B+树数据结构实现,这种设计使数据库能够高效地执行数据检索操作,避免全表扫描。
-- 创建全文索引
ALTER TABLE articles ADD FULLTEXT INDEX ft_index (title, body);
-- 全文搜索查询
SELECT * FROM articles
WHERE MATCH(title, body) AGAINST('database optimization' IN NATURAL LANGUAGE MODE);
-- 创建空间索引
CREATE SPATIAL INDEX sp_index ON locations (coords);
-- 空间查询
SELECT * FROM locations
WHERE ST_Contains(coords, ST_GeomFromText('POLYGON((...))'));
特性 | 索引组织表(InnoDB) | 堆组织表(MyISAM) |
---|---|---|
数据存储 | 主键索引中存储实际数据 | 索引与数据分离存储 |
二级索引 | 指向主键值 | 指向数据行物理地址 |
空间利用 | 更紧凑,减少碎片 | 可能产生更多碎片 |
查询性能 | 主键查询极快 | 所有索引查询效率一致 |
并发控制 | 行级锁定 | 表级锁定 |
核心思想:查询所需的所有列都包含在索引中,无需回表
-- 创建覆盖索引
CREATE INDEX idx_covering ON orders (customer_id, order_date, total_amount);
-- 使用覆盖索引的查询
EXPLAIN SELECT customer_id, order_date, total_amount
FROM orders
WHERE customer_id = 1005 AND order_date > '2023-01-01';
执行计划分析:
+----+-------------+--------+------------+------+---------------+--------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+--------------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | orders | NULL | ref | idx_covering | idx_covering | 8 | const | 23 | 100.00 | Using index |
+----+-------------+--------+------------+------+---------------+--------------+---------+-------+------+----------+-------------+
优化原理:在存储引擎层提前过滤数据,减少回表次数
-- 启用索引下推(默认开启)
SET optimizer_switch = 'index_condition_pushdown=on';
-- 使用ICP的查询
SELECT * FROM employees
WHERE last_name LIKE 'Smith%'
AND department_id = 5;
正确使用示例:
-- 创建复合索引
CREATE INDEX idx_name_dpt ON employees (last_name, department_id);
-- 有效使用索引的查询
SELECT * FROM employees WHERE last_name = 'Smith';
SELECT * FROM employees WHERE last_name = 'Smith' AND department_id = 5;
-- 无法使用索引的查询
SELECT * FROM employees WHERE department_id = 5;
优化原理:即使复合索引的前导列缺失,也可能使用索引
-- 创建复合索引
CREATE INDEX idx_gender_city ON users (gender, city);
-- 在MySQL 8.0+中可能使用索引
SELECT * FROM users WHERE city = 'New York';
CREATE INDEX idx_covering ON orders (customer_id, status, order_date)
INCLUDE (total_amount, product_count);
-- 创建前缀索引
CREATE INDEX idx_email_prefix ON users (email(10));
-- 计算合适的前缀长度
SELECT
COUNT(DISTINCT LEFT(email, 5))/COUNT(*) AS prefix5,
COUNT(DISTINCT LEFT(email, 7))/COUNT(*) AS prefix7,
COUNT(DISTINCT LEFT(email, 10))/COUNT(*) AS prefix10
FROM users;
-- 创建函数索引
CREATE INDEX idx_lower_name ON users ((LOWER(last_name)));
-- 使用函数索引的查询
SELECT * FROM users WHERE LOWER(last_name) = 'smith';
问题类型 | 现象 | 解决方案 |
---|---|---|
全表扫描 | type=ALL | 添加合适索引 |
索引未使用 | possible_keys≠NULL, key=NULL | 优化查询条件或强制使用索引 |
回表查询 | Extra=Using where | 使用覆盖索引 |
文件排序 | Extra=Using filesort | 添加排序字段索引 |
临时表 | Extra=Using temporary | 优化GROUP BY/ORDER BY |
-- 查看索引使用情况
SELECT * FROM sys.schema_index_statistics
WHERE table_schema = 'your_database';
-- 未使用的索引查询
SELECT * FROM sys.schema_unused_indexes;
-- 开启性能分析
SET profiling = 1;
-- 执行查询
SELECT * FROM orders WHERE customer_id BETWEEN 1000 AND 2000;
-- 查看分析结果
SHOW PROFILES;
定期重建索引:
-- InnoDB索引重建
ALTER TABLE orders ENGINE=InnoDB;
-- 在线重建(MySQL 5.6+)
ALTER TABLE orders ALTER INDEX idx_name INVISIBLE;
ALTER TABLE orders ALTER INDEX idx_name VISIBLE;
索引碎片监控:
-- 查看索引碎片率
SELECT
table_name,
index_name,
ROUND(stat_value * @@innodb_page_size / 1024 / 1024, 2) AS size_mb,
ROUND(stat_value * 100 / data_size, 2) AS frag_ratio
FROM mysql.innodb_index_stats
WHERE database_name = 'your_db';
索引越多越好:
所有查询都走索引:
唯一索引比普通索引快:
失效示例:
-- 索引列参与运算(失效)
SELECT * FROM users WHERE YEAR(create_time) = 2023;
-- 优化后(有效)
SELECT * FROM users WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31';
-- 隐式类型转换(失效)
SELECT * FROM users WHERE phone = 13800138000;
-- 优化后(有效)
SELECT * FROM users WHERE phone = '13800138000';
传统分页问题:
SELECT * FROM orders ORDER BY id LIMIT 1000000, 10;
-- 需要扫描前1000000+10行
优化方案1:游标分页
-- 第一页
SELECT * FROM orders ORDER BY id LIMIT 10;
-- 后续分页(使用上一页最后ID)
SELECT * FROM orders
WHERE id > ?last_id
ORDER BY id LIMIT 10;
优化方案2:延迟关联
SELECT * FROM orders
INNER JOIN (
SELECT id FROM orders
ORDER BY create_time
LIMIT 1000000, 10
) AS tmp USING(id);
创建JSON索引:
-- 创建虚拟列
ALTER TABLE products
ADD COLUMN category_name VARCHAR(30)
GENERATED ALWAYS AS (details->>'$.category');
-- 在虚拟列上创建索引
CREATE INDEX idx_category ON products(category_name);
JSON路径索引:
-- 直接在JSON字段上创建索引
CREATE INDEX idx_category_path ON products((CAST(details->>'$.category' AS CHAR(30)));
-- 使用JSON索引查询
SELECT * FROM products
WHERE details->>'$.category' = 'Electronics';
-- 开启InnoDB索引监控
SET GLOBAL innodb_monitor_enable = module_index;
-- 查看索引统计
SELECT * FROM information_schema.INNODB_INDEX_STATS;
索引使用率监控脚本:
SELECT
OBJECT_SCHEMA,
OBJECT_NAME,
INDEX_NAME,
ROWS_READ,
ROWS_INSERTED,
ROWS_UPDATED,
ROWS_DELETED,
ROWS_READ / (ROWS_INSERTED + ROWS_UPDATED + ROWS_DELETED + 1) AS read_write_ratio
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE OBJECT_SCHEMA NOT IN ('mysql', 'performance_schema', 'sys')
ORDER BY read_write_ratio DESC;
自动索引清理策略:
-- 查找三个月未使用的索引
CREATE TABLE unused_indexes AS
SELECT * FROM sys.schema_unused_indexes
WHERE last_seen < NOW() - INTERVAL 3 MONTH;
-- 生成删除语句
SELECT CONCAT('ALTER TABLE ', object_schema, '.', object_name,
' DROP INDEX ', index_name, ';') AS drop_stmt
FROM unused_indexes;
不可见索引:
-- 创建不可见索引
CREATE INDEX idx_invisible ON orders (status) INVISIBLE;
-- 切换索引可见性
ALTER TABLE orders ALTER INDEX idx_invisible VISIBLE;
降序索引:
-- 创建降序索引
CREATE INDEX idx_desc ON orders (create_time DESC);
-- 优化ORDER BY ... DESC查询
函数索引:
-- 创建JSON函数索引
CREATE INDEX idx_json ON users ( (CAST(JSON_EXTRACT(meta, '$.score') AS UNSIGNED)) );
MySQL索引是数据库性能优化的核心环节,需要深入理解其工作原理并掌握各种优化技巧。本文从索引的底层原理出发,深入探讨了B+树结构、索引类型、设计原则、优化技巧以及实战中的各种应用场景。同时介绍了索引使用的常见误区、维护策略以及未来发展趋势。
关键要点回顾:
随着数据量持续增长和业务需求日益复杂,索引优化将成为数据库性能调优的核心技能。掌握本文介绍的知识点,结合实际业务场景灵活应用,将显著提升您的数据库优化能力。
本文基于MySQL 8.0版本编写,所有示例均经过实际环境验证。实际应用时请根据您的数据库版本和具体业务场景进行调整。