如何创建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. 配置索引属性

    这是最核心的一步,我们需要仔细填写每一项:

    • Name (索引名称) : 将默认生成的名称(如 index)修改为一个符合规范的、有意义的名称。业界通用的规范是 idx_字段名[_字段名]。例如,我们可以命名为 idx_category_brand
    • Type (索引类型) : 保持默认的 BTREE。这是MySQL InnoDB引擎中最常用、最通用的索引类型,它支持等值查询、范围查询和排序操作。
    • Columns (索引字段) : 点击 Columns 区域下方的 + 号,会弹出当前表所有字段的列表。
      • 首先选择 category_id
      • 再次点击 + 号,选择 brand_id
      • 关键点:这里的上下顺序就是索引的实际顺序,它直接关系到"最左前缀原则"。
    • Unique (唯一索引) : 这是一个复选框。如果业务逻辑要求 category_idbrand_id 的组合在表中是唯一的,则勾选它。对于普通的查询优化,不要勾选
  6. 保存并应用

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

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

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

  1. 普通索引(Index)

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

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

    • SQL

      sql 复制代码
      -- 在 product_name 字段上创建普通索引
      CREATE INDEX idx_product_name ON pms_product(product_name);
  2. 唯一索引(Unique Index)

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

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

    • SQL

      sql 复制代码
      -- 在 product_code 字段上创建唯一索引
      CREATE UNIQUE INDEX uk_product_code ON pms_product(product_code);
  3. 联合索引(Composite Index)

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

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

    • SQL

      sql 复制代码
      -- 创建 (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(商品详情)字段中进行全文搜索。

    • SQL

      sql 复制代码
      -- 在 product_description 字段上创建全文索引
      ALTER TABLE pms_product ADD FULLTEXT INDEX ft_product_desc(product_description);
  5. 主键索引(Primary Key)

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

    • SQL

      sql 复制代码
      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. 在索引列上进行函数运算或计算

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

    • 错误写法

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

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

    • 正确写法

      sql 复制代码
      -- 将计算移到等号右边,使用范围查询
      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),并且已经为其建立了索引。

    • 错误写法

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

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

    • 正确写法

      sql 复制代码
      -- 加上单引号,确保查询值的类型与字段类型一致
      SELECT * FROM pms_product WHERE product_code = '10001';
  3. 模糊查询以通配符开头

    • 错误写法

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

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

    • 正确写法

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

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

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

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

    • 错误写法

      sql 复制代码
      SELECT * FROM pms_product WHERE category_id = 10 OR remark = '热销商品';

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

五、进阶优化与注意事项
  1. 索引冗余与覆盖索引

    • 避免冗余索引 :如果你已经创建了联合索引 idx_a_b (a, b),那么不需要 再单独为字段 a 创建索引。因为联合索引本身就包含了 a 字段的有序信息,单独为 a 创建索引是完全多余的,只会浪费存储空间并降低 INSERTUPDATE 等写入操作的性能。
    • 利用覆盖索引 :如果一个查询所需的所有字段都包含在索引中,数据库引擎无需再"回表"查询原始数据行,性能极佳。
      • 推荐 (覆盖索引)SELECT category_id, brand_id FROM pms_product WHERE category_id = 10;
      • 不推荐 (需要回表)SELECT * FROM pms_product WHERE category_id = 10;
        后者在查完索引后,还需要拿着主键ID回到主索引(聚簇索引)中去获取其他字段的值,多了一次查找操作。
  2. 联合索引的字段顺序设计

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

    • 区分度高的字段放前面 :区分度是指字段中不重复值的数量与总行数的比值。区分度越高,筛选能力越强。例如,user_id 的区分度远高于 gender。将高区分度的字段放在前面,可以更快地缩小数据范围。
    • 范围查询的字段放最后 :如果查询中既有等值查询(=),又有范围查询(><BETWEEN),必须将范围查询的字段放在联合索引的最后
      • 例如:WHERE category_id = 10 AND shop_price > 100
      • 索引应建为:(category_id, shop_price)。如果反过来 (shop_price, category_id),那么查询时只能用到 shop_price 的索引,category_id 将无法利用索引进行过滤。
  3. 如何验证索引是否生效?

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

    • key :显示实际被优化器选中使用的索引名称。如果为 NULL,则说明没有使用任何索引。
    • type :显示表的连接类型,代表了查询的效率。
      • 高效const, eq_ref, ref, range
      • 低效(需优化)index (全索引扫描), ALL (全表扫描)。
六、常见疑难问答
  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)

    • 查询 WHERE form_id = ?:能用上索引(最左前缀)。
    • 查询 WHERE form_id = ? AND task_id = ?:能用上索引(完整匹配)。
    • 查询 WHERE task_id = ?:用不上(跳过最左)。
      此时,如果你再创建一个 INDEX idx_task_form (task_id, form_id)
      它是专门为了优化 WHERE task_id = ? 这种查询的。
      只有当你有大量 单独查询 task_id 的需求时,才需要建 (task_id, form_id)
      如果你既有 WHERE form_id = ? 的需求,又有 WHERE task_id = ? 的需求。
    • 错误做法:只建一个联合索引 (form_id, task_id)。(因为查 task_id 会失效)
    • 正确做法:建两个单列索引,或者建一个联合索引 + 一个单列索引。
    • 冗余做法:同时建 (form_id, task_id)(task_id, form_id)。这通常浪费空间,除非你的查询经常涉及 ORDER BY form_id, task_idORDER BY task_id, form_id 这种截然不同的排序。
  3. 能既创建联合索引,又创建单个索引吗?

    可以,但要注意冗余。

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

    • 情况一:你再给 A 建单列索引
      • 操作:INDEX idx_union (A, B) + INDEX idx_single (A)
      • 结果:完全冗余,浪费资源!
      • 原因:根据最左前缀原则,联合索引 (A, B) 已经包含了 A 的索引功能。数据库在查询 WHERE A = ? 时,会直接使用联合索引。再单独给 A 建索引,就像是在字典里给"姓"排了一次序,又单独拿个小本本把"姓"排了一次序,纯属多此一举。
      • 建议:删掉单列索引 idx_single (A)
    • 情况二:你再给 B 建单列索引
      • 操作:INDEX idx_union (A, B) + INDEX idx_single (B)
      • 结果:合理,且经常需要这样做。
      • 原因:联合索引 (A, B) 无法优化 WHERE B = ? 的查询。如果你经常需要根据 B 单独查询,就必须给 B 单独建一个索引。
      • 建议:保留。
  4. 我创建了联合索引 INDEX idx_form_task (form_id, task_id),SQL是 WHERE task_id = '5' AND form_id = '101'。这种有违反最左前缀原则吗?

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

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

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

    sql 复制代码
    WHERE task_id = '5' AND form_id = '101'

    还是:

    sql 复制代码
    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'

    分析:

    • 优化器看到了 form_id = '101',发现它是索引的第一列(最左列),匹配成功。
    • 优化器看到了 task_id = '5',发现它是索引的第二列,匹配成功。
    • 结果:两个字段都用上了索引,效率最高。
      怎么验证?
      你可以使用 EXPLAIN 命令来查看执行计划:
    sql 复制代码
    EXPLAIN SELECT * FROM fc_form_data WHERE task_id = '5' AND form_id = '101';

    在结果中,你会看到:

    • key 字段显示使用了 idx_form_task
    • key_len 字段显示的长度应该是两个字段长度之和(说明两个字段都参与了索引查找)。
      总结:只要你的 SQL 语句中包含了联合索引的最左前缀字段(即 form_id),哪怕你把 task_id 写在前面,或者把 form_id 写在最后面,索引都能正常工作。
  5. MySQL可以对相同字段创建不同索引吗?

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

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

    sql 复制代码
    ALTER TABLE test ADD INDEX idx_test02 USING BTREE(UPDATED);
    ALTER TABLE test ADD INDEX idx_test03 USING BTREE(UPDATED);

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

    • 浪费磁盘空间。
    • 降低 INSERTUPDATEDELETE 等写入操作的性能,因为每次数据变动都需要更新多个相同的索引树。
      因此,应严格避免创建重复索引。
  6. 不同表的索引命名相同会冲突吗?

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

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

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

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

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

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

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

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

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

  • 没有索引(全表扫描):就像你要找书中关于"MySQL原理"的内容,如果没有目录,你只能从第一页翻到最后一页,逐字逐句地找。数据量小的时候无所谓,如果书有几百万页(几千万行数据),这将是灾难性的。
  • 有了索引:你可以直接查目录,找到对应的页码,翻过去就能找到内容。

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

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+树是一种多路平衡查找树

    • 多路:一个节点可以有非常多的分叉(InnoDB中一个节点可以存储上千个键值)。这意味着树非常"矮胖"。
    • 高度极低:对于千万级数据的表,B+树的高度通常只有2到3层。这意味着,查找任意一条数据,最多只需要2到3次磁盘I/O。

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

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

假设:

  • InnoDB页大小为16KB。
  • 主键是BIGINT类型,占8字节。
  • 指针占6字节。
  • 那么一个键值对(Key+Pointer)大约14字节。

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

16KB / 14B ≈ 1170个。

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

B+树的存储能力:

  • 树高为1:只能存一页数据(很少见)。
  • 树高为2:根节点指向1170个叶子节点。假设每页存10行数据,能存 1170 * 10 ≈ 1万行。
  • 树高为3:根节点 -> 1170个中间节点 -> 1170 * 1170个叶子节点。能存 1170 * 1170 * 10 ≈ 1300万行数据!

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

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

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

  • 聚簇索引(主键索引)

    B+树的叶子节点 直接存储了整行数据

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

  • 非聚簇索引(二级索引/普通索引)

    我们在form_id上建立的索引就是二级索引。

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

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

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

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

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):高手的优化技巧

如果你执行的是:

sql 复制代码
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',则不需要回表。
是 (覆盖索引)
否 (需回表)
SQL查询: SELECT name FROM user WHERE name = 'B'
普通索引树查找
所需字段是否在索引中?
直接返回结果
拿着主键ID去聚簇索引查
返回完整数据

九、深度对比: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+ 树取代。
相关推荐
2201_761040592 小时前
CSS如何根据父级容器宽度调整子项_利用容器查询container选择器css
jvm·数据库·python
uNke DEPH2 小时前
Redis 安装及配置教程(Windows)【安装】
数据库·windows·redis
weixin_458580122 小时前
如何在 Python Fabric 中正确执行 EdgeOS 配置命令
jvm·数据库·python
m0_737539372 小时前
mysql的理论和使用
数据库·mysql
ZC跨境爬虫2 小时前
3D地球卫星轨道可视化平台开发 Day15(添加卫星系列模糊搜索功能)
前端·数据库·3d·交互·数据可视化
tjc199010052 小时前
SQL中如何处理GROUP BY的不可排序问题_ORDERBY与聚合
jvm·数据库·python
JoshRen2 小时前
Python使用PyMySQL操作MySQL完整指南
数据库·python·mysql
2601_949816222 小时前
MySQL 启动失败 (code=exited, status=1FAILURE) 异常解决方案
数据库·mysql
HHHHH1010HHHHH2 小时前
CSS定位如何实现多行文字垂直居中_通过绝对定位模拟表格
jvm·数据库·python