MySQL索引的数据结构

Posted 蚂蚁小哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL索引的数据结构相关的知识,希望对你有一定的参考价值。

一:索引概述

MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。
索引的本质:索引是数据结构。可以简单理解为“排好序的快速查找数据结构”,满足特定查找算法。这些数据结构以某种方式指向数据, 这样就可以在这些数据结构的基础上实现高级查找算法。

1:索引优缺点

说明:索引是存储引擎用于快速找到数据记录的一种数据结构,就好比一本教课书的目录部分,通过目录中找到对应文章的页码,便可快速定位到需
     要的文章。MySQL中也是一样的道理,进行数据查找时,首先查看查询条件是否命中某条索引,符合则通过索引查找相关数据,如果不符合
     则需要全表扫描,即需要一条一条地查找记录,直到找到与条件符合的记录。
优点:
    ①:提高数据检索的效率,降低`数据库的IO成本`,这是创建索引最主要的原因。
    ②:通过创建唯一索引,可以保证数据库表中每一行`数据的唯一性`。
    ③:可以`加速表和表之间的连接`。对于有依赖关系的子表和父表联合查询时,可以提高查询速度。
    ④:在使用分组和排序子句进行数据查询时,可以显著`减少查询中分组和排序的时间`,降低了CPU的消耗。
缺点:
    ①:创建索引和维护索引要`耗费时间`,并且随着数据量的增加,所耗费的时间也会增加。
    ②:索引需要占`磁盘空间`,除了数据表占数据空间之外,每一个索引还要占一定的物理空间`存储在磁盘上`,
       如果有大量的索引,索引文件就可能比数据文件更快达到最大文件尺寸。
    ③:虽然索引大大提高了查询速度,同时却会`降低更新表的速度`。
       当对表中的数据进行增加、删除和修改的时候,索引也要动态地维护,这样就降低了数据的维护速度。

二:InnoDB引擎中索引推演

  我们要知道在InnoDB中使用的默认索引是B+Tree,但是上来直接介绍则会显得枯燥,所以按照顺序慢慢推演到B+Tree

1:未使用索引的查询

如下精确查找语句例子(其中id为唯一值主键):
    假设表中数据:
        +--------+------------+--------+
        | sid    | sname      | sage   |
        +--------+------------+--------+
        |     34 | 张悌斯      |     22 |
        |     77 | 时党舒      |     23 |
        |     5  | 萧百徽      |     18 |
        |     91 | 葛星申      |     18 |
        |     22 | 池樵霆      |     23 |
        |     89 | 路党拓      |     22 |
        |     23 | 魏沁霭      |     23 |
        +--------+------------+--------+
    SELECT [列名列表] FROM 表名 WHERE 列名 = xxx;
Ⅰ:在一个页中查询:
    假设目前表中的记录比较少,所有的记录都可以被存放在一个页(后面说明)中,在查找记录的时候可以根据搜索条件的不同分为两种情况
    ①:以主键为搜索条件:
        可以在页目录中使用`二分法`快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。
        从图中可以看出把sid主键转换为了一个二叉树,这样我们假设查询sid为91的数据就会变得特别方便,只要遍历3次即可,然后
        根据对应主键找到对应数据行
    ②:以其它列作为搜索条件:
        因为在数据页中并没有对非主键建立所谓的页目录,所以我们无法通过二分法快速定位相应的槽。这种情况下只能从最小记录开始
        依次遍历单链表中的每条记录,然后对比每条记录是不是符合搜索条件。很显然,这种查找的效率是非常低的。可以说是全文检索
Ⅱ:在多个页中查询:
    大部分情况下我们表中存放的记录都是非常多的,需要好多的数据页来存储这些记录。在很多页中查找记录的话可以分为两个步骤:
    ①:定位到记录所在的页。
    ②:从所在的页内查找相应的记录。
    在没有索引的情况下,不论是根据主键列或者其它列的值进行查找,由于我们并不能快速的定位到记录所在的页,所以只能`从第一
    个页`沿着`双向链表`一直往下找,在每一个页中根据我们上面的查找方式去查找指定的记录。因为要遍历所有的数据页,所以这种方
    式显然是`超级耗时`的。
    如上图是一页记录,那么当有许多页则页需要依次都要想那样遍历,页与页直接是存在双向链表,可以在当前一页知道下一页在哪

2:使用索引方案

Ⅰ:创建基本表(为后面的演变说明)
    CREATE DATABASE IF NOT EXISTS demo_test;
    CREATE TABLE IF NOT EXISTS demo_test.index_demo(
        id INT PRIMARY KEY COMMENT \'主键ID\',
        c2 INT COMMENT \'测试字段2\',
        c3 CHAR(1) COMMENT \'测试字段3\'
    )ENGINE=InnoDB ROW_FORMAT = Compact;
    说明:index_demo表中的 id 字段为INT类型的主键,c2为INT类型的普通列,c3为CHAR类型的普通列;
    这个表使用 Compact 行格式来实际存储记录的。这里我们简化了index_demo表的行格式示意图:
    各个记录字段说明:
        ①:record_type:记录头信息的一项属性,表示记录的类型,0 表示普通记录、2 表示最小记录、3 表示最大记录、 1后面说
        ②:next_record:记录头信息的一项属性,表示下一条地址相对于本条记录的地址偏移量,我们用箭头来表明下一条记录是谁。
        ③:各个列的值:这里只记录在 index_demo 表中的三个列,分别是 id 、 c2 和 c3 。
        ④:其它信息:除了上述3种信息以外的所有信息,包括其他隐藏列的值以及记录的额外信息(具体InnoDB引擎结构说明)。
    把表中的记录放到页里示意图就是:
Ⅱ:一个简单的索引设计方案
    我们在根据某个搜索条件查找一些记录时为什么要遍历所有的数据页呢(生产环境上数据往往有特别多的页)?因为各个页中的记录并没有
    规律,我们并不知道我们的搜索条件匹配哪些页中的记录,所以不得不依次遍历所有的数据页。所以如果们 想快速的定位到需要查找的记
    录在哪些数据页中该咋办?我们可以为快速定位记录所在的数据页而建立一个目录 ,建这个目录必须完成下边这些事:
        ①:下一页的数据中,用户记录的主键值必须大于上一个页中用户记录的主键值。
        ②:给所以页建立一个目录项(查询指定数据时,防止遍历全部页)
            测试数据并按照①和②构建索引设计方案:
            INSERT INTO index_demo VALUES(1,4,\'u\'),(3,9,\'d\'),(4,4,\'a\'),(5,3,\'y\'),(8,7,\'a\'),(10,4,\'o\'),
            (12,7,\'d\'),(20,2,\'e\'),(200,9,\'x\'),(209,5,\'b\'),(220,6,\'i\'),(300,8,\'a\');
        以 页28 为例,它对应 目录项2 ,这个目录项中包含着该页的页号 28 以及该页中用户记录的最小主键值 5 。我们只需要把几个
        目录项在物理存储器上连续存储(比如:数组),就可以实现根据主键值快速查找某条记录的功能了。
        比如:查找主键值为 20 的记录,具体查找过程分两步:
            1. 先从目录项中根据 二分法 快速确定出主键值为 20 的记录在 目录项3 中(因为12<20<209),它对应的页是 页9
            2. 再根据前边说的在页中查找记录的方式去 页9 中定位具体的记录。
    至此,针对数据页做的简易目录就搞定了。这个目录有一个别名,称为索引。

3:InnoDB中索引方案之B+Tree

Ⅰ:迭代1次:目录项记录的页
    我们把上边索引设计方案使用到的目录项放到数据页中的样子就是这样:
    从图中可以看出来,我们新分配了一个编号为30的页来专门存储目录项记录。这里再次强调目录项记录和普通的用户记录的区别
    不同点:
        ①:目录项记录的record_type值是1,而普通用户记录的record_type值是0。
        ②:目录项记录只有主键值和页的编号两个列,而普通的用户记录的列是用户自己定义的,可能包含很多列,另外还有InnoDB自
            己添加的隐藏列。
        ③:了解:记录头信息里还有一个叫min_rec_mask的属性,只有在存储目录项记录的页中的主键值最小的目录项记录的
            min_rec_mask值为1 ,其他别的记录的 min_rec_mask 值都是 0   相同点:
        ①:两者用的是一样的数据页,都会为主键值生成Page Directory(页目录),从而在按照主键值进行查找时可以使用二分法
            来加快查询速度。
    现在以查找主键为 20 的记录为例,根据某个主键值去查找记录的步骤就可以大致拆分成下边两步:
        1. 先到存储目录项记录的页,也就是页30中通过二分法快速定位到对应目录项,
            因为 12 < 20 <209 ,所以定位到对应的记录所在的页就是页9。
        2. 再到存储用户记录的页9中根据 二分法 快速定位到主键值为 20 的用户记录。

Ⅱ:迭代2次,多个目录项记录的页
    从图中可以看出,我们插入了一条主键值为320的用户记录之后需要两个新的数据页:
        ①:为存储该用户记录而新生成了 页31 。
        ②:因为原先存储目录项记录的页30的容量已满(我们前边假设只能存储4条目录项记录),
            所以不得不需要一个新的页32来存放 页31 对应的目录项。
    现在因为存储目录项记录的页不止一个,所以我们想根据主键值查找一条用户记录大致需要3个步骤,以查找主键值为20的记录为例:
        ①:确定目录项记录页:
            我们现在的存储目录项记录的页有两个,即 页30 和 页32 ,又因为页30表示的目录项的主键值的范围是 [1, 320),
            页32表示的目录项的主键值不小于 320 ,所以主键值为 20 的记录对应的目录项记录在页30中。
        ②:通过目录项记录页确定用户记录真实所在的页。
            在一个存储目录项记录的页中通过主键值定位一条目录项记录的方式说过了。
        ③:在真实存储用户记录的页中定位到具体的记录。

Ⅲ:迭代3次,目录项记录页的目录页
    如图,我们生成了一个存储更高级目录项的页33,这个页中的两条记录分别代表页30和页32,如果用户记录的主键值在[1, 320)之间,
    则到页30中查找更详细的目录项记录,如果主键值不小于320的话,就到页32中查找更详细的目录项记录。
Ⅳ:B+Tree数据结构:

三:常见索引概念

  在InnoDB中索引按照物理实现方式,索引可以分为 2 种:聚簇(聚集)和非聚簇(非聚集)索引。我们也把非聚集索引称为二级索引或者辅助索引。

1:聚簇索引

特点:
    Ⅰ:使用记录主键值的大小进行记录和页的排序,这包括三个方面的含义:
        ①:页内的记录是按照主键的大小顺序排成一个单向链表。
        ②:各个存放用户记录的页也是根据页中用户记录的主键大小顺序排成一个双向链表。
        ③:存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个双向链表。
    Ⅱ:B+Tree的叶子节点存储的是完整的用户记录。
        所谓完整的用户记录,就是指这个记录中存储了所有列的值(包括隐藏列)。
优点:
    ①:数据访问更快,因为聚簇索引将索引和数据保存在同一个B+Tree中,因此从聚簇索引中获取数据比非聚簇索引更快
    ②:聚簇索引对于主键的排序查找和范围查找速度非常快
    ③:按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不用从多个数据块中提取数据,
        所以节省了大量的io操作 。
缺点:
    ①:插入速度严重依赖于插入顺序 ,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,
        我们一般都会定义一个自增的ID列为主键
    ②:更新主键的代价很高 ,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新
    ③:二级索引访问需要两次索引查找 ,第一次找到主键值,第二次根据主键值找到行数据

2:二级索引(辅助索引,非聚簇索引)

假设我们以这个c2列大小排序来生成一个B+Tree(非聚簇索引);它只能确定我们要查找记录的主键值所以如果我们想根据c2列的值查找到完整的
用户记录的话,仍然需要到 聚簇索引 中再查一遍,这个过程称为 回表 。也就是根据c2列的值查询一条完整的用户记录需要使用到 2 棵B+树!

小结:聚簇索引与非聚簇索引的区别:
  ①:聚簇索引的叶子节点存储的是我们的数据记录,非聚簇索引的叶子节点存储的是数据位置。非聚簇索引不会影响数据表的物理存储顺序
  ②:一个表只能有一个聚簇索引,因为只能有一种排序存储的方式,但可以有多个非聚簇索引,也就是多个索引目录提供数据检索。
  ③:使用聚簇索引的时候,数据的查询效率高,但如果对数据进行插入,删除,更新等操作,效率会比非聚簇索引低。

3:联合索引

我们也可以同时以多个列的大小作为排序规则,也就是同时为多个列建立索引,
    比方说我们想让B+Tree按照 c2和c3列 的大小进行排序,这个包含两层含义:
        ①:先把各个记录和页按照c2列进行排序。
        ②:在记录的c2列相同的情况下,采用c3列进行排序
    注意一点,以c2和c3列的大小为排序规则建立的B+树称为 联合索引 ,本质上也是一个二级索引。它的意思与分别为c2和c3列分别建立索引
    的表述是不同的,不同点如下:
        ①:建立 联合索引 只会建立如上图一样的1棵B+树。
        ②:为c2和c3列分别建立索引会分别以c2和c3列的大小为排序规则建立2棵B+树。

四:MyISAM中的索引方案

  我们知道在MyISAM里面创建一个表会出现3个对应的文件,一个是.frm文件存储表结构,一个是.MYD文件(D代表Data),是MyISAM的数据文件,存放数据记录,一个是.MYI文件(I代表Index),是MyISAM的索引文件,存放索引;所以说在MyISAM里面,结构、索引、数据是三个独立的文件;

如果我们在Col2上建立一个二级索引,则此索引的结构如下图所示:

 

 

 

五:MyISAM和InnoDB的区别

MyISAM的索引方式都是“非聚簇”的,与InnoDB包含1个聚簇索引是不同的。小结两种引擎中索引的区别:
    ①:在InnoDB存储引擎中,我们只需要根据主键值对聚簇索引进行一次查找就能找到对应的记录,而在MyISAM中却需要进行一次回表操作,
        意味着MyISAM中建立的索引相当于全部都是二级索引。
    ②:InnoDB的数据文件本身就是索引文件,而MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。
    ③:InnoDB的非聚簇索引data域存储相应记录主键的值,而MyISAM索引记录的是地址。换句话说,InnoDB的所有非聚簇索引都引用主键
        作为data域。
    ④:MyISAM回表操作十分快速,因为是拿着地址偏移量直接到文件中取数据的,反观InnoDB是通过获取主键之后再去聚簇索引里找记录,
        虽然说也不慢,但还是比不上直接用地址去访问。
    ⑤:InnoDB要求表必须有主键(MyISAM可以没有)。如果没有显式指定,则MySQL系统会自动选择一个可以非空且唯一标识数据记录的列
    作为主键。如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整型。

.

mysql数据库怎么创建索引

普通索引 添加INDEX

ALTER TABLE `table_name` ADD INDEX index_name ( `column` )

下面演示下给user表的name字段添加一个索引
mysql数据库如何创建索引
mysql数据库如何创建索引

主键索引 添加PRIMARY KEY

ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` )
mysql数据库如何创建索引
mysql数据库如何创建索引

唯一索引 添加UNIQUE

ALTER TABLE `table_name` ADD UNIQUE ( `column` )
mysql数据库如何创建索引

全文索引 添加FULLTEXT

ALTER TABLE `table_name` ADD FULLTEXT ( `column`)
mysql数据库如何创建索引

如何添加多列索引

ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` )
mysql数据库如何创建索引
参考技术A

在满足语句需求的情况下,尽量少的访问资源是数据库设计的重要原则,这和执行的 SQL 有直接的关系,索引问题又是 SQL 问题中出现频率最高的,常见的索引问题包括:无索引(失效)、隐式转换。1. SQL 执行流程看一个问题,在下面这个表 T 中,如果我要执行 需要执行几次树的搜索操作,会扫描多少行?

这分别是 ID 字段索引树、k 字段索引树。

这条 SQL 语句的执行流程:

1. 在 k 索引树上找到 k=3,获得 ID=3002. 回表到 ID 索引树查找 ID=300 的记录,对应 R33. 在 k 索引树找到下一个值 k=5,ID=5004. 再回到 ID 索引树找到对应 ID=500 的 R4

5. 在 k 索引树去下一个值 k=6,不符合条件,循环结束

这个过程读取了 k 索引树的三条记录,回表了两次。因为查询结果所需要的数据只在主键索引上有,所以必须得回表。所以,我们该如何通过优化索引,来避免回表呢?2. 常见索引优化2.1 覆盖索引覆盖索引,换言之就是索引要覆盖我们的查询请求,无需回表。

如果执行的语句是 ,这样的话因为 ID 的值在 k 索引树上,就不需要回表了。

覆盖索引可以减少树的搜索次数,显著提升查询性能,是常用的性能优化手段。

但是,维护索引是有代价的,所以在建立冗余索引来支持覆盖索引时要权衡利弊。

2.2 最左前缀原则

B+ 树的数据项是复合的数据结构,比如 的时候,B+ 树是按照从左到右的顺序来建立搜索树的,当 这样的数据来检索的时候,B+ 树会优先比较 name 来确定下一步的检索方向,如果 name 相同再依次比较 sex 和 age,最后得到检索的数据。

可以清楚的看到,A1 使用 tl 索引,A2 进行了全表扫描,虽然 A2 的两个条件都在 tl 索引中出现,但是没有使用到 name 列,不符合最左前缀原则,无法使用索引。所以在建立联合索引的时候,如何安排索引内的字段排序是关键。评估标准是索引的复用能力,因为支持最左前缀,所以当建立(a,b)这个联合索引之后,就不需要给 a 单独建立索引。原则上,如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的。上面这个例子中,如果查询条件里只有 b,就是没法利用(a,b)这个联合索引的,这时候就不得不维护另一个索引,也就是说要同时维护(a,b)、(b)两个索引。这样的话,就需要考虑空间占用了,比如,name 和 age 的联合索引,name 字段比 age 字段占用空间大,所以创建(name,age)联合索引和(age)索引占用空间是要小于(age,name)、(name)索引的。

2.3 索引下推

以人员表的联合索引(name, age)为例。如果现在有一个需求:检索出表中“名字第一个字是张,而且年龄是26岁的所有男性”。那么,SQL 语句是这么写的

    通过最左前缀索引规则,会找到 ID1,然后需要判断其他条件是否满足在 MySQL 5.6 之前,只能从 ID1 开始一个个回表。到主键索引上找出数据行,再对比字段值。而 MySQL 5.6 引入的索引下推优化(index condition pushdown),可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。这样,减少了回表次数和之后再次过滤的工作量,明显提高检索速度。

    2.4 隐式类型转化

    隐式类型转化主要原因是,表结构中指定的数据类型与传入的数据类型不同,导致索引无法使用。所以有两种方案:

    修改表结构,修改字段数据类型。

    修改应用,将应用中传入的字符类型改为与表结构相同类型。

    3. 为什么会选错索引3.1 优化器选择索引是优化器的工作,其目的是找到一个最优的执行方案,用最小的代价去执行语句。在数据库中,扫描行数是影响执行代价的因素之一。扫描的行数越少,意味着访问磁盘数据的次数越少,消耗的 CPU 资源越少。当然,扫描行数并不是唯一的判断标准,优化器还会结合是否使用临时表、是否排序等因素进行综合判断。

    3.2 扫描行数

    MySQL 在真正开始执行语句之前,并不能精确的知道满足这个条件的记录有多少条,只能通过索引的区分度来判断。显然,一个索引上不同的值越多,索引的区分度就越好,而一个索引上不同值的个数我们称为“基数”,也就是说,这个基数越大,索引的区分度越好。

    MySQL 使用采样统计方法来估算基数:采样统计的时候,InnoDB 默认会选择 N 个数据页,统计这些页面上的不同值,得到一个平均值,然后乘以这个索引的页面数,就得到了这个索引的基数。而数据表是会持续更新的,索引统计信息也不会固定不变。所以,当变更的数据行数超过 1/M 的时候,会自动触发重新做一次索引统计。

    在 MySQL 中,有两种存储索引统计的方式,可以通过设置参数 innodb_stats_persistent 的值来选择:

    on 表示统计信息会持久化存储。默认 N = 20,M = 10。

    off 表示统计信息只存储在内存中。默认 N = 8,M = 16。

    由于是采样统计,所以不管 N 是 20 还是 8,这个基数都很容易不准确。所以,冤有头债有主,MySQL 选错索引,还得归咎到没能准确地判断出扫描行数。

    可以用 来重新统计索引信息,进行修正。

    3.3 索引选择异常和处理1. 采用 force index 强行选择一个索引。2. 可以考虑修改语句,引导 MySQL 使用我们期望的索引。3. 有些场景下,可以新建一个更合适的索引,来提供给优化器做选择,或删掉误用的索引。

    请点击输入图片描述

以上是关于MySQL索引的数据结构的主要内容,如果未能解决你的问题,请参考以下文章

MySQL索引和SQL调优

MySQL索引

mysql数据库怎么创建索引

Mysql 专题:深入了解Mysql索引数据结构

数据库-mysql索引

MySQL索引