在数据驱动的时代,数据库性能是应用系统的生命线,当数据量从千条跃升至百万、甚至千万级别时,一个简单的查询操作可能会变得异常缓慢,数据库索引便成为优化性能最直接、最有效的手段之一,理解并正确使用索引,是每一位后端开发者和数据库管理员的必备技能。

想象一下,在一本没有目录的厚书中寻找某个特定的词语,你只能一页一页地翻阅,直到找到为止,这便是数据库在没有索引时执行查询的方式,我们称之为“全表扫描”,而数据库索引,就如同书本的目录,它记录了特定列值与数据行物理位置的映射关系,使得数据库能够无需扫描整张表,就能快速定位到所需的数据行。
索引的工作原理与代价
索引的核心思想是“以空间换时间”,它通过创建一个额外的、有序的数据结构(最常见的是B+Tree),来加速数据的检索过程,当执行带有索引列的查询时,数据库会首先遍历这个小巧的索引结构,根据查询条件快速找到数据行的指针(或主键),然后再去数据页中获取完整的行数据,这个过程极大地减少了磁盘I/O操作,从而显著提升查询速度。
索引并非没有代价,它会带来两个主要的负面影响:
- 写入性能下降:每当对表进行插入(INSERT)、更新(UPDATE)或删除(DELETE)操作时,数据库不仅要操作数据行,还需要同步更新相关的索引结构,这会增加额外的开销,索引越多,写操作的性能损耗就越大。
- 占用存储空间:索引本身也是一个数据文件,需要占用磁盘空间,对于大型表,多个索引可能会占用相当可观的存储容量。
创建索引并非越多越好,而是一门需要在查询性能和写入性能之间做出权衡的艺术。
常见的索引类型
根据不同的数据结构和应用场景,数据库提供了多种类型的索引,选择合适的索引类型是发挥其最大效能的关键。
| 索引类型 | 数据结构 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| B-Tree 索引 | 平衡多路查找树 | 全值匹配、范围查询、前缀匹配(如 LIKE 'abc%') |
通用性强,支持范围查询和排序,是大多数数据库的默认索引类型 | 对于某些特定查询模式效率不高 |
| 哈希索引 | 哈希表 | 精确匹配查询( 或 IN) |
查询速度极快,理论上可达 O(1) | 不支持范围查询、排序;哈希冲突可能影响性能 |
| 全文索引 | 特殊的分词索引 | 在大段文本中进行关键词搜索(如文章内容、商品描述) | 支持模糊匹配、关键词搜索,功能强大 | 占用空间大,建索引和维护成本高;通常不支持中文分词(需特定插件) |
| 空间索引 | R-Tree 等 | 地理位置数据类型(如点、线、面) | 高效处理地理空间查询(如“查找我附近的所有餐厅”) | 应用场景特定,不适用于常规数据 |
如何为数据库表添加索引
掌握创建、查看和删除索引的SQL语句是基础操作,以下以主流的MySQL/MariaDB为例进行说明。
创建索引
最常用的方式是使用 CREATE INDEX 语句或 ALTER TABLE 语句。
语法:

-- 方式一:CREATE INDEX CREATE [UNIQUE] INDEX index_name ON table_name (column1(length), column2(length)); -- 方式二:ALTER TABLE ALTER TABLE table_name ADD [UNIQUE] INDEX index_name (column1(length), column2(length));
UNIQUE关键字用于创建唯一索引,确保索引列中的所有值都是唯一的。column(length)用于对字符串类型(如CHAR, VARCHAR, TEXT)的列进行前缀索引,只索引前N个字符,以节省空间。
示例:
假设有一个 users 表,我们经常根据 email 列来查询用户信息。
-- 在 users 表的 email 列上创建一个名为 idx_email 的索引 CREATE INDEX idx_email ON users (email); -- 或者使用 ALTER TABLE ALTER TABLE users ADD INDEX idx_email (email);
创建复合索引
当查询条件同时涉及多个列时,创建复合索引(或称多列索引)能带来巨大性能提升。
示例: 如果经常执行如下查询:
SELECT * FROM orders WHERE customer_id = 123 AND order_status = 'shipped';
可以创建一个 (customer_id, order_status) 的复合索引:
CREATE INDEX idx_customer_status ON orders (customer_id, order_status);
重要原则:最左前缀原则
复合索引遵循“最左前缀”原则,这意味着索引可以被用于查询条件中从最左边开始连续的列组合,对于上面的 (customer_id, order_status) 索引,以下查询会使用到索引:
WHERE customer_id = 123(使用第一列)WHERE customer_id = 123 AND order_status = 'shipped'(使用第一、二列)
但以下查询不会使用该索引:
WHERE order_status = 'shipped'(跳过了最左边的列)
在创建复合索引时,需要将使用频率最高、选择性(列中不同值的数量)最好的列放在最左边。
查看索引
要查看表中已有的索引,可以使用 SHOW INDEX 语句。

SHOW INDEX FROM users;
这条语句会返回索引的详细信息,包括索引名称、关联的列、是否唯一、索引类型等。
删除索引
当某个索引不再需要,或者发现它对性能提升不大反而影响写入速度时,应该及时将其删除。
DROP INDEX index_name ON table_name;
示例:
DROP INDEX idx_email ON users;
索引使用的最佳实践
- 为搜索、排序和分组的列建索引:优先为
WHERE子句、JOIN的ON子句、ORDER BY和GROUP BY子句中频繁出现的列创建索引。 - 考虑列的基数:基数是指列中不同值的数量,为基数高的列(如用户ID、手机号)建索引效果通常很好,但对于基数很低的列(如性别、布尔值),索引可能效果不佳,因为数据库可能认为全表扫描更快。
- 避免过度索引:如前所述,索引会拖慢写入速度,定期审查索引的使用情况,移除那些很少被查询优化器使用的冗余索引。
- 使用
EXPLAIN分析查询:这是判断索引是否生效的终极武器,在SELECT语句前加上EXPLAIN,可以查看数据库的执行计划,关注key字段(显示实际使用的索引)、type字段(显示访问类型,ALL代表全表扫描,ref,range,const等代表使用了索引)以及Extra字段(避免出现Using filesort和Using temporary)。 - 注意索引列上的函数操作:如果在索引列上使用了函数(如
WHERE YEAR(create_time) = 2025),索引通常会失效,应尽量改写为WHERE create_time >= '2025-01-01' AND create_time < '2025-01-01'的形式。
相关问答FAQs
索引是不是越多越好?为什么?
答: 绝对不是,索引是一把双刃剑,它在提升查询性能的同时,必然会带来负面影响,过多的索引会显著降低数据写入(INSERT, UPDATE, DELETE)的性能,因为每次数据变更都需要同步更新所有相关的索引结构,增加了CPU和I/O的负担,每个索引都需要占用额外的磁盘空间,对于数据量巨大的表,多个索引可能会占用非常可观的存储资源,最佳实践是“按需创建”,只为那些确实能显著提升关键查询性能的列创建索引,并定期使用EXPLAIN等工具分析索引的使用效率,及时清理无用或低效的索引。
如何判断一个SQL查询是否有效使用了索引?
答: 最直接、最有效的方法是使用数据库提供的查询分析工具,在MySQL中就是 EXPLAIN 命令,只需在你的 SELECT 语句前加上 EXPLAIN 关键字并执行,数据库就会返回一张执行计划表,而不是实际的查询结果,你需要重点关注这张表中的几个字段:
type:访问类型,这是衡量性能的关键指标,理想的值从好到差依次是const、eq_ref、ref、range、index、ALL,如果看到ALL,就意味着发生了全表扫描,没有使用索引。key:显示查询实际使用的索引名称,如果这里是NULL,则表示没有使用任何索引。key_len:使用的索引长度,可以帮助判断是否使用了复合索引的全部或部分列。rows:预估需要扫描的行数,这个值越小越好。Extra:额外信息,如果看到Using filesort(需要额外的排序)或Using temporary(使用了临时表),通常意味着当前的索引不够优化,有进一步优化的空间,如果看到Using index,则表示查询仅通过索引就能得到所需结果,是“索引覆盖”,效率非常高。