作者:陌北有棵树
数据库是一个只要从事后端开发,就永远离不开的技术,大部分企业选择的数据库都是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中,表是根据主键顺序存储的(索引组织表),
上图是page offset为3的数据构成,蓝框的部分是File Header和Page Header,红框的部分是Infimum Records,后面是Supremum Records。
通过Infimum Records可以找到主键为1的键值,就是绿框的部分,后面的值00 00 00 05就是数据页页号,再后面分别是主键为2和4对应的Pointer。
【三】聚集索引
InnoDB存储引擎中,表是索引组织表,所以准确来讲,聚集索引不是一种索引类型,而是一种InnoDB中数据的存储方式,在这种存储结构中,同时保存了索引和数据行。
简要聚集索引特点:
- 按照主键构建B+树
- 叶子节点就是数据页(存了该行记录的所有数据)
- 一张表只能有一个聚集索引
【四】索引的使用
- 不同应用中有区别:
- 因为涉及多表连接,所以建索引是有意义的
- 通常对时间字段建索引,因为需要按时间维度筛选数据
- OLTP:只需通过索引获取表中少部分数据,建索引才有意义
- OLAP:
联合索引
- 联合索引的第一列可以直接使用这棵B+树
- 对于“where col1=? order by col2 ” 的查询可以直接使用这棵B+树,因为已经对col2做了排序处理
- 本质是键值数量大于等于2的B+树
- 优点:
覆盖索引
- 直接从辅助索引查询,不需查询聚集索引
- 辅助索引:键+聚集索引键,通俗解释,会根据该索引列建一棵B+树,键是键索引的列,但值指向该条记录对应的聚集索引的页,
- 覆盖索引:查找该条记录时,不用从头遍历聚集索引这棵B+树,可以从辅助索引这棵B+树,直接跳到聚集索引那棵B+树的中间
- 由于辅助索引不会存储整行数据的信息,所以大小远远小于聚集索引,所以减少了大量的IO操作
一些老生常谈的索引使用建议,在日常使用时,要知其然并知其所以然,才能将索引用好
- 一般为where和join中用到的列建立索引
- B-Tree索引生效的场景:<,<=,=,>,>=,BETWEEN,IN,以及不以通配符开始的like
- 索引字段尽量是简单数据类型
- 尽量不要让索引值默认为NULL
- 遵守“最左前缀”原则
- 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));