Skip to content

Commit a7995c1

Browse files
committed
linkedList
1 parent 44fb83b commit a7995c1

3 files changed

Lines changed: 229 additions & 68 deletions

File tree

docs/data-store/MySQL/MySQL-Index.md

Lines changed: 149 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,32 @@
3737
因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,
3838
都会调整因为更新所带来的键值变化后的索引信息
3939

40-
### mysql索引分类
4140

42-
- **单值索引**:一个索引只包含单个列,一个表可以有多个单列索引
43-
- **唯一索引**:索引列的值必须唯一,但允许有空值
44-
- **复合索引**:一个索引包含多个列
41+
42+
### MySQL 索引分类
43+
44+
#### 数据结构角度
45+
46+
- B+树索引
47+
- Hash索引
48+
- Full-Text全文索引
49+
- R-Tree索引
50+
51+
#### 从物理存储角度
52+
53+
- 聚集索引(clustered index)
54+
55+
- 非聚集索引(non-clustered index),也叫辅助索引(secondary index)
56+
57+
聚集索引和非聚集索引都是B+树结构
58+
59+
#### 从逻辑角度
60+
61+
- 主键索引:主键索引是一种特殊的唯一索引,不允许有空值
62+
- 普通索引或者单列索引:每个索引只包含单个列,一个表可以有多个单列索引
63+
- 多列索引(复合索引、联合索引):复合索引指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用复合索引时遵循最左前缀集合
64+
- 唯一索引或者非唯一索引
65+
- 空间索引:空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON。 MYSQL使用SPATIAL关键字进行扩展,使得能够用于创建正规索引类型的语法创建空间索引。创建空间索引的列,必须将其声明为NOT NULL,空间索引只能在存储引擎为MYISAM的表中创建
4566

4667

4768

@@ -119,27 +140,115 @@
119140

120141
- **<mark>InnoDB引擎索引结构的叶子节点的数据域,存放的就是实际的数据记录</mark>**(对于主索引,此处会存放表中所有的数据记录;对于辅助索引此处会引用主键,检索的时候通过主键到主键索引中找到对应数据行),或者说,InnoDB的数据文件本身就是主键索引文件,这样的索引被称为***“<mark>聚簇索引</mark>”*****一个表只能有一个聚簇索引**
121142

122-
- 检索原理
143+
- B-Tree是为磁盘等外存储设备设计的一种平衡查找树。
123144

124-
![bTree](../../_images/mysql/bTree.png)
145+
系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。
125146

126-
【初始化介绍】
127-
上图是一颗b+树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3,
128-
P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。
129-
真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。
130-
非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。
147+
InnoDB 存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB 存储引擎中默认每个页的大小为16KB,可通过参数 `innodb_page_size` 将页的大小设置为 4K、8K、16K,在 MySQL 中可通过如下命令查看页的大小:`show variables like 'innodb_page_size';`
148+
149+
而系统一个磁盘块的存储空间往往没有这么大,因此 InnoDB 每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小 16KB。InnoDB 在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率。
150+
151+
B-Tree 结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述 B-Tree,首先定义一条记录为一个二元组[key, data] ,key为记录的键值,对应表中的主键值,data 为一行记录中除主键外的数据。对于不同的记录,key值互不相同。
152+
153+
一棵m阶的B-Tree有如下特性:
154+
155+
1. 每个节点最多有m个孩子
156+
2. 除了根节点和叶子节点外,其它每个节点至少有Ceil(m/2)个孩子。
157+
3. 若根节点不是叶子节点,则至少有2个孩子
158+
4. **所有叶子节点都在同一层,且不包含其它关键字信息**
159+
5. 每个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn)
160+
6. 关键字的个数n满足:ceil(m/2)-1 <= n <= m-1
161+
7. ki(i=1,…n)为关键字,且关键字升序排序
162+
8. Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的所有节点关键字均小于ki,但都大于k(i-1)
163+
164+
B-Tree 中的每个节点根据实际情况可以包含大量的关键字信息和分支,如下图所示为一个 3 阶的 B-Tree:
165+
166+
![索引](https://tva1.sinaimg.cn/large/007S8ZIlly1gg1de1fj9qj30ou08aaas.jpg)
167+
168+
每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为17和35,P1指针指向的子树的数据范围为小于17,P2指针指向的子树的数据范围为17~35,P3指针指向的子树的数据范围为大于35。
169+
170+
模拟查找关键字29的过程:
171+
172+
1. 根据根节点找到磁盘块1,读入内存。【磁盘I/O操作第1次】
173+
2. 比较关键字29在区间(17,35),找到磁盘块1的指针P2。
174+
3. 根据P2指针找到磁盘块3,读入内存。【磁盘I/O操作第2次】
175+
4. 比较关键字29在区间(26,30),找到磁盘块3的指针P2。
176+
5. 根据P2指针找到磁盘块8,读入内存。【磁盘I/O操作第3次】
177+
6. 在磁盘块8中的关键字列表中找到关键字29。
178+
179+
分析上面过程,发现需要3次磁盘I/O操作,和3次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。而3次磁盘I/O操作是影响整个B-Tree查找效率的决定因素。B-Tree相对于AVLTree缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率。
180+
181+
B+Tree 是在 B-Tree 基础上的一种优化,使其更适合实现外存储索引结构,InnoDB 存储引擎就是用 B+Tree 实现其索引结构。
182+
183+
从上一节中的 B-Tree 结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,**所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上**,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。
184+
185+
B+Tree相对于B-Tree有几点不同:
186+
187+
1. 非叶子节点只存储键值信息;
188+
2. 所有叶子节点之间都有一个链指针;
189+
3. 数据记录都存放在叶子节点中
190+
191+
将上一节中的B-Tree优化,由于B+Tree的非叶子节点只存储键值信息,假设每个磁盘块能存储4个键值及指针信息,则变成B+Tree后其结构如下图所示: ![img](https://tva1.sinaimg.cn/large/007S8ZIlly1gf3t57jvq1j30sc0aj0tj.jpg)
192+
193+
通常在B+Tree上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对B+Tree进行两种查找运算:一种是对于主键的范围查找和分页查找,另一种是从根节点开始,进行随机查找。
194+
195+
可能上面例子中只有22条数据记录,看不出B+Tree的优点,下面做一个推算:
196+
197+
InnoDB存储引擎中页的大小为16KB,一般表的主键类型为INT(占用4个字节)或BIGINT(占用8个字节),指针类型也一般为4或8个字节,也就是说一个页(B+Tree中的一个节点)中大概存储16KB/(8B+8B)=1K个键值(因为是估值,为方便计算,这里的K取值为10^3)。也就是说一个深度为3的B+Tree索引可以维护10^3 * 10^3 * 10^3 = 10亿 条记录。
198+
199+
实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree的高度一般都在2-4层。MySQL的InnoDB存储引擎在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只需要1~3次磁盘I/O操作。
200+
201+
B+Tree性质
131202

132-
133-
【查找过程】
134-
如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。
135-
136-
真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。
137-
138-
#### b+树性质
139-
140203
1. 通过上面的分析,我们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。这也是为什么b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于1时将会退化成线性表。
204+
2. 当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即**索引的最左匹配特性**
205+
206+
##### MyISAM主键索引与辅助索引的结构
207+
208+
MyISAM引擎的索引文件和数据文件是分离的。**MyISAM引擎索引结构的叶子节点的数据域,存放的并不是实际的数据记录,而是数据记录的地址**。索引文件与数据文件分离,这样的索引称为"**非聚簇索引**"。MyISAM的主索引与辅助索引区别并不大,只是主键索引不能有重复的关键字。
209+
210+
![img](https://tva1.sinaimg.cn/large/007S8ZIlly1gewoy5bddkj31bp0u04lv.jpg)
211+
212+
在 MyISAM 中,索引(含叶子节点)存放在单独的 .myi 文件中,叶子节点存放的是数据的物理地址偏移量(通过偏移量访问就是随机访问,速度很快)。
213+
214+
主索引是指主键索引,键值不可能重复;辅助索引则是普通索引,键值可能重复。
215+
216+
通过索引查找数据的流程:先从索引文件中查找到索引节点,从中拿到数据的文件指针,再到数据文件中通过文件指针定位了具体的数据。辅助索引类似。
217+
218+
##### InnoDB主键索引与辅助索引的结构
219+
220+
**InnoDB引擎索引结构的叶子节点的数据域,存放的就是实际的数据记录**(对于主索引,此处会存放表中所有的数据记录;对于辅助索引此处会引用主键,检索的时候通过主键到主键索引中找到对应数据行),或者说,**InnoDB的数据文件本身就是主键索引文件**,这样的索引被称为"“聚簇索引”,一个表只能有一个聚簇索引。
221+
222+
###### 主键索引:
223+
224+
我们知道InnoDB索引是聚集索引,它的索引和数据是存入同一个.idb文件中的,因此它的索引结构是在同一个树节点中同时存放索引和数据,如下图中最底层的叶子节点有三行数据,对应于数据表中的id、stu_id、name数据项。
225+
226+
![img](https://tva1.sinaimg.cn/large/007S8ZIlly1gewoy2lhr5j320d0u016k.jpg)
227+
228+
在Innodb中,索引分叶子节点和非叶子节点,非叶子节点就像新华字典的目录,单独存放在索引段中,叶子节点则是顺序排列的,在数据段中。Innodb的数据文件可以按照表来切分(只需要开启`innodb_file_per_table)`,切分后存放在`xxx.ibd`中,默认不切分,存放在`xxx.ibdata`中。
229+
230+
###### 辅助(非主键)索引:
231+
232+
这次我们以示例中学生表中的name列建立辅助索引,它的索引结构跟主键索引的结构有很大差别,在最底层的叶子结点有两行数据,第一行的字符串是辅助索引,按照ASCII码进行排序,第二行的整数是主键的值。
233+
234+
这就意味着,对name列进行条件搜索,需要两个步骤:
235+
236+
① 在辅助索引上检索name,到达其叶子节点获取对应的主键;
237+
238+
② 使用主键在主索引上再进行对应的检索操作
239+
240+
这也就是所谓的“**回表查询**
141241

142-
2. 当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。
242+
![img](https://tva1.sinaimg.cn/large/007S8ZIlly1gewsc7l623j320r0u0gwt.jpg)
243+
244+
**InnoDB 索引结构需要注意的点**
245+
246+
1. 数据文件本身就是索引文件
247+
2. 表数据文件本身就是按 B+Tree 组织的一个索引结构文件
248+
3. 聚集索引中叶节点包含了完整的数据记录
249+
4. InnoDB 表必须要有主键,并且推荐使用整型自增主键
250+
251+
正如我们上面介绍 InnoDB 存储结构,索引与数据是共同存储的,不管是主键索引还是辅助索引,在查找时都是通过先查找到索引节点才能拿到相对应的数据,如果我们在设计表结构时没有显式指定索引列的话,MySQL 会从表中选择数据不重复的列建立索引,如果没有符合的列,则 MySQL 自动为 InnoDB 表生成一个隐含字段作为主键,并且这个字段长度为6个字节,类型为整型。
143252

144253
#### <font color=#0000FF>**Hash索引**</font>
145254

@@ -213,4 +322,24 @@
213322

214323

215324

325+
### 哪些情况不要创建索引
326+
327+
1. 表记录太少
328+
2. 经常增删改的表
329+
3. 数据重复且分布均匀的表字段,只应该为最经常查询和最经常排序的数据列建立索引(如果某个数据类包含太多的重复数据,建立索引没有太大意义)
330+
4. 频繁更新的字段不适合创建索引(会加重IO负担)
331+
5. where条件里用不到的字段不创建索引
332+
333+
### MySQL高效索引
334+
335+
**覆盖索引**(Covering Index),或者叫索引覆盖, 也就是平时所说的不需要回表操作
336+
337+
- 就是 select 的数据列只用从索引中就能够取得,不必读取数据行,MySQL 可以利用索引返回 select 列表中的字段,而不必根据索引再次读取数据文件,换句话说**查询列要被所建的索引覆盖**
338+
339+
- 索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据,当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含(覆盖)满足查询结果的数据就叫做覆盖索引。
340+
341+
- **判断标准**
342+
343+
使用explain,可以通过输出的extra列来判断,对于一个索引覆盖查询,显示为**using index**,MySQL查询优化器在执行查询前会决定是否有索引覆盖查询
344+
216345
> [美团技术-MySQL索引原理及慢查询优化](https://tech.meituan.com/2014/06/30/mysql-index.html)

0 commit comments

Comments
 (0)