10年 Java程序员,硬核人生!勇往直前,永不退缩!

欢迎围观我的git:https://github.com/R1310328554/spring_security_learn 寻找志同道合的有志于研究技术的朋友,关注本人微信公众号: 觉醒的码农,或Q群 165874185

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

 

一、索引类型:Normal(普通索引) vs Unique(唯一索引)

核心差异是 是否对字段值施加「唯一性约束」,其他特性均围绕这一点展开:
 
对比维度Normal(普通索引)Unique(唯一索引)
核心约束 无唯一性要求,字段值可重复 强制字段值唯一,不允许重复(NULL 特殊处理)
NULL 值支持 允许多个 NULL 值(InnoDB/MyISAM 均支持) 仅允许 1 个 NULL 值(InnoDB/MyISAM 通用)
插入 / 更新逻辑 直接执行,无需检查重复 先检查字段值是否已存在,重复则报 Duplicate entry 错误
索引结构 与 Unique 一致(默认 BTree),仅多存储重复值 与 Normal 结构相同,但叶子节点无重复值
性能特点 插入 / 更新效率略高(无重复检查);查询效率与 Unique 基本一致 插入 / 更新效率略低(需额外检查唯一性);查询效率略优(叶子节点数据量少,查找更快)
适用场景 频繁查询、字段值可能重复的场景(如:商品分类、用户昵称、文章标签) 字段值唯一的场景(如:手机号、身份证号、用户名、订单号),需确保数据唯一性

关键补充:

  1. 主键索引是特殊的 Unique 索引:PRIMARY KEY 本质是「非空 + 唯一」的 Unique 索引(不允许 NULL 值),优先级高于普通 Unique 索引。
  2. 组合 Unique 索引:若创建 UNIQUE KEY (col1, col2),则约束「col1+col2 的组合值唯一」,而非单个字段唯一(比如 col1 可重复,但 col1+col2 不可重复)。
  3. 唯一性约束的本质:Unique 索引的核心作用是「保证数据唯一性」,查询优化是附加作用;而 Normal 索引的核心作用就是「加速查询」。

二、索引方法:BTree(默认) vs Hash

核心差异是 索引的存储结构和查询逻辑,直接决定了「哪些查询场景能生效」,MySQL 中最常用的是 BTree(实际是 B+Tree),Hash 仅适用于特定场景:
 
对比维度BTree(实际为 B+Tree)Hash 索引
存储结构 平衡多路查找树(B+Tree),数据按「字段值有序排列」,叶子节点串联成链表 哈希表结构,通过「字段值哈希计算」得到索引位置,数据无序
适用查询类型 支持所有查询场景:等值查询(=)、范围查询(>、<、BETWEEN)、排序(ORDER BY)、分组(GROUP BY)、前缀匹配(LIKE 'abc%') 仅支持 等值查询(=、<>、IN),不支持范围、排序、模糊查询(除精确匹配外均失效)
查询效率 等值查询:O (logn)(稳定高效);范围查询:O (logn + 扫描行数)(因有序,无需全表扫描) 理想情况:O (1)(直接通过哈希值定位);存在哈希冲突时:O (k)(k 为冲突数据量,冲突多则效率下降)
数据有序性 索引本身有序(叶子节点按字段值升序 / 降序排列) 索引无序(哈希值随机分布)
字段类型支持 支持所有字段类型(字符串、数字、日期等) 仅支持「能计算哈希值」的字段类型(数字、字符串等),不支持 blob、text 等类型
覆盖索引支持 支持(可直接从索引叶子节点获取查询字段,无需回表) 不支持(哈希索引仅存储「哈希值 + 行指针」,必须回表查询原始数据)
NULL 值处理 正常存储,支持 NULL 值查询 支持 NULL 值(哈希值统一处理),但同样仅支持等值查询
存储引擎支持 所有主流引擎(InnoDB、MyISAM、Memory)默认支持 仅 Memory 引擎默认支持;InnoDB 不支持手动创建 Hash 索引(仅内部自适应哈希索引,不可控)
适用场景 绝大多数业务场景(需范围查询、排序、模糊匹配) 仅内存数据库(Memory)+ 纯等值查询场景(如:缓存键值对查询、简单字典查询)

关键补充:

  1. B+Tree 是 MySQL 的默认选择:InnoDB 和 MyISAM 均默认使用 B+Tree 索引,因为它能适配业务中 99% 的查询场景(比如分页查询 LIMIT、范围筛选 price BETWEEN 100 AND 200、前缀模糊查询 name LIKE '张%')。
  2. Hash 索引的局限性:
    • 不支持范围查询:比如 id > 100 无法通过 Hash 索引查询(哈希值无序,无法确定范围);
    • 不支持排序:ORDER BY col 需全表排序(Hash 索引无序);
    • 哈希冲突问题:若多个字段值哈希后得到相同位置,需线性探测 / 链表解决,冲突多则效率接近全表扫描;
  3. InnoDB 的自适应哈希索引(AHI):InnoDB 会自动对频繁等值查询的 BTree 索引创建 Hash 索引(内存中),无需手动干预,目的是优化等值查询效率,但不可控(无法手动创建 / 删除)。

三、总结:如何选择?

  1. 索引类型(Normal/Unique):
    • 需保证字段值唯一 → 用 Unique(或主键索引);
    • 无需唯一性,仅需加速查询 → 用 Normal。
  2. 索引方法(BTree/Hash):
    • 绝大多数场景(需范围、排序、模糊查询) → 用默认的 BTree(无需手动指定);
    • 仅 Memory 引擎 + 纯等值查询 → 考虑 Hash 索引(极少用)。
 
最终组合示例:
 
  • 用户名(唯一 + 需查询)→ UNIQUE KEY idx_username (username)(BTree+Unique);
  • 商品价格(非唯一 + 需范围查询)→ KEY idx_price (price)(BTree+Normal);
  • 内存数据库中的字典表(纯等值查询)→ KEY idx_dict_key (dict_key) USING HASH(Hash+Normal)。

 

 

MySQL的 索引类型 normal、 unique 其实差不多,如果有limit 1这样的条件。

 

你说 Normal 索引的查询效率与 Unique 基本一致; 如果我在某字段创建了 Normal 索引,然后在这个字段上做where条件查询同时 limit 1,那么是不是意味着需要扫描它的全部索引, 那么速度也会比较慢吧, 如果是 unique, 那么不需要吧、效率就差比较多了吧 

-- 这里其实问错了, 本意是询问没有limit 1的情况

你的核心误解是 “Normal 索引加 LIMIT 1 需要扫描全部索引” —— 这其实是错的!不管是 Normal 还是 Unique 索引(默认都是 BTree 结构),WHERE 字段=xxx LIMIT 1 的查询逻辑都是 “快速定位到第一个匹配项,然后直接返回,不会扫描全部索引”,因此两者的查询效率几乎没有差距,远达不到 “差比较多” 的程度。

一、先理清核心逻辑:BTree 索引 + LIMIT 1 的查询过程(Normal 和 Unique 通用)

BTree 索引的本质是「有序的平衡树」,叶子节点按字段值排序且串联成链表。不管是 Normal 还是 Unique,执行 WHERE col=xxx LIMIT 1 时,查询流程完全一致:
 
  1. 从 BTree 的根节点开始,通过二分查找快速定位到「字段值 = xxx」的叶子节点(时间复杂度 O (logn),和索引大小无关,只和树的高度有关,通常 3-4 层);
  2. 找到第一个匹配的叶子节点后,因为有 LIMIT 1,直接返回该节点对应的行数据,停止后续所有扫描(不会继续查找后面是否有重复值,哪怕是 Normal 索引允许重复)。
 
举个例子:假设字段name是 Normal 索引,有 100 万条数据,其中 10 万条都是name='张三'。执行 WHERE name='张三' LIMIT 1 时,数据库找到第一个「name=' 张三 '」的叶子节点就直接返回,不会去扫剩下的 99999 个「张三」—— 这和 Unique 索引(只有 1 个「张三」)的查询过程完全一样,都是 “找到第一个就停”。

二、Normal 和 Unique 在「WHERE+LIMIT 1」场景下的效率差异:几乎可以忽略

两者的差异仅来自「Unique 索引的唯一性约束对索引结构的微小影响」,但对查询速度的影响极小:
 
对比点Normal 索引Unique 索引
定位第一个匹配项的速度 O (logn)(和 Unique 完全一致) O (logn)(和 Normal 完全一致)
停止条件 找到第一个匹配项,因 LIMIT 1 直接返回 找到第一个匹配项,因 Unique 约束确认无重复,直接返回
实际扫描量 1 个叶子节点(最多 2-3 个相邻节点,因 BTree 页存储多条数据) 1 个叶子节点(无需考虑后续重复,停止更 “果断”)
效率差异 几乎无差异,耗时差距在微秒级 略快,但人类感知不到(比如 0.1ms vs 0.09ms)
 
简单说:查询阶段的核心耗时是 “定位到第一个匹配项”(O (logn)),而两者在这一步完全一致;后续的 “停止逻辑” 差异对总耗时的影响微乎其微。

三、什么时候会出现明显效率差距?(几乎不涉及 LIMIT 1 场景)

只有当你的查询不满足 “快速定位第一个匹配项” 时,两者才可能有可感知的差距,但这种场景和LIMIT 1无关:
 
  1. 无 WHERE 条件,仅 LIMIT 1:比如 SELECT * FROM t LIMIT 1。此时 Normal 和 Unique 索引都用不上(无过滤条件),都走全表扫描(或主键索引扫描),效率完全一致;
  2. 查询重复值极多的字段,且不带 LIMIT 1:比如 SELECT * FROM t WHERE name='张三'(10 万条结果)。此时:
    • Normal 索引:需要扫描所有 10 万个「张三」对应的叶子节点(有序串联,扫描效率仍比全表高);
    • Unique 索引:因约束无重复,扫描 1 个节点就结束;
       
      这种场景下 Unique 确实快很多,但你加了 LIMIT 1,就屏蔽了这个差异;
  3. 组合索引场景:比如组合 Normal 索引 (col1, col2),查询 WHERE col1='a' LIMIT 1。同样是定位到第一个col1='a'的节点就返回,和组合 Unique 索引效率一致。

四、关键结论:不用纠结 “Normal+LIMIT 1” 的效率,核心看是否需要唯一性约束

  1. 查询效率层面:Normal索引+WHERE+LIMIT 1 和 Unique索引+WHERE+LIMIT 1 的效率几乎没有区别,都能快速定位并返回,不会扫描全部索引;
  2. 差异本质:Unique 索引的核心价值是「保证数据唯一性」(避免重复插入),而不是 “查询更快”—— 查询效率的微弱优势只是唯一性约束带来的 “附加福利”;
  3. 选择逻辑:
    • 若字段值必须唯一(如手机号、用户名)→ 用 Unique 索引(优先保证数据正确性,顺带略提查询效率);
    • 若字段值可重复(如商品分类、用户昵称)→ 用 Normal 索引(无需唯一性检查,插入 / 更新效率略高,查询 + LIMIT 1 完全够用)。
 
举个实际测试场景(InnoDB,100 万条数据):
 
  • Normal 索引 idx_name (name),查询 SELECT id FROM t WHERE name='张三' LIMIT 1 → 耗时 0.12ms;
  • Unique 索引 uk_name (name),相同查询 → 耗时 0.10ms;
     
    两者差距仅 0.02ms,完全不影响业务性能。真正影响效率的是 “是否建索引”“索引是否被命中”,而不是 Normal 和 Unique 的选择。

 

你的质疑非常关键!核心结论先明确:你说的 “查询到一个就返回” 是对的,但 “全表扫描” 的定义需要澄清 —— 这里的 “全表扫描”≠扫描所有数据,而是 “扫描表的主数据载体(聚集索引 / 表文件)”,且会在拿到第一条数据后立即停止,不会扫完整张表。
 
之前的表述不够精准,导致了误解,现在详细拆解:

一、先纠正 “全表扫描” 的误区:MySQL 中 “全表扫描(Full Table Scan)” 的真实含义

“全表扫描” 不是 “必须扫描表中所有行”,而是指:
 
  • 数据库没有使用二级索引(Normal/Unique 都是二级索引),而是直接扫描表的「主数据载体」:
    • InnoDB:扫描「聚集索引」(主键索引,表数据本身就是按聚集索引组织的,叶子节点就是完整行数据);
    • MyISAM:扫描「表文件本身」(按数据插入顺序存储的堆表)。
  • 扫描过程中,只要满足条件(这里是 “拿到 1 行数据”),就会立即停止扫描,不会继续往后找。
 
简单说:SELECT * FROM t LIMIT 1 的 “全表扫描” 是「短路扫描」—— 只扫 1 行就停,不是扫完整张表。

二、为什么二级索引(Normal/Unique)用不上?

核心原因是 SELECT * 需要获取「整行数据」,而二级索引(不管 Normal 还是 Unique)的结构是:索引字段值 → 主键ID(InnoDB),无法直接存储整行数据。
 
如果强行用二级索引查询,流程是:
 
  1. 扫描二级索引,找到第一条数据(因为二级索引有序,找第一条很快);
  2. 拿到主键 ID 后,再去「聚集索引」中查完整行数据(这一步叫 “回表”);
  3. 因为 LIMIT 1,返回数据,停止操作。
 
而直接扫描「聚集索引 / 表文件」的流程是:
 
  1. 从聚集索引 / 表文件的第一条数据开始读;
  2. 读到第一条就直接返回,停止操作。
 
对比下来,“直接扫主数据载体” 比 “走二级索引 + 回表” 少一步,效率更高。所以 MySQL 优化器会自动选择前者,放弃二级索引 —— 不是二级索引不能用,而是用了反而更慢,优化器会规避这种低效路径。

三、不同存储引擎的实际执行逻辑(验证 “只扫 1 行”)

1. InnoDB(有聚集索引)

  • 执行 SELECT * FROM t LIMIT 1 时,优化器会选择扫描「聚集索引」(主键索引);
  • 聚集索引是有序的(按主键排序),数据库从聚集索引的最左叶子节点(第一条数据)开始读取,拿到 1 行后立即终止扫描,不会继续往后遍历;
  • 耗时极短(通常微秒级),和 “扫全表”(遍历所有数据)完全是两回事。

2. MyISAM(无聚集索引,堆表)

  • 表数据按插入顺序存储在文件中,执行查询时,从文件开头读第一行数据,拿到后立即停止;
  • 同样只扫 1 行,不会读取整个文件。

四、特殊情况:什么时候会走二级索引?(和 LIMIT 1 无关,和查询字段有关)

如果查询的字段全部包含在二级索引中(即 “覆盖索引”),优化器会选择走二级索引,因为无需回表,效率和扫聚集索引差不多:
 
sql
 
 
-- 假设二级索引 idx_name (name),查询字段只有 name 和 id(id是主键,会自动包含在二级索引中)
SELECT name, id FROM t LIMIT 1;
 
 
此时:
 
  • 二级索引的叶子节点包含 name + id,无需回表,优化器会走二级索引,读取第一条数据后停止;
  • 但这和 “是否 LIMIT 1” 无关,核心是 “覆盖索引”;如果还是 SELECT *,即使 LIMIT 1,仍会走聚集索引 / 表文件。

五、总结:澄清 3 个关键认知

  1. 你的核心判断是对的:LIMIT 1 会在拿到第一条数据后立即返回,不会扫描整张表;
  2. “全表扫描” 的精准定义:是 “扫描表的主数据载体(聚集索引 / 表文件)”,而非 “扫描所有数据”;带 LIMIT 1 的全表扫描是「短路扫描」,仅读 1 行;
  3. 二级索引(Normal/Unique)用不上的原因:SELECT * 需要整行数据,走二级索引需回表,效率低于直接扫主数据载体,优化器会自动放弃。
 
简单验证方法(MySQL 中执行):
 
用 EXPLAIN 查看执行计划,会发现 type: ALL(表示全表扫描),但 Rows 字段会显示 “预估扫描行数”(通常是 1),Extra 无 “Using filesort”“Using temporary”,说明仅扫描 1 行就停止:
 
sql
 
 
EXPLAIN SELECT * FROM t LIMIT 1;
-- 结果示例:
-- id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra
-- 1  | SIMPLE      | t     | ALL  | NULL          | NULL | NULL    | NULL | 1    |
 
 
这里的 rows: 1 就是优化器预估的扫描行数,证明不会扫全表。
 
最终结论:无 WHERE 仅 LIMIT 1 时,确实不用二级索引(Normal/Unique 都不用),但 “全表扫描” 是短路的,只扫 1 行就返回,效率极高,和 “扫完整张表” 完全不是一个概念。你担心的 “扫全表变慢” 是不存在的~
 
 
总结:
BTree(默认) vs Hash 是索引的存储结构 ; Normal(普通索引) vs Unique(唯一索引) 是「索引的约束类型」
 
 
 
 
posted on 2025-11-22 21:40  CanntBelieve  阅读(0)  评论(0)    收藏  举报