MySQL的 索引类型 normal、 unique 和 索引方法 btree 、 hash
<p> </p><h3 class="header-iWP5WJ auto-hide-last-sibling-br">一、索引类型:Normal(普通索引) vs Unique(唯一索引)</h3>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">核心差异是 是否对字段值施加「唯一性约束」,其他特性均围绕这一点展开:</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<div class="auto-hide-last-sibling-br mdbox-table-root table-container-cYf_5N" data-scroll-inline-overflow="true" data-scroll-inline-at-start="false" data-scroll-inline-start-overflow="false" data-scroll-inline-at-end="false" data-scroll-inline-end-overflow="true">
<div class="table-scroll-container-Gyf4hQ mdbox-table-scroll-container">
<table>
<thead>
<tr><th>对比维度</th><th>Normal(普通索引)</th><th>Unique(唯一索引)</th></tr>
</thead>
<tbody>
<tr>
<td>核心约束</td>
<td>无唯一性要求,字段值可重复</td>
<td>强制字段值唯一,不允许重复(NULL 特殊处理)</td>
</tr>
<tr>
<td>NULL 值支持</td>
<td>允许多个 NULL 值(InnoDB/MyISAM 均支持)</td>
<td>仅允许 1 个 NULL 值(InnoDB/MyISAM 通用)</td>
</tr>
<tr>
<td>插入 / 更新逻辑</td>
<td>直接执行,无需检查重复</td>
<td>先检查字段值是否已存在,重复则报 <code>Duplicate entry</code> 错误</td>
</tr>
<tr>
<td>索引结构</td>
<td>与 Unique 一致(默认 BTree),仅多存储重复值</td>
<td>与 Normal 结构相同,但叶子节点无重复值</td>
</tr>
<tr>
<td>性能特点</td>
<td>插入 / 更新效率略高(无重复检查);查询效率与 Unique 基本一致</td>
<td>插入 / 更新效率略低(需额外检查唯一性);查询效率略优(叶子节点数据量少,查找更快)</td>
</tr>
<tr>
<td>适用场景</td>
<td>频繁查询、字段值可能重复的场景(如:商品分类、用户昵称、文章标签)</td>
<td>字段值唯一的场景(如:手机号、身份证号、用户名、订单号),需确保数据唯一性</td>
</tr>
</tbody>
</table>
</div>
</div>
<h4 class="header-iWP5WJ auto-hide-last-sibling-br">关键补充:</h4>
<ol class="auto-hide-last-sibling-br">
<li>主键索引是特殊的 Unique 索引:<code>PRIMARY KEY</code> 本质是「非空 + 唯一」的 Unique 索引(不允许 NULL 值),优先级高于普通 Unique 索引。</li>
<li>组合 Unique 索引:若创建 <code>UNIQUE KEY (col1, col2)</code>,则约束「col1+col2 的组合值唯一」,而非单个字段唯一(比如 col1 可重复,但 col1+col2 不可重复)。</li>
<li>唯一性约束的本质:Unique 索引的核心作用是「保证数据唯一性」,查询优化是附加作用;而 Normal 索引的核心作用就是「加速查询」。</li>
</ol>
<h3 class="header-iWP5WJ auto-hide-last-sibling-br">二、索引方法:BTree(默认) vs Hash</h3>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">核心差异是 索引的存储结构和查询逻辑,直接决定了「哪些查询场景能生效」,MySQL 中最常用的是 BTree(实际是 B+Tree),Hash 仅适用于特定场景:</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<div class="auto-hide-last-sibling-br mdbox-table-root table-container-cYf_5N" data-scroll-inline-overflow="true" data-scroll-inline-at-start="true" data-scroll-inline-start-overflow="false" data-scroll-inline-at-end="false" data-scroll-inline-end-overflow="true">
<div class="table-scroll-container-Gyf4hQ mdbox-table-scroll-container">
<table>
<thead>
<tr><th>对比维度</th><th>BTree(实际为 B+Tree)</th><th>Hash 索引</th></tr>
</thead>
<tbody>
<tr>
<td>存储结构</td>
<td>平衡多路查找树(B+Tree),数据按「字段值有序排列」,叶子节点串联成链表</td>
<td>哈希表结构,通过「字段值哈希计算」得到索引位置,数据无序</td>
</tr>
<tr>
<td>适用查询类型</td>
<td>支持所有查询场景:等值查询(=)、范围查询(>、<、BETWEEN)、排序(ORDER BY)、分组(GROUP BY)、前缀匹配(LIKE 'abc%')</td>
<td>仅支持 等值查询(=、<>、IN),不支持范围、排序、模糊查询(除精确匹配外均失效)</td>
</tr>
<tr>
<td>查询效率</td>
<td>等值查询:O (logn)(稳定高效);范围查询:O (logn + 扫描行数)(因有序,无需全表扫描)</td>
<td>理想情况:O (1)(直接通过哈希值定位);存在哈希冲突时:O (k)(k 为冲突数据量,冲突多则效率下降)</td>
</tr>
<tr>
<td>数据有序性</td>
<td>索引本身有序(叶子节点按字段值升序 / 降序排列)</td>
<td>索引无序(哈希值随机分布)</td>
</tr>
<tr>
<td>字段类型支持</td>
<td>支持所有字段类型(字符串、数字、日期等)</td>
<td>仅支持「能计算哈希值」的字段类型(数字、字符串等),不支持 blob、text 等类型</td>
</tr>
<tr>
<td>覆盖索引支持</td>
<td>支持(可直接从索引叶子节点获取查询字段,无需回表)</td>
<td>不支持(哈希索引仅存储「哈希值 + 行指针」,必须回表查询原始数据)</td>
</tr>
<tr>
<td>NULL 值处理</td>
<td>正常存储,支持 NULL 值查询</td>
<td>支持 NULL 值(哈希值统一处理),但同样仅支持等值查询</td>
</tr>
<tr>
<td>存储引擎支持</td>
<td>所有主流引擎(InnoDB、MyISAM、Memory)默认支持</td>
<td>仅 Memory 引擎默认支持;InnoDB 不支持手动创建 Hash 索引(仅内部自适应哈希索引,不可控)</td>
</tr>
<tr>
<td>适用场景</td>
<td>绝大多数业务场景(需范围查询、排序、模糊匹配)</td>
<td>仅内存数据库(Memory)+ 纯等值查询场景(如:缓存键值对查询、简单字典查询)</td>
</tr>
</tbody>
</table>
</div>
</div>
<h4 class="header-iWP5WJ auto-hide-last-sibling-br">关键补充:</h4>
<ol class="auto-hide-last-sibling-br">
<li>B+Tree 是 MySQL 的默认选择:InnoDB 和 MyISAM 均默认使用 B+Tree 索引,因为它能适配业务中 99% 的查询场景(比如分页查询 <code>LIMIT</code>、范围筛选 <code>price BETWEEN 100 AND 200</code>、前缀模糊查询 <code>name LIKE '张%'</code>)。</li>
<li>Hash 索引的局限性:
<ul class="auto-hide-last-sibling-br">
<li>不支持范围查询:比如 <code>id > 100</code> 无法通过 Hash 索引查询(哈希值无序,无法确定范围);</li>
<li>不支持排序:<code>ORDER BY col</code> 需全表排序(Hash 索引无序);</li>
<li>哈希冲突问题:若多个字段值哈希后得到相同位置,需线性探测 / 链表解决,冲突多则效率接近全表扫描;</li>
</ul>
</li>
<li>InnoDB 的自适应哈希索引(AHI):InnoDB 会自动对频繁等值查询的 BTree 索引创建 Hash 索引(内存中),无需手动干预,目的是优化等值查询效率,但不可控(无法手动创建 / 删除)。</li>
</ol>
<h3 class="header-iWP5WJ auto-hide-last-sibling-br">三、总结:如何选择?</h3>
<ol class="auto-hide-last-sibling-br">
<li>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">索引类型(Normal/Unique):</div>
<ul class="auto-hide-last-sibling-br">
<li>需保证字段值唯一 → 用 Unique(或主键索引);</li>
<li>无需唯一性,仅需加速查询 → 用 Normal。</li>
</ul>
</li>
<li>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">索引方法(BTree/Hash):</div>
<ul class="auto-hide-last-sibling-br">
<li>绝大多数场景(需范围、排序、模糊查询) → 用默认的 BTree(无需手动指定);</li>
<li>仅 Memory 引擎 + 纯等值查询 → 考虑 Hash 索引(极少用)。</li>
</ul>
</li>
</ol>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">最终组合示例:</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<ul class="auto-hide-last-sibling-br">
<li>用户名(唯一 + 需查询)→ <code>UNIQUE KEY idx_username (username)</code>(BTree+Unique);</li>
<li>商品价格(非唯一 + 需范围查询)→ <code>KEY idx_price (price)</code>(BTree+Normal);</li>
<li>内存数据库中的字典表(纯等值查询)→ <code>KEY idx_dict_key (dict_key) USING HASH</code>(Hash+Normal)。</li>
</ul>
<p> </p>
<p> </p>
<p>MySQL的 索引类型 normal、 unique 其实差不多,如果有limit 1这样的条件。</p>
<p> </p>
<p><span style="background-color: rgba(255, 0, 0, 1)">你说 Normal 索引的查询效率与 Unique 基本一致; 如果我在某字段创建了 Normal 索引,然后在这个字段上做where条件查询同时 limit 1,那么是不是意味着需要扫描它的全部索引, 那么速度也会比较慢吧, 如果是 unique, 那么不需要吧、效率就差比较多了吧 </span></p>
<p><span style="background-color: rgba(255, 0, 0, 1)">-- 这里其实问错了, 本意是询问没有limit 1的情况</span></p>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">你的核心误解是 “Normal 索引加 LIMIT 1 需要扫描全部索引” —— 这其实是错的!不管是 Normal 还是 Unique 索引(默认都是 BTree 结构),<code>WHERE 字段=xxx LIMIT 1</code> 的查询逻辑都是 “快速定位到第一个匹配项,然后直接返回,不会扫描全部索引”,因此两者的查询效率几乎没有差距,远达不到 “差比较多” 的程度。</div>
<h3 class="header-iWP5WJ auto-hide-last-sibling-br">一、先理清核心逻辑:BTree 索引 + LIMIT 1 的查询过程(Normal 和 Unique 通用)</h3>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">BTree 索引的本质是「有序的平衡树」,叶子节点按字段值排序且串联成链表。不管是 Normal 还是 Unique,执行 <code>WHERE col=xxx LIMIT 1</code> 时,查询流程完全一致:</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<ol class="auto-hide-last-sibling-br">
<li>从 BTree 的根节点开始,通过二分查找快速定位到「字段值 = xxx」的叶子节点(时间复杂度 O (logn),和索引大小无关,只和树的高度有关,通常 3-4 层);</li>
<li>找到第一个匹配的叶子节点后,因为有 <code>LIMIT 1</code>,直接返回该节点对应的行数据,停止后续所有扫描(不会继续查找后面是否有重复值,哪怕是 Normal 索引允许重复)。</li>
</ol>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">举个例子:假设字段<code>name</code>是 Normal 索引,有 100 万条数据,其中 10 万条都是<code>name='张三'</code>。执行 <code>WHERE name='张三' LIMIT 1</code> 时,数据库找到第一个「name=' 张三 '」的叶子节点就直接返回,不会去扫剩下的 99999 个「张三」—— 这和 Unique 索引(只有 1 个「张三」)的查询过程完全一样,都是 “找到第一个就停”。</div>
<h3 class="header-iWP5WJ auto-hide-last-sibling-br">二、Normal 和 Unique 在「WHERE+LIMIT 1」场景下的效率差异:几乎可以忽略</h3>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">两者的差异仅来自「Unique 索引的唯一性约束对索引结构的微小影响」,但对查询速度的影响极小:</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<div class="auto-hide-last-sibling-br mdbox-table-root table-container-cYf_5N" data-scroll-inline-overflow="true" data-scroll-inline-at-start="true" data-scroll-inline-start-overflow="false" data-scroll-inline-at-end="false" data-scroll-inline-end-overflow="true">
<div class="table-scroll-container-Gyf4hQ mdbox-table-scroll-container">
<table>
<thead>
<tr><th>对比点</th><th>Normal 索引</th><th>Unique 索引</th></tr>
</thead>
<tbody>
<tr>
<td>定位第一个匹配项的速度</td>
<td>O (logn)(和 Unique 完全一致)</td>
<td>O (logn)(和 Normal 完全一致)</td>
</tr>
<tr>
<td>停止条件</td>
<td>找到第一个匹配项,因 LIMIT 1 直接返回</td>
<td>找到第一个匹配项,因 Unique 约束确认无重复,直接返回</td>
</tr>
<tr>
<td>实际扫描量</td>
<td>1 个叶子节点(最多 2-3 个相邻节点,因 BTree 页存储多条数据)</td>
<td>1 个叶子节点(无需考虑后续重复,停止更 “果断”)</td>
</tr>
<tr>
<td>效率差异</td>
<td>几乎无差异,耗时差距在微秒级</td>
<td>略快,但人类感知不到(比如 0.1ms vs 0.09ms)</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">简单说:查询阶段的核心耗时是 “定位到第一个匹配项”(O (logn)),而两者在这一步完全一致;后续的 “停止逻辑” 差异对总耗时的影响微乎其微。</div>
<h3 class="header-iWP5WJ auto-hide-last-sibling-br">三、什么时候会出现明显效率差距?(几乎不涉及 LIMIT 1 场景)</h3>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">只有当你的查询不满足 “快速定位第一个匹配项” 时,两者才可能有可感知的差距,但这种场景和<code>LIMIT 1</code>无关:</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<ol class="auto-hide-last-sibling-br">
<li>无 WHERE 条件,仅 LIMIT 1:比如 <code>SELECT * FROM t LIMIT 1</code>。此时 Normal 和 Unique 索引都用不上(无过滤条件),都走全表扫描(或主键索引扫描),效率完全一致;</li>
<li>查询重复值极多的字段,且不带 LIMIT 1:比如 <code>SELECT * FROM t WHERE name='张三'</code>(10 万条结果)。此时:
<ul class="auto-hide-last-sibling-br">
<li>Normal 索引:需要扫描所有 10 万个「张三」对应的叶子节点(有序串联,扫描效率仍比全表高);</li>
<li>Unique 索引:因约束无重复,扫描 1 个节点就结束;
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
这种场景下 Unique 确实快很多,但你加了 LIMIT 1,就屏蔽了这个差异;</li>
</ul>
</li>
<li>组合索引场景:比如组合 Normal 索引 <code>(col1, col2)</code>,查询 <code>WHERE col1='a' LIMIT 1</code>。同样是定位到第一个<code>col1='a'</code>的节点就返回,和组合 Unique 索引效率一致。</li>
</ol>
<h3 class="header-iWP5WJ auto-hide-last-sibling-br">四、关键结论:不用纠结 “Normal+LIMIT 1” 的效率,核心看是否需要唯一性约束</h3>
<ol class="auto-hide-last-sibling-br">
<li>查询效率层面:<code>Normal索引+WHERE+LIMIT 1</code> 和 <code>Unique索引+WHERE+LIMIT 1</code> 的效率几乎没有区别,都能快速定位并返回,不会扫描全部索引;</li>
<li>差异本质:Unique 索引的核心价值是「保证数据唯一性」(避免重复插入),而不是 “查询更快”—— 查询效率的微弱优势只是唯一性约束带来的 “附加福利”;</li>
<li>选择逻辑:
<ul class="auto-hide-last-sibling-br">
<li>若字段值必须唯一(如手机号、用户名)→ 用 Unique 索引(优先保证数据正确性,顺带略提查询效率);</li>
<li>若字段值可重复(如商品分类、用户昵称)→ 用 Normal 索引(无需唯一性检查,插入 / 更新效率略高,查询 + LIMIT 1 完全够用)。</li>
</ul>
</li>
</ol>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">举个实际测试场景(InnoDB,100 万条数据):</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<ul class="auto-hide-last-sibling-br">
<li>Normal 索引 <code>idx_name (name)</code>,查询 <code>SELECT id FROM t WHERE name='张三' LIMIT 1</code> → 耗时 0.12ms;</li>
<li>Unique 索引 <code>uk_name (name)</code>,相同查询 → 耗时 0.10ms;
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
两者差距仅 0.02ms,完全不影响业务性能。真正影响效率的是 “是否建索引”“索引是否被命中”,而不是 Normal 和 Unique 的选择。</li>
</ul>
<p> </p>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">你的质疑非常关键!核心结论先明确:你说的 “查询到一个就返回” 是对的,但 “全表扫描” 的定义需要澄清 —— 这里的 “全表扫描”≠扫描所有数据,而是 “扫描表的主数据载体(聚集索引 / 表文件)”,且会在拿到第一条数据后立即停止,不会扫完整张表。</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">之前的表述不够精准,导致了误解,现在详细拆解:</div>
<h3 class="header-iWP5WJ auto-hide-last-sibling-br">一、先纠正 “全表扫描” 的误区:MySQL 中 “全表扫描(Full Table Scan)” 的真实含义</h3>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">“全表扫描” 不是 “必须扫描表中所有行”,而是指:</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<ul class="auto-hide-last-sibling-br">
<li>数据库没有使用二级索引(Normal/Unique 都是二级索引),而是直接扫描表的「主数据载体」:
<ul class="auto-hide-last-sibling-br">
<li>InnoDB:扫描「聚集索引」(主键索引,表数据本身就是按聚集索引组织的,叶子节点就是完整行数据);</li>
<li>MyISAM:扫描「表文件本身」(按数据插入顺序存储的堆表)。</li>
</ul>
</li>
<li>扫描过程中,只要满足条件(这里是 “拿到 1 行数据”),就会立即停止扫描,不会继续往后找。</li>
</ul>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">简单说:<code>SELECT * FROM t LIMIT 1</code> 的 “全表扫描” 是「短路扫描」—— 只扫 1 行就停,不是扫完整张表。</div>
<h3 class="header-iWP5WJ auto-hide-last-sibling-br">二、为什么二级索引(Normal/Unique)用不上?</h3>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">核心原因是 <code>SELECT *</code> 需要获取「整行数据」,而二级索引(不管 Normal 还是 Unique)的结构是:<code>索引字段值 → 主键ID</code>(InnoDB),无法直接存储整行数据。</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">如果强行用二级索引查询,流程是:</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<ol class="auto-hide-last-sibling-br">
<li>扫描二级索引,找到第一条数据(因为二级索引有序,找第一条很快);</li>
<li>拿到主键 ID 后,再去「聚集索引」中查完整行数据(这一步叫 “回表”);</li>
<li>因为 <code>LIMIT 1</code>,返回数据,停止操作。</li>
</ol>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">而直接扫描「聚集索引 / 表文件」的流程是:</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<ol class="auto-hide-last-sibling-br">
<li>从聚集索引 / 表文件的第一条数据开始读;</li>
<li>读到第一条就直接返回,停止操作。</li>
</ol>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">对比下来,“直接扫主数据载体” 比 “走二级索引 + 回表” 少一步,效率更高。所以 MySQL 优化器会自动选择前者,放弃二级索引 —— 不是二级索引不能用,而是用了反而更慢,优化器会规避这种低效路径。</div>
<h3 class="header-iWP5WJ auto-hide-last-sibling-br">三、不同存储引擎的实际执行逻辑(验证 “只扫 1 行”)</h3>
<h4 class="header-iWP5WJ auto-hide-last-sibling-br">1. InnoDB(有聚集索引)</h4>
<ul class="auto-hide-last-sibling-br">
<li>执行 <code>SELECT * FROM t LIMIT 1</code> 时,优化器会选择扫描「聚集索引」(主键索引);</li>
<li><span style="background-color: rgba(255, 0, 0, 1)">聚集索引是有序的(按主键排序),数据库从聚集索引的最左叶子节点(第一条数据)开始读取,拿到 1 行后立即终止扫描,不会继续往后遍历;</span></li>
<li>耗时极短(通常微秒级),和 “扫全表”(遍历所有数据)完全是两回事。</li>
</ul>
<h4 class="header-iWP5WJ auto-hide-last-sibling-br">2. MyISAM(无聚集索引,堆表)</h4>
<ul class="auto-hide-last-sibling-br">
<li><span style="background-color: rgba(255, 0, 0, 1)">表数据按插入顺序存储在文件中,执行查询时,从文件开头读第一行数据,拿到后立即停止;</span></li>
<li>同样只扫 1 行,不会读取整个文件。</li>
</ul>
<h3 class="header-iWP5WJ auto-hide-last-sibling-br">四、特殊情况:什么时候会走二级索引?(和 LIMIT 1 无关,和查询字段有关)</h3>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">如果查询的字段全部包含在二级索引中(即 “覆盖索引”),优化器会选择走二级索引,因为无需回表,效率和扫聚集索引差不多:</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<div class="code-block-element-R6c8c0 light custom-code-block-container--sql hide-indicator disable-theme-style custom-code-block-container">
<div class="code-area-yxsM36 code-area" dir="ltr">
<div class="header-wrapper-Mbk8s6">
<div class="header-IAeXdE">
<div class="title-TXcgFG clickable-cKrA4B" data-popupid="m7wlga8">
<div class="text-OkYU_0">sql</div>
<div class="icon-UKNQeW"> </div>
</div>
<div class="action-ysQCxz"> </div>
</div>
</div>
<div class="content-y8qlFa code-content light-scrollbar-FFk5j5">
<pre class="container-S2LAkl language-sql"><code class="language-sql"><span class="token comment">-- 假设二级索引 idx_name (name),查询字段只有 name 和 id(id是主键,会自动包含在二级索引中)
<span class="token keyword">SELECT name<span class="token punctuation">, id <span class="token keyword">FROM t <span class="token keyword">LIMIT <span class="token number">1<span class="token punctuation">;
</span></span></span></span></span></span></span></code></pre>
</div>
<div class="mask-wrapper-ujeSdZ"> </div>
</div>
</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">此时:</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<ul class="auto-hide-last-sibling-br">
<li>二级索引的叶子节点包含 <code>name + id</code>,无需回表,优化器会走二级索引,读取第一条数据后停止;</li>
<li>但这和 “是否 LIMIT 1” 无关,核心是 “覆盖索引”;如果还是 <code>SELECT *</code>,即使 LIMIT 1,仍会走聚集索引 / 表文件。</li>
</ul>
<h3 class="header-iWP5WJ auto-hide-last-sibling-br">五、总结:澄清 3 个关键认知</h3>
<ol class="auto-hide-last-sibling-br">
<li>你的核心判断是对的:<code>LIMIT 1</code> 会在拿到第一条数据后立即返回,不会扫描整张表;</li>
<li>“全表扫描” 的精准定义:是 “扫描表的主数据载体(聚集索引 / 表文件)”,而非 “扫描所有数据”;带 LIMIT 1 的全表扫描是「短路扫描」,仅读 1 行;</li>
<li>二级索引(Normal/Unique)用不上的原因:<code>SELECT *</code> 需要整行数据,走二级索引需回表,效率低于直接扫主数据载体,优化器会自动放弃。</li>
</ol>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">简单验证方法(MySQL 中执行):
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
用 <code>EXPLAIN</code> 查看执行计划,会发现 <code>type: ALL</code>(表示全表扫描),但 <code>Rows</code> 字段会显示 “预估扫描行数”(通常是 1),<code>Extra</code> 无 “Using filesort”“Using temporary”,说明仅扫描 1 行就停止:</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<div class="code-block-element-R6c8c0 light custom-code-block-container--sql hide-indicator disable-theme-style custom-code-block-container">
<div class="code-area-yxsM36 code-area" dir="ltr">
<div class="header-wrapper-Mbk8s6">
<div class="header-IAeXdE">
<div class="title-TXcgFG clickable-cKrA4B" data-popupid="d40ag8x">
<div class="text-OkYU_0">sql</div>
<div class="icon-UKNQeW"> </div>
</div>
<div class="action-ysQCxz"> </div>
</div>
</div>
<div class="content-y8qlFa code-content light-scrollbar-FFk5j5">
<pre class="container-S2LAkl language-sql"><code class="language-sql"><span class="token keyword">EXPLAIN <span class="token keyword">SELECT <span class="token operator">* <span class="token keyword">FROM t <span class="token keyword">LIMIT <span class="token number">1<span class="token punctuation">;
<span class="token comment">-- 结果示例:
<span class="token comment">-- id | select_type | table | type | possible_keys | key| key_len | ref| rows | Extra
<span class="token comment">-- 1| SIMPLE | t | ALL| NULL | NULL | NULL | NULL | 1 |
</span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<div class="mask-wrapper-ujeSdZ"> </div>
</div>
</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">这里的 <code>rows: 1</code> 就是优化器预估的扫描行数,证明不会扫全表。</div>
<div class="container-Uxvbjy md-box-line-break wrapper-GYqxgQ undefined"> </div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">最终结论:无 WHERE 仅 LIMIT 1 时,确实不用二级索引(Normal/Unique 都不用),但 “全表扫描” 是短路的,只扫 1 行就返回,效率极高,和 “扫完整张表” 完全不是一个概念。你担心的 “扫全表变慢” 是不存在的~</div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space"> </div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space"> </div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">总结:</div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space">BTree(默认) vs Hash 是索引的存储结构 ; Normal(普通索引) vs Unique(唯一索引) 是「索引的约束类型」</div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space"> </div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space"> </div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space"> </div>
<div class="auto-hide-last-sibling-br paragraph-pP9ZLC paragraph-element br-paragraph-space"> </div>
</div>
<div id="MySignature" role="contentinfo">
<h3>
<br>
<fieldset>
<legend>版权声明</legend>
<br>
本文原创发表于 博客园,作者为 阿K .
本文欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。
<br>
欢迎关注本人微信公众号:觉醒的码农,或者扫码进群:<br>
<img src="https://files.cnblogs.com/files/FlyAway2013/wechat111.bmp">
<img src="https://files-cdn.cnblogs.com/files/FlyAway2013/qrcode_for_gh_3f0d03520497_128.bmp">
</fieldset>
</h3><br><br>
来源:https://www.cnblogs.com/FlyAway2013/p/19258666
頁:
[1]