Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > MySQL创建索引

一文带你搞懂MySQL如何创建索引

作者:李少兄

这篇文章主要为大家详细介绍了MySQL创建索引的相关知识,包括索引的创建、使用与优化,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

前言

在数据库开发中,索引是提升查询性能最核心、最有效的手段。一个设计精良的索引可以将查询速度提升数个数量级,而一个糟糕的索引设计,不仅无法提升性能,反而会浪费磁盘空间,拖慢数据写入速度。

一、在IDEA中图形化创建索引

IntelliJ IDEA(及其专业版内置的DataGrip)的Database工具窗口功能强大,让我们无需手写SQL即可高效地管理数据库对象。对于索引的创建,图形化界面不仅直观,还能有效避免因拼写错误导致的语法问题。

我们以一个电商系统中的商品表 pms_product 为例,该表包含 product_code (商品编码), category_id (分类ID), brand_id (品牌ID), shop_price (商城价格), product_name (商品名称) 等字段。

1.定位目标表:在IDEA右侧的 Database 工具窗口中,展开你的数据源,找到目标数据库,再找到 pms_product 表。

2.打开表结构编辑器:右键点击 pms_product 表,在弹出的菜单中选择 Modify Object... (或者直接使用快捷键 F4)。这将打开一个详细的表结构编辑界面。

3.切换到索引标签页:在表结构编辑界面的上方,你会看到 Columns, Keys, Indices, Foreign Keys 等多个标签页。点击 Indices 标签页。

4.新建索引:Indices 标签页中,点击工具栏上的 + 号(或使用快捷键 Alt + Insert),从下拉菜单中选择 Index。此时,界面下方会出现一个新的索引配置行。

5.配置索引属性:这是最核心的一步,我们需要仔细填写每一项:

6.保存并应用

配置完成后,点击窗口右下角的 OK 按钮。IDEA会自动在后台生成并执行对应的 CREATE INDEX SQL语句。

二、MySQL索引的常见类型与SQL创建方式

虽然图形化界面很方便,但理解其背后的SQL语句是成为高级工程师的必经之路。索引的类型多种多样,每种都有其特定的应用场景。

1.普通索引(Index)

这是最基本的索引类型,没有任何限制。它的作用是加速对表中数据的查询。

场景product_name 字段经常被用于搜索,但允许重复。

-- 在 product_name 字段上创建普通索引
CREATE INDEX idx_product_name ON pms_product(product_name);

2.唯一索引(Unique Index)

唯一索引与普通索引类似,但索引列的值必须唯一,允许有空值。它能保证数据列的唯一性。

场景product_code 是商品的唯一编码,绝不允许重复。

-- 在 product_code 字段上创建唯一索引
CREATE UNIQUE INDEX uk_product_code ON pms_product(product_code);

3.联合索引(Composite Index)

联合索引是在多个字段上创建一个索引。它遵循“最左前缀原则”,即查询条件中必须包含索引定义的最左侧字段,索引才能生效。

场景:经常需要根据 category_idbrand_id 组合查询商品。

-- 创建 (category_id, brand_id) 联合索引
CREATE INDEX idx_category_brand ON pms_product(category_id, brand_id);

4.全文索引(Fulltext Index)

全文索引主要用于对大文本字段(如 TEXT, VARCHAR)进行关键词搜索。它比 LIKE '%keyword%' 高效得多。

场景:在 product_description(商品详情)字段中进行全文搜索。

-- 在 product_description 字段上创建全文索引
ALTER TABLE pms_product ADD FULLTEXT INDEX ft_product_desc(product_description);

5.主键索引(Primary Key)

主键索引是一种特殊的唯一索引,不允许有空值。一个表只能有一个主键。通常在创建表时定义。

CREATE TABLE pms_product (
    id BIGINT NOT NULL AUTO_INCREMENT,
    product_code VARCHAR(64),
    -- ... 其他字段
    PRIMARY KEY (id) -- 定义主键索引
);

三、核心原理:深入理解最左前缀原则

最左前缀原则是理解联合索引的钥匙,也是面试和实战中最常被考察的知识点。很多开发者误以为SQL语句中字段的书写顺序必须和索引定义完全一致,或者不理解为何索引会“莫名其妙”地失效。

什么是“最左前缀”?

联合索引 (category_id, brand_id) 在底层的B+树数据结构中,是按照以下规则进行排序和存储的:

  1. 首先,所有数据严格按照 category_id 的值进行排序。
  2. category_id 值相同的情况下,再按照 brand_id 的值进行排序。

这就像一本电话簿,首先是按“姓氏”排序,在姓氏相同的人群内部,再按“名字”排序。

场景实战分析

基于我们刚刚创建的索引 idx_category_brand (category_id, brand_id),我们来看几种不同的查询场景:

场景SQL查询语句索引使用情况解析
精准匹配WHERE category_id = 10 AND brand_id = 5完全生效既用了“姓”,也用了“名”,可以精准定位到目标记录,效率最高。
只查最左列WHERE category_id = 10生效只要知道“姓氏”,就能快速找到所有该姓氏的人。索引被有效利用。
跳过最左列WHERE brand_id = 5失效只知道“名”而不知道“姓”,在电话簿里“名”是无序的,只能从头到尾翻找(全表扫描)。
SQL顺序颠倒WHERE brand_id = 5 AND category_id = 10完全生效MySQL的查询优化器非常智能,它会自动识别并调整条件的匹配顺序,依然能完美利用索引。

结论:SQL语句中 WHERE 子句的书写顺序不会影响索引的使用。但查询条件中必须包含联合索引定义的最左侧字段(即 category_id),索引才能被激活。如果缺少最左列,索引将完全失效。

四、索引失效的常见“陷阱”

即使建立了索引,如果SQL语句编写不当,索引依然会失效,导致数据库进行低效的全表扫描。以下是生产环境中最常见的几种“陷阱”。

1.在索引列上进行函数运算或计算

这是新手最容易犯的错误,也是最容易被忽略的性能杀手。

错误写法

-- 对 create_time 字段使用了 YEAR() 函数
SELECT * FROM pms_product WHERE YEAR(create_time) = 2023;

后果:索引失效。数据库为了判断每一行是否符合条件,必须取出 create_time 的值并执行 YEAR() 函数。这个操作破坏了索引列的原始值,导致无法利用B+树的有序性进行快速查找,只能进行全表扫描。

正确写法

-- 将计算移到等号右边,使用范围查询
SELECT * FROM pms_product 
WHERE create_time >= '2023-01-01 00:00:00' AND create_time < '2024-01-01 00:00:00';

这种写法直接对索引列进行范围比较,可以高效地利用索引。

2.隐式类型转换

这是一个非常隐蔽的“陷阱”,往往在上线后才暴露问题。

场景:假设 product_code 字段在数据库中被定义为 VARCHAR(64),并且已经为其建立了索引。

错误写法

-- 查询值没有加单引号,被MySQL识别为数字类型
SELECT * FROM pms_product WHERE product_code = 10001;

后果:MySQL发现字段是字符串类型,而查询值是数字类型,会自动执行一个隐式转换,相当于 WHERE CAST(product_code AS SIGNED) = 10001。这等同于在索引列上使用了函数,导致索引失效。

正确写法

-- 加上单引号,确保查询值的类型与字段类型一致
SELECT * FROM pms_product WHERE product_code = '10001';

3.模糊查询以通配符开头

错误写法

-- 百分号在最前面
SELECT * FROM pms_product WHERE product_name LIKE '%手机%';

后果:B+树是按照字段值的前缀进行排序的。% 开头意味着无法确定查找的起点,数据库只能扫描表中所有行的 product_name 字段,导致索引失效。

正确写法

-- 百分号在后面(前缀匹配)
SELECT * FROM pms_product WHERE product_name LIKE '华为%';

这种前缀匹配可以利用索引快速定位到以“华为”开头的记录。

4.使用 OR 连接非索引字段

场景category_id 字段有索引,但 remark(备注)字段没有索引。

错误写法

SELECT * FROM pms_product WHERE category_id = 10 OR remark = '热销商品';

后果:只要 OR 连接的任意一个字段没有索引,MySQL优化器通常会认为使用索引的成本更高,从而直接放弃索引,选择全表扫描。

五、进阶优化与注意事项

索引冗余与覆盖索引

联合索引的字段顺序设计

在设计联合索引 (A, B) 时,字段的顺序至关重要,通常遵循以下两个原则:

如何验证索引是否生效?

不要靠猜测,一定要使用 EXPLAIN 命令。在你的 SELECT 语句前加上 EXPLAIN,重点关注结果中的以下两列:

六、常见疑难问答

1.最左前缀原则只会出现在联合索引中吗?

是的,最左前缀原则是联合索引的专属特性。对于单列索引(只有一个字段的索引),数据库要么用,要么不用,不存在“用一半”的情况,所以谈不上“最左”前缀。

2.我可以在一张表中创建一个 INDEX idx_form_task (form_id, task_id),再创建一个 INDEX idx_task_form (task_id, form_id) 吗?

技术上完全允许,但在实际开发中往往没必要,甚至属于资源浪费。

假设你创建了联合索引:INDEX idx_form_task (form_id, task_id)

3.能既创建联合索引,又创建单个索引吗?

可以,但要注意冗余。

假设你创建了联合索引:INDEX idx_union (A, B)

情况一:你再给 A 建单列索引

情况二:你再给 B 建单列索引

4.我创建了联合索引 INDEX idx_form_task (form_id, task_id),SQL是 WHERE task_id = '5' AND form_id = '101'。这种有违反最左前缀原则吗?

完全没有违反,索引依然会生效。

这其实是数据库开发中一个非常经典的问题,很多新手都会误以为 SQL 里的字段顺序必须和索引顺序一模一样。

核心结论:MySQL 的查询优化器非常聪明,它会自动分析你的 SQL 语句。无论你写成:

WHERE task_id = '5' AND form_id = '101'

还是:

WHERE form_id = '101' AND task_id = '5'

数据库在执行时,都会识别出这两个条件,并根据你建立的索引 INDEX idx_form_task (form_id, task_id),自动调整匹配顺序。它会先利用 form_id 定位,再利用 task_id 过滤。

所以,SQL 语句中 WHERE 条件的书写顺序,不影响索引的使用。

“最左前缀原则”里的顺序,指的是索引定义时的顺序以及查询条件是否包含最左边的列,而不是 SQL 语句的书写顺序。

你的索引定义:(form_id, task_id)

你的查询:WHERE task_id = '5' AND form_id = '101'

分析:

EXPLAIN SELECT * FROM fc_form_data WHERE task_id = '5' AND form_id = '101';

在结果中,你会看到:

怎么验证?

5.MySQL可以对相同字段创建不同索引吗?

可以。MySQL允许对同一个字段创建多个名称不同但结构相同的索引,但这是一种极其糟糕的实践,会造成严重的资源浪费。

例如,以下两条SQL语句都可以成功执行:

ALTER TABLE test ADD INDEX idx_test02 USING BTREE(UPDATED);
ALTER TABLE test ADD INDEX idx_test03 USING BTREE(UPDATED);

从效果上看,这两个索引保留一个即可。因为它们只是名称不同,索引字段相同,实际上就是相同的索引。创建重复索引会:

因此,应严格避免创建重复索引。

6.不同表的索引命名相同会冲突吗?

不会。MySQL对索引名称的作用域限制在单个表内。也就是说,同一数据库中不同表可以拥有相同名称的索引而不会产生冲突。

然而,同一张表内的索引名称必须唯一,不能重复。

尽管如此,在实际开发中,建议采用统一规范为索引命名,比如结合表名和字段名来定义索引名称。这样不仅有助于区分不同表的索引,还能提高代码可读性和维护性。例如,对于用户表userid字段索引,可以命名为idx_user_id;订单表order的日期字段date索引则命名为idx_order_date

七、拓展:索引底层原理深度剖析——为什么加了索引会更快?

我们在前面学会了如何创建索引、如何避免索引失效,但你是否思考过:为什么加了索引,查询速度就能从几秒甚至几分钟缩短到几毫秒?这背后到底发生了什么?

要理解这个问题,我们需要深入到MySQL的存储引擎(以最常用的InnoDB为例)的底层,从数据结构磁盘I/O两个维度来揭开索引的神秘面纱。

1. 索引的本质:空间换时间

索引的本质,其实就是一种数据结构

如果把数据库表比作一本书,那么索引就是书的目录

在计算机领域,这是一种典型的**“空间换时间”**的策略。索引文件需要占用额外的磁盘空间来存储,但它能极大地减少查询时需要扫描的数据量,从而换取查询时间的缩短。

2. 为什么MySQL选择B+树?(数据结构层面的降维打击)

MySQL InnoDB引擎默认使用的索引结构是B+树。为什么不是数组、链表或者二叉树?这完全是为了适应磁盘存储的特性。

磁盘I/O的瓶颈:计算机的内存速度极快,但数据是持久化存储在磁盘上的。磁盘的读写速度(I/O)比内存慢几十万倍。因此,数据库性能优化的核心目标就是:尽量减少磁盘I/O的次数

磁盘读取数据是按“页”(Page)为单位的,InnoDB中默认一页的大小是16KB。每次I/O,至少读取一页。

二叉树的缺陷(树太高了)

如果使用二叉树(每个节点最多两个分叉),数据量一大,树的高度就会变得非常高(瘦高型)。

查找一个数据,可能需要从根节点遍历到叶子节点,经过几十层。每一层节点如果不在内存中,就需要一次磁盘I/O。如果树高20,就需要20次I/O,这在数据库领域是不可接受的慢。

B+树的优势(矮胖子)

B+树是一种多路平衡查找树

3. B+树是如何实现的?(硬核原理解析)

让我们通过一个简单的计算,来看看B+树到底有多快。

假设:

一个非叶子节点能存多少个键值?

16KB / 14B ≈ 1170个。

这意味着,B+树的一个节点(一页)可以指向1170个子节点。

B+树的存储能力:

结论:几千万行数据的表,B+树高度仅为3。也就是说,无论数据量多大,InnoDB主键索引查询最多只需要3次磁盘I/O。这就是为什么加了索引会快的根本原因——它将O(N)的全表扫描复杂度降低到了O(log N),且常数极小。

4. 聚簇索引与非聚簇索引(数据到底存在哪?)

在InnoDB中,索引不仅仅是“目录”,它和数据是绑定在一起的。

聚簇索引(主键索引):B+树的叶子节点直接存储了整行数据

当你通过主键查询时,一旦在B+树中定位到叶子节点,数据就已经拿到了,不需要再做任何操作。这就是为什么主键查询最快。

非聚簇索引(二级索引/普通索引):我们在form_id上建立的索引就是二级索引。

它的B+树叶子节点不存整行数据,只存索引列的值主键值

5. 回表(Table Lookup):为什么查主键最快?

这就引出了一个重要的概念——回表

假设你执行了这样一条SQL:

SELECT * FROM fc_form_data WHERE form_id = '101';

因为你查的是SELECT *(所有字段),而form_id索引树上只有form_idid(主键)。

  1. 第一步:先在form_id的二级索引树中找到form_id = '101'的记录,拿到主键id(假设是1001)。
  2. 第二步:拿着主键id = 1001,去**主键索引树(聚簇索引)**中再查找一遍,获取完整的行数据(如task_id, create_time等)。

这个过程叫回表。回表意味着多了一次B+树查询(多几次I/O)。

6. 覆盖索引(Covering Index):高手的优化技巧

如果你执行的是:

SELECT id, form_id FROM fc_form_data WHERE form_id = '101';

你会发现,查询需要的字段idform_idform_id的索引树上全都有!这时候,MySQL就不需要去查主键索引树了,直接返回索引树上的数据即可。

这就叫覆盖索引。覆盖索引避免了回表,是性能优化的重要手段。

总结

索引之所以快,是因为:

  1. 使用了B+树数据结构,将树高控制在极低水平(2-3层),极大地减少了磁盘I/O次数
  2. 利用了数据的有序性,让数据库能像查字典一样通过二分查找快速定位,而不是逐行扫描。
  3. 通过覆盖索引等机制,避免了额外的数据读取操作。

八、可视化图解:B+树结构与回表流程

为了更直观地理解索引的运作机制,我们使用 Mermaid 流程图来展示 InnoDB 中 B+ 树的结构以及“回表”查询的全过程。

假设我们有一张表 user,包含字段 id (主键), name (普通索引), age

1. B+树结构图解

InnoDB 中,主键索引(聚簇索引)的叶子节点存储整行数据,而普通索引(二级索引)的叶子节点只存储主键值。

图解说明:

  1. 左侧(普通索引):当我们执行 SELECT * FROM user WHERE name = 'B' 时,首先在 name 的索引树中找到记录。
  2. 中间(获取主键):在 name 索引的叶子节点中,我们只找到了主键值 id=5
  3. 右侧(回表):拿着 id=5 回到主键索引树(聚簇索引)中查找,最终获取到完整的行数据(name, age 等)。这个过程就是回表

2. 覆盖索引流程图

如果 SQL 优化为 SELECT id, name FROM user WHERE name = 'B',则不需要回表。

九、深度对比:B+树、B树与哈希索引

MySQL 之所以选择 B+ 树作为默认索引结构,是因为它在磁盘 I/O、范围查询和稳定性之间取得了最佳平衡。以下是三种主流索引结构的详细对比。

对比维度B+ 树 (MySQL InnoDB默认)B 树 (平衡多路查找树)哈希索引 (Hash Index)
数据存储位置仅叶子节点存储完整数据,内部节点仅存索引键。所有节点都存储索引键和对应数据。仅存储哈希值和指向数据的指针。
树的高度更低(矮胖)。因内部节点不存数据,单页可存更多键值,IO次数更少。较高。因节点存数据,单页存储的键值少,树更高。无树结构,基于哈希表。
范围查询极强。叶子节点通过双向链表连接,只需遍历链表即可。较弱。需进行中序遍历,涉及大量随机 IO。不支持。哈希值是无序的,无法进行范围扫描。
排序查询支持。索引本身有序,可直接利用索引顺序进行 ORDER BY支持不支持
模糊查询支持前缀匹配 (如 LIKE 'abc%')。支持不支持
等值查询 (O(log N))。所有查询都需走到叶子节点,性能稳定。 (可能 O(1)~O(log N))。若数据在非叶子节点命中则极快,但不稳定。极快 (O(1))。直接计算哈希地址定位,无 IO 冲突时最快。
适用场景通用型。绝大多数业务场景,尤其是涉及范围查询、排序、分页的。较少用于数据库主索引,多用于文件系统元数据管理。特定型。仅适用于内存数据库(如Redis)或纯等值查询场景(如配置表)。

核心总结:

  1. B+ 树胜在“范围查询”与“I/O友好”:B+ 树的内部节点不存数据,这使得它比 B 树更“矮胖”,同样的磁盘页能容纳更多索引键,大大降低了树的高度,减少了磁盘 I/O 次数。同时,叶子节点的链表设计让它成为了范围查询(BETWEEN, >, <)和排序(ORDER BY)的王者。
  2. 哈希索引胜在“精准打击”:虽然哈希索引在 = 查询上速度无敌,但它是个“偏科生”。一旦遇到 >< 或者 ORDER BY,它就完全失效。因此,InnoDB 仅在内存中提供“自适应哈希索引”来辅助热点数据的等值查询,而不会将其作为默认的持久化索引结构。
  3. B 树的“中庸之道”:B 树虽然也能工作,但由于其内部节点存储数据导致树高较高,且范围查询效率不如 B+ 树,因此在现代关系型数据库中逐渐被 B+ 树取代。

以上就是一文带你搞懂MySQL如何创建索引的详细内容,更多关于MySQL创建索引的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文