Skip to content

MySQL 索引类型选择

索引类型概述

索引的作用

作用详细说明
加速查询减少需要扫描的数据量,提高查询效率
保证数据唯一性通过唯一索引实现数据完整性约束
加速排序和分组利用索引的有序性,避免额外的排序操作
优化JOIN操作用于表连接的条件,提高连接效率
减少I/O操作只读取索引数据即可满足查询,降低磁盘I/O

索引类型分类

分类维度索引类型特点
数据结构B-Tree、Hash、R-Tree、Full-Text基于不同的数据结构实现,适用于不同场景
存储方式聚簇索引、非聚簇索引数据是否与索引存储在一起
功能特性普通索引、唯一索引、主键索引、空间索引不同的功能用途和约束
字段数量单列索引、复合索引索引包含的字段数量
索引状态正常索引、失效索引索引是否被查询使用

常见索引类型详解

B-Tree 索引

基本特性

特性描述
数据结构平衡多路查找树,MySQL中实际使用B+Tree
适用场景范围查询、排序、分组、前缀匹配
存储顺序按索引键值排序存储,支持顺序访问
支持的比较操作=, >, >=, <, <=, BETWEEN, LIKE 'prefix%'
存储引擎支持InnoDB, MyISAM, Memory

工作原理

  1. 根节点:包含指向子节点的指针,不存储数据
  2. 中间节点:存储索引键值和指向子节点的指针
  3. 叶子节点
    • InnoDB:存储索引键值和完整的行数据(聚簇索引)或主键值(非聚簇索引)
    • MyISAM:存储索引键值和指向数据文件的指针

版本差异

版本特性
5.6支持前缀索引,索引长度限制767字节
5.7支持3072字节索引长度,引入虚拟列
8.0增强了B-Tree索引的性能,支持降序索引

使用案例

sql
-- 创建B-Tree索引(默认类型)
CREATE INDEX idx_username ON users(username);

-- 适用的查询
SELECT * FROM users WHERE username = 'admin'; -- 精确匹配
SELECT * FROM users WHERE username > 'a' AND username < 'z'; -- 范围查询
SELECT * FROM users ORDER BY username; -- 排序
SELECT * FROM users GROUP BY username; -- 分组
SELECT * FROM users WHERE username LIKE 'admin%'; -- 前缀匹配

-- 不适用的查询
SELECT * FROM users WHERE username LIKE '%admin'; -- 后缀匹配
SELECT * FROM users WHERE username LIKE '%admin%'; -- 中缀匹配

Hash 索引

基本特性

特性描述
数据结构哈希表,通过哈希函数将键映射到桶
适用场景精确匹配查询,不支持范围和排序
存储顺序无序存储,不支持顺序访问
支持的比较操作=, <=> (NULL安全比较)
存储引擎支持Memory, InnoDB (自适应哈希索引)

工作原理

  1. 计算索引键的哈希值
  2. 将哈希值映射到哈希表的桶
  3. 桶中存储指向数据的指针或主键值

版本差异

版本特性
5.6仅Memory引擎支持显式创建Hash索引
5.7InnoDB自适应哈希索引默认开启,增强了性能
8.0进一步优化了自适应哈希索引,支持更多场景

使用案例

sql
-- 创建Hash索引(仅Memory引擎支持显式创建)
CREATE TABLE hash_table (
    id INT,
    name VARCHAR(50),
    INDEX USING HASH (name)
) ENGINE=MEMORY;

-- 适用的查询
SELECT * FROM hash_table WHERE name = 'test'; -- 精确匹配
SELECT * FROM hash_table WHERE name <=> NULL; -- NULL安全比较

-- 不适用的查询
SELECT * FROM hash_table WHERE name > 'a'; -- 范围查询
SELECT * FROM hash_table ORDER BY name; -- 排序
SELECT * FROM hash_table GROUP BY name; -- 分组
SELECT * FROM hash_table WHERE name LIKE 'test%'; -- 模糊查询

聚簇索引

基本特性

特性描述
存储方式索引和数据存储在一起,叶子节点包含完整行数据
叶子节点包含完整的行数据,按索引顺序排列
数量限制每个表只能有一个聚簇索引
自动创建InnoDB表自动为主键创建聚簇索引
存储引擎支持InnoDB

优势

  1. 减少I/O:查询通过聚簇索引找到数据,无需二次查找
  2. 排序效率高:数据按索引顺序存储,范围查询和排序操作快
  3. 主键查询快:直接通过主键访问数据,无需回表

结构

聚簇索引B+Tree结构:
┌─────────────┐
│  根节点     │
│  100, 200   │
└─────────────┘


┌─────────────┐  ┌─────────────┐  ┌─────────────┐
│  叶子节点1  │  │  叶子节点2  │  │  叶子节点3  │
│  id=1-99   │  │  id=100-199 │  │  id=200+    │
│  完整数据  │  │  完整数据  │  │  完整数据  │
└─────────────┘  └─────────────┘  └─────────────┘

版本差异

版本特性
5.6聚簇索引只能基于主键,不支持无主键表
5.7增强了聚簇索引的性能,支持更多数据类型作为主键
8.0进一步优化了聚簇索引的存储结构,提高了查询效率

使用案例

sql
-- InnoDB表自动为主键创建聚簇索引
CREATE TABLE users (
    id INT PRIMARY KEY, -- 聚簇索引
    username VARCHAR(50),
    email VARCHAR(100)
) ENGINE=InnoDB;

-- 高效查询:直接通过聚簇索引访问
SELECT * FROM users WHERE id = 123;

-- 高效范围查询:利用聚簇索引的有序性
SELECT * FROM users WHERE id BETWEEN 100 AND 200;

-- 高效排序:数据已按主键排序
SELECT * FROM users ORDER BY id DESC LIMIT 10;

非聚簇索引

基本特性

特性描述
存储方式索引和数据分开存储,索引单独存储
叶子节点InnoDB:存储索引键值和主键值
MyISAM:存储索引键值和数据文件指针
数量限制一个表可以有多个非聚簇索引
存储引擎支持InnoDB, MyISAM

工作原理

InnoDB中的非聚簇索引

  1. 非聚簇索引叶子节点存储主键值
  2. 通过主键值回表查询完整数据(二次查找)
  3. 也称为二级索引

MyISAM中的非聚簇索引

  1. 非聚簇索引叶子节点存储数据文件指针
  2. 直接通过指针访问数据,无需回表
  3. 主键索引与普通索引结构相同

版本差异

版本特性
5.6非聚簇索引叶子节点存储主键值,支持前缀索引
5.7支持虚拟列作为索引列,增强了非聚簇索引的灵活性
8.0支持降序索引,优化了非聚簇索引的存储和查询

使用案例

sql
-- 创建非聚簇索引
CREATE INDEX idx_username ON users(username);

-- 查询过程:
-- 1. 通过idx_username找到username='admin'对应的主键值
-- 2. 通过主键值(聚簇索引)找到完整数据
SELECT * FROM users WHERE username = 'admin';

-- 覆盖索引查询:不需要回表
-- 只查询索引包含的字段(id和username)
SELECT id, username FROM users WHERE username = 'admin';

唯一索引

基本特性

特性描述
唯一性确保索引列的值唯一,不允许重复
允许NULL可以包含NULL值,但最多只能有一个NULL
自动创建UNIQUE约束自动创建唯一索引
适用场景需要保证唯一性的字段,如邮箱、手机号

版本差异

版本特性
5.6支持唯一索引,索引长度限制767字节
5.7支持3072字节索引长度,支持虚拟列作为唯一索引列
8.0增强了唯一索引的性能,支持降序唯一索引

使用案例

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

-- 或通过约束创建
ALTER TABLE users ADD CONSTRAINT uk_email UNIQUE (email);

-- 适用场景:用户邮箱唯一
INSERT INTO users(email) VALUES('user@example.com');
-- 重复插入会失败
INSERT INTO users(email) VALUES('user@example.com'); -- 报错:Duplicate entry

-- 允许NULL值
INSERT INTO users(email) VALUES(NULL);
-- 但只能有一个NULL
INSERT INTO users(email) VALUES(NULL); -- 报错:Duplicate entry

主键索引

基本特性

特性描述
唯一性确保主键值唯一,不允许重复
非空性不允许NULL值,强制非空约束
聚簇索引InnoDB中自动作为聚簇索引
自动创建PRIMARY KEY约束自动创建
数量限制每个表只能有一个主键索引

版本差异

版本特性
5.6支持自增主键,不支持无主键表
5.7增强了自增主键的性能,支持更多数据类型作为主键
8.0支持UUID作为主键的优化,增强了主键索引的性能

使用案例

sql
-- 自增主键索引(推荐)
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50)
);

-- 复合主键
CREATE TABLE order_items (
    order_id INT,
    product_id INT,
    quantity INT,
    PRIMARY KEY (order_id, product_id) -- 复合主键
);

-- UUID主键(不推荐,性能较差)
CREATE TABLE users (
    id VARCHAR(36) PRIMARY KEY DEFAULT (UUID()),
    username VARCHAR(50)
);

空间索引

基本特性

特性描述
数据结构R-Tree,用于空间数据的索引
适用场景地理空间数据查询,如地图应用
支持的数据类型GEOMETRY, POINT, LINESTRING, POLYGON
存储引擎支持MyISAM (5.7+), InnoDB (5.7+)

版本差异

版本特性
5.6仅MyISAM支持空间索引
5.7InnoDB开始支持空间索引,增强了空间数据的处理能力
8.0进一步优化了空间索引,支持更多空间函数和查询类型

使用案例

sql
-- 创建包含空间数据的表
CREATE TABLE locations (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    position POINT NOT NULL,
    SPATIAL INDEX idx_position (position)
) ENGINE=InnoDB;

-- 插入空间数据
INSERT INTO locations (name, position) VALUES 
('Office', ST_GeomFromText('POINT(116.4074 39.9042)')),
('Home', ST_GeomFromText('POINT(116.3972 39.9097)'));

-- 查询距离某个点500米范围内的位置
SELECT 
    name,
    ST_Distance_Sphere(position, ST_GeomFromText('POINT(116.4074 39.9042)')) AS distance
FROM locations
WHERE ST_Distance_Sphere(position, ST_GeomFromText('POINT(116.4074 39.9042)')) < 500
ORDER BY distance;

全文索引

基本特性

特性描述
适用场景全文搜索,支持自然语言查询
支持的查询MATCH() AGAINST()
支持的存储引擎MyISAM, InnoDB (5.6+)
支持的语言多种语言,包括中文、英文、日文等

版本差异

版本特性
5.6InnoDB开始支持全文索引,支持自然语言搜索
5.7增强了全文索引的性能,支持中文全文搜索
8.0进一步优化了全文索引,支持更多搜索模式和语言

使用案例

sql
-- 创建包含全文索引的表
CREATE TABLE articles (
    id INT PRIMARY KEY,
    title VARCHAR(100),
    content TEXT,
    FULLTEXT idx_fulltext (title, content)
) ENGINE=InnoDB;

-- 插入测试数据
INSERT INTO articles (title, content) VALUES 
('MySQL索引优化', '本文介绍MySQL索引的优化技巧,包括B-Tree索引、Hash索引等...'),
('数据库性能调优', '数据库性能调优包括索引优化、查询优化、硬件优化等方面...'),
('MySQL全文索引', 'MySQL全文索引用于高效的文本搜索,支持自然语言查询...');

-- 自然语言模式搜索
SELECT 
    title,
    MATCH(title, content) AGAINST('MySQL索引') AS relevance
FROM articles
WHERE MATCH(title, content) AGAINST('MySQL索引');

-- 布尔模式搜索
SELECT 
    title
FROM articles
WHERE MATCH(title, content) AGAINST('+MySQL -调优' IN BOOLEAN MODE);

索引类型选择原则

根据查询类型选择

查询类型推荐索引类型不推荐索引类型
精确匹配B-Tree, Hash全文索引
范围查询B-TreeHash, 全文索引
排序/分组B-TreeHash
全文搜索全文索引B-Tree, Hash
地理空间查询空间索引其他索引
JOIN操作B-TreeHash

根据数据特征选择

数据特征推荐索引类型说明
唯一值多唯一索引保证数据唯一性,提高查询效率
唯一值少普通索引避免唯一索引的额外检查开销
自增序列自增主键索引插入性能好,索引结构紧凑
随机值普通索引,避免作为主键随机值作为主键会导致频繁页分裂
文本数据全文索引或前缀索引长文本适合全文索引,短文本适合前缀索引
空间数据空间索引专门针对地理空间数据优化

根据表大小选择

表大小索引策略说明
小表(<1000行)可能不需要索引表小,全表扫描速度快,索引维护成本高
中型表为常用查询字段创建索引平衡查询性能和索引维护成本
大表精心设计索引,避免过多索引索引过多会影响写入性能,需要针对性设计

根据更新频率选择

更新频率索引策略说明
频繁更新少创建索引,避免复杂索引减少索引维护开销,提高写入性能
频繁插入避免在插入频繁的列创建索引插入会导致索引更新,影响性能
只读表可以创建较多索引无需考虑写入性能,优化查询性能
读写均衡平衡索引数量和查询性能根据实际负载调整索引策略

索引类型选择案例

案例一:用户表索引选择

表结构

sql
CREATE TABLE users (
    id INT,
    username VARCHAR(50),
    email VARCHAR(100),
    phone VARCHAR(20),
    created_at DATETIME
) ENGINE=InnoDB;

查询需求

查询类型查询语句
按ID查询SELECT * FROM users WHERE id = 123
按用户名查询SELECT * FROM users WHERE username = 'admin'
按邮箱查询SELECT * FROM users WHERE email = 'user@example.com'
按创建时间范围查询SELECT * FROM users WHERE created_at BETWEEN '2023-01-01' AND '2023-12-31'
按手机号查询SELECT * FROM users WHERE phone = '13800138000'

索引选择

sql
-- 主键索引(聚簇索引)
ALTER TABLE users ADD PRIMARY KEY (id);

-- 用户名索引(频繁查询)
CREATE INDEX idx_username ON users(username);

-- 唯一邮箱索引(确保唯一)
CREATE UNIQUE INDEX idx_email ON users(email);

-- 手机号索引(频繁查询)
CREATE INDEX idx_phone ON users(phone);

-- 创建时间索引(范围查询)
CREATE INDEX idx_created_at ON users(created_at);

案例二:订单表索引选择

表结构

sql
CREATE TABLE orders (
    id INT,
    user_id INT,
    order_no VARCHAR(20),
    total_amount DECIMAL(10,2),
    status TINYINT,
    created_at DATETIME,
    updated_at DATETIME
) ENGINE=InnoDB;

查询需求

查询类型查询语句
按订单号查询SELECT * FROM orders WHERE order_no = '202301010001'
按用户ID查询SELECT * FROM orders WHERE user_id = 123
按状态和创建时间查询SELECT * FROM orders WHERE status = 1 AND created_at > '2023-01-01'
按用户ID和状态查询SELECT * FROM orders WHERE user_id = 123 AND status = 2
按创建时间排序SELECT * FROM orders ORDER BY created_at DESC

索引选择

sql
-- 主键索引
ALTER TABLE orders ADD PRIMARY KEY (id);

-- 唯一订单号索引(确保订单号唯一)
CREATE UNIQUE INDEX idx_order_no ON orders(order_no);

-- 用户ID索引(频繁用于JOIN和查询)
CREATE INDEX idx_user_id ON orders(user_id);

-- 复合索引:状态+创建时间(用于范围查询)
CREATE INDEX idx_status_created_at ON orders(status, created_at);

-- 复合索引:用户ID+状态(用于组合查询)
CREATE INDEX idx_user_id_status ON orders(user_id, status);

-- 创建时间索引(用于排序)
CREATE INDEX idx_created_at ON orders(created_at);

案例三:商品表索引选择

表结构

sql
CREATE TABLE products (
    id INT,
    category_id INT,
    name VARCHAR(100),
    price DECIMAL(10,2),
    stock INT,
    description TEXT,
    created_at DATETIME
) ENGINE=InnoDB;

查询需求

查询类型查询语句
按分类ID和价格范围查询SELECT * FROM products WHERE category_id = 1 AND price BETWEEN 100 AND 200
按名称模糊查询SELECT * FROM products WHERE name LIKE 'iPhone%'
按库存查询SELECT * FROM products WHERE stock > 0
全文搜索商品描述SELECT * FROM products WHERE MATCH(description) AGAINST('手机')

索引选择

sql
-- 主键索引
ALTER TABLE products ADD PRIMARY KEY (id);

-- 分类ID+价格复合索引(用于范围查询)
CREATE INDEX idx_category_price ON products(category_id, price);

-- 名称前缀索引(用于模糊查询)
CREATE INDEX idx_name_prefix ON products(name(20));

-- 库存索引(用于库存查询)
CREATE INDEX idx_stock ON products(stock);

-- 全文索引(用于描述搜索)
CREATE FULLTEXT idx_fulltext_description ON products(description);

索引类型对比

B-Tree 与 Hash 索引对比

对比项B-Tree 索引Hash 索引
数据结构平衡多路查找树(B+Tree)哈希表
排序支持支持不支持
范围查询支持不支持
模糊查询支持前缀匹配不支持
精确匹配支持支持
NULL处理支持支持
插入性能中等(需要维护树结构)高(简单哈希计算)
查询性能稳定(O(log n))极快(O(1),精确匹配)
适用场景大多数场景,尤其是范围查询和排序仅适用于精确匹配查询

聚簇索引与非聚簇索引对比

对比项聚簇索引非聚簇索引
存储方式索引与数据一起存储索引与数据分开存储
数量限制每个表一个多个
主键关系通常是主键可以是任何列
查询性能更快(无需回表)较慢(可能需要回表)
插入性能可能较慢(页分裂)较快
适用场景主键查询、范围查询非主键字段查询

唯一索引与普通索引对比

对比项唯一索引普通索引
唯一性约束有,确保数据唯一无,允许重复值
NULL允许允许一个NULL允许多个NULL
查询性能略快(索引更小,无需检查重复)略慢
插入性能较慢(需要检查唯一性)较快
适用场景需要保证唯一性的字段不需要唯一性的字段

索引类型选择最佳实践

优先选择B-Tree索引

  • B-Tree索引适用于大多数场景,支持范围查询、排序、分组等操作
  • 所有存储引擎都支持B-Tree索引,兼容性好
  • MySQL 8.0支持降序B-Tree索引,进一步优化排序性能

合理使用唯一索引

  • 对于需要保证唯一性的字段使用唯一索引,如邮箱、手机号、订单号
  • 避免过度使用唯一索引,增加写入开销
  • 唯一索引可以提高查询效率,因为索引更小,查找更快

主键选择策略

  1. 优先使用自增主键

    • 插入性能好,不会导致页分裂
    • 索引结构紧凑,查询效率高
    • 适用场景:大多数业务表
  2. 避免使用UUID作为主键

    • 插入性能差,导致频繁页分裂
    • 索引结构松散,查询效率低
    • 适用场景:分布式系统需要全局唯一ID的情况
  3. 复合主键谨慎使用

    • 仅在需要时使用,如多对多关系表
    • 复合主键字段顺序很重要,影响查询效率
    • 适用场景:关联表、中间表

全文索引的合理使用

  • 仅在需要全文搜索时使用,如文章内容、商品描述
  • 避免在频繁更新的列上使用,维护成本高
  • 合理设置分词规则,提高搜索准确性
  • MySQL 5.7+支持中文全文搜索,无需额外插件

空间索引的使用

  • 仅在处理地理空间数据时使用,如地图应用、位置服务
  • 确保数据格式正确,使用ST_GeomFromText()等函数创建空间数据
  • 合理设计查询,利用空间函数优化查询性能

考虑覆盖索引

  • 设计包含查询所需所有字段的索引,避免回表查询
  • 覆盖索引可以显著提高查询性能,减少I/O操作
  • 适用场景:频繁执行的查询,字段较少的查询

索引类型选择的常见误区

误区一:所有查询都需要索引

错误观点:为所有查询字段创建索引,提高所有查询的性能

正确做法

  • 只为频繁查询的字段创建索引,考虑索引的维护成本
  • 小表(<1000行)可能不需要索引,全表扫描速度更快
  • 分析查询频率和执行计划,针对性创建索引

误区二:Hash索引总是更快

错误观点:Hash索引比B-Tree索引快,应该优先使用

正确做法

  • Hash索引仅适用于精确匹配查询,不支持范围查询和排序
  • 大多数业务场景需要范围查询和排序,B-Tree索引更实用
  • InnoDB的自适应哈希索引会自动为频繁访问的索引创建Hash索引,无需手动创建

误区三:主键必须是自增的

错误观点:主键必须是自增整数,其他类型不适合作为主键

正确做法

  • 自增主键是推荐做法,但不是必须的
  • 在某些场景下,复合主键或业务主键更合适
  • 如多对多关系表使用复合主键,外部系统ID作为业务主键

误区四:唯一索引比普通索引查询快很多

错误观点:唯一索引查询性能比普通索引好很多,应该尽可能使用

正确做法

  • 唯一索引和普通索引查询性能差异很小,主要区别在于写入时的唯一性检查
  • 唯一索引的主要作用是保证数据唯一性,而不是提高查询性能
  • 根据业务需求选择,不要为了性能而滥用唯一索引

总结

选择合适的索引类型是MySQL性能优化的关键一步,需要综合考虑以下因素:

  1. 查询需求:根据常见的查询类型选择合适的索引类型
  2. 数据特征:考虑数据的分布、唯一性、更新频率等
  3. 表的大小:大表和小表的索引策略不同
  4. 存储引擎:不同存储引擎对索引的支持和实现不同
  5. 版本特性:MySQL 5.6/5.7/8.0在索引方面有不同的特性和优化
  6. 性能平衡:平衡查询性能和插入/更新性能

通过合理选择索引类型,可以显著提高MySQL的查询性能,减少资源消耗,提高系统的整体性能和可扩展性。作为DBA,应该深入理解各种索引类型的特性和适用场景,根据实际需求做出合适的选择,并定期监控和优化索引使用情况。