如何创建高性能的索引
Posted 老程不秃
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何创建高性能的索引相关的知识,希望对你有一定的参考价值。
如何创建高性能的索引
EXPLAIN 类型分析
explain 指令可以帮助我们查看查询优化器 处理 执行计划 的一些细节信息
语法: explain + 执行计划
假如我们有这样的两张表(分类表和商品表),我们将结合explain 字段进行说明:
#分类
CREATE TABLE category (
id int primary key auto_increment,
name varchar(50) not null,
c_desc varchar(200),
create_time datetime not null default now(),
key name_index(`name`)
)ENGINE = INNODB;
#商品
CREATE TABLE product (
p_id int PRIMARY KEY auto_increment,
category_id int not null,
name VARCHAR(50) not null
)ENGINE = INNODB;
复制代码
字段说明:
id:选择标识符
通过这个字段,我们可以知道sql语句执行的顺序.
当id相同时,从上到下执行,id不同时,越大的越先执行
select_type:表示查询的类型
SIMPLE : 简单查询
PRIMARY : 子查询时外面的语句会被标记为 PRIMARY,主表
UNION : 使用UNION 连接表查询时处于后面的查询
DEPENDENT UNION : UNION中的第二个或后面的select语句,取决于外面的查询
UNION RESULT : UNION的结果,union语句中第二个查询语句开始以及后面所有select的结果集
SUBQUERY : 子查询中的第一个SELECT , 结果不依赖于外部查询
DEPENDENT SUBQUERY : 子查询中的第一个SELECT , 结果依赖外部查询
DERIVED : 派生表的SELECT, FROM子句的子查询
UNCACHEABLE SUBQUERY : 一个子查询的结果不能被缓存,必须重新评估外链接的第一行
table:输出结果集的表
这一行分析对应的是哪一张数据库表,如果有别名则会显示别名
partitions:匹配的分区 (5.5及之前的版本是没有的,需要使用explain partitions select ……来显示带有partitions 的列)
表示使用的哪个分区,如果没有对表进行显式分区是看不到的
type:使用的索引星级
从优到劣依次为: system > const > eq_ref > ref > range > index > all
索引优化一般需要达到最少range级别
possible_keys:表示查询时,可能使用的索引
表示此次查询可以用到的索引,如果没有相关的索引,此列是NULL
key:表示实际使用的索引
如果此次查询没有用到索引,此列是NULL
key_len:索引字段的长度
索引使用到的字节数,该列显示的为索引字段的最大可能长度,并非世纪使用的长度,在定义索引是计算出来的,并非实时计算出来.
长度计算公式:
varchr(10)变长字段且允许NULL = 10 * ( character set:uff8mb4=4, utf8=3,gbk=2,latin1=1)+1(NULL)+2(变长字段)
varchr(10)变长字段且不允许NULL = 10 *( character set: uff8mb4=4,utf8=3,gbk=2,latin1=1)+2(变长字段)
char(10)固定字段且允许NULL = 10 * ( character set:uff8mb4=4,utf8=3,gbk=2,latin1=1)+1(NULL)
char(10)固定字段且不允许NULL = 10 * ( character set:uff8mb4=4,utf8=3,gbk=2,latin1=1)
ref: 引用到上一个表的列
rows:扫描出的行数(估算的行数)
filtered:按表条件过滤的行百分比
Extra:额外的信息说明
这里挑比较常见的几个做下说明:
Using index: 使用到了覆盖索引
Using where:
1.查询的列未被索引覆盖,where子句筛选条件不满足前缀索引(索引失效)
2.查询的列未被索引覆盖,where子句筛选条件非索引列
Using index condition: 范围过滤索引
Using filesort: 使用到了磁盘排序,这是应该避免的
Using temporary : 使用到了临时表,这是应该避免的
索引是什么?
索引是帮助查询快速找到记录的一种高效的查询数据结构.
索引是针对查询优化最有效的手段.索引能够将查询性能提高好几个数量级.
索引是如何工作的?
在mysql中,首先存储引擎会根据匹配到的索引,在索引上找到对应的值,然后根据匹配上的索引记录找到对应的数据行.
这个过程就像,我们尝试在一本书上找到指定知识点的内容,会先翻到书的“目录”,然后根据指定“目录”找到对应的页码.
CREATE TABLE hero (
id int primary key auto_increment,
name varchar(50) not null,
hero_desc varchar(200),
key name_index(`name`)
)ENGINE = INNODB;
INSERT INTO hero VALUES(1,'张三','法外狂徒...');
INSERT INTO hero VALUES(2,'李四','...');
INSERT INTO hero VALUES(3,'王五','...');
INSERT INTO hero VALUES(4,'赵六','...');
INSERT INTO hero VALUES(5,'冯七','...');
INSERT INTO hero VALUES(6,'莫八','...');
INSERT INTO hero VALUES(7,'莫九','...');
复制代码
假如我们要查找“name”为“张三”的数据.会先根据name_index 索引 查找到name='张三' 的数据行的主键id,然后通过 id = 1查询主键索引 找到指定的数据行返回
select * from hero where name = '张三';
复制代码
索引的类型
索引的类型有很多,我们可以根据不同的场景选择不同的索引.mysql的索引是在存储引擎层使用的.不同的存储引擎实现索引的方式也不相同.每个存储引擎支持的索引也不相同,也不是所有的存储引擎都支持所有的索引类型.
我们常用的存储引擎一般是INNODB , 如果没有特殊说明,使用的索引默认为B-Tree 索引.
B-Tree 索引
B-Tree 使用 B+ 树数据结构来存储数据.大多数的mysql 存储引擎都支持这种索引.存储引擎以不同的方式使用B-Tree索引,性能也各有不同.例如MyISAM 引擎使用前缀压缩技术来使索引更小,而InnoDB 则按照原始数据格式进行存储.MyISAM索引通过数据存储的物理位置来引用索引列,而InnoDB则 根据主键来引用索引列.
B-Tree 通常意味着所有的值都是按顺序存储的,并且每一个叶子页到跟的距离相同.
建立在 B+ Tree数据结构上的索引(InnoDB引擎):
B-tree能够加快数据访问的速度,因为存储引擎不再需要进行全表扫描来找到指定的数据.它从树结构的根结点出发,跟节点种存放了指向子节点的指针,存储引擎根据这些指针向下层查找.通过比
较节点页的值和要查找的值可以找到合适的指针进入下一层子节点.这些指针实际上定义了子节点页中值的下限和上限.存储引擎要么最终找到要查找的值,要么该值不存在.
叶子节点不用于子节点,它的指针指向的是被索引的数据,可能是指向聚簇索引的指针或数据行.
可以使用B-Tree 索引 的查询类型:
B-Tree 适合用于全键值,键值范围或键前缀查找.其中键前缀查找只适用于键最左前缀的查找.
创建一张商品表.创建主键索引,单值索引,复合索引.
create table product(
id int primary key auto_increment,
name varchar(50) not null comment '商品名称',
category_id int not null comment '分类id',
price decimal not null comment '价格',
key product_name(`name`),
key category_and_name(`category_id`,`name`)
)engine = innodb;
复制代码
全值匹配:
全值匹配指的是和索引中所有列进行匹配.如:
select * from product where name = '笔记本';
复制代码
select * from product where category_id = 1 and name = '阿巴阿巴';
复制代码
匹配最左前缀:
最左前缀适用于复合索引(多列索引),如上面的category_and_name 索引.
最左前缀,故名思义,从左到右匹配索引.如 a ,b c 字段建立复合索引(a,b,c):
那么索引在什么时候能生效呢?
a ,
a b ,
a b c
select * from product where category_id = 1;
复制代码
匹配列前缀:
匹配某一索引列的值的开头的部分,like 'a%',like 'b%' . startWith
select * from product where name like '笔记%';
复制代码
匹配范围值:
对索引列的范围查找,如 between , > < ,≥,≤
select * from product where id < 5;
复制代码
select * from product where id between 1 and 5;
复制代码
精确匹配某一列并范围匹配另一列:
select * from product where name = '笔记本' and category_id > 1;
复制代码
只访问索引的查询:
即覆盖索引 , 不用再回表查询数据行 , 使用explain 分析时,extra 列 为 Using index;
select name from product where name = '笔记本' ;
复制代码
B-Tree 索引采用B+ Tree的数据存储结构,因此索引树中的节点是有序的,所以除了按值查找之外,索引还可以用户查询中的Order By 操作(按顺序查找); 一般来说,如果B-Tree 可以按照某种方式查找到值,那么也可以按照这个方式进行排序.
所以说Order by 子句如果满足上面几种查询类型,则这个索引也可以满足对应的排序需求.
上面说了几种可以使用索引的查询类型,遵循上述的查询规则可以合理的使用索引,提高查询效率.
哈希索引
哈希表是基于哈希表实现的 , 只有精准匹配索引所有列的查询才有效.
对与索引列的每一行数据,存储引擎都会为其计算出来一个hash码,哈希码是一个较小的值,不同键值的行计算出来的hash码也不一样.哈希索引将所有哈希码存储在索引中,同时在哈希表中保存一个每个数据行的指针.如果发生了hash碰撞就会以链表的形式存放在一个hash条目中.
在mysql中,只有Memory引擎显式支持哈希索引.在这里我们不过多的去解释他.
空间数据索引
MyISAM引擎支持空间索引,可以用作地理数据存储.
与B-Tree索引不同(主要与存储结构有关),它无需前缀索引.空间索引会从所有维度来索引数据.查询时,可以有效地使用任意维度来组合查询.必须使用MYSQL的GIS相关函数如 MBRCONTAINS()等来维护数据.MySQL·对GIS的支持并不完善,所以这个索引基本不会被使用.
开源关系库系统中对GIS的解决方案做得比较好的是PostgreSQL的 PostGIS;
全文索引
全文索引是一种特殊类型的索引,他查找的是文本中的关键词,而不是直接比较索引中的值.全文索引更类似于搜索引擎做的事情,而不是简单的where匹配,这里不去过多解释它.
使用索引有哪些好处?
最直接的,索引可以帮助我们从大量数据行中快速的找到我们需要的数据.像最常见的B-Tree索引,因其存储结构,可以帮助我们进行Order by 和 Group by操作.总结以下优点:
1.大大减少了服务器所需要扫描的数据行数,提高了查询的效率
2.避免了生成 temp table 以及file sorted
3.避免了随机I/O,将其转换为有序I/O
索引不是越多越好,数据量越大,建立,使用,维护索引的成本越大.当数据表体量较小时,直接全表扫描比查询索引更快(需要回表查询的时候).只有当索引帮助存储引擎快速查找到记录带来的好处大于其带来的额外工作时,索引才是最有效的.
如果创建高性能的索引?
遵循索引的特性,避免索引失效.
索引失效的情况:
使用 or (除非所有or字段都添加了索引)
不满足最佳左前缀原则.
like 查询时 以 % 开头.
匹配字符串时不加“”号,需要类型转换
使用范围查询后,后边的查询条件都不能使用索引匹配
查询条件中索引列使用了函数;
mysql优化器优化后,认为全表扫描更快时
前缀索引和索引选择性
有时候需要索引很长的字符串,这会让索引变的很大,并且越来越慢.
通常可以索引开始的部分字符,这样可以大大节约索引空间,从而提升索引效率.但这样也会降低索引的选择行.
索引的选择性是指,不重复的索引值(也称为索引基数) 和数据表的记录的总条目(#T)的比值. 范围从 1/#T 到 1 之间.
索引的选择性越高则查询效率越高,因为选择性高的索引可以让MySQL在查找时过滤掉更多的行(降低索引命中行数).
比如说主键索引,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的.
InnoDB引擎的前缀长度可以达到767字节,如果启用了innodb_large_prefix选项,则可以达到3072字节.
MyISAM引擎的前缀限制的1000字节.text,blob,或者很长的varchar类型的列必须使用前缀索引
前缀索引最好保证较高的选择性的同时也不能太长.
前缀索引的基数应该趋近于完成列的基数,
1.索引列基数 ≈ 完整列的基数 (索引列基数/完整列的基数≈1)
2.索引基数 / 索引总条目数≈ 完整列基数 / 完整列总条目数
前缀索引对覆盖索引的影响
使用前缀索引将无法利用覆盖索引的优化。
查询时,系统并不确定前缀索引的定义是否截断了完整信息。
复合索引(多列索引)
索引能够同时覆盖多个数据列,对于复合索引来说:
mysql从左到右的使用索引中的字段,一个查询可以只使用索引的一部分,但是只能从最左侧开始.
例如:
我们定义了复合索引 index(c1,c2,c3),则我们进行查找的时候可以 c1 , c2 ,c3 | c1 ,c2 | c1 这三种组合来查找,只能从最左边来开始,
如果使用c2 , c3 进行查找则索引会失效.当最左侧字段是常量引用时,索引就十分有效.
当我们需要频繁的用到某些字段并且我们能确定使用字段的顺序时,我们就可以创建复合索引;12
但如果我们并不确定要用到哪些字段时就只能单独的为这些字段添加索引,添加无用的复合索引会引起索引失效的同时给mysql更改添加删除带来压力.
复合索引对排序的优化:
我们要知道复合索引只会对与创建索引时的排序顺序完全相同或相反的 order by语句进行优化
索引列的顺序
索引的顺序是至关重要的,正确的顺序依赖于使用该索引的查询,并且同时需要考虑如何更好地满足排序和需要.
根据复合索引的最佳左前缀原则,意味着索引首先按照最左列进行排序,然后依次往后排列.
当不需要考虑排序和分组时,应该优先考虑把选择性高的索引列放在前面.
假如我们有这样一张章节表:
CREATE TABLE `chapter` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '章节名称',
`category_id` int(11) NOT NULL COMMENT '分类id',
`project_id` int(11) NOT NULL COMMENT '项目 id',
`subject_id` int(11) NOT NULL COMMENT '科目 id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
复制代码
按照业务需求,我们希望根据分类,项目,科目这些查询条件,查询出符合条件的章节,我们应该如何设计一个复合索引?
首先我们先尝试计算这些列的选择性.
得出选择性最高的列依次为subject_id,project_id,category_id,在不考虑分组和排序的情况下,索引应该建立为:
ALTER TABLE `chapter`
ADD INDEX `chapter_category`(`subject_id`, `project_id`, `category_id`)
复制代码
聚簇索引
聚簇索引
聚簇:表示数据行和相邻的键值紧凑地存储在一起。
聚簇索引 通过关键字 primary key 来声明,一个表只能有一个聚簇索引(覆盖索引可以模拟多个聚簇索引).
聚簇索引并不是一种索引类型,而是一种数据存储结构(B-Tree).
不同于Mysql中其他是 B-Tree类型索引,聚簇索引在叶子页中还保存了数据行,而其他B-Tree类型索引中叶子页存储的是主键id,也就是聚簇索引的key,当其查询结果不能满足当前查询时,会
通过“回表”操作,查询聚簇索引.
因为不是所有的存储引擎都支持聚簇索引,在这里我们主要关注InnoDB ,但原理针对于任何支持聚簇索引的存储引擎都是支持的.
下图展示了聚簇索引中的记录是如何存放的. 注意到,叶子页包含了行的全部数据,但是节点也只包含了索引列.
Innodb 的 主键索引被定义为聚簇索引, 如果一个表没有主动声明primary key ,InnoDB会选择一个唯一非空的索引代替,如果都没有,InnoDb会 隐式定义一个主键来作为聚簇索引.
InoDB只聚集同一个页面中的索引,相邻键值的页面可能会相距很远.
聚簇主键可能对性能有帮助,但也可能导致严重的性能问题.所以需要仔细地考虑聚簇索引,尤其是将表的存储引擎从InnoDb钙哼其他引擎的时候(反之亦然).
聚簇索引的优点:
可以把相关数据保存在一起
数据访问更快(聚集索引将索引和数据保存在同一个b-tree中)
使用覆盖索引扫描的查询可以直接使用页节点中的主键值
聚簇索引的缺点:
聚簇数据提高了IO性能,如果数据全部放在内存中,则访问的顺序就没那么重要了.
插入速度严重依赖插入顺序。按主键的顺序插入是速度最快的。但如果不是按照主键顺序加载数据,则需在加载完成后最好使用optimize table重新组织一下表.
更新聚簇索引列的代价很高。因为会强制InnoDB将每个被更新的行移动到新的位置.
基于聚簇索引的表在插入新行,或主键被更新导致需要移动行的时候,可能面临页分裂的问题。页分裂会导致表占用更多的磁盘空间.
聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏,或由于页分裂导致数据存储不连续的时.
非聚集索引比想象的更大,因为二级索引的叶子节点包含了引用行的主键列.
非聚集索引访问需要两次索引查找(非聚集索引中叶子节点保存的行指针指向的是行的主键值),对于innodb自适应哈希索引可以减少这样的重复工作.
InnoDB 和 MyISAM 的数据分布对比
聚簇索引与 非聚簇索引 的数据分布是有一定区别的,非聚簇索引的叶子结点存储的是聚簇索引的key,也就是主键id.
在使用非聚簇索引查询时,往往需要查询两边索引,先根据索引列查询到主键id,然后根据主键id从聚簇索引中查找到指定的数据行.
假如有这样一张表:
CREATE TABLE layout_test(
col1 int NOT NULL,
col2 int NOT NULL,
PRIMARY KEY(col1),
KEY(col2)
);
insert into layout_test values (99,8),(12,56),(3000,62),....(18,8),(4700,13),(3,93);
复制代码
MyISAM:
MyISAM 是不支持聚簇索引的,相对来说数据分布比较简单.
MyISAM的每个索引中叶子结点存储的都是指向数据行的地址值.我们用两张图大致来说明存储方式.
layou_test表的数据分布:
MyISAM 按照 数据插入的顺序存储在磁盘上.
col1 主键索引分布:
col2 普通索引分布:
这两个存储结构是一致的,除了节点存储的key 是根据索引列顺序排列的.
InnoDB:
InnoDB区分聚簇索引与非聚簇索引.
col1主键索引分布:
聚簇索引的每一个叶子节点都包含了主键值、事务ID、用于事务和MVVC的回滚指针以及所有剩余列
col2普通索引分布:
叶子结点存储的是聚簇索引的节点 key
不同点:
在InnoDB表中按主键顺序插入行的重要性
数据页:
每个页默认大小是16k(在mysql5.6之后可以通过 innodb_page_size进行选择8/16k).
每个页最多可以存储 16k/2 - 200 = 7992行记录(每个记录最少2条记录,每个页都需要预留200byte)
每次加载数据页从磁盘到内存都需要进行一次I/O读取.
每个页最少要存储2行记录(虚拟记录,用来限定记录的边界,最大虚拟记录-最小虚拟记录)
InnoDB 的数据页由以下 7 个部分组成:
- 文件头(File Header) 固定 38 个字节 (页的位置,上一页下一页位置,checksum , LSN)
- 数据页头( Page Header)固定 56 个字节 包含slot数目,可重用空间起始地址,第一个记录地址,记录数,最大事务ID等
- 虚拟的最大最小记录 (Infimum + Supremum Record)
- 用户记录 (User Records) 包含已经删除的记录以链表的形式构成可重用空间
- 待分配空间 (Free spaces) 未分配的空间
- 页目录 (Page Directory) slot 信息
- 文件尾 (File Trailer) 固定8个字节,用来保证页的完整性
非叶子节点存储的是key + 指针,假设key是 bigint类型 , 则 一个节点可以存储 16k/(8byte + 6byte)
如果表中数据量不大,并且后期增长稳定,不需要考虑分表,那么最好可以定义一个可以控制插入顺序的主键.最简单的方法是使用声明auto_increment 自增列.这样既可以保证数据行写入的顺序,对于根据主键做关联操作的性能也会更好.(外键关联并不推荐,建议业务上做关联)
最好避免随机的(不连续且值的分布范围非常大)聚簇索引,特别是对于I/O密集型的应用. 如,从性能的角度考虑,使用UUID来作为聚簇索引是非常糟糕的.它使得聚簇索引的插入变得完全随机,这是最坏的情况,使得数据没有任何聚集特性.
因为是顺序存储的,同页的上一条数据与下一条数据总是相邻的.当页背填充到最大阈值时,就会生成新的页来存储后面的记录.之后的数据就会按照顺序存储到新的页中.
这总能保证每个页都能被填满.
但是如果不是顺序存储的,就可能会出现需要频繁分裂页,数据的碎片化,需要更多的存储空间,随机的I/O读取,这往往需要mysql做更多的处理操作.
因为新插入的键值不一定比之前插入的键值大,索引InnoDB无法简单地总是把新行插入到索引的最后,而是需要为新的行寻找合适的位置,通常是已由数据的中间位置,并且为之分配空间.这会增加很多额外的工作,并导致数据分布不够优化.
缺点:
- 写入的目标页可能已经刷到磁盘中并从缓存中移除,或者是还没有被加载到内存中,InnoDB在插入之前不得不先找到并从磁盘读取目标页到内存中.这将导致大量的随机I/O
- 因为写入是无序的,InnoDB不得不频繁地做页分裂操作,以便为新的行分配空间.页分裂会导致移动大量数据,一次插入最少需要修改三个页而不是一个页
- 频繁的页分裂,会导致数据存储稀疏并且不规则,导致数据碎片化.
通过比较,很直观的可以看出保证索引键值插入顺序带来的好处.
覆盖索引
覆盖索引是优化查询的一种手段,避免二级索引进行回表查询,只使用二级索引树提供的信息就能满足当前查询.
在我们创建索引时,我们总是根据where 条件的需要创建合适的索引,这只用到了索引一方面的特性.在我们创建索引时应该考虑到整个查询,不单单是where条件的部分 , 还有 select 结果集部分.
如果索引能满足我们需要的结果集,就不需要再去读取数据行了. 当我们只是需要查询一本书指定目录的标题时,目录页就能满足我们的需求,我们也没有必要去翻到指定页再查看目录了.
如果一个索引包含所有需要查询的字段(select 后面的部分)的值,我们就称为“覆盖索引”.我们应该指明我们需要select 的字段,以便mysql能对查询进行进一步的优化.
假如有这样一张分类表:
CREATE TABLE category (
id int primary key auto_increment,
name varchar(50) not null,
c_desc varchar(200),
create_time datetime not null default now(),
key name_index(`name`)
)ENGINE = INNODB;
INSERT INTO category VALUES(1,'分类1','',default);
INSERT INTO category VALUES(2,'分类2','',default);
INSERT INTO category VALUES(3,'分类3','',default);
INSERT INTO category VALUES(4,'分类4','',default);
INSERT INTO category VALUES(5,'分类5','',default);
INSERT INTO category VALUES(6,'分类6','',default);
INSERT INTO category VALUES(7,'分类7','',default);
复制代码
有一个需求,希望出对应 分类 name的id
1.直接 select * 查询全部字段
EXPLAIN SELECT * FROM category WHERE name = '分类1';
复制代码
使用explain 指令分析, 可以看到 mysql使用了name_index索引进行查询,但Extra中没有任何额外信息,说明该查询进行了回表操作
(Mysql5.6之后加入了减少回表查询次数以及Mysql server层 和 存储引擎层交互的次数的优化技术ICP[index condiyion pushdown])
2.select id,name 查询指定字段
EXPLAIN SELECT id FROM category WHERE name = '分类1';
复制代码
使用explain 指令分析, 可以看到 mysql使用了name_index索引进行查询, Extra = Using index,说明使用到了覆盖索引.
覆盖索引能带来什么好处? 覆盖索引避免了回表查询,使查询效率大大提高.
-
不同于主键索引,二级索引上叶子节点存放的是索引列+主键id,如果只需要读取二级索引便能满足我们的查询,不必再回表查询,极大的减少了数据访问量.
要知道mysql是分页从磁盘上读取数据到缓存中的,磁盘的读取是比较耗时的.索引相对较小,更容易全部放入到内存中,减少了I/O消耗,对I/O密集型应用很有帮助.
-
因为索引是按照索引列值顺序存储的,所以对于I/O密集型的范围查询会比随机从磁盘读取每一行数据的I/O要少得多.(如果进行回表查询,是根据主键id去匹配的,id之间是不连续的,会造成大量的随机I/O)
当一个查询被覆盖索引优化式,我们可以通过EXPLAIN 指令 在Extra 列中看到 Using index.
需要注意的一些点
1.使用索引扫描来做排序
mysql有两种方式可以生成有序的结果:通过排序操作或者按索引顺序扫描,如果explain出来的type列的值为index,则说明mysql使用了索引扫描来做排序
复制代码
扫描索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录。但如果索引不能覆盖查询所需的全部列,那么就不得不每扫描一条索引记录就得回表查询一次对应的行
这基本都是随机IO,因此按索引顺序读取数据的速度通常要比顺序地全表扫描慢
mysql可以使用同一个索引即满足排序,又用于查找行,如果可能的话,设计索引时应该尽可能地同时满足这两种任务。
只有当索引的列顺序和order by子句的顺序完全一致,并且所有列的排序方式都一样时,mysql才能够使用索引来对结果进行排序(Mysql8.0之后增加了尾部索引的特性,支持desc从大到
小).如果查询需要关联多张表,则只有当orderby子句引的字段全部为第一张表时,才能使用索引做排序。order by子句和查找型查询的限制是一样的,需要满足索引的最左前缀的要求,
否则,mysql都需要执行顺序操作,而无法利用索引排序
2.避免冗余和重复的索引
MySQL允许在相同列上创建多个索引,并且需要单独维护重复的索引.如果在查询时能够利用到这些索引,那么查询优化器在评估成本时也需要逐个进行考虑,增加性能损耗.
同一个列可以创建顺序相同,索引字段相同 不同索引名称的索引.我们应该避免这样的操作.在发现后也应该立即移除.如
create index index_name1(name
);
create index index_name2(name
);
create unique index index_name3(name
);
冗余索引与重复索引有些不同,如 已经创建了索引(A,B),再创建了索引(A),这就属于冗余索引,因为(A,B)的前缀已经包含了A.
对于InnoDB来说主键列应包含在二级索引中了,所以这也是冗余的,站在性能方面,但这是不可避免的.
大多数情况下都不需要冗余索引,当一个索引不满足我们的查询时,我们首先想到的应该是如何扩展它.尽量不要创建新的索引,不合理索引的存在只会增大我们的空间占用和加剧性能的损耗.
但有些时候出于性能方面的考虑需要冗余索引,因为扩展已有的索引会导致其变得太大,从而影响其他查询使用该索引时的查询性能.
3.索引和锁
索引可以让查询锁定更少的行.如果查询公布访问那些不需要的行,那么就会锁定更少的行,从这两个方面来看这对性能都有好处.虽然InnnoDB行锁效率很高,内存使用也很小,但是锁定行的时候仍然会带来额外的锁开销;其次,锁定超过需要的行也会增加锁争用并减少并发性.
4.减少数据和索引的碎片
首先碎片化的原因,碎片化是如何产生的.大量的删除数据,修改索引列的值,不规则的随机插入都会造成数据和索引的碎片化
B-Tree索引可能会碎片化,这会降低查询的效率.碎片化的索引可能会以很差或者无序的方式存储在磁盘上.
根据设计,B-Tree需要随机磁盘访问才能定位到叶子页,所以随机访问是不可避免的.
如果叶子页在物理分布上是顺序且紧密的,那么查询的性能就会更好.
否则,对于范围查询,索引覆盖扫描等操作来说,速度可能会降低很多倍,对于索引覆盖扫描咋饿一点更加明显.
表的数据存储也可能碎片化.然而,数据存储的碎片化比索引更加复杂.有三种类型的数据碎片:
行碎片(Row fragmentation)
这种碎片指的是数据行被存储为多个地方的多个片段中.即使查询只从索引种访问一行记录,行碎片也会导致性能下降
行间碎片(Intra-row fragmentation)
行间碎片是指逻辑上顺序的页,或者在磁盘上不是顺序存储的.行间碎片对诸如全表扫描和聚簇索引扫描之类的操作有很大的影响,应为这些操作原本能够从磁盘上顺序存储的数据中获益.
剩余空间碎片(Free space fragmentation)
剩余空间碎片是指数据页中有大量的空余空间.这会导致服务器读取大量不需要的数据,从而造成浪费.
对于MyISAM表,这三类碎片化都可能发生.但InnoDB不会出现短小的行碎片.InnoDB会移动短小的行并重写到一个片段中.
可以通过执行 OPTIMIZE TABLE(运行过程中,会锁表) 或者导出再导入的方式重新整理数据.这对多数处处引擎都是有效的.
- 对于InnoDB的表,MySQL实现原理其实是在线重建了表及其索引,并重新收集了统计信息。
MySQL关于表碎片整理OPTIMIZE TABLE操作的官方建议:
1.MySQL官方建议不要经常(每小时或每天)进行碎片整理,一般根据实际情况,只需要每周或者每月整理一次即可,可以写成定时任务来做。
2.OPTIMIZE TABLE只对MyISAM,BDB和InnoDB表起作用,尤其是MyISAM表的作用最为明显。此外,并不是所有表都需要进行碎片整理,一般只需要对包含上述可变长度的文本数据类
型的表进行整理即可。
3.在OPTIMIZE TABLE运行过程中:
MyISAM会一直锁着表. InnoDB在Mysql 5.6.17之前,优化表不使用在线DDL。因此,当优化表运行时,不允许在表上并发DML (INSERT, UPDATE, DELETE)
4.默认情况下,直接对InnoDB引擎的数据表使用OPTIMIZE TABLE,可能会显示「 Table does not support optimize, doing recreate + analyze instead」的提示信息。这个时候,我们可以
用alter table T engine=InnoDB 来重建索引.
总结
在MySQL中, 大多数情况下都会B-Tree索引.如果在合适的场景中使用索引,将大大提高查询的响应时间,
在选择索引和使用这些索引进行查询时,有三个原则需要记住:
-
单行访问是很慢的,特别是机械硬盘存储中.
-
按顺序访问范围数据是很快的。
顺序的I/O不需要多次磁盘寻道,比随机I/O快,
假如服务器可以顺序读取,就无需额外的排序操作,而且 GROUP BY 也无需再次排序和将行按组进行聚合计算。
-
索引覆盖是很快的.假如索引包含 所需要查询的所有列,那么存储引擎就无需回表查找,避免大量的单行访问.
以上是关于如何创建高性能的索引的主要内容,如果未能解决你的问题,请参考以下文章