玖叶教程网

前端编程开发入门

数据库mysql(1)——B+TREE索引原理

一、B+Tree索引详解

1.什么是索引?

索引:加速查询的数据结构。

2.索引常见数据结构:

#1.顺序查找: 最基本的查询算法-复杂度O(n),大数据量此算法效率糟糕。

#2.二叉树查找(binary tree search): O(log2n)

左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址(注意逻辑上相邻的记录在磁盘上也并不是一定物理相邻的)。为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在O(log2n)O(log2n)的复杂度内获取到相应数据。

#3.hash索引 无法满足范围查找。

#4.二叉树、红黑树 [复杂度O(h)]导致树高度非常高(平衡二叉树一个节点只能有左子树和右子树),逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,IO次数多查找慢,效率低。todo 逻辑上相邻节点没法直接通过顺序指针关联,可能需要迭代回到上层节点重复向下遍历找到对应节点,效率低

B-Tree 和 B+Tree数据结构:

#4.B-Tree:

结构:B-TREE 每个节点都是一个二元数组: [key, data],所有节点都可以存储数据。key为索引key,data为除key之外的数据。结构如下:

Mysql中B+Tree:在经典B+Tree的基础上进行了优化,增加了顺序访问指针。在B+Tree的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的B+Tree。这样就提高了区间访问性能:如果要查询key为从18到49的所有数据记录,当找到18后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效率(无需返回上层父节点重复遍历查找减少IO操作)。

结构如下:

3.为什么Mysql选择B+TREE索引? B+TREE索引有什么好处?

索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,所以索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数,提升索引效率。

磁盘存取原理:

索引一般以文件形式存储在磁盘上,索引检索需要磁盘I/O操作。与主存不同,磁盘I/O存在机械运动耗费,因此磁盘I/O的时间消耗是巨大的。

图5

一个磁盘由大小相同且同轴的圆形盘片组成,磁盘可以转动(各个磁盘必须同步转动)。在磁盘的一侧有磁头支架,磁头支架固定了一组磁头,每个磁头负责存取一个磁盘的内容。磁头不能转动,但是可以沿磁盘半径方向运动(实际是斜切向运动),每个磁头同一时刻也必须是同轴的,即从正上方向下看,所有磁头任何时候都是重叠的(不过目前已经有多磁头独立技术,可不受此限制)。

磁盘结构:

图6

磁道: 每个同心环叫做一个 扇区: 磁盘的最小存储单元。 当需要从磁盘读取数据时,系统会将数据逻辑地址传给磁盘,磁盘的控制电路按照寻址逻辑将逻辑地址翻译成物理地址,即确定要读的数据在哪个磁道,哪个扇区。为了读取这个扇区的数据,需要将磁头放到这个扇区上方,为了实现这一点,磁头需要移动对准相应磁道,这个过程叫做寻道,所耗费时间叫做寻道时间,然后磁盘旋转将目标扇区旋转到磁头下,这个过程耗费的时间叫做旋转时间。

局部性原理与磁盘预读:

由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,磁盘的存取速度往往是主存的几百分分之一,因此为了提高效率,要尽量减少磁盘I/O。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存预读可以提高I/O效率。预读的长度一般为页(page:计算机管理存储器的逻辑块-通常为4k)的整倍数. 主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中。


B-/+Tree索引的性能优势: 一般使用磁盘I/O次数评价索引优劣。

1.结合操作系统存储结构优化处理: mysql巧妙运用操作系统存储结构(一个节点分配到一个存储页中->尽量减少IO次数) & 磁盘预读(缓存预读->加速预读马上要用到的数据).

2.B+Tree 单个节点能放多个子节点,相同IO次数,检索出更多信息。

3.B+TREE 只在叶子节点存储数据 & 所有叶子结点包含一个链指针 & 其他内层非叶子节点只存储索引数据。只利用索引快速定位数据索引范围,先定位索引再通过索引高效快速定位数据。

详解:Mysql设计利用了磁盘预读原理,将一个B+Tree节点大小设为一个页大小,在新建节点时直接申请一个页的空间,这样就能保证一个节点物理上存储在一个页里,加之计算机存储分配都是按页对齐的,这样就实现了每个Node节点只需要一次I/O操作。

B-Tree索引、B+Tree索引: 单个节点能放多个子节点,查询IO次数相同(mysql查询IO次数最多3-5次-所以需要每个节点需要存储很多数据)

B+TREE 只在叶子节点存储数据 & 所有叶子结点包含一个链指针 & 其他内层非叶子节点只存储索引数据。只利用索引快速定位数据索引范围,先定位索引再通过索引高效快速定位数据。

B+Tree更适合外存索引,原因和内节点出度d有关。从上面分析可以看到,d越大索引的性能越好,而出度的上限取决于节点内key和data的大小:

B+Tree内节点去掉了data域,因此可以拥有更大的出度,拥有更好的性能。只利用索引快速定位数据索引范围,先定位索引再通过索引高效快速定位数据。

dmax=floor(pagesize/(keysize+datasize+pointsize))


Mysql 索引实现-MyISAM & InnoDB: important

聚簇索引: 索引 和 数据文件为同一个文件。非聚簇索引: 索引 和 数据文件分开的索引。

MyISAM & InnoDB 都使用B+Tree索引结构。但是底层索引存储不同,MyISAM 采用非聚簇索引,而InnoDB采用聚簇索引。

MyISAM索引原理:采用非聚簇索引-MyISAM myi索引文件和myd数据文件分离,索引文件仅保存数据记录的指针地址。叶子节点data域存储指向数据记录的指针地址。(底层存储结构: frm -表定义、 myi -myisam索引、 myd-myisam数据)

MyISAM索引按照B+Tree搜索,如果指定的Key存在,则取出其data域的值,然后以data域值-数据指针地址去读取相应数据记录。辅助索引和主索引在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。MyISAM索引树如下:

MyISAM非聚簇索引


InnoDB优势:高扩展性,充分发挥硬件性能 Crash Safe、 支持事务、 可以在线热备份

InnoDB特性:

1. 事务支持(ACID)2. 扩展性优良 3. 读写不冲突 4. 缓存加速

2. 功能组件: redo/undo & 异步IO & MVCC & 行级别锁 & Page Cache(LRU)

InnoDB物理存储结构如下图:

InnoDB表空间管理

InnoDB物理存储文件结构说明:

InnoDB以表空间Tablespace(idb文件)结构进行组织,每个Tablespace 包含多个Segment段,每个段(分为2种段:叶子节点Segment&非叶子节点Segment), 一个Segment段包含多个Extent,一个Extent占用1M空间包含64个Page(每个Page 16k),InnoDB B-Tree 一个逻辑节点就分配一个物理Page,一个节点一次IO操作。,一个Page里包含很多有序数据Row行数据,Row行数据中包含Filed属性数据等信息。

? 表空间(ibd文件)

? 段(一个索引2段:叶子节点Segment & 非叶子节点Segment

? Extent(1MB):一个Extent(1M) 包含64个 Page(16k),一个Page里包含很多有序行数据 , InnoDB B-Tree 一个逻辑节点就分配一个物理Page,一个节点一次IO操作。

? Page(16KB)

? Row

? Field

表插入数据扩展原理: 一次扩张一个Extent空间(1M),64个Page,按照顺序结构向每个page中插入顺序。

InnoDB逻辑组织结构:

InnoDB索引树结构

每个索引一个B+树, 一个B+树节点 = 一个物理Page(16K)

? 数据按16KB切片为Page 并编号, 编号可映射到物理文件偏移(16K * N), B+树叶子节点前后形成双向链表, 数据按主键索引聚簇, 二级索引叶节点存储主键值, 通过叶节点主键值回表查找数据。

InnoDB索引原理:

采用聚簇索引- InnoDB数据&索引文件为一个idb文件,表数据文件本身就是主索引,相邻的索引临近存储。 叶节点data域保存了完整的数据记录(数据[除主键id外其他列data]+主索引[索引key:表主键id])。 叶子节点直接存储数据记录,以主键id为key,叶子节点中直接存储数据记录。(底层存储结构: frm -表定义、 ibd: innoDB数据&索引文件)

注:由于InnoDB采用聚簇索引结构存储,索引InnoDB的数据文件需要按照主键聚集,因此InnoDB要求表必须有主键(MyISAM可以没有)。如果没有指定mysql会自动选择一个可以唯一表示数据记录的列作为主键,如果不存在这样的列,mysql自动为InnoDB表生成一个隐含字段(6个字节长整型)作为主键。 InnoDB的所有 辅助索引 都引用 数据记录的主键 作为data域。

聚簇索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得数据记录主键,然后用主键到主索引中检索获得数据记录。InnoDB聚簇索引结构:

InnoDB聚簇索引

索引查找流程:

#1.索引精确查找: 确定定位条件, 找到根节点Page No, 根节点读到内存, 逐层向下查找, 读取叶子节点Page,通过 二分查找找到记录或未命中。(select * from user_info where id = 23)

索引精确查找过程

#2.索引范围查找:读取根节点至内存, 确定索引定位条件id=18, 找到满足条件第一个叶节点

, 顺序扫描所有结果, 直到终止条件满足id >=22 (select * from user_info where id >= 18 and id < 22)

索引范围查找过程

#3.全表扫描:直接读取叶节点头结点, 顺序扫描, 返回符合条件记录, 到最终节点结束

(select * from user_info where name = 'abc')

全表扫描过程

#4.二级索引查找

二级索引查找过程

Create table table_x(int id primary key, varchar(64) name,key sec_index(name) )

? Select * from table_x where name = “d”;

通过二级索引查出对应主键,拿主键回表查主键索引得到数据, 二级索引可筛选掉大量无效记录,提高效率

Innodb对索引的优化 Insert Buffer todo

Innodb Buffer Pool: todo

Innodb 异步IO框架:

Innodb ACID如何保证:

?原子性 redo + undo ? 一致性 redo ? 隔离性 锁 + MVCC ? 持久性 redo

Innodb Redo日志:

Innodb 行级别锁:

二、MySQL高级 之 explain执行计划详解

使用explain关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的,分析你的查询语句或是表结构的性能瓶颈。

explain执行计划包含的信息

其中最重要的字段为:id、type、key、rows、Extra

各字段详解

一、id

select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序

三种情况:

1、id相同:执行顺序由上至下

2、id不同:如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行

3、id相同又不同(两种情况同时存在):id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行

二、select_type

查询的类型,主要是用于区分普通查询、联合查询、子查询等复杂的查询

1、SIMPLE:简单的select查询,查询中不包含子查询或者union

2、PRIMARY:查询中包含任何复杂的子部分,最外层查询则被标记为primary

3、SUBQUERY:在select 或 where列表中包含了子查询

4、DERIVED:在from列表中包含的子查询被标记为derived(衍生),mysql或递归执行这些子查询,把结果放在零时表里

5、UNION:若第二个select出现在union之后,则被标记为union;若union包含在from子句的子查询中,外层select将被标记为derived

6、UNION RESULT:从union表获取结果的select

三、type

访问类型,sql查询优化中一个很重要的指标,结果值从好到坏依次是:

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

一般来说,好的sql查询至少达到range级别,最好能达到ref

1、system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,可以忽略不计

2、const:表示通过索引一次就找到了,const用于比较primary key 或者 unique索引。因为只需匹配一行数据,所有很快。如果将主键置于where列表中,mysql就能将该查询转换为一个const

3、eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键 或 唯一索引扫描。

注意:ALL全表扫描的表记录最少的表如t1表

4、ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质是也是一种索引访问,它返回所有匹配某个单独值的行,然而他可能会找到多个符合条件的行,所以它应该属于查找和扫描的混合体

5、range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了那个索引。一般就是在where语句中出现了bettween、<、>、in等的查询。这种索引列上的范围扫描比全索引扫描要好。只需要开始于某个点,结束于另一个点,不用扫描全部索引

6、index:Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常为ALL块,应为索引文件通常比数据文件小。(Index与ALL虽然都是读全表,但index是从索引中读取,而ALL是从硬盘读取)

7、ALL:Full Table Scan,遍历全表以找到匹配的行

四、possible_keys

查询涉及到的字段上存在索引,则该索引将被列出,但不一定被查询实际使用

五、key

实际使用的索引,如果为NULL,则没有使用索引。

查询中如果使用了覆盖索引,则该索引仅出现在key列表中

六、key_len

表示索引中使用的字节数,查询中使用的索引的长度(最大可能长度),并非实际使用长度,理论上长度越短越好。key_len是根据表定义计算而得的,不是通过表内检索出的

七、ref

显示索引的那一列被使用了,如果可能,是一个常量const。

八、rows

根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数

九、Extra

不适合在其他字段中显示,但是十分重要的额外信息

1、Using filesort :

mysql对数据使用一个外部的索引排序,而不是按照表内的索引进行排序读取。也就是说mysql无法利用索引完成的排序操作成为“文件排序”

由于索引是先按email排序、再按address排序,所以查询时如果直接按address排序,索引就不能满足要求了,mysql内部必须再实现一次“文件排序”

2、Using temporary:

使用临时表保存中间结果,也就是说mysql在对查询结果排序时使用了临时表,常见于order by 和 group by

3、Using index:

表示相应的select操作中使用了覆盖索引(Covering Index),避免了访问表的数据行,效率高

如果同时出现Using where,表明索引被用来执行索引键值的查找(参考上图)

如果没用同时出现Using where,表明索引用来读取数据而非执行查找动作

覆盖索引(Covering Index):也叫索引覆盖。就是select列表中的字段,只用从索引中就能获取,不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。

注意:

a、如需使用覆盖索引,select列表中的字段只取出需要的列,不要使用select *

b、如果将所有字段都建索引会导致索引文件过大,反而降低crud性能

4、Using where :

使用了where过滤

5、Using join buffer :

使用了链接缓存

6、Impossible WHERE:

where子句的值总是false,不能用来获取任何元祖

7、select tables optimized away:

在没有group by子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段在进行计算,查询执行计划生成的阶段即可完成优化

8、distinct:

优化distinct操作,在找到第一个匹配的元祖后即停止找同样值得动作

综合Case

执行顺序

1(id = 4)、【select id, name from t2】:select_type 为union,说明id=4的select是union里面的第二个select。

2(id = 3)、【select id, name from t1 where address = ‘11’】:因为是在from语句中包含的子查询所以被标记为DERIVED(衍生),where address = ‘11’ 通过复合索引idx_name_email_address就能检索到,所以type为index。

3(id = 2)、【select id from t3】:因为是在select中包含的子查询所以被标记为SUBQUERY。

4(id = 1)、【select d1.name, … d2 from … d1】:select_type为PRIMARY表示该查询为最外层查询,table列被标记为 “derived3”表示查询结果来自于一个衍生表(id = 3 的select结果)。

5(id = NULL)、【 … union … 】:代表从union的临时表中读取行的阶段,table列的 “union 1, 4”表示用id=1 和 id=4 的select结果进行union操作。

三、总结

一、为什么选择B+Tree。

每次磁盘读取都是一页数据,4k的倍数,我们就算16KB,索引存储的KEY为int为4B或者bigInt8B,指针一般也为4B或者8B,这样每页可以存储的数据为 16KB/(8B+8B)=1K,也就是磁盘每次可以读取1K的索引数据个数,也就表示n层数据可以存储K的n次幂条数据,假如1K=1000,1亿条数据就代表只需要3层树,磁盘查询效果就只需要3次就可以了;如果使用B-Tree,因为它非叶子节点也会存储数据包,所以他每页存储的数据为16KB/(8B+8B+数据大小)<<1K,数量一定的情况,每页存储数据降低,那就表示树的高度增加,查询是每页预读,层层往下查找,树的高度代表查询磁盘的次数,磁盘查询效率低下,因此也代表,查询次数过多。

二、为什么查询需要建立索引。

如果不建立索引,则表示全表扫描,叶子节点的存储了key,指针和数据,每页存储的数据量非常少,全表扫描需要从前往后遍历叶子节点,查询的数据大多数不可能在第一页就命中,数据页数越靠后,读取磁盘的次数越多,查询效率就越慢,所以需要建立索引,每一个索引都是一棵树,使用主键索引效率最高,辅助索引是通过辅助索引查询到主键索引,之后查询数据,就按3层数据运算,也只需要6次磁盘读取就可以定位数据,也会远远大于全表遍历;联合索引是多个数据拼接的一个数据作为索引,所以在order by,group by多个字段的时候,索引顺序需要匹配,order by 默认使用asc,可以建立当存在desc和asc时可以使用虚拟列建立索引,例子:

ALTER TABLE `veh_signal_fault`

ADD COLUMN `fault_order_by` varchar(255) GENERATED ALWAYS AS (CONCAT(is_receive,LPAD(timestampdiff(second ,end_time,'9999-01-01 12:00:00'),16,'0'),fault_level)) VIRTUAL。

三、为什么索引不宜过多,联合索引需要慎用。

因为每一个索引都是一个B+Tree,每次删除和新增都可能破坏所有的索引树,修改可能会破坏索引树,破坏了索引树,系统就需要把索引树重新平衡,这样就需要消耗性能,当索引过多,那每次新增、删除和修改就需要消耗更多的性能,所以索引不宜过多,联合索引需要先拼接,而且树的高度超过了普通索引,性能消耗更大,所以联合索引需要慎用。

第四、选择建多个列的复合索引还是建多个单个列的索引。

索引提高性能的地方在于减少磁盘的读取次数,首先,B+TREE一般只会使用一个索引,当查询条件有多个的情况,可能命中率不会太高,导致查询到的数据块还是过多,需要查询磁盘的次数过高,即使能使用多个索引,查询磁盘的次数也会多余复合索引,所以当业务能满足复合索引的情况,就尽量建复合索引,具体情况具体分析。

第五、MYSQL的索引建立规则。

1、MYSQL一般使用一个索引。

2、 在WHERE条件后的查询信息尽量使用=(联合索引)。

3、范围查找会使得后面的匹配条件失效(联合索引),所以根据实际情况,最好把范围查找放在后面,where条件有多少个信息,它就会打散的遍历多少遍,所以需要根据实际情况调整位置。

4、当索引不生效时,可以使用强制索引(前提条件是强制索引真的能提高效率):SELECT * FROM TABLE FORCE INDEX(强制索引名称:INDEX_FLAG_PRO_BEGIN_HANDLE_COMAPNNY_BUS) WHERE 1=1;

参考:

http://blog.codinglabs.org/articles/theory-of-mysql-index.html

https://segmentfault.com/a/1190000004690721

Mysql索引和查询优化

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言