MySQL索引 #

一、索引概述 #

1.1 什么是索引 #

索引是帮助MySQL高效获取数据的数据结构,类似于书籍的目录。

text
没有索引:
┌─────────────────────────────────────┐
│ 数据页 → 数据页 → 数据页 → 数据页  │
│   ↓       ↓       ↓       ↓        │
│  扫描所有数据页查找目标            │
└─────────────────────────────────────┘

有索引:
┌─────────────────────────────────────┐
│        索引树                       │
│       /      \                      │
│     节点     节点                   │
│     / \      / \                    │
│   数据 数据 数据 数据               │
│   快速定位目标数据                  │
└─────────────────────────────────────┘

1.2 索引的优缺点 #

优点 缺点
加快数据检索速度 占用磁盘空间
加速排序和分组 降低写入性能
加速表连接 需要维护成本
保证数据唯一性 可能选择错误索引

1.3 索引数据结构 #

sql
-- MySQL主要使用B+树索引
-- B+树特点:
-- 1. 非叶子节点只存储键值
-- 2. 叶子节点存储所有数据和指针
-- 3. 叶子节点通过指针连接,便于范围查询

-- InnoDB存储结构
-- 聚簇索引:叶子节点存储完整行数据
-- 二级索引:叶子节点存储主键值

二、索引类型 #

2.1 主键索引 #

sql
-- 主键自动创建索引
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(50)
);

-- 或
ALTER TABLE users ADD PRIMARY KEY (id);

2.2 唯一索引 #

sql
-- 创建表时指定
CREATE TABLE users (
    id INT PRIMARY KEY,
    email VARCHAR(100) UNIQUE
);

-- 创建唯一索引
CREATE UNIQUE INDEX uk_email ON users(email);

-- 或
ALTER TABLE users ADD UNIQUE INDEX uk_email (email);

2.3 普通索引 #

sql
-- 创建普通索引
CREATE INDEX idx_name ON users(name);

-- 或
ALTER TABLE users ADD INDEX idx_name (name);

-- 创建表时指定
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    INDEX idx_name (name)
);

2.4 组合索引 #

sql
-- 创建组合索引(多列索引)
CREATE INDEX idx_name_age ON users(name, age);

-- 最左前缀原则
-- 索引可用于以下查询:
SELECT * FROM users WHERE name = 'John';
SELECT * FROM users WHERE name = 'John' AND age = 25;
SELECT * FROM users WHERE name = 'John' ORDER BY age;

-- 索引不可用于以下查询:
SELECT * FROM users WHERE age = 25;  -- 不满足最左前缀

2.5 全文索引 #

sql
-- 创建全文索引
CREATE FULLTEXT INDEX ft_content ON articles(content);

-- 使用全文索引
SELECT * FROM articles 
WHERE MATCH(content) AGAINST('MySQL database');

-- 自然语言模式
SELECT * FROM articles 
WHERE MATCH(content) AGAINST('MySQL database' IN NATURAL LANGUAGE MODE);

-- 布尔模式
SELECT * FROM articles 
WHERE MATCH(content) AGAINST('+MySQL -Oracle' IN BOOLEAN MODE);

2.6 空间索引 #

sql
-- 创建空间索引(需要空间数据类型)
CREATE TABLE locations (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    point POINT,
    SPATIAL INDEX idx_point (point)
);

-- 使用空间函数查询
SELECT * FROM locations 
WHERE ST_Distance(point, POINT(0, 0)) < 100;

三、索引操作 #

3.1 创建索引 #

sql
-- 方式1:CREATE INDEX
CREATE INDEX idx_name ON users(name);

-- 方式2:ALTER TABLE
ALTER TABLE users ADD INDEX idx_name (name);

-- 方式3:创建表时指定
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    INDEX idx_name (name)
);

3.2 查看索引 #

sql
-- 查看表索引
SHOW INDEX FROM users;

-- 查看索引信息
SELECT 
    index_name,
    column_name,
    non_unique,
    seq_in_index
FROM information_schema.statistics
WHERE table_name = 'users';

3.3 删除索引 #

sql
-- 方式1:DROP INDEX
DROP INDEX idx_name ON users;

-- 方式2:ALTER TABLE
ALTER TABLE users DROP INDEX idx_name;

-- 删除主键
ALTER TABLE users DROP PRIMARY KEY;

3.4 重建索引 #

sql
-- 重建表(包括索引)
ALTER TABLE users ENGINE=InnoDB;

-- 分析表
ANALYZE TABLE users;

-- 优化表
OPTIMIZE TABLE users;

四、索引设计原则 #

4.1 适合创建索引的场景 #

sql
-- 1. WHERE条件经常使用的列
SELECT * FROM users WHERE email = 'john@example.com';
-- 为email创建索引
CREATE INDEX idx_email ON users(email);

-- 2. JOIN连接的列
SELECT * FROM orders o JOIN users u ON o.user_id = u.id;
-- 为user_id创建索引
CREATE INDEX idx_user_id ON orders(user_id);

-- 3. ORDER BY排序的列
SELECT * FROM users ORDER BY created_at DESC;
-- 为created_at创建索引
CREATE INDEX idx_created ON users(created_at);

-- 4. GROUP BY分组的列
SELECT status, COUNT(*) FROM users GROUP BY status;
-- 为status创建索引
CREATE INDEX idx_status ON users(status);

-- 5. DISTINCT去重的列
SELECT DISTINCT name FROM users;
-- 为name创建索引
CREATE INDEX idx_name ON users(name);

4.2 不适合创建索引的场景 #

sql
-- 1. 数据量小的表
-- 小表全表扫描更快

-- 2. 频繁更新的列
-- 索引维护成本高

-- 3. 区分度低的列
SELECT * FROM users WHERE gender = 'male';
-- gender只有两个值,区分度低

-- 4. 很少使用的列
-- 浪费存储空间

4.3 组合索引设计 #

sql
-- 组合索引设计原则
-- 1. 最常用的列放在最左边
-- 2. 区分度高的列放在左边
-- 3. 范围查询的列放在最后

-- 示例:用户查询场景
-- 查询1:WHERE status = 1 AND age > 20
-- 查询2:WHERE status = 1 AND name = 'John'

-- 推荐索引
CREATE INDEX idx_status_name_age ON users(status, name, age);
-- status放在最左边(最常用)
-- name放在中间(等值查询)
-- age放在最后(范围查询)

五、索引优化 #

5.1 EXPLAIN分析 #

sql
-- 使用EXPLAIN分析查询
EXPLAIN SELECT * FROM users WHERE name = 'John';

-- 重要字段说明
-- id:查询标识符
-- select_type:查询类型
-- table:表名
-- type:访问类型(重要)
-- possible_keys:可能使用的索引
-- key:实际使用的索引
-- key_len:使用的索引长度
-- rows:预估扫描行数
-- Extra:额外信息

-- type类型(从好到差)
-- system > const > eq_ref > ref > range > index > ALL

5.2 索引使用情况 #

sql
-- 查看索引使用统计
SELECT 
    index_name,
    rows_read,
    rows_inserted,
    rows_updated,
    rows_deleted
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE object_schema = 'mydb' AND object_name = 'users';

-- 查找未使用的索引
SELECT 
    object_schema,
    object_name,
    index_name
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE index_name IS NOT NULL
    AND count_star = 0
    AND object_schema = 'mydb';

5.3 索引失效场景 #

sql
-- 1. 使用函数
SELECT * FROM users WHERE YEAR(created_at) = 2024;
-- 优化
SELECT * FROM users WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';

-- 2. 隐式类型转换
SELECT * FROM users WHERE phone = 13800138000;  -- phone是VARCHAR
-- 优化
SELECT * FROM users WHERE phone = '13800138000';

-- 3. LIKE以%开头
SELECT * FROM users WHERE name LIKE '%John%';
-- 优化
SELECT * FROM users WHERE name LIKE 'John%';

-- 4. OR条件
SELECT * FROM users WHERE name = 'John' OR age = 25;
-- 优化:使用UNION
SELECT * FROM users WHERE name = 'John'
UNION
SELECT * FROM users WHERE age = 25;

-- 5. 不等于操作
SELECT * FROM users WHERE status != 1;
-- 可能导致全表扫描

-- 6. IS NULL / IS NOT NULL
SELECT * FROM users WHERE email IS NULL;
-- 可能导致全表扫描

-- 7. 组合索引不满足最左前缀
-- 索引:idx_name_age(name, age)
SELECT * FROM users WHERE age = 25;  -- 不使用索引
SELECT * FROM users WHERE name = 'John';  -- 使用索引

5.4 索引提示 #

sql
-- 强制使用索引
SELECT * FROM users FORCE INDEX (idx_name) WHERE name = 'John';

-- 忽略索引
SELECT * FROM users IGNORE INDEX (idx_name) WHERE name = 'John';

-- 使用索引
SELECT * FROM users USE INDEX (idx_name) WHERE name = 'John';

六、索引监控 #

6.1 查看索引大小 #

sql
-- 查看表索引大小
SELECT 
    table_name,
    index_name,
    ROUND(stat_value * @@innodb_page_size / 1024 / 1024, 2) AS size_mb
FROM mysql.innodb_index_stats
WHERE database_name = 'mydb'
    AND stat_name = 'size'
ORDER BY size_mb DESC;

6.2 查看索引基数 #

sql
-- 查看索引基数(区分度)
SHOW INDEX FROM users;

-- Cardinality越高,区分度越好
-- 可以通过ANALYZE TABLE更新统计信息
ANALYZE TABLE users;

6.3 慢查询日志 #

sql
-- 开启慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;  -- 超过1秒的查询

-- 查看慢查询日志位置
SHOW VARIABLES LIKE 'slow_query_log_file';

-- 分析慢查询
-- 使用mysqldumpslow工具
-- mysqldumpslow -s t /var/log/mysql/slow.log

七、索引最佳实践 #

7.1 索引命名规范 #

sql
-- 主键索引:pk_表名
ALTER TABLE users ADD CONSTRAINT pk_users PRIMARY KEY (id);

-- 唯一索引:uk_表名_字段名
CREATE UNIQUE INDEX uk_users_email ON users(email);

-- 普通索引:idx_表名_字段名
CREATE INDEX idx_users_name ON users(name);

-- 组合索引:idx_表名_字段1_字段2
CREATE INDEX idx_users_name_age ON users(name, age);

-- 全文索引:ft_表名_字段名
CREATE FULLTEXT INDEX ft_articles_content ON articles(content);

7.2 索引维护 #

sql
-- 定期分析表
ANALYZE TABLE users;

-- 定期优化表
OPTIMIZE TABLE users;

-- 检查表
CHECK TABLE users;

-- 重建表
ALTER TABLE users ENGINE=InnoDB;

7.3 索引设计检查清单 #

检查项 说明
主键 是否有合适的主键
外键 外键列是否有索引
WHERE条件 常用条件列是否有索引
ORDER BY 排序列是否有索引
GROUP BY 分组列是否有索引
组合索引 是否满足最左前缀
区分度 索引列区分度是否足够
冗余索引 是否存在冗余索引

八、总结 #

索引要点:

类型 说明 创建语法
主键索引 唯一标识行 PRIMARY KEY
唯一索引 值唯一 UNIQUE INDEX
普通索引 加速查询 INDEX
组合索引 多列索引 INDEX(col1, col2)
全文索引 文本搜索 FULLTEXT INDEX

最佳实践:

  1. 为常用查询条件创建索引
  2. 组合索引遵循最左前缀原则
  3. 避免在区分度低的列创建索引
  4. 定期分析和优化索引
  5. 使用EXPLAIN分析查询计划

下一步,让我们学习视图和存储过程!

最后更新:2026-03-26