learn.lianglianglee.com/文章/彻底理解 MySQL 的索引机制.md.html
2022-08-14 03:40:33 +08:00

717 lines
42 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>彻底理解 MySQL 的索引机制.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/文章/AQS 万字图文全面解析.md.html">AQS 万字图文全面解析</a>
</li>
<li>
<a href="/文章/Docker 镜像构建原理及源码分析.md.html">Docker 镜像构建原理及源码分析</a>
</li>
<li>
<a href="/文章/ElasticSearch 小白从入门到精通.md.html">ElasticSearch 小白从入门到精通</a>
</li>
<li>
<a href="/文章/JVM CPU Profiler技术原理及源码深度解析.md.html">JVM CPU Profiler技术原理及源码深度解析</a>
</li>
<li>
<a href="/文章/JVM 垃圾收集器.md.html">JVM 垃圾收集器</a>
</li>
<li>
<a href="/文章/JVM 面试的 30 个知识点.md.html">JVM 面试的 30 个知识点</a>
</li>
<li>
<a href="/文章/Java IO 体系、线程模型大总结.md.html">Java IO 体系、线程模型大总结</a>
</li>
<li>
<a href="/文章/Java NIO浅析.md.html">Java NIO浅析</a>
</li>
<li>
<a href="/文章/Java 面试题集锦(网络篇).md.html">Java 面试题集锦(网络篇)</a>
</li>
<li>
<a href="/文章/Java-直接内存 DirectMemory 详解.md.html">Java-直接内存 DirectMemory 详解</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决.md.html">Java中9种常见的CMS GC问题分析与解决</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决.md.html">Java中9种常见的CMS GC问题分析与解决</a>
</li>
<li>
<a href="/文章/Java中的SPI.md.html">Java中的SPI</a>
</li>
<li>
<a href="/文章/Java中的ThreadLocal.md.html">Java中的ThreadLocal</a>
</li>
<li>
<a href="/文章/Java线程池实现原理及其在美团业务中的实践.md.html">Java线程池实现原理及其在美团业务中的实践</a>
</li>
<li>
<a href="/文章/Java魔法类Unsafe应用解析.md.html">Java魔法类Unsafe应用解析</a>
</li>
<li>
<a href="/文章/Kafka 源码阅读笔记.md.html">Kafka 源码阅读笔记</a>
</li>
<li>
<a href="/文章/Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html">Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB Buffer Pool.md.html">MySQL · 引擎特性 · InnoDB Buffer Pool</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB IO子系统.md.html">MySQL · 引擎特性 · InnoDB IO子系统</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 事务系统.md.html">MySQL · 引擎特性 · InnoDB 事务系统</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 同步机制.md.html">MySQL · 引擎特性 · InnoDB 同步机制</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 数据页解析.md.html">MySQL · 引擎特性 · InnoDB 数据页解析</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB崩溃恢复.md.html">MySQL · 引擎特性 · InnoDB崩溃恢复</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · 临时表那些事儿.md.html">MySQL · 引擎特性 · 临时表那些事儿</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 半同步复制.md.html">MySQL 主从复制 半同步复制</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 基于GTID复制.md.html">MySQL 主从复制 基于GTID复制</a>
</li>
<li>
<a href="/文章/MySQL 主从复制.md.html">MySQL 主从复制</a>
</li>
<li>
<a href="/文章/MySQL 事务日志(redo log和undo log).md.html">MySQL 事务日志(redo log和undo log)</a>
</li>
<li>
<a href="/文章/MySQL 亿级别数据迁移实战代码分享.md.html">MySQL 亿级别数据迁移实战代码分享</a>
</li>
<li>
<a href="/文章/MySQL 从一条数据说起-InnoDB行存储数据结构.md.html">MySQL 从一条数据说起-InnoDB行存储数据结构</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:事务和锁的面纱.md.html">MySQL 地基基础:事务和锁的面纱</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据字典.md.html">MySQL 地基基础:数据字典</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据库字符集.md.html">MySQL 地基基础:数据库字符集</a>
</li>
<li>
<a href="/文章/MySQL 性能优化:碎片整理.md.html">MySQL 性能优化:碎片整理</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html">MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:如何在日志中轻松定位大事务.md.html">MySQL 故障诊断:如何在日志中轻松定位大事务</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:教你快速定位加锁的 SQL.md.html">MySQL 故障诊断:教你快速定位加锁的 SQL</a>
</li>
<li>
<a href="/文章/MySQL 日志详解.md.html">MySQL 日志详解</a>
</li>
<li>
<a href="/文章/MySQL 的半同步是什么?.md.html">MySQL 的半同步是什么?</a>
</li>
<li>
<a href="/文章/MySQL中的事务和MVCC.md.html">MySQL中的事务和MVCC</a>
</li>
<li>
<a href="/文章/MySQL事务_事务隔离级别详解.md.html">MySQL事务_事务隔离级别详解</a>
</li>
<li>
<a href="/文章/MySQL优化优化 select count().md.html">MySQL优化优化 select count()</a>
</li>
<li>
<a href="/文章/MySQL共享锁、排他锁、悲观锁、乐观锁.md.html">MySQL共享锁、排他锁、悲观锁、乐观锁</a>
</li>
<li>
<a href="/文章/MySQL的MVCC多版本并发控制.md.html">MySQL的MVCC多版本并发控制</a>
</li>
<li>
<a href="/文章/QingStor 对象存储架构设计及最佳实践.md.html">QingStor 对象存储架构设计及最佳实践</a>
</li>
<li>
<a href="/文章/RocketMQ 面试题集锦.md.html">RocketMQ 面试题集锦</a>
</li>
<li>
<a href="/文章/SnowFlake 雪花算法生成分布式 ID.md.html">SnowFlake 雪花算法生成分布式 ID</a>
</li>
<li>
<a href="/文章/Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html">Spring Boot 2.x 结合 k8s 实现分布式微服务架构</a>
</li>
<li>
<a href="/文章/Spring Boot 教程:如何开发一个 starter.md.html">Spring Boot 教程:如何开发一个 starter</a>
</li>
<li>
<a href="/文章/Spring MVC 原理.md.html">Spring MVC 原理</a>
</li>
<li>
<a href="/文章/Spring MyBatis和Spring整合的奥秘.md.html">Spring MyBatis和Spring整合的奥秘</a>
</li>
<li>
<a href="/文章/Spring 帮助你更好的理解Spring循环依赖.md.html">Spring 帮助你更好的理解Spring循环依赖</a>
</li>
<li>
<a href="/文章/Spring 循环依赖及解决方式.md.html">Spring 循环依赖及解决方式</a>
</li>
<li>
<a href="/文章/Spring中眼花缭乱的BeanDefinition.md.html">Spring中眼花缭乱的BeanDefinition</a>
</li>
<li>
<a href="/文章/Vert.x 基础入门.md.html">Vert.x 基础入门</a>
</li>
<li>
<a href="/文章/eBay 的 Elasticsearch 性能调优实践.md.html">eBay 的 Elasticsearch 性能调优实践</a>
</li>
<li>
<a href="/文章/不可不说的Java“锁”事.md.html">不可不说的Java“锁”事</a>
</li>
<li>
<a href="/文章/互联网并发限流实战.md.html">互联网并发限流实战</a>
</li>
<li>
<a href="/文章/从ReentrantLock的实现看AQS的原理及应用.md.html">从ReentrantLock的实现看AQS的原理及应用</a>
</li>
<li>
<a href="/文章/从SpringCloud开始聊微服务架构.md.html">从SpringCloud开始聊微服务架构</a>
</li>
<li>
<a href="/文章/全面了解 JDK 线程池实现原理.md.html">全面了解 JDK 线程池实现原理</a>
</li>
<li>
<a href="/文章/分布式一致性理论与算法.md.html">分布式一致性理论与算法</a>
</li>
<li>
<a href="/文章/分布式一致性算法 Raft.md.html">分布式一致性算法 Raft</a>
</li>
<li>
<a href="/文章/分布式唯一 ID 解析.md.html">分布式唯一 ID 解析</a>
</li>
<li>
<a href="/文章/分布式链路追踪:集群管理设计.md.html">分布式链路追踪:集群管理设计</a>
</li>
<li>
<a href="/文章/动态代理种类及原理,你知道多少?.md.html">动态代理种类及原理,你知道多少?</a>
</li>
<li>
<a href="/文章/响应式架构与 RxJava 在有赞零售的实践.md.html">响应式架构与 RxJava 在有赞零售的实践</a>
</li>
<li>
<a href="/文章/大数据算法——布隆过滤器.md.html">大数据算法——布隆过滤器</a>
</li>
<li>
<a href="/文章/如何优雅地记录操作日志?.md.html">如何优雅地记录操作日志?</a>
</li>
<li>
<a href="/文章/如何设计一个亿级消息量的 IM 系统.md.html">如何设计一个亿级消息量的 IM 系统</a>
</li>
<li>
<a href="/文章/异步网络模型.md.html">异步网络模型</a>
</li>
<li>
<a href="/文章/当我们在讨论CQRS时我们在讨论些神马.md.html">当我们在讨论CQRS时我们在讨论些神马</a>
</li>
<li>
<a class="current-tab" href="/文章/彻底理解 MySQL 的索引机制.md.html">彻底理解 MySQL 的索引机制</a>
</li>
<li>
<a href="/文章/最全的 116 道 Redis 面试题解答.md.html">最全的 116 道 Redis 面试题解答</a>
</li>
<li>
<a href="/文章/有赞权限系统(SAM).md.html">有赞权限系统(SAM)</a>
</li>
<li>
<a href="/文章/有赞零售中台建设方法的探索与实践.md.html">有赞零售中台建设方法的探索与实践</a>
</li>
<li>
<a href="/文章/服务注册与发现原理剖析Eureka、Zookeeper、Nacos.md.html">服务注册与发现原理剖析Eureka、Zookeeper、Nacos</a>
</li>
<li>
<a href="/文章/深入浅出Cache.md.html">深入浅出Cache</a>
</li>
<li>
<a href="/文章/深入理解 MySQL 底层实现.md.html">深入理解 MySQL 底层实现</a>
</li>
<li>
<a href="/文章/漫画讲解 git rebase VS git merge.md.html">漫画讲解 git rebase VS git merge</a>
</li>
<li>
<a href="/文章/生成浏览器唯一稳定 ID 的探索.md.html">生成浏览器唯一稳定 ID 的探索</a>
</li>
<li>
<a href="/文章/缓存 如何保证缓存与数据库的双写一致性?.md.html">缓存 如何保证缓存与数据库的双写一致性?</a>
</li>
<li>
<a href="/文章/网易严选怎么做全链路监控的?.md.html">网易严选怎么做全链路监控的?</a>
</li>
<li>
<a href="/文章/美团万亿级 KV 存储架构与实践.md.html">美团万亿级 KV 存储架构与实践</a>
</li>
<li>
<a href="/文章/美团点评Kubernetes集群管理实践.md.html">美团点评Kubernetes集群管理实践</a>
</li>
<li>
<a href="/文章/美团百亿规模API网关服务Shepherd的设计与实现.md.html">美团百亿规模API网关服务Shepherd的设计与实现</a>
</li>
<li>
<a href="/文章/解读《阿里巴巴 Java 开发手册》背后的思考.md.html">解读《阿里巴巴 Java 开发手册》背后的思考</a>
</li>
<li>
<a href="/文章/认识 MySQL 和 Redis 的数据一致性问题.md.html">认识 MySQL 和 Redis 的数据一致性问题</a>
</li>
<li>
<a href="/文章/进阶Dockerfile 高阶使用指南及镜像优化.md.html">进阶Dockerfile 高阶使用指南及镜像优化</a>
</li>
<li>
<a href="/文章/铁总在用的高性能分布式缓存计算框架 Geode.md.html">铁总在用的高性能分布式缓存计算框架 Geode</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html">阿里云PolarDB及其共享存储PolarFS技术实现分析</a>
</li>
<li>
<a href="/文章/面试最常被问的 Java 后端题.md.html">面试最常被问的 Java 后端题</a>
</li>
<li>
<a href="/文章/领域驱动设计在互联网业务开发中的实践.md.html">领域驱动设计在互联网业务开发中的实践</a>
</li>
<li>
<a href="/文章/领域驱动设计的菱形对称架构.md.html">领域驱动设计的菱形对称架构</a>
</li>
<li>
<a href="/文章/高效构建 Docker 镜像的最佳实践.md.html">高效构建 Docker 镜像的最佳实践</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>彻底理解 MySQL 的索引机制</h1>
<p>每当我们遇到数据库查询耗时过长,总会第一时间想到,在经常使用的条件上添加索引。我们知道索引会帮我们更快地查询到想要的数据,但是我们真的清楚究竟什么是索引,为什么索引能帮我们将查询时间缩短十倍百倍甚至更多吗?接下来请大家根据下文,一起深入索引的世界吧。</p>
<h1>从磁盘上获取数据,讲究在哪儿消耗了时间?</h1>
<p><strong>什么是磁盘 IO</strong></p>
<p>磁盘读取数据靠的是机械运动,一般来说,一次读取数据的时间 = 寻道时间 + 旋转延迟 + 传输时间。</p>
<p><img src="assets/0578cc60-fc40-11e9-87eb-dd0ee8ac57cd.jpg.png" alt="img" /></p>
<p><img src="assets/1899cd30-fc40-11e9-87eb-dd0ee8ac57cd.jpg.png" alt="img" /></p>
<p><strong>名词解释</strong></p>
<ul>
<li><strong>寻道时间</strong>:磁头移动到指定磁道所需要的时间,主流磁盘一般在 5ms 以下,平均 3-15ms。</li>
<li><strong>旋转延迟</strong>:指盘片旋转将请求数据所在的扇区移动到读写磁盘下方所需要的时间。磁盘转速,比如一个磁盘 7200 转,表示每分钟能转 7200 次,也就是说 1 秒钟能转 120 次,旋转延迟就是 1/120/2 = 4.17ms。</li>
<li><strong>传输时间</strong>:从磁盘读出或将数据写入磁盘的时间,一般在零点几毫秒,远远小于前面消耗的时间,几乎可以忽略不计。</li>
</ul>
<p><strong>什么是预读?</strong></p>
<p>因为磁盘 IO 是非常昂贵的操作,所以计算机系统对此做了一些优化,当一次 IO 时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,因为局部预读性原理告诉我们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到,所以每次读取页的整数倍(通常一个节点就是一页)。</p>
<h1>什么是索引,索引的数据结构为何选择 B+Tree</h1>
<p><strong>定义</strong></p>
<blockquote>
<p>索引是帮助 MySQL 高效获取数据的数据结构。总结一下,索引是一种排好序的数据结构。(引用自:<a href="http://blog.csdn.net/zq602316498/article/details/39323803">http://blog.csdn.net/zq602316498/article/details/39323803</a></p>
</blockquote>
<p>索引存储在文件里,如下图所示(针对 InnoDB 而言):</p>
<p><img src="assets/c1c2ec50-fbcf-11e9-9de7-3b2fa7b65fa0.jpg.png" alt="img" /></p>
<p>InnoDB 的索引和数据都存放在同一文件中,而 MyIsAm 的索引和数据分别存放在不同的文件中。</p>
<p>我们知道MySQL 的 InnoDB 引擎下的索引数据结构为 B+tree 和 hash为什么在这么多数据结构中会选择 B+tree 和 hash 呢?</p>
<p><strong>首先我们先来了解一下以下四种数据结构</strong>(不会详细分析,毕竟主题是 MySQL 索引)。</p>
<p><strong>1. 二叉树</strong></p>
<p>特征:要保证父节点大于左子结点,小于右子节点。</p>
<p>极端情况下会产生如下所示的树:</p>
<p><img src="assets/6dc8d710-fbd7-11e9-9c25-e79d31f34796.jpg.png" alt="img" /></p>
<p><strong>2. 平衡二叉树AVL</strong></p>
<p>特征:它或者是一颗空树,或者具有以下性质的二叉排序树:它的左子树和右子树的深度之差 (平衡因子) 的绝对值不超过 1且它的左子树和右子树都是一颗平衡二叉树。</p>
<p><img src="assets/ab643690-fbd8-11e9-b911-b94a6d515967.jpg.png" alt="img" /></p>
<p>随着数据的不断增加,虽然平衡二叉树在二叉树的基础上做了优化,但是树的高度还是会增加并且不可控。</p>
<p><strong>3. 红黑树</strong></p>
<p>特征红黑树Red-Black Tree 「RBT」是一个自平衡 (不是绝对的平衡) 的二叉查找树 (BST),树上的每个节点都遵循下面的规则:</p>
<ul>
<li>每个节点都有红色或黑色</li>
<li>树的根始终是黑色的</li>
<li>没有两个相邻的红色节点(红色节点不能有红色父节点或红色子节点, 并没有说不能出现连续的黑色节点)</li>
<li>从节点(包括根)到其任何后代 NULL 节点(叶子结点下方挂的两个空节点,并且认为他们是黑色的)的每条路径都具有相同数量的黑色节点</li>
</ul>
<p><img src="assets/bbea72d0-fbd9-11e9-9c25-e79d31f34796.jpg.png" alt="img" /></p>
<p>和平衡二叉树一样,在数据量很大的情况下,红黑树也无法保证树的高度。</p>
<p><strong>4. B 树</strong></p>
<p>特征(参考《数据结构》) </p>
<ul>
<li>树中的每个结点最多含有 m 个孩子;</li>
<li>除了根结点和叶子结点,其他结点至少有 [<strong>ceil(m/2)(代表是取上限的函数)</strong>] 个孩子</li>
<li>若根结点不是叶子结点时,则至少有两个孩子(除了没有孩子的根结点)</li>
<li>所有的叶子结点都出现在同一层中,叶子结点不包含任何关键字信息</li>
</ul>
<p>一颗 m=3 阶的 B 树如下所示:</p>
<p><img src="assets/75adba00-fbdb-11e9-b911-b94a6d515967.jpg.png" alt="img" /></p>
<p>B-tree 又做了优化,可以在磁盘容量允许的情况可控树的高度。</p>
<p>但是B-tree 每个节点都保存了数据,因此在每一个磁盘页中所能存放的节点数就变得少了,而 B+tree 的中间节点不保存数据,所以磁盘页能容纳更多节点元素,更“矮胖”,将高度降得越低。</p>
<p>一个 m 阶 B+ 树的性质(和 B 树有一些共同点,但是 B+ 树具备一些新的特性):</p>
<ol>
<li>有 K 个子树的根节点和中间节点包含 K 个元素B 树种是 K-1 个元素),每个元素不保存数据,只用来索引,所有的数据都保存在叶子节点上</li>
<li>所有的叶子节点包含了所有的元素的信息,且所有的叶子节点根据元素的大小从小到大组成一个链表</li>
<li>根节点以及所有的中间节点同时在于子节点,在子节点中是最大(或最小)元素</li>
</ol>
<p>下图是一个 3 阶 B+ 树:</p>
<p><img src="assets/cd659240-09c3-11ea-8740-81f35d7b0e34.jpg.png" alt="img" /></p>
<p><strong>B+ Tree 的优点</strong></p>
<p>查询单个元素:</p>
<ul>
<li>B+ 树中间节点不存储数据因此同样大小的磁盘页可以存储更多的元素当数据量相同的时候B+ 树要比 B 树更加“矮胖”,因此 IO 次数更少</li>
<li>B 树查询时只要找到匹配的元素即可性能不稳定最好情况是查找根节点最坏情况是查找叶子节点。B+ 树查询必须最终查找到叶子节点(元素数据都在叶子节点中),因此 B+ 树每次查找都是稳定的。</li>
</ul>
<p>范围查询:</p>
<ul>
<li>B 树范围查询需要进行中序遍历B+ 树只需要在叶子节点组成的链表上遍历即可,比 B 树要简单很多。</li>
</ul>
<p>从文首可知,从 MySQL 获取数据消耗的时间主要是 IO 操作消耗的时间,因此减少 IO 操作次数,才能缩短获取数据需要的时间,而一般获取数据需要操作的 IO 次数等于树的高度,所以减少树的高度,也就是减少 IO 次数,从而达到减少获取数据消耗的时间。</p>
<p>此时,假如我们要查询索引为 35 的数据:</p>
<ol>
<li>第一步65&gt;50 所以进入左边节点</li>
<li>第二步:发现 65 在节点中,获取到 65 的索引</li>
<li>第三步:通过索引直接定位到叶子节点上的数据</li>
</ol>
<p>结论:我们只通过三次 IO 操作就获取到 65 的数据,大大的节省 IO 操作的时间,这在大数据量的情况下,节省的时间更加不可想象。</p>
<p><strong>为什么要选择 B+ 树</strong></p>
<p>此时我们的心里的流程是这样的:如何减少获取数据的时间 —-&gt; 减少 IO 操作 ——&gt; 如何减少 IO 操作 —&gt; 减少树的高度 —&gt; 什么树能稳定的可控树的高度 —&gt;B 树和 B+ 树)—&gt; 那为什么选择 B+ 树 —–&gt; 因为 B+ 树节点不保存全部数据,因此在一页(一个节点)上能够存更加多的索引数据,让树的高度更低。</p>
<p><strong>还有一点很重要:</strong></p>
<p>对于组合索引B+tree 索引是按照索引列名 (从左到右的顺序) 进行顺序排序的,因此可以将随机 IO 转换为顺序 IO 提升 IO 效率;并且可以支持 order by/group 等排序需求;适合范围查询。</p>
<p><strong>另外</strong></p>
<p>MySQL 还支持 Hash 索引,但是 Hash 索引只能<strong>自适应</strong>,也就是说不能由我们手动指定,只能在优化器阶段,由优化器自主优化是使用 B+tree 还是 Hash 结构的索引,因此 Hash 在此就不赘述了。</p>
<h1>如何创建高性能索引?</h1>
<p><strong>1. 最左前缀匹配原则</strong></p>
<p>特性解释:</p>
<blockquote>
<p>当 B+ 树的数据项是复合的数据结构比如name、age、sex的时候B+ 数是按照<strong>从左到右的顺序</strong>来建立搜索树的,比如当 <strong>(张三,20,F)</strong> 这样的数据来检索的时候B+ 树会优先比较 name 来确定下一步的所搜方向,如果 name 相同再依次比较 age 和 sex最后得到检索的数据但当 (20,F) 这样的没有 name 的数据来的时候B+ 树就不知道下一步该查哪个节点,因为建立搜索树的时候 name 就是第一个比较因子,必须要先根据 name 来搜索才能知道下一步去哪里查询。比如当 (张三,F) 这样的数据来检索时B+ 树可以用 name 来指定搜索方向,但下一个字段 age 的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是 F 的数据了,这个是非常重要的性质,即索引的最左匹配特性。( 引用自:<a href="https://tech.meituan.com/mysql-index.html">https://tech.meituan.com/mysql-index.html</a></p>
</blockquote>
<p><strong>通过实例来看</strong></p>
<p>创建表,并建立组合索引:</p>
<p><img src="assets/bdeeec80-ff96-11e9-a77e-5f3e5bb81911.jpg" alt="img" /></p>
<p><img src="assets/8a5f0b60-ff97-11e9-a77e-5f3e5bb81911.jpg.png" alt="img" /></p>
<p>上述 SQL 可以使用到 (name,age,sex) 这个索引。</p>
<p><img src="assets/92fdec50-ff97-11e9-a77e-5f3e5bb81911.jpg.png" alt="img" /></p>
<p>上述 SQL 可以使用到 (name,age,sex) 这个索引中的 name因为缺少 age所以也无法使用到 sex。</p>
<p><img src="assets/9bfaf2d0-ff97-11e9-bac6-87ace92597d6.jpg.png" alt="img" /></p>
<p>上述 SQL 无法使用到 (name,age,sex) 这个索引,因为缺少最左列 name违反了最左前缀原则。</p>
<p><strong>2. 选择区分度高的列作为索引</strong></p>
<p>通过</p>
<pre><code>select count(Distinct columnName)/count(*) from Table
</code></pre>
<p>获取这个列在表中的度,度的值范围在 (0,1],度越大越好,主键索引的度为 1一般来说我们将度越大的列放在组合索引越左的位置上以便于最快的速度过滤掉无效的数据。</p>
<p>度的值很难确定,一般需要 join 的字段我们都要求是 0.1 以上,即平均 1 条扫描 10 条记录。</p>
<p><strong>3. 前缀索引</strong></p>
<p>如何创建前缀索引:</p>
<pre><code>ALTER TABLE person ADD KEY(name(7));
</code></pre>
<p>前缀索引是针对大类型字段,比如 varchar、text、blob如果使用这样的列做索引的话会很消耗内存资源而且大而慢。而且 MySQL 不允许索引这些列的完整长度。</p>
<p><strong>那么我们如何解决此类索引问题呢?</strong></p>
<p>通常我们可以选择索引开始的部分字符,这样可以大大的节约索引空间,从而提高索引效率,但这样会降低索引的度。</p>
<p>那么我们如何选择前缀,使得前缀的度接近于完成列的度,而且前缀又能足够短(以便节约索引空间)。</p>
<pre><code>//获取完整列的度 A
SELECT COUNT(*) cnt,name FROM person GROUP BY name ORDER BY cnt DESC;
//获取前N个字符的列的度 B
SELECT COUNT(*) cnt,LEFT(name,N) name FROM person GROUP BY name ORDER BY cnt DESC;
</code></pre>
<p>当 B 接近于 A 的时候N 即为该大字段所要设置的字符长度。</p>
<p>前缀索引:</p>
<pre><code>ALTER TABLE person ADD KEY (name (N));
</code></pre>
<p>前缀索引的缺点:无法使用前缀索引做 ORDER BY 和 GRUOP BY也无法使用前缀索引做覆盖索引。</p>
<p><strong>4. 索引列不能参与计算</strong></p>
<p><img src="assets/49a05260-ffc3-11e9-b0f3-059626abdf6a.jpg.png" alt="img" /></p>
<p>上述 SQL 无法使用到 (name,age,sex) 这个索引,因为 name 参与了计算,所以导致整个索引都无法使用。</p>
<p><strong>5. 尽量的扩展索引,不要新建索引</strong></p>
<p>索引的数目不是越多越好。每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变得很浪费时间。</p>
<p>比如:表中已经有 name 的索引,现在要加 (name,age) 的索引,那么只需要修改原来的索引即可</p>
<p><strong>6. 重复索引和冗余索引</strong></p>
<p>重复索引:相同列上按照相同顺序创建的相同类型的索引。</p>
<p>冗余索引:已有索引 (name,age),现在 创建索引 (name) 就是一个冗余索引,因为,索引 (name) 完全可以被 (name,age) 替代。然而 (name,age)、(age) 并不是 (name,age) 的冗余索引。</p>
<p>另外当 Id 列是主键,(name,Id) 是冗余索引,因为二级缓存的叶子节点包含了主键值。直接使用 (name) 作为索引即可。</p>
<h1>如何进行慢查询优化?</h1>
<blockquote>
<p>首先我们来看下一个 SQL 的执行过程:</p>
</blockquote>
<p><img src="assets/d55a4440-fc40-11e9-87eb-dd0ee8ac57cd.jpg.png" alt="img" /></p>
<p>接下来为大家介绍一个慢查询优化神器——explain 命令。</p>
<p>explain 命令大家应该都很熟悉,具体使用说明大家可以参照官网 <a href="https://dev.mysql.com/doc/refman/5.5/en/explain-output.html">explain 官网</a></p>
<p>然后为大家介绍慢查询优化基本步骤:</p>
<ol>
<li>设置 SQL_NO_CACHE 后,查看 SQL 是否真的很慢</li>
<li>使用 explain 命令来查询 MySQL 的查询计划</li>
<li>了解业务的使用场景</li>
<li>(通过上面创建索引的规则)添加索引</li>
<li>分解关联查询</li>
<li>优化 limit 分页</li>
<li>优化表结构</li>
</ol>
<h1>写高性能 SQL需要注意什么</h1>
<p><strong>1. SQL 语句中 IN 包含的值不应过多</strong></p>
<p>MySQL 对于 IN 做了相应的优化,即将 IN 中的常量全部存储在一个数组里面,而且这个数组是排好序的。但是如果数值较多,产生的消耗也是比较大的。再例如:<code>select id from table_name where num in (1,2,3)</code> 对于连续的数值,能用 between 就不要用 in 了;再或者使用连接来替换。</p>
<p>**2. SELECT 语句尽量使用具体列代替 ***</p>
<p><code>SELECT *</code> 增加很多不必要的消耗CPU、IO、内存、网络带宽增加了使用覆盖索引的可能性当表结构发生改变时前断也需要更新。所以要求直接在 select 后面接上字段名。</p>
<p><strong>3. 当能确定查询 n 条数据的时候n 不宜过大),使用 limit n</strong></p>
<p>这是为了使 EXPLAIN 中 type 列达到 const 类型。</p>
<p><strong>4.count()</strong></p>
<p>count() 函数有两种含义:统计行数、统计列数。</p>
<p>比如count(*) 代表统计的行数count(talbe.cloumn) 代表统计的是这个列不为 null 的数量。</p>
<p><strong>5.Union</strong></p>
<p>需要将 where、order by、limit 这些限制放入到每个子查询,才能重分提升效率。另外如非必须,尽量使用 Union all因为 union 会给每个子查询的临时表加入 distinct对每个临时表做唯一性检查效率较差。</p>
<p><strong>6. 关联查询优化</strong></p>
<ol>
<li>确保 ON 和 USING 字句中的列上有索引</li>
<li>确保任何的 GROUP BY 和 ORDER BY 中的表达式只涉及到一个表中的列,这样 MySQL 才有可能使用索引来优化。</li>
</ol>
<p><strong>7. 区分 in 和 exists not in 和 not exists</strong></p>
<p>in 和 exists 主要是造成了驱动顺序的改变(这是性能变化的关键),如果是 exists那么以外层表为驱动表先被访问如果是 IN那么先执行子查询。所以 IN 适合于外表大而内表小的情况EXISTS 适合于外表小而内表大的情况。</p>
<p>关于 not in 和 not exists推荐使用 not exists不仅仅是效率问题not in 可能存在逻辑问题。</p>
<p><strong>8. 不建议使用 % 前缀模糊查询</strong></p>
<p>Like &quot;% name&quot; 或者 LIKE &quot;% name%&quot;,这种查询会导致索引失效而进行全表扫描。但是可以使用 LIKE &quot;name%&quot;</p>
<p><strong>9. 避免在 where 子句中对字段进行 null 值判断</strong></p>
<p>对于 null 的判断会导致引擎放弃使用索引而进行全表扫描。</p>
<p><strong>10. 分段查询</strong></p>
<p>在一些查询中,可能一些查询的时间范围过大,造成查询缓慢。主要的原因是扫描行数过多。这个时候可以通过程序,分段进行查询,循环遍历,将结果合并处理进行展示。</p>
<p>这些优化手段只是诸多优化手段里面的很少的几种,漫漫优化路,还是需要我们在以后的实战中,慢慢积累。</p>
<h1>总结</h1>
<p>本文从数据结构层面深入剖析了索引,解释为什么在众多数据结构中选择了 B+ 树,以及如何创建高性能的索引,并例举了许多大家平时开发中时常遇到的 MySQL 优化案例,希望能给大家带来帮助。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/文章/当我们在讨论CQRS时我们在讨论些神马.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/文章/最全的 116 道 Redis 面试题解答.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"7099805b4f4b8b66","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>