【MySQL】深入分析MySQL索引机制的实现

数据库是一个只要从事后端开发,就永远离不开的技术,大部分企业选择的数据库都是MySQL,所以需要我们对MySQL有着足够的了解。

而MySQL索引,我们都知道提高性能要加索引,也知道索引的结构是B-Tree,也都可以说出几条加索引的原则,但再深入一点,往往就会词穷,这可能就是知其然而不知其所以然的结果了。这会让我们在实际的开发中,涉及到究竟要给哪个字段加索引,就“拄杖落手心茫然”了。

于是我就问了自己这样几个问题,索引究竟是什么呢?索引是存在哪里的呢?MySQL是怎样通过索引,就优化了性能呢?

本文的研究对象是InnoDB存储引擎,其他存储引擎并没有涉及。

首先想要了解索引究竟存在哪里,如何发挥作用,就不能只研究索引,要结合整个数据库的存储结构,运行流程,来分析索引是如何优化查询过程的。

【一】MySQL的逻辑存储结构

在这里插入图片描述

  • 表空间(tablespace):可以默认为存储引擎逻辑结构的最高层,存放所有的数据。
  • 段(segment):包括数据段、索引段、回滚段,对于段的管理,由存储引擎自身完成。
  • 区(extent):1MB,一个区64个页,InnoDB一次从磁盘申请4~5个区,一个区中有连续的64个页。
  • 页(page)/块(block):每个页固定大小16KB。
  • 行(row):有Compact和Redundant两种行存储格式。

关于一个页的具体组成,可以从下图中有一个更直观的认识:

在这里插入图片描述

各个数据页之间通过双向链表连接,每个页内各条行记录用单链表连接。

如果没有任何索引优化,在查询数据时只能先遍历双向链表找到对应的页,加了索引,就可以通过索引的B+树结构定位对应的页。

【二】实验MySQL的数据存储

为了有更清晰的认识,我们在数据库中做一个实验,

在数据库创建member表,结构如下

DROP TABLE IF EXISTS `member`;
CREATE TABLE `member` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `sex` smallint(2) DEFAULT NULL,
  `desc` text,
  PRIMARY KEY (`id`),
	UNIQUE KEY(`id`,`NAME`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建表后,member.ibd大小是112KB,可见有7个页,然后用《MySQL技术内幕-InnoDB存储引擎》中提到的python小工具,查看表空间中各个页的信息,其中有offset为3和4的两个数据页,此时还没有索引节点。

在这里插入图片描述

中间出现的小插曲,导入下面的数据,发现页信息是这样的,:

INSERT INTO `member` SELECT '1', '李白', '1', REPEAT('且放白鹿青崖间',500);
INSERT INTO `member` SELECT '2', '苏轼', '1', REPEAT('暮云收尽溢清寒',500);
INSERT INTO `member` SELECT '3', '白居易', '1',REPEAT('我寄人间雪满头',500);
INSERT INTO `member` SELECT '4', '姜夔', '1',REPEAT('淮南皓月冷千山',500);

在这里插入图片描述

出现这种情况是因为,当行记录的长度超过行记录最大长度时,变长列(variable-length column)会选择外部溢出页(overflow page,一般是Uncompressed BLOB Page)进行存储,于是调整desc列的长度:

INSERT INTO `member` SELECT '1', '李白', '1', REPEAT('且放白鹿青崖间',300);
INSERT INTO `member` SELECT '2', '苏轼', '1', REPEAT('暮云收尽溢清寒',300);
INSERT INTO `member` SELECT '3', '白居易', '1',REPEAT('我寄人间雪满头'300);
INSERT INTO `member` SELECT '4', '姜夔', '1',REPEAT('淮南皓月冷千山',300);

在这里插入图片描述

我们知道,InnoDB中,表是根据主键顺序存储的(索引组织表),

[外链图片转存失败(img-b2C4SZmn-1564622074079)(D:\Code\MarkdownPhotosRepository\Res\ArticlePic\索引节点结构分析.png)]

上图是page offset为3的数据构成,蓝框的部分是File Header和Page Header,红框的部分是Infimum Records,后面是Supremum Records。

通过Infimum Records可以找到主键为1的键值,就是绿框的部分,后面的值00 00 00 05就是数据页页号,再后面分别是主键为2和4对应的Pointer。

[外链图片转存失败(img-WggFASLO-1564622074079)(D:\Code\MarkdownPhotosRepository\Res\ArticlePic\索引结构.png)]

【三】聚集索引

InnoDB存储引擎中,表是索引组织表,所以准确来讲,聚集索引不是一种索引类型,而是一种InnoDB中数据的存储方式,在这种存储结构中,同时保存了索引和数据行。

简要聚集索引特点:

  1. 按照主键构建B+树
  2. 叶子节点就是数据页(存了该行记录的所有数据)
  3. 一张表只能有一个聚集索引

【四】索引的使用

  • 不同应用中有区别:
    1. OLTP:只需通过索引获取表中少部分数据,建索引才有意义
    2. OLAP:
      1. 因为涉及多表连接,所以建索引是有意义的
      2. 通常对时间字段建索引,因为需要按时间维度筛选数据
  • 联合索引
    1. 本质是键值数量大于等于2的B+树
    2. 优点:
      1. 联合索引的第一列可以直接使用这棵B+树
      2. 对于“where col1=? order by col2 ” 的查询可以直接使用这棵B+树,因为已经对col2做了排序处理
  • 覆盖索引
    1. 直接从辅助索引查询,不需查询聚集索引
    2. 辅助索引:键+聚集索引键,通俗解释,会根据该索引列建一棵B+树,键是键索引的列,但值指向该条记录对应的聚集索引的页,
    3. 覆盖索引:查找该条记录时,不用从头遍历聚集索引这棵B+树,可以从辅助索引这棵B+树,直接跳到聚集索引那棵B+树的中间
    4. 由于辅助索引不会存储整行数据的信息,所以大小远远小于聚集索引,所以减少了大量的IO操作
  • 一些老生常谈的索引使用建议,在日常使用时,要知其然并知其所以然,才能将索引用好
    1. 一般为where和join中用到的列建立索引
    2. B-Tree索引生效的场景:<,<=,=,>,>=,BETWEEN,IN,以及不以通配符开始的like
    3. 索引字段尽量是简单数据类型
    4. 尽量不要让索引值默认为NULL
    5. 遵守“最左前缀”原则
    6. order by也要遵守“最左前缀”原则

【五】几种类型索引的常用操作:

  • 普通索引

    # 创建索引
    CREATE INDEX idx_name ON member(name)
    # 修改表结构
    ALTER TABLE member ADD INDEX idx_name (name)
    #创建表直接指定
    CREATE TABLE mytable( 
        id INT NOT NULL, 
        username VARCHAR(16) NOT NULL, 
        INDEX idx_name (username(2)) )
    # 删除索引
    DROP INDEX idx_name ON member;
    
  • 唯一索引:唯一索引与普通索引的不同点在于,普通索引列允许重复值,唯一索引列值必须唯一,但允许NULL值。

    # 创建索引
    CREATE UNIQUE INDEX idx_name ON member(name)
    # 修改表结构
    ALTER TABLE member ADD UNIQUE INDEX idx_name (name)
    #创建表直接指定
    CREATE TABLE mytable( 
        id INT NOT NULL, 
        username VARCHAR(16) NOT NULL, 
        UNIQUE INDEX idx_name (username(2)) )
    
  • 主键索引:是特殊的唯一索引,不允许NULL值,一个表中只有一个。

    # 一般在建表时指定
    CREATE TABLE mytable( 
        id INT NOT NULL, 
        username VARCHAR(16) NOT NULL, 
    	PRIMARY KEY(id))
    
  • 组合索引

    ALTER TABLE member ADD INDEX idx_name_sex_desc (name,sex,`desc`(10));
    

参考:

《MySQL技术内幕-InnoDB存储引擎》

《高性能MySQL》

发布了23 篇原创文章 · 获赞 3 · 访问量 1860

猜你喜欢

转载自blog.csdn.net/HoyingHan/article/details/98028649
今日推荐