在数据库系统中,索引是提高查询性能的关键因素。作为一名长期与MySQL打交道的开发者,我深刻理解索引的重要性以及不当使用索引带来的性能问题。本文将深入探讨MySQL索引的方方面面,从底层数据结构到实际应用策略,帮助您全面掌握这一核心技术。
索引是数据库表中一列或多列的值进行排序的一种数据结构,使用索引可以快速访问数据库表中的特定信息。简单来说,索引就像是书籍的目录,能够帮助我们快速定位到需要的内容,而不必逐页翻阅。
MySQL最常用的索引类型是基于B+Tree(B-Tree的变种)实现的。让我们先了解这两种数据结构:
B-Tree特点:
B+Tree特点:
MySQL的InnoDB存储引擎使用B+Tree作为索引结构,原因在于:
除了B+Tree,MySQL还支持哈希索引(Memory引擎默认):
哈希索引特点:
InnoDB引擎有一个自适应哈希索引功能,会自动为频繁访问的索引页建立哈希索引。
CREATE TABLE users (
id INT NOT NULL AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
PRIMARY KEY (id)
);
特点:
CREATE TABLE users (
id INT NOT NULL AUTO_INCREMENT,
email VARCHAR(100) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_email (email)
);
特点:
CREATE TABLE orders (
id INT NOT NULL AUTO_INCREMENT,
user_id INT NOT NULL,
order_date DATETIME,
PRIMARY KEY (id),
INDEX idx_user (user_id)
);
特点:
CREATE TABLE logs (
id INT NOT NULL AUTO_INCREMENT,
user_id INT NOT NULL,
action VARCHAR(50) NOT NULL,
created_at DATETIME NOT NULL,
PRIMARY KEY (id),
INDEX idx_user_action (user_id, action)
);
特点:
CREATE TABLE articles (
id INT NOT NULL AUTO_INCREMENT,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
PRIMARY KEY (id),
FULLTEXT KEY ft_content (content)
);
特点:
CREATE TABLE locations (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
position POINT NOT NULL,
PRIMARY KEY (id),
SPATIAL KEY sp_position (position)
);
特点:
聚簇索引:
非聚簇索引(二级索引):
InnoDB使用索引组织表(IOT)结构,表数据按照主键顺序存储。如果没有显式定义主键:
InnoDB中索引和数据存储在表空间文件中(.ibd),由页(page,默认16KB)组成:
页结构示例:
| Fil Header (38) | Page Header (56) | Infimum+Supremum (26) | User Records | Free Space | Page Directory | Fil Trailer (8) |
索引不是免费的,它带来以下开销:
经验法则:不要创建超过5-6个索引,写密集的表应更少。
选择性 = 不同值的数量 / 总行数
高选择性(接近1)的列适合建索引,如用户ID;低选择性的列(如性别)不适合单独建索引。
当索引包含查询所需的所有字段时,称为覆盖索引,可以避免回表:
-- 假设有索引 (user_id, status)
EXPLAIN SELECT user_id, status FROM orders WHERE user_id = 100;
-- 可能看到 "Using index" 表示使用了覆盖索引
MySQL 5.6+引入,将WHERE条件推到存储引擎层过滤:
-- 假设有索引 (name, age)
SELECT * FROM users WHERE name LIKE '张%' AND age = 20;
没有ICP:存储引擎查找所有’张%'的记录,服务层过滤age=20
有ICP:存储引擎直接查找’张%'且age=20的记录
多范围读取优化,先收集主键再排序后访问数据,减少随机IO:
-- 假设有索引 (age)
SELECT * FROM users WHERE age BETWEEN 20 AND 30;
MySQL可以使用多个索引的交集或并集:
-- 假设有索引 (a) 和 (b)
SELECT * FROM table WHERE a = 1 OR b = 2;
-- 可能使用 index_merge 优化
-- 1. 使用函数或运算
SELECT * FROM users WHERE YEAR(create_time) = 2023;
-- 改为: SELECT * FROM users WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31';
-- 2. 隐式类型转换
SELECT * FROM users WHERE phone = 13800138000; -- phone是varchar类型
-- 改为: SELECT * FROM users WHERE phone = '13800138000';
-- 3. 前导模糊查询
SELECT * FROM users WHERE name LIKE '%三';
-- 改为: SELECT * FROM users WHERE name LIKE '张%' (如果可以)
-- 4. 不符合最左前缀
-- 索引 (a,b,c)
SELECT * FROM table WHERE b = 1 AND c = 2; -- 无法使用索引
使用FORCE INDEX或优化统计信息:
ANALYZE TABLE users; -- 更新统计信息
SELECT * FROM users FORCE INDEX(idx_age) WHERE age > 20;
对于TEXT/BLOB等大字段,可以建前缀索引:
CREATE TABLE articles (
id INT PRIMARY KEY,
content TEXT,
INDEX idx_content (content(100)) -- 前100个字符
);
-- 用户表
CREATE TABLE users (
user_id BIGINT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
phone VARCHAR(20),
register_time DATETIME NOT NULL,
UNIQUE KEY uk_email (email),
UNIQUE KEY uk_phone (phone),
INDEX idx_username (username),
INDEX idx_register_time (register_time)
);
-- 订单表
CREATE TABLE orders (
order_id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
order_status TINYINT NOT NULL,
total_amount DECIMAL(10,2) NOT NULL,
create_time DATETIME NOT NULL,
pay_time DATETIME,
INDEX idx_user_status (user_id, order_status),
INDEX idx_create_time (create_time),
INDEX idx_user_pay (user_id, pay_time)
);
-- 商品表
CREATE TABLE products (
product_id BIGINT PRIMARY KEY,
category_id INT NOT NULL,
product_name VARCHAR(200) NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock INT NOT NULL,
INDEX idx_category (category_id),
INDEX idx_name (product_name),
INDEX idx_price (price)
);
-- 查看索引统计信息
SHOW INDEX FROM users;
-- 查看索引使用情况
SELECT * FROM sys.schema_index_statistics
WHERE table_schema = 'your_db' AND table_name = 'users';
-- 通过performance_schema查看
SELECT * FROM performance_schema.table_io_waits_summary_by_index_usage;
-- 重建索引(InnoDB)
ALTER TABLE users ENGINE=InnoDB;
-- 优化表(重建表并优化索引)
OPTIMIZE TABLE users;
-- 分析表(更新索引统计信息)
ANALYZE TABLE users;
倒序索引:支持索引的降序扫描
CREATE INDEX idx_desc ON users(register_time DESC);
隐藏索引:可以标记索引为不可见(测试删除索引影响)
CREATE INDEX idx_temp ON users(phone) INVISIBLE;
ALTER INDEX idx_temp ON users VISIBLE;
函数索引:基于表达式或函数创建索引
CREATE INDEX idx_func ON users((JSON_EXTRACT(data, '$.region')));
索引是MySQL性能优化的核心,合理使用索引可以提升查询性能几个数量级。但索引并非越多越好,需要根据实际查询模式、数据分布和写负载来精心设计。理解索引的底层原理有助于做出更好的设计决策,避免常见的性能陷阱。
记住,索引设计是一门艺术,需要在查询性能与写开销之间找到平衡点。定期监控索引使用情况,删除无用索引,优化现有索引,才能保持数据库长期高效运行。