← 返回 MYSQL 列表

索引基础

索引基础

一、索引分类

1.1 按数据结构分类

索引类型 说明 适用场景
B+Tree 索引 InnoDB 默认,支持范围查询和排序 绝大多数场景
Hash 索引 Memory 引擎默认,等值查询极快 等值查询、不支持范围
Full-Text 索引 全文检索 大文本模糊搜索
R-Tree 索引 空间索引 地理数据(MyISAM)

1.2 按逻辑功能分类

sql
-- 主键索引(PRIMARY KEY)
-- 唯一且非空,一张表只能有一个
CREATE TABLE t1 (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50));

-- 唯一索引(UNIQUE)
-- 允许 NULL(可多个NULL),保证列值唯一
CREATE TABLE t2 (
    id INT PRIMARY KEY,
    phone VARCHAR(20) UNIQUE,
    email VARCHAR(100),
    UNIQUE KEY uk_email (email)
);

-- 普通索引(INDEX/KEY)
-- 仅加速查询,无唯一性约束
CREATE TABLE t3 (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    INDEX idx_name (name)
);

-- 联合索引(复合索引)
-- 多个字段组合成一个索引,遵循最左前缀原则
CREATE TABLE t4 (
    id INT PRIMARY KEY,
    a INT,
    b INT,
    c INT,
    INDEX idx_a_b_c (a, b, c)
);

1.3 其他分类方式

维度 分类
数据存储 聚簇索引(Clustered)、非聚簇索引(Secondary)
索引数量 单列索引、联合索引
是否唯一 唯一索引、普通索引

二、索引操作语法

2.1 创建索引

sql
-- 建表时创建
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50),
    email VARCHAR(100),
    dept_id INT,
    INDEX idx_name (name),
    UNIQUE INDEX idx_email (email),
    INDEX idx_dept_name (dept_id, name)
);

-- 建表后创建
CREATE INDEX idx_name ON users(name);
CREATE UNIQUE INDEX idx_email ON users(email);
CREATE INDEX idx_dept_name ON users(dept_id, name);

-- 用 ALTER 创建
ALTER TABLE users ADD INDEX idx_name (name);
ALTER TABLE users ADD UNIQUE INDEX idx_email (email);
ALTER TABLE users ADD INDEX idx_dept_name (dept_id, name);

2.2 删除索引

sql
DROP INDEX idx_name ON users;
ALTER TABLE users DROP INDEX idx_name;

2.3 查看索引

sql
-- 查看表上的所有索引
SHOW INDEX FROM users;

-- 或使用
SHOW CREATE TABLE users;

三、索引适用场景

3.1 适合创建索引的场景

sql
-- 1. WHERE 条件字段
-- 频繁作为查询条件的字段应该加索引
SELECT * FROM orders WHERE status = 1;  -- status 建索引

-- 2. JOIN 关联字段
-- 多表 JOIN 时,ON 后面的关联字段应建索引
SELECT * FROM orders o
JOIN users u ON o.user_id = u.id;  -- o.user_id, u.id 建索引

-- 3. ORDER BY 排序字段
-- 利用索引有序性避免文件排序
SELECT * FROM products ORDER BY create_time DESC;  -- create_time 建索引

-- 4. 覆盖索引字段
-- 如果查询的字段都在索引中,无需回表
-- 建联合索引 idx_a_b,查询 a,b 无需回表
SELECT a, b FROM t WHERE a = 1;  -- 覆盖索引

-- 5. 分组字段
SELECT dept_id, COUNT(*) FROM employee GROUP BY dept_id;  -- dept_id 建索引

3.2 不适合创建索引的场景

sql
-- 1. 数据量小的表(几千行以内),全表扫描更快
-- 2. 频繁更新的字段,索引维护开销大
-- 3. 区分度低的字段(如 gender:男/女),过滤效果差
-- 4. 长文本字段(如 TEXT、超长 VARCHAR),索引体积大且效率低

3.3 索引使用禁忌

禁忌 原因 示例
过多索引 INSERT/UPDATE/DELETE 时维护成本高 单表不超过5个索引
冗余索引 浪费空间,降低写入性能 idx_a_b 和 idx_a 同时存在
长字段索引 索引体积大、B+树层级深 TEXT、VARCHAR(500) 直接建索引

四、实战案例

案例1:查询性能对比

sql
-- 准备大量数据
CREATE TABLE t_user (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50),
    age INT,
    email VARCHAR(100),
    create_time DATETIME
);

-- 插入 100 万条数据(存储过程)
DELIMITER $$
CREATE PROCEDURE init_data()
BEGIN
    DECLARE i INT DEFAULT 1;
    WHILE i <= 1000000 DO
        INSERT INTO t_user(name, age, email, create_time)
        VALUES (CONCAT('user', i), FLOOR(RAND()*100),
                CONCAT(i, '@email.com'), NOW());
        SET i = i + 1;
    END WHILE;
END$$
DELIMITER ;
CALL init_data();

-- 无索引查询:全表扫描
SELECT * FROM t_user WHERE name = 'user500000';  -- 耗时 800ms+

-- 创建索引后
CREATE INDEX idx_name ON t_user(name);

-- 再次查询:索引查找
SELECT * FROM t_user WHERE name = 'user500000';  -- 耗时 < 10ms

案例2:联合索引设计

sql
-- 业务查询模式
-- 场景1:按状态和创建时间查订单
SELECT * FROM orders WHERE status = 1 ORDER BY create_time DESC;

-- 场景2:按用户查订单
SELECT * FROM orders WHERE user_id = 100;

-- 场景3:按状态、用户、时间组合查
SELECT * FROM orders
WHERE status = 1 AND user_id = 100
ORDER BY create_time DESC;

-- 索引设计方案
-- 根据查询频率,优先为高频查询设计索引
CREATE INDEX idx_status_create ON orders(status, create_time);  -- 覆盖场景1
CREATE INDEX idx_user_id ON orders(user_id);                     -- 覆盖场景2
-- 场景3可以通过 idx_status_create 部分利用

案例3:覆盖索引优化

sql
-- 原始查询:需要回表
SELECT id, name, age FROM t_user WHERE name = 'user500000';
-- 索引 idx_name 找到 id,再回表查 age

-- 优化:创建覆盖索引
CREATE INDEX idx_name_age ON t_user(name, age);

-- 再次查询:无需回表,直接在索引中拿到 name 和 age
SELECT id, name, age FROM t_user WHERE name = 'user500000';

案例4:索引下推(ICP)

sql
-- MySQL 5.6+ 支持索引下推
-- 联合索引 idx_name_age
SELECT * FROM t_user WHERE name LIKE 'user5%' AND age = 30;

-- 没有ICP:通过 name 条件找到主键,回表后再过滤 age
-- 启用ICP:在索引遍历时就同时过滤 age,减少回表次数
-- 默认启用,无需特殊配置

五、常见问题

5.1 为什么不建议在每个字段上都建索引?

sql
-- 写入变慢:INSERT 一条记录需要同时维护所有索引的 B+ 树
-- 占用空间:每个索引都是一棵 B+ 树
-- 优化器困扰:过多的索引会让优化器选错执行计划

5.2 如何查看索引是否被使用?

sql
-- 查看索引使用情况
SHOW STATUS LIKE 'Handler_read%';
-- Handler_read_key:索引读取次数(越高越好)
-- Handler_read_rnd_next:全表扫描次数(越低越好)

-- 查看未使用的索引
SELECT * FROM sys.schema_unused_indexes;

5.3 索引选择性是什么?

sql
-- 选择性 = COUNT(DISTINCT col) / COUNT(*)
-- 选择性越高(接近1),索引效果越好
-- 选择性 < 20% 的字段不太适合单独建索引

-- 计算选择性
SELECT COUNT(DISTINCT name) / COUNT(*) AS selectivity FROM t_user;

总结

分类 要点
索引类型 主键/唯一/普通/联合,按需选择
创建时机 建表时规划,根据查询模式设计
适用场景 WHERE/JOIN/ORDER BY/GROUP BY 字段
禁忌 不过度索引、避免冗余、长字段慎用
优化要点 覆盖索引、索引下推、高选择性字段优先

索引问题

CREATE INDEX idx_status_create ON orders(status, create_time);和
CREATE INDEX idx_status ON orders(status);CREATE INDEX idx_create ON orders( create_time);
有什么区别,两个字段一起建立索引有什么好处和坏处

一、两种索引写法核心区别

语句1:联合索引(复合索引)

sql
CREATE INDEX idx_status_create ON orders(status, create_time);

语句2:两个单列索引

sql
CREATE INDEX idx_status ON orders(status);
CREATE INDEX idx_create ON orders(create_time);

二、联合索引 vs 单列索引 差异、优缺点

1. 查询命中规则(关键:最左前缀原则)

(1)联合索引 (status, create_time)

能命中索引的场景

  • 只用 status 查询
  • status + create_time 组合查询
  • status 模糊右匹配(如 status LIKE 'xxx%'

无法完整命中

  • 仅单独用 create_time 查询 → 不走这个联合索引

(2)两个单列索引 statuscreate_time

  • 单独查 status:命中对应单列索引
  • 单独查 create_time:命中对应单列索引
  • status + create_time 组合查询:
    MySQL 只会选择其中一个最优单列索引,不会同时用两个,剩余条件走全表过滤
    极少数场景会触发 索引合并,但效率远低于联合索引。

2. 联合索引(多字段一起建)优点

  1. 组合查询效率极高
    业务高频 status + create_time 筛选、排序、分页时,联合索引一次定位,无需回表/二次过滤,性能远优于两个单列索引。
  2. 支持索引排序
    WHERE status=? ORDER BY create_time:联合索引天然有序,避免文件排序(Using filesort)
  3. 索引体积更小、维护开销低
    一条联合索引 ≈ 一条索引结构;
    两条单列索引是两份独立索引,占用更多磁盘、内存
  4. 可实现覆盖索引
    若查询字段只有 status、create_time,直接从索引取数,无需回表

3. 联合索引 缺点

  1. 受最左前缀限制
    单独查后序字段(仅 create_time)无法使用该索引,灵活性差。
  2. 字段顺序影响极大
    顺序写反 (create_time, status),查询逻辑也会跟着受限。
  3. 字段过多会导致索引臃肿
    联合索引不建议超过3个字段,索引树变大、查询变慢。
  4. 写入开销略增(相对单字段)
    新增/更新 statuscreate_time,都要维护这条联合索引;字段越多,写入压力越大。

4. 两个单列索引 优点

  1. 查询灵活性强
    两个字段单独查询都能走索引,互不影响。
  2. 单字段更新时,索引维护压力分散。

5. 两个单列索引 缺点

  1. 组合查询性能弱
    多条件同时筛选时,大多只选一个索引,另一个条件全表过滤;索引合并效率很低。
  2. 占用存储空间更大
    两份独立索引,缓冲池占用更高。
  3. 无法优化联合排序
    WHERE status=? ORDER BY create_time 极易出现 Using filesort
  4. 索引数量多,插入/更新/删除整体开销更大(每条DML都要维护多个索引)。

三、业务场景该怎么选?

场景1:业务主流是 status + create_time 组合查询、筛选、排序

优先用 联合索引 (status, create_time)
例:查询「某状态下、按创建时间排序」的订单。

场景2:两个字段经常单独查询,很少组合使用

优先用 两个单列索引
例:有时只查状态、有时只按时间查,组合查询极少。

场景3:既要单独查,又要高频组合查

方案:

  1. 保留联合索引 (status, create_time)(覆盖单status + 组合查询
  2. 额外单独给 create_time 建一个单列索引(满足仅查时间的场景)

四、补充总结(精简版)

  1. 联合索引:适合多字段组合查询/排序,性能强、体积小;缺点是受最左前缀约束,单独查后置字段失效。
  2. 多个单列索引:适合字段独立查询,灵活度高;组合查询性能差、索引冗余、写入开销大。
  3. 索引不是越多越好,线上优先按业务查询模式选型,严控索引数量。