mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-24 20:26:41 +08:00
385 lines
26 KiB
HTML
385 lines
26 KiB
HTML
<!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>08 索引:排序的艺术.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="/专栏/MySQL实战宝典/00 开篇词 从业务出发,开启海量 MySQL 架构设计.md.html">00 开篇词 从业务出发,开启海量 MySQL 架构设计</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/01 数字类型:避免自增踩坑.md.html">01 数字类型:避免自增踩坑</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/02 字符串类型:不能忽略的 COLLATION.md.html">02 字符串类型:不能忽略的 COLLATION</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/03 日期类型:TIMESTAMP 可能是巨坑.md.html">03 日期类型:TIMESTAMP 可能是巨坑</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/04 非结构存储:用好 JSON 这张牌.md.html">04 非结构存储:用好 JSON 这张牌</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/05 表结构设计:忘记范式准则.md.html">05 表结构设计:忘记范式准则</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/06 表压缩:不仅仅是空间压缩.md.html">06 表压缩:不仅仅是空间压缩</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/07 表的访问设计:你该选择 SQL 还是 NoSQL?.md.html">07 表的访问设计:你该选择 SQL 还是 NoSQL?</a>
|
||
</li>
|
||
<li>
|
||
<a class="current-tab" href="/专栏/MySQL实战宝典/08 索引:排序的艺术.md.html">08 索引:排序的艺术</a>
|
||
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/09 索引组织表:万物皆索引.md.html">09 索引组织表:万物皆索引</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/10 组合索引:用好,性能提升 10 倍!.md.html">10 组合索引:用好,性能提升 10 倍!</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/11 索引出错:请理解 CBO 的工作原理.md.html">11 索引出错:请理解 CBO 的工作原理</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/12 JOIN 连接:到底能不能写 JOIN?.md.html">12 JOIN 连接:到底能不能写 JOIN?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/13 子查询:放心地使用子查询功能吧!.md.html">13 子查询:放心地使用子查询功能吧!</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/14 分区表:哪些场景我不建议用分区表?.md.html">14 分区表:哪些场景我不建议用分区表?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/15 MySQL 复制:最简单也最容易配置出错.md.html">15 MySQL 复制:最简单也最容易配置出错</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/16 读写分离设计:复制延迟?其实是你用错了.md.html">16 读写分离设计:复制延迟?其实是你用错了</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/17 高可用设计:你怎么活用三大架构方案?.md.html">17 高可用设计:你怎么活用三大架构方案?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/18 金融级高可用架构:必不可少的数据核对.md.html">18 金融级高可用架构:必不可少的数据核对</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/19 高可用套件:选择这么多,你该如何选?.md.html">19 高可用套件:选择这么多,你该如何选?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/20 InnoDB Cluster:改变历史的新产品.md.html">20 InnoDB Cluster:改变历史的新产品</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/21 数据库备份:备份文件也要检查!.md.html">21 数据库备份:备份文件也要检查!</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/22 分布式数据库架构:彻底理解什么叫分布式数据库.md.html">22 分布式数据库架构:彻底理解什么叫分布式数据库</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/23 分布式数据库表结构设计:如何正确地将数据分片?.md.html">23 分布式数据库表结构设计:如何正确地将数据分片?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/24 分布式数据库索引设计:二级索引、全局索引的最佳设计实践.md.html">24 分布式数据库索引设计:二级索引、全局索引的最佳设计实践</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/25 分布式数据库架构选型:分库分表 or 中间件 ?.md.html">25 分布式数据库架构选型:分库分表 or 中间件 ?</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/26 分布式设计之禅:全链路的条带化设计.md.html">26 分布式设计之禅:全链路的条带化设计</a>
|
||
</li>
|
||
<li>
|
||
<a href="/专栏/MySQL实战宝典/27 分布式事务:我们到底要不要使用 2PC?.md.html">27 分布式事务:我们到底要不要使用 2PC?</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>08 索引:排序的艺术</h1>
|
||
<p>在模块一中,我们学习了怎么根据合适的类型正确地创建一张表,但创建的表不能立刻用在真正的业务系统上。因为表结构设计只是设计数据库最初的环节之一,我们还缺少数据库设计中最为重要的一个环节——索引设计,<strong>只有正确设计索引,业务才能达到上线的初步标准</strong>。</p>
|
||
<p>所以模块二我会讲索引的设计、业务应用与调优等案例。今天我们先来学习关系型数据库最核心的概念——索引,对索引做一个初步的概述,让你对数据库中的索引有一个体系的认知,并用好 B+ 树索引。</p>
|
||
<h3>索引是什么?</h3>
|
||
<p>相信你在面试时,通常会被问到“什么是索引?”而你一定要能脱口而出:<strong>索引是提升查询速度的一种数据结构</strong>。</p>
|
||
<p>索引之所以能提升查询速度,在于它在插入时对数据进行了排序(显而易见,它的缺点是影响插入或者更新的性能)。</p>
|
||
<p>所以,索引是一门排序的艺术,有效地设计并创建索引,会提升数据库系统的整体性能。在目前的 MySQL 8.0 版本中,InnoDB 存储引擎支持的索引有 B+ 树索引、全文索引、R 树索引。<strong>这一讲我们就先关注使用最为广泛的 B+ 树索引</strong>。</p>
|
||
<h3>B+树索引结构</h3>
|
||
<p>B+ 树索引是数据库系统中最为常见的一种索引数据结构,几乎所有的关系型数据库都支持它。</p>
|
||
<p><strong>那为什么关系型数据库都热衷支持 B+树索引呢</strong>?因为它是目前为止排序最有效率的数据结构。像二叉树,哈希索引、红黑树、SkipList,在海量数据基于磁盘存储效率方面远不如 B+ 树索引高效。</p>
|
||
<p>所以,上述的数据结构一般仅用于内存对象,基于磁盘的数据排序与存储,最有效的依然是 B+ 树索引。</p>
|
||
<p><strong>B+树索引的特点是:</strong> 基于磁盘的平衡树,但树非常矮,通常为 3~4 层,能存放千万到上亿的排序数据。树矮意味着访问效率高,从千万或上亿数据里查询一条数据,只用 3、4 次 I/O。</p>
|
||
<p>又因为现在的固态硬盘每秒能执行至少 10000 次 I/O ,所以查询一条数据,哪怕全部在磁盘上,也只需要 0.003 ~ 0.004 秒。另外,因为 B+ 树矮,在做排序时,也只需要比较 3~4 次就能定位数据需要插入的位置,排序效率非常不错。</p>
|
||
<p>B+ 树索引由根节点(root node)、中间节点(non leaf node)、叶子节点(leaf node)组成,其中叶子节点存放所有排序后的数据。<strong>当然也存在一种比较特殊的情况</strong>,比如高度为 1 的B+ 树索引:</p>
|
||
<p><img src="assets/CioPOWCk3P2AETnSAADbd7NkkIw226.png" alt="Drawing 1.png" /></p>
|
||
<p>上图中,第一个列就是 B+ 树索引排序的列,你可以理解它是表 User 中的列 id,类型为 8 字节的 BIGINT,所以列 userId 就是索引键(key),类似下表:</p>
|
||
<pre><code>CREATE TABLE User (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
name VARCHAR(128) NOT NULL,
|
||
sex CHAR(6) NOT NULL,
|
||
registerDate DATETIME NOT NULL,
|
||
...
|
||
)
|
||
</code></pre>
|
||
<p>所有 B+ 树都是从高度为 1 的树开始,然后根据数据的插入,慢慢增加树的高度。<strong>你要牢记:索引是对记录进行排序,</strong> 高度为 1 的 B+ 树索引中,存放的记录都已经排序好了,若要在一个叶子节点内再进行查询,只进行二叉查找,就能快速定位数据。</p>
|
||
<p>可随着插入 B+ 树索引的记录变多,1个页(16K)无法存放这么多数据,所以会发生 B+ 树的分裂,B+ 树的高度变为 2,当 B+ 树的高度大于等于 2 时,根节点和中间节点存放的是索引键对,由(索引键、指针)组成。</p>
|
||
<p>索引键就是排序的列,而指针是指向下一层的地址,在 MySQL 的 InnoDB 存储引擎中占用 6 个字节。下图显示了 B+ 树高度为 2 时,B+ 树索引的样子:</p>
|
||
<p><img src="assets/CioPOWCk3QiACiKfAAGD5zqFkGQ603.png" alt="Drawing 3.png" /></p>
|
||
<p>可以看到,在上面的B+树索引中,若要查询索引键值为 5 的记录,则首先查找根节点,查到键值对(20,地址),这表示小于 20 的记录在地址指向的下一层叶子节点中。接着根据下一层地址就可以找到最左边的叶子节点,在叶子节点中根据二叉查找就能找到索引键值为 5 的记录。</p>
|
||
<p>那一个高度为 2 的 B+ 树索引,理论上最多能存放多少行记录呢?</p>
|
||
<p>在 MySQL InnoDB 存储引擎中,一个页的大小为 16K,在上面的表 User 中,键值 userId 是BIGINT 类型,则:</p>
|
||
<pre><code>根节点能最多存放以下多个键值对 = 16K / 键值对大小(8+6) ≈ 1100
|
||
</code></pre>
|
||
<p>再假设表 User 中,每条记录的大小为 500 字节,则:</p>
|
||
<pre><code>叶子节点能存放的最多记录为 = 16K / 每条记录大小 ≈ 32
|
||
</code></pre>
|
||
<p>综上所述,树高度为 2 的 B+ 树索引,最多能存放的记录数为:</p>
|
||
<pre><code>总记录数 = 1100 * 32 = 35,200
|
||
</code></pre>
|
||
<p>也就是说,35200 条记录排序后,生成的 B+ 树索引高度为 2。在 35200 条记录中根据索引键查询一条记录只需要查询 2 个页,一个根叶,一个叶子节点,就能定位到记录所在的页。</p>
|
||
<p>高度为 3 的 B+ 树索引本质上与高度 2 的索引一致,如下图所示,不再赘述:</p>
|
||
<p><img src="assets/CioPOWCk3RKAPxMHAAF2YtY7XzI340.png" alt="Drawing 5.png" /></p>
|
||
<p>同理,树高度为 3 的 B+ 树索引,最多能存放的记录数为:</p>
|
||
<pre><code>总记录数 = 1100(根节点) * 1100(中间节点) * 32 = 38,720,000
|
||
</code></pre>
|
||
<p>讲到这儿,你会发现,高度为 3 的 B+ 树索引竟然能存放 3800W 条记录。在 3800W 条记录中定位一条记录,只需要查询 3 个页。<strong>那么 B+ 树索引的优势是否逐步体现出来了呢</strong>?</p>
|
||
<p>不过,在真实环境中,每个页其实利用率并没有这么高,还会存在一些碎片的情况,我们假设每个页的使用率为60%,则:</p>
|
||
<p><img src="assets/Cgp9HWCk3RqAWic4AADLJpJ9heo761.png" alt="Drawing 6.png" /></p>
|
||
<p>表格显示了 B+ 树的威力,即在 50 多亿的数据中,根据索引键值查询记录,只需要 4 次 I/O,大概仅需 0.004 秒。如果这些查询的页已经被缓存在内存缓冲池中,查询性能会更快。</p>
|
||
<p>在数据库中,上述的索引查询请求对应的 SQL 语句为:</p>
|
||
<pre><code>SELECT * FROM User WHERE id = ?
|
||
</code></pre>
|
||
<p>用户可以通过命令 EXPLAIN 查看是否使用索引:</p>
|
||
<pre><code>mysql> EXPLAIN SELECT * FROM User WHERE id = 1\G
|
||
********************** 1. row **********************
|
||
id: 1
|
||
select_type: SIMPLE
|
||
table: User
|
||
partitions: NULL
|
||
type: const
|
||
possible_keys: PRIMARY
|
||
key: PRIMARY
|
||
key_len: 8
|
||
ref: const
|
||
rows: 1
|
||
filtered: 100.00
|
||
Extra: NULL
|
||
</code></pre>
|
||
<p>在输出的 EXPLIAN 结果中,可以看到列 key 显示 PRIMARY,这表示根据主键索引进行查询。若没有根据索引进行查询,如根据性别进行查询,则会显示类似如下内容:</p>
|
||
<pre><code>mysql> EXPLAIN SELECT * FROM User WHERE sex = 'male'\G
|
||
********************** 1. row **********************
|
||
id: 1
|
||
select_type: SIMPLE
|
||
table: User
|
||
partitions: NULL
|
||
type: ALL
|
||
possible_keys: NULL
|
||
key: NULL
|
||
key_len: NULL
|
||
ref: NULL
|
||
rows: 986400
|
||
filtered: 50.00
|
||
Extra: Using where
|
||
</code></pre>
|
||
<p>讲到这儿,你应该了解了 B+ 树索引的组织形式,以及为什么在上亿的数据中可以通过B+树索引快速定位查询的记录。<strong>但 B+ 树的查询高效是要付出代价的,就是我们前面说的插入性能问题</strong>,接下去咱们就来讨论一下。</p>
|
||
<h3>优化 B+ 树索引的插入性能</h3>
|
||
<p>B+ 树在插入时就对要对数据进行排序,但排序的开销其实并没有你想象得那么大,因为排序是 CPU 操作(当前一个时钟周期 CPU 能处理上亿指令)。</p>
|
||
<p><strong>真正的开销在于 B+ 树索引的维护,保证数据排序,这里存在两种不同数据类型的插入情况</strong>。</p>
|
||
<ul>
|
||
<li><strong>数据顺序(或逆序)插入:</strong> B+ 树索引的维护代价非常小,叶子节点都是从左往右进行插入,比较典型的是自增 ID 的插入、时间的插入(若在自增 ID 上创建索引,时间列上创建索引,则 B+ 树插入通常是比较快的)。</li>
|
||
<li><strong>数据无序插入:</strong> B+ 树为了维护排序,需要对页进行分裂、旋转等开销较大的操作,另外,即便对于固态硬盘,随机写的性能也不如顺序写,所以磁盘性能也会收到较大影响。比较典型的是用户昵称,每个用户注册时,昵称是随意取的,若在昵称上创建索引,插入是无序的,索引维护需要的开销会比较大。</li>
|
||
</ul>
|
||
<p>你不可能要求所有插入的数据都是有序的,因为索引的本身就是用于数据的排序,插入数据都已经是排序的,那么你就不需要 B+ 树索引进行数据查询了。</p>
|
||
<p>所以对于 B+ 树索引,在 MySQL 数据库设计中,仅要求主键的索引设计为顺序,比如使用自增,或使用函数 UUID_TO_BIN 排序的 UUID,而不用无序值做主键。</p>
|
||
<p>我们再回顾 05 讲的自增、UUID、UUID 排序的插入性能对比:</p>
|
||
<p><img src="assets/CioPOWCk3TuAchxwAACr8OD6suQ711.png" alt="Drawing 8.png" /></p>
|
||
<p>可以看到,UUID 由于是无序值,所以在插入时性能比起顺序值自增 ID 和排序 UUID,性能上差距比较明显。</p>
|
||
<p><strong>所以,我再次强调:</strong> 在表结构设计时,主键的设计一定要尽可能地使用顺序值,这样才能保证在海量并发业务场景下的性能。</p>
|
||
<p>以上就是索引查询和插入的知识,接下来我们就分析怎么在 MySQL 数据库中查看 B+ 树索引。</p>
|
||
<h3>MySQL 中 B+ 树索引的设计与管理</h3>
|
||
<p>在 MySQL 数据库中,可以通过查询表 mysql.innodb_index_stats 查看每个索引的大致情况:</p>
|
||
<pre><code>SELECT
|
||
table_name,index_name,stat_name,
|
||
stat_value,stat_description
|
||
FROM innodb_index_stats
|
||
WHERE table_name = 'orders' and index_name = 'PRIMARY';
|
||
+----------+------------+-----------+------------+------------------+
|
||
|table_name| index_name | stat_name | stat_value |stat_description |
|
||
+----------+-------------------+------------+------------+----------+
|
||
| orders | PRIMARY|n_diff_pfx01|5778522 | O_ORDERKEY |
|
||
| orders | PRIMARY|n_leaf_pages|48867 | Number of leaf pages |
|
||
| orders | PRIMARY|size |49024 | Number of pages in the index|
|
||
+--------+--------+------------+------+-----------------------------+
|
||
3 rows in set (0.00 sec)
|
||
</code></pre>
|
||
<p>从上面的结果中可以看到,表 orders 中的主键索引,大约有 5778522 条记录,其中叶子节点一共有 48867 个页,索引所有页的数量为 49024。根据上面的介绍,你可以推理出非叶节点的数量为 49024 ~ 48867,等于 157 个页。</p>
|
||
<p>另外,我看见网上一些所谓的 MySQL“军规”中写道“一张表的索引不能超过 5 个”。<strong>根本没有这样的说法,完全是无稽之谈。</strong></p>
|
||
<p>在我看来,如果业务的确需要很多不同维度进行查询,那么就该创建对应多索引,这是没有任何值得商讨的地方。</p>
|
||
<p><strong>真正在业务上遇到的问题是:</strong> 由于业务开发同学对数据库不熟悉,创建 N 多索引,但实际这些索引从创建之初到现在根本就没有使用过!因为优化器并不会选择这些低效的索引,这些无效索引占用了空间,又影响了插入的性能。</p>
|
||
<p><strong>那你怎么知道哪些 B+树索引未被使用过呢</strong>?在 MySQL 数据库中,可以通过查询表sys.schema_unused_indexes,查看有哪些索引一直未被使用过,可以被废弃:</p>
|
||
<pre><code>SELECT * FROM schema_unused_indexes
|
||
WHERE object_schema != 'performance_schema';
|
||
+---------------+-------------+--------------+
|
||
| object_schema | object_name | index_name |
|
||
+---------------+-------------+--------------+
|
||
| sbtest | sbtest1 | k_1 |
|
||
| sbtest | sbtest2 | k_2 |
|
||
| sbtest | sbtest3 | k_3 |
|
||
| sbtest | sbtest4 | k_4 |
|
||
| tpch | customer | CUSTOMER_FK1 |
|
||
| tpch | lineitem | LINEITEM_FK2 |
|
||
| tpch | nation | NATION_FK1 |
|
||
| tpch | orders | ORDERS_FK1 |
|
||
| tpch | partsupp | PARTSUPP_FK1 |
|
||
| tpch | supplier | SUPPLIER_FK1 |
|
||
+---------------+-------------+--------------+
|
||
</code></pre>
|
||
<p>如果数据库运行时间比较长,而且索引的创建时间也比较久,索引还出现在上述结果中,DBA 就可以考虑删除这些没有用的索引。</p>
|
||
<p>而 MySQL 8.0 版本推出了索引不可见(Invisible)功能。在删除废弃索引前,用户可以将索引设置为对优化器不可见,然后观察业务是否有影响。若无,DBA 可以更安心地删除这些索引:</p>
|
||
<pre><code>ALTER TABLE t1
|
||
ALTER INDEX idx_name INVISIBLE/VISIBLE;
|
||
</code></pre>
|
||
<h3>总结</h3>
|
||
<p>这一讲我对索引做了一个较为初步地概述,学完这一讲,我相信你能非常清晰地知道:</p>
|
||
<ul>
|
||
<li>索引是加快查询的一种数据结构,其原理是插入时对数据排序,缺点是会影响插入的性能;</li>
|
||
<li>MySQL 当前支持 B+树索引、全文索引、R 树索引;</li>
|
||
<li>B+ 树索引的高度通常为 3~4 层,高度为 4 的 B+ 树能存放 50 亿左右的数据;</li>
|
||
<li>由于 B+ 树的高度不高,查询效率极高,50 亿的数据也只需要插叙 4 次 I/O;</li>
|
||
<li>MySQL 单表的索引没有个数限制,业务查询有具体需要,创建即可,不要迷信个数限制;</li>
|
||
<li>可以通过表 sys.schema_unused_indexes 和索引不可见特性,删除无用的索引。</li>
|
||
</ul>
|
||
<p>总的来讲,关于索引虽然老生常谈,但是它是所有关系型数据库的核心,我希望你反复阅读本文,真正理解 B+ 树索引的实现。</p>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div style="float: left">
|
||
<a href="/专栏/MySQL实战宝典/07 表的访问设计:你该选择 SQL 还是 NoSQL?.md.html">上一页</a>
|
||
</div>
|
||
<div style="float: right">
|
||
<a href="/专栏/MySQL实战宝典/09 索引组织表:万物皆索引.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":"7099730b4a943d60","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>
|