MYSQL系列——索引知识点归纳

MYSQL系列——索引知识点归纳

内容整理范围来源较广,若涉及原作者引用,立即更新。

文章目录

  • MYSQL系列——索引知识点归纳
  • 一、索引使用及分类
    • MYSQL索引分类
      • 索引创建:
        • 普通(二级)索引:
        • 唯一索引:
        • 主键索引:
        • 前缀索引:
        • 后缀索引(suffix index)(一般不用)
        • 全文索引:
        • 组合索引:
        • 降序索引的应用
        • 自适应哈希索引
          • 开启自适应hash索引
          • 触发器维护hash值:
          • 哈希索引使用情况查看
            • 插入缓冲和自适应哈希索引
          • 哈希索引弊端
    • 索引原理
      • B+树
        • B+树的特征:
        • B+树的优势:
        • B+树分裂原理(基于mysql8版本)
        • 启发式优化规则(MYSQL8版本)
      • 聚族索引
        • 聚族索引的目的
        • 聚族索引的弊端:
        • 聚族索引原理
        • 辅助索引
      • LSM-TREE概述
    • 索引设计
      • 建立的索引是否适合
        • 三星索引构建:
      • 相关知识点
        • 数据扫描方式:
        • IO逻辑
  • 二、MYSQL索引问题排查
    • 相关参数
      • 检查是否禁用索引扩展提高性能
      • Handler类参数调用
    • 索引失效场景汇总
    • 注意事项:
    • 索引统计
    • 二级索引快速建立
      • Innodb快速在线索引创建功能
      • 索引并行相关参数
    • 索引-----锁关系
    • 索引使用情况查询工具推荐-----pt-index-usage
    • 冗余索引查询工具-----pt-duplicate-key-checker

一、索引使用及分类

MYSQL索引分类

索引创建:

普通(二级)索引:

create index index_name on table_name(column_list);
alter table table_name add index(column_list);

唯一索引:

#允许为空,防止数据重复
create unique index index_name on table_name(column_list);
alter table table_name add constraint constraint_name unique key(name1,name2);

主键索引:

alter table table_name add PRIMARY key(‘column’)

前缀索引:

alter table city_demo add key (city(7));
#前缀索引不支持order by 、group by ;也无法做到覆盖扫描;
#区分度检测(前缀长度经过测试为7为最佳;如果再增加前缀长度,选择性提升幅度就很小了)
select count(distinct right(shi_Guid,6))/count(*) from ZhuanJia_Info;

后缀索引(suffix index)(一般不用)

例如查找某个域名的所有电子邮件地址;MYSQL原生不支持反向索引,但可以把字符串反转后存储,基于此建立前缀索引通过触发器来维护这种索引

全文索引:

create table test(
id int auto_increment not null primary key,
title varchar(200),
body TEXT,
fulltext(title,body)
)type=myisam;

CREATE TABLE article (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
title VARCHAR (200),
body TEXT,
FULLTEXT (title, body) WITH PARSER ngram ## 全文索引
) ENGINE = INNODB DEFAULT CHARSET=utf8mb4 COMMENT=‘文章表’;
#创建
alter table test add FULLTEXT index idx_name (name)
ALTER TABLE article ADD FULLTEXT INDEX title_body_index (title,body) WITH PARSER ngram;
#自然语言搜索模式 (默认)
SELECT * FROM article WHERE MATCH (title,body) AGAINST (‘精神’ IN NATURAL LANGUAGE MODE);
SELECT * FROM article WHERE MATCH (title,body) AGAINST (‘精神’);

#索引不支持查询字符串:
SELECT * FROM information_schema.INNODB_FT_DEFAULT_STOPWORD;

组合索引:

alter table a add index idx_sellname_gmt_sellid(gmt_create,seller_name,seller_id);
#最左前缀原则:
1、where 条件调用组合索引最左边的字段才会触发组合索引,当遇到范围查询(>、<、between、like)停止匹配,后面字段不会使用索引;
2、字段顺序无影响,MYSQL内部会自动优化;
3、基于B+树特点,所有叶节点存储在同一层,组合索引可以最大发挥B+树顺序查找优势;
4、ORDER BY列跟在匹配列(它们都使用等值条件)的后面,从而规避了排序;则该查询只需访问索引而无须回表;
例如:
对(a,b,c,d)建立索引,查询条件为a = 1 and b = 2 and c > 3 and d = 4,那么a、b和c三个字段能用到索引,而d无法使用索引。因为遇到了范围查询。
#分页优化(深度分页问题)

update tb_user_info set user_img=replace(user_img,'http://','https://') limit 1,1000;

mysql的limit游标进行的范围查找原理,是下沉到B+数的叶子节点进行的向后遍历查找,在limit数据比较小的情况下还好,limit数据量比较大的情况下,效率很低接近于全表扫描。
如果对于有where 条件,又想走索引用limit的,必须设计一个索引,将where 放第一位,limit用到的主键放第2位,而且只能select 主键!

SELECT * FROM table WHERE ID>=( SELECT ID FROM table ORDER BY ID LIMIT 90000,1) limit 100;

降序索引的应用

对单列进行排序,5.7和8版本没区别;MySQL8.0开始真正支持降序索引,只有InnoDB引擎支持降序索引,且必须是BTREE降序索引,MySQL8.0不再对group by操作进行隐式排序。

当查询需要对多列排序,顺序要求不一致时,就可以使用降序索引来避免filesort了;

索引定义中的DESC不再被忽略,而是按降序存储键值。以前,可以以相反的顺序扫描索引,但是会导致性能损失。下行索引可以按前向顺序扫描,效率更高。当最有效的扫描顺序混合了某些列的升序和其他列的降序时,降序索引也使优化器能够使用多列索引。

降序索引利用的是MYSQL的反向扫描;
在8版本的explain中,extr新增了 Backward index scan 专门描述反向扫描;

自适应哈希索引

MySQL并没有显式支持Hash索引,而是作为内部的一种优化。具体在Innodb存储引擎里,会监控对表上二级索引的查找,如果发现某二级索引被频繁访问,二级索引成为热数据,就为之建立hash索引。因此,在MySQL的Innodb里,对于热点的数据会自动生成Hash索引。
一般情况下查找时间复杂度为O(1),仅需一次查找就能定位数据;
#自适应哈希索引是通过缓冲池的B+树页构造的;建的很快,而且不需要对整张表构建哈希索引;innodb会自动根据访问的频率和模式来自动为某些热点页建立哈希索引
据官方解释:启动AHI后,读取和写入速度可提高2倍,辅助索引连接操作性能提高5倍;无需人工对数据库调整;
#仅使用=或<=>非常快;
#不能适用于order by;
#只能使用整个键来搜索行;

#MEMORY引擎默认使用哈希索引,将键的哈希值和指向数据行的指针保存在哈希索引中。
优点:访问速度较快。
缺点:
哈希索引数据不是按照索引值顺序存储,无法用于排序。
不支持部分索引匹配查找,因为哈希索引是使用索引列的全部内容来计算哈希值的。
只支持等值比较,不支持范围查询。
当出现哈希冲突时,存储引擎需要遍历链表中所有的行指针,逐行进行比较,直到找到符合条件的行。

开启自适应hash索引

在等值查询确实会提升效率50%以上;
#前提条件
AHI的要求是对这个页的连续访问模式必须一样;
例如:对于(a,b)这样的联合索引页,其访问模式可以是:
where a=xxx
where a=xxx and b=xxx
1)访问模式一样是查询条件一样,如果交替进行,则Innodb不会对该页构造AHI;
2)按该模式访问了100次
3)页通过该模式访问了N次,其中N=页中记录*1/16;

内部判断索引值使用非常频繁,会基于BTree上创建一个哈希索引,这种哈希查找是内部行为,如果有必要可以关闭该功能
set global innodb_adaptive_hash_index=0;

哈希索引可以提高查询性能,但是,在高并发情况下,会造成 RW-latch争用,进而堵塞进程。可以使用命令“show engine innodb status\G;”来监控SEMAPHORES
由于自适应哈希索引造成大量的锁争用, 进而堵塞很多进程,最终导致MySQL崩溃重启。
InnoDB: Warning: a long semaphore wait --Thread 140570431108864 has waited at btr0cur.c line 528 for 241.00 secondsthe semaphore: X-lock on RW-latch at 0x7fd9142bfcc8 created in file dict0dict.c line 1838

触发器维护hash值:

DELIMITER //
create trigger pseudohash_crc_ins BEFORE INSERT ONpseudohash FOR EACH ROW BEGIN SET NEW.url_crc=crc32(NEW.url);
END;
//
create trigger pseudohash_crc_upd before update onpseudohash for each row begin set new.url_crc=crc32(NEW.url);
END;
//

DELIMITER ;

insert into pseudohash (url) values(‘http://www.mysql.com’);
select * from pseudohash;
update pseudohash set url='http://www.mysql.com/111’where id=1;
select * from pseudohash;

#不要使用SHA1()和MD5()作为哈希函数;因为值非常长,虽然是强加密函数,但涉及目标是最大限度消除冲突;
如果数据量非常大,crc32()会出现大量哈希冲突,
建议用MD5()函数返回值得一部分作为自定义哈希函数;可能比自己写一个哈希算法性能要差;
#哈希查询必须where条件带哈希值和对应列值;
因为生日悖论,出现哈希冲突概率增长速度可能比想象的快的多;crc32()返回的是32位整数,当索引93000条时,出现冲突概率为1%;冲突会返回多条记录;
#错误写法
select word,crc from words where crc = CRC32(‘gnu’);
#正确:
select word,crc from words where crc = CRC32(‘gnu’)and word =‘gnu’;

哈希索引使用情况查看

可通过该show engine innodb status
#1640.60 hash searches/s, 3709.46 non-hash searches/s
#AHI大小、使用情况、每秒使用AHI搜索;

插入缓冲和自适应哈希索引
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 0 merges
#Ibuf:表示缓冲索引树的当前大小,free list len:空闲列表长度
seg size:文件段中已分配段的个数,merges:合并页的个数;
merged operations:
 insert 0, delete mark 0, delete 0
#insert插入缓冲的次数,标记删除的次数,delete表示purge的次数
discarded operations:
 insert 0, delete mark 0, delete 0
#多少个insert buffer被丢弃,多少个insert buffer被标记已删除,purge多少个insert buffer等;
Hash table size 73751, node heap has 0 buffer(s)
#自适应哈希索引的单元格数量&预留缓冲结构的数量
Hash table size 73751, node heap has 0 buffer(s)
| innodb_adaptive_hash_index       | OFF   |
Hash table size 73751, node heap has 0 buffer(s)
Hash table size 73751, node heap has 0 buffer(s)
0.00 hash searches/s, 2.67 non-hash searches/s
#使用&不能使用哈希索引向下搜索B+树索引的次数
哈希索引弊端

哈希索引可以提高查询性能,但是,在高并发 情况下,会造成 RW-latch争用,进而堵塞进程。可以使用命令“show engine innodb status\G;”来监控SEMAPHORES
由于自适应哈希索引造成大量的锁争用, 进而堵塞很多进程,最终导致MySQL崩溃重启。
InnoDB: Warning: a long semaphore wait
–Thread 140570431108864 has waited at btr0cur.c line 528 for 241.00 seconds the semaphore:
X-lock on RW-latch at 0x7fd9142bfcc8 created in file dict0dict.c line 1838

索引原理

MYSQL使用的是索引组织表,索引结构为B+树结构;

索引底层存储逻辑可以利用回表的概念理解为一种表,索引列值对应主键顺序存储;那么在RC、RR下还受MVCC控制;
准确来说:key
从最底层来讲,MySQL的索引通俗来说就是一个特殊的表。
例如:
如果有个表:name,age字段,primary key 是name;
name age
lisi 24
zhangsan 18
那么,CREATE INDEX idxage ON a (age),
这个idx_age索引在磁盘上的存储粗略是:
age name
18 zhangsan
24 lisi

#回表
#每个普通索引都会关联主键ID
当select字段无法从索引字段获取时,需要通过该索引查找对应主键索引树;

#索引合并
索引合并是种优化的结果,也反应了表上的索引建的很糟糕;
#执行计划type中出现:index_merge
1)当出现多个索引(多个and情况),没有联合索引;
2)当多个索引做联合操作时(多个or),需耗费大量CPU、内存资源在算法上的缓存、排序和合并上,特别是有些索引选择性不高,需要合并时返回大量数据;
3)优化器不会把这些计算到查询成本中,优化器只关心随机页面读取,导致执行计划还不如直接走全表扫描

对于一个高度为3的B+树来说,可以存2千万数据
假设主键为bigint,占8字节,一个索引除本身还有一个指针占6字节
16K/(8+6)B=1170
第二层:117016K=18M
第三层:1170
117016K=21902400
如果数据行占用空间更小,则可以存更多数据;单纯更加数据行判断是否需要分表不是那么合理;
#一个页可存储2的32次方文件
innodb表空间可存放20亿左右的页面
16KB=64TB;
mysql用区存储页,一个区可存储64页,大概是1M,一个区是256组;MYSQL又用组来存储区;

B+树

创建高性能索引
1)过多索引会导致过高的磁盘使用率&过高内存占用;
2)避免事后想起添加索引;

B-TREE索引是目前关系型数据库查找数据最为常用和有效的索引,innodb使用的是B+TREE;
B+树索引并不能找到一个给定键值的具体行,找到是被查找数据行所在的页,数据库将页读到内存中,在内存中查找,最后找到查找的数据。
通过二分查找,每个页的page directory的slot是按主键顺序存放的,对于某一条具体记录的查询是通过对page directory进行二分查找得到的;
B+树是M叉树(多路搜索树)
1)所有关键字(数据)存储在叶子节点(leaf page),非叶子节点(index page)不存储真正数据,所有记录节点按键值大小顺序存储同一层叶子节点,所有叶子节点由指针连接(建索引时指针额外占空间字节为6)。
2)每个节点的大小设置为一个页的整数倍;叶子节点使用指针链接的好处是可以进行区间访问;如果有指针,当查找20

这里顺便提一下MYSQL申请磁盘数据页的的预读原理;和oracle不同的是: oracle采用的是数据块预读:
先从索引片上收集指针,然后再进行多重随机IO并行读取表行;(如果不同表行位于不同磁盘驱动器上,则并行执行)
#oracle位图索引(使用的话,可以理解为大表的同一列中,该索引字段列的不同值很少时可批量计算不同列值的位向量,利于翻倍提升count等聚合查询);

#磁盘存储原理:磁盘本身存取本身比主存慢,加机械运动损耗,磁盘读取速度是主存几百万分之一,为减少IO,磁盘会进行预读,将某位置后顺序读取一定长度的数据放入内存中,预读长度一般为页整数倍;
#页是存储器的逻辑块,OS会将磁盘存储区分割成等大小的块,OS中页大小为4K,当程序读取数据不在主存中,触发缺页提醒,系统会想磁盘发出读盘信号,磁盘找到数据起始位置并向后连续读取一页或几页载入内存中后返回;
MYSQL利用磁盘预读原理,每个节点都为页的整数倍,当新建节点时,直接申请页空间,OS的物理空间也相应存储一个页,实现读取一个节点只需一次IO,因此树高一般比较小,不超过3;

B+树的特征:

1.有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。
2.所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
3.所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。

B+树的优势:

1.单一节点存储更多的元素,使得查询的IO次数更少。
2.所有查询都要查找到叶子节点,查询性能稳定。
3.所有叶子节点形成有序链表,便于范围查询。
B+树的中间节点只有索引没有卫星数据,所以同样大小的存储引擎支持的数据页可容纳更多节点元素;
数据量相同的情况下,B+树的结构比B-树更矮胖,查询IO次数更少;
B+树和B树的非叶子节点都存了索引key和指针,只是B+树的非叶子节点只存了子节点的临界值(max,min),所有同样大小的节点,B+树相对于B树能有更多的分支,使得这棵树更加矮胖,查询IO次数更少;

B+树分裂原理(基于mysql8版本)

左旋操作
通过旋转操作可以最大限度的减少页分裂,从而减少索引维护过程中的磁盘的I/O操作,也提高索引维护效率。需要注意的是,删除节点跟插入节点类似,仍然需要旋转和拆分操作,这里就不再说明。
因为B+树总会保持平衡,为平衡对于新插入的键值可能需要做大量的拆分页split操作;因为B+树主要用于磁盘,页拆分意味着磁盘操作,应尽可能减少页拆分,所有才有旋转操作:retation;
#当左叶子节点已满,其左右兄弟节点没满情况下,B+树不急于做拆分操作,而是将记录移到所在页的兄弟节点上,通常左节点会先被检查做旋转;

B+树删除操作的三种情况:
B+树使用填充因子(fill factor)控制树删除变化,50%填充因子可设最小值;
B+树删除操作统一必须保证删除后叶子节点记录依然排序;

启发式优化规则(MYSQL8版本)

优化器对于选取索引还是选取排序是基于代价估算模型的;查询优化器对二者进行计算,取代价最小的;

基于成本模型:
如果SQL包含limit,会通过成本评估可能会使用优先队列来避免磁盘文件排序,提升排序效率;
使用排序模式时,由于记录数太多需要磁盘文件时,通过主键ID缓存到随机读缓冲区(read rnd buffer),缓冲区满了对主键ID排序再通过主键ID去存储引擎读取客户端需要的字段,尽可能把随机IO变为顺序IO,提升排序效率;

基于代价模型,比较排序和利用索引的代价大小;选取代价少的方式;
在基于代价的同时,也基于索引的情况下进行启发式的优化order by:
1)如果数据获取方式为quick select,且limit值极小,则直接采用索引进行优化(make_join_select实现)
2)基于代价模型确定选取索引还是选取文件排序进行排序操作;依赖test_if_cheaper_ordering完成;用于计算索引的代价和基于文件排序的代价;

#但计算代价需要花费时间,可能MYSQL未来版本会提供参数用来开启&关闭启发式优化规则

#引用:https://blog.csdn.net/fly2nn/article/details/61924543?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_title~default-0.pc_relevant_paycolumn_v3&spm=1001.2101.3001.4242.1&utm_relevant_index=3

聚族索引

#聚族索引和辅助索引不同的是叶子节点存放的是否是一整行数据;
将数据存储与索引放到一块,找到索引就找到数据了;
按照每张表的主键构建一棵B+树,同时叶子节点存放的即为整张表行记录数据;也将聚集索引叶子节点称为数据页;聚集索引特性决定了索引组织表数据也是索引的一部分;每个数据页通过双向链表进行链接;

聚族索引的目的

是使得表行存储顺序尽可能预索引行顺序保持一致,若没有聚族索引,则新插入表行将会被放置在表最后一个页上;

#ORACLE和SQLSERVER并不支持新加的表页选择操作影响的聚族索引;通过频繁地重组表来使得表行按照所需顺序存储----通过重载某个特定索引读取表行来实现,通过重载前对数据进行排序来实现;

聚族索引的弊端:

如果二级索引使用的是直接指向表行指针,则主键(聚集)索引的叶子页分裂将导致其余索引的 大量IO;
主键索引更新会导致移动索引行;
#SQLSERVER会把主键索引键作为指向聚集索引的指针值,可以避免叶子页分裂带来的额外负载;所以SQLSERVER会选择不会更新的索引作为聚集索引;

聚族索引原理

聚族索引中,每条记录存在2个隐藏列,
trx_id和roll_pointer;
trx_id
一个事务可以是只读事务,也可以是一个读写事务
只读事务:可以对临时表进行增删改查(START TRANSACTION READ ONLY开启),只读事务只有在对临时表进行写操作时才会分配事务ID
读写事务:可以对普通表/临时表进行增删改查(START TRANSACTION READ WRITE开启/START TRANSACTION开启),读写事务只有在对普通表/临时表进行写操作时才会分配事务ID
事务ID生成方式
MySQL维护一个全局变量,当需要为某个事务分配事务ID时,将该变量的值作为事务id分配给事务,然后将变量自增1
当这个变量为256的倍数时,将变量的值刷新到系统表空间中页号为5的页面中的Max Trx ID属性中

聚集索引中,叶子节点直接包含卫星数据,非聚集索引中,叶子节点带有指向卫星数据的指针;
聚集索引对于主键排序查找和范围查找速度非常快,叶子节点数据就是用户查询的数据;因用户查询一张注册用户的表,查询最后注册的10位用户,由于B+树索引是双向链表,用户可快速查找最后一个数据页并取出10条记录;
即便是使用了order by排序但extra并没有filesort操作;这就是聚集索引的特点;
#范围查询(range query):如果查找主键某一范围内的数据,通过叶子节点上层中间节点就可得到页的范围,之后直接读取数据页即可;

辅助索引

innodb中,聚族索引之上创建的是辅助索引,辅助索引访问数据需要二次查找;
非聚族索引都是辅助索引,像复合索引,前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值;
非聚族索引:将数据存储在索引分开结构,索引结构的叶子节点指向了数据对应行;
myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时,通过索引访问数据,在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这就是索引当不在key_buffer命中时,速度慢的原因;

查询二级索引,innodb会遍历索引通过叶级别的指针获取指向主键索引的主键;通过主键索引来找到一个完整的行记录;

在SQL server中,有种堆表的表类型,即行存储按插入顺序存放,与MYISAM类似;
堆表特性决定了索引都是非聚集的,主键与非主键只有非空区别;使用书签行标识符(RID)定位实际行数据;
从这点看,SQL SERVER的聚集索引会比Innodb的非聚集索引慢一些;
SQL SERVER更善于OLAP在线分析处理(查询多);
但如果看表是否频繁更新、排序和范围查找,innodb通过B+树的中间节点就能找到所有页进行读取;这个是堆表不能实现的;

LSM-TREE概述

代表:ROCKSDB、CLICKHOUSE;

相比于传统的B+树,LSM-Tree结构具有更好的写性能,可以将离散的随机写请求转换成批量的顺序写操作。
其性能衡量主要考虑三类因素:空间放大、读放大和写放大。

LSM-Tree最初的设计理念是想通过空间放大和读放大来换取写放大的降低,从而达到极致的写性能,但也需要做好三方面因素的权衡,可以从降低写放大、降低读放大、降低空间放大三方面来对LSM-Tree结构进行优化,从而提升数据库性能。

#空间放大—space amplification:
LSM写操作是顺序追加写,对于数据更新操作不会将新值覆盖到旧值上,而是创建新空间存储新值,(out-place update);产生的过期旧值占用空间大于数据本身大小现象;

#如果完全不做compaction操作,即一直顺序写,LSM-Tree就会退化为log文件,这时写性能达到最佳。因为只需要顺序写log即可,不需要做任何操作。但读性能将会处于最差状态,因为在没有任何索引、无法保证有序性的情况下,每次想读到固定的数据项,就需要扫描所有的SST件。

#如果compaction操作做到极致,实现所有数据全局有序,此时读性能最优。查询时只需要通过索引和二分法即可迅速找到要读的键值的位置,一次IO操作即可完成,但代价是需要频繁进行compaction操作来维持全局有序状态,从而造成严重的写放大,即写性能变差。

#读放大:
外存索引结构(B+树,LSM-TREE):
读操作需要从上到下层层读取索引节点,可能存在一条数据读取触发多次IO操作,即一次读操作的实际数据量大于目标数据本身大小,影响读性能;

#写放大:
对于LSM,compaciton操作将多个SST文件反复读取,合并为新STABLE文件再次写入磁盘,因此一条 k-v数据多次反复写入操作,带来的IO性能损失为写放大;

分为内存和磁盘两部分:
内存采用MemTable数据结构;可通过B+树或跳表实现;
MemTable接受最新数据更新操作,维护内部数据in-tree逻辑的有序;

LSM树(Log-Structured-Merge-Tree)正如它的名字一样,

LSM树会将所有的数据插入、修改、删除等操作记录(注意是操作记录)保存在内存之中,当此类操作达到一定的数据量后,再批量地顺序写入到磁盘当中。
这与B+树不同,B+树数据的更新会直接在原数据所在处修改对应的值,但是LSM数的数据更新是日志式的,当一条数据更新是直接append一条更新记录完成的。这样设计的目的就是为了顺序写,不断地将Immutable MemTable flush到持久化存储即可,而不用去修改之前的SSTable中的key,保证了顺序写。

索引设计

建立的索引是否适合

#索引将相关记录放在一起获取为一星;
#索引中的数据顺序和查找中的排序顺序一致则获取二星;
#索引列包含查询中需要的全部列则获取三星;

三星索引构建:

一星: 取出等值谓词的列:where col=… 作为索引最开始的列 例如lname,city
这种情况下必须扫描索引片宽度被缩减至最窄; 二星: 将order by 的字段列加入索引中;order by
只会作用于后面跟着的列;order by name,city 时只有将name列放入索引中时,结果集无需排序已经是正确的顺序排列; 三星:
将查询语句中的剩余列加入索引中,联合索引的添加顺序对查询性能无影响,但变化的列放在最后会有效降低更新成本;
三星索引最终效果是无须回表的访问路径所需的所有列;

ORDER BY列跟在匹配列(它们都使用等值条件)的后面,从而规避了排序;此外,该查询只需访问索引而无须回表

相关知识点

数据扫描方式:

MYSQL在实际情况中通常是将多个页读取到缓存池中,并按顺序处理它们;且能识别出那些不在缓冲池中的页;随后,将发出多页IO请求;
每次请求的页数量由DBMS决定;只有不在缓存池中的页会被从磁盘服务器读取;因为那些已经在缓存池中的页中可能包含了尚未被写入磁盘的更新数据;
1、全表扫描;
2、全索引扫描
3、索引片扫描
4、通过聚族索引扫描表行

IO逻辑

数据都是落在磁盘的一个个扇区上,写满切换扇区时,通过盘片的转动找到目标扇区,物理运维;
顺序IO:写入的下一个扇区与当前扇区是衔接的;
随机IO,写入扇区与当前扇区间隔几个扇区,随机IO需要更长转动时间;
所以mysql会采用B+数减少随机IO;

二、MYSQL索引问题排查

相关参数

检查是否禁用索引扩展提高性能

#优化器对索引扩展的使用受制于通常的 索引中关键部分的数量限制 (16) 和 最大密钥长度(3072 字节)

SET optimizer_switch = 'use_index_extensions=off';

Handler类参数调用

**#总结
1. Handler_read_key的值越大越好,代表基于索引的查询较多。
2. Handler_read_first,Handler_read_last,Handler_read_next,Handler_read_prev都会利用索引。但查询是否高效还需要结合其它Handler_read值来判断。
3. Handler_read_rnd不宜过大。
4. Handler_read_rnd_next不宜过大,过大的话,代表全表扫描过多,要引起足够的警惕。**

handler是一个类,里面按不同的功能模块定义了若干接口(具体可参考sql/handler.h)。
1. 无论是基于主键,还是二级索引进行等值查询,Handler_read_key都会加1。
2. 对于二级索引,如果返回了N条记录,Handler_read_next会相应加N。

其中:

Handler_commit:内部提交语句数
Handler_delete:表删除行次数
Handler_discover:查找表的次数
Handler_external_lock:与锁定操作数量有关,访问表开始到结束起作用
Handler_mrr_init 存储引擎自己实现多范围读取的次数
Handler_prepare :两阶段提交操作的准备阶段计数器
Handler_read_first:索引第一条被读取次数,如果较高表明服务器正大量执行全索引扫描
Handler_read_key:如果高,说明查询和表索引使用正确;
Handler_read_last:键读取最后一行请求数;
Handler_read_next:如果较高,则索引利用率低;
键读取下一行请求数,范围约束或执行索引扫描查询所有列,会增加;另外建立索引也会大量增加该值;
Handler_read_prev:键顺序读取前一行请求数,主要用于优化order by
Handler_read_rnd:数据文件固定位置读一行请求数,如果较高,则索引利用率低;
Handler_read_rnd_next:数据文件读取下一行请求数,如果较高,则索引利用率低;
Handler_rollback:内部ROLLBACK数量
Handler_savepoint:保存点的请求数量
Handler_savepoint_rollback:存储引擎要求回滚到一个保存点数量;
Handler_update:请求更新表一行的次数
Handler_write:请求插入一行的次数

索引失效场景汇总

1、有or必有全文索引;or 的两个字段不会同时使用索引;
2、不满足最左前缀原则;
3、范围索引列需要放最后
4、select *
5、使用计算逻辑
6、索引列使用函数
7、字符类型没有加引号
8、is null 和 is not null 没注意字段是否允许为空;
9、like %test;
10、如果优化器认为全表扫描更快
11、优化器选错索引,可以用force index 强制查询SQL走索引;
------- force index、ignore index
12、如果不是按照索引最左列开始查询,则无法使用索引;
13 、使用索引时不能跳过索引中的列;
14、not in 和<> 无法使用索引;
15、mysql会将字符串转换为数字,所以查询时:字符串查询数字必须加’';
16、避免多个范围条件
17、实际开发中,我们会经常使用多个范围条件,比如想查询某个时间段内登录过的用户:
18、select user.* from user where login_time > ‘2017-04-01’ and age between 18 and 30
当有两个范围条件,login_time列和age列,MySQL可以使用login_time列的索引或者age列的索引,但无法同时使用它们。

注意事项:

1、索引会加重insert delete update的负担,增加写操作成本;
2、太多索引会增加查询优化器的分析选择时间;
3、mysql运行时也会消耗资源和索引;
4、列值很少,不会提高查询效率;
5、text、image、bit数据类型的列不要建立索引;
6、2000条数据以内无需建索引;查询<0.1秒无需建索引;
7、前缀索引可兼顾索引大小和查询速度,但不能用于order by 和group by 也不能covering index :即当索引本身包含查询所需全部数据时,不再访问数据文件本身;
8、视图只是屏蔽或高效集合多表数据的一种方式,视图和表join不会有任何效果;
9、NULL的列改为not null列不会对性能提升有帮助(因为mysql的每个null都表示不同值(参数可以全局配置)),但如果要在该列创建索引,就需要设置为not null;
#含有null值得列加索引后会附加一个额外的字节,在myisam中,可能造成固定大小的索引变成可变大小的索引;

10、mysql对blob和text进行排序时,只对每个列的最前max_sort_length字节而不是整个字符串做排序;不会对整个字符串进行索引,也不能使用这些属于消除排序
如果查询使用blob或text并且需要使用隐式临时表,会使用磁盘临时表

show status where Variable_name like 'Handler%' or variable_name like 'Created%';

索引统计

innodb统计索引信息是通过抽样完成的;随机读取少量索引页为样本计算索引统计信息;

show variables like '%innodb_stats_sample_pages%';默认为8


#统计信息查询

show tables from mysql like 'innodb%stats%';

#索引页大小查询

select stat_value pages,index_name,stat_name,(stat_value * @@innodb_page_size /1024/1024) as size from mysql.innodb_index_stats where table_name='tb_xyh_product' and stat_description = 'Number of pages in the index' and stat_name = 'size' group by index_name;

#占用空间最大的前10个索引

select 
iis.database_name, 
iis.table_name, 
iis.index_name, 
round((iis.stat_value*@@innodb_page_size)/1024/1024, 2) SizeMB, 
-- round(((100/(SELECT INDEX_LENGTH FROM INFORMATION_SCHEMA.TABLES t WHERE t.TABLE_NAME = iis.table_name and t.TABLE_SCHEMA = iis.database_name))*(stat_value*@@innodb_page_size)), 2) `Percentage`,
s.NON_UNIQUE,
s.INDEX_TYPE,
GROUP_CONCAT(s.COLUMN_NAME order by SEQ_IN_INDEX) COLUMN_NAME
from (select * from mysql.innodb_index_stats WHERE index_name  not in ('PRIMARY','GEN_CLUST_INDEX') and stat_name='size' order by (stat_value*@@innodb_page_size) desc limit 10) iis 
left join INFORMATION_SCHEMA.STATISTICS s
on (iis.database_name=s.TABLE_SCHEMA and iis.table_name=s.TABLE_NAME and iis.index_name=s.INDEX_NAME)
GROUP BY iis.database_name,iis.TABLE_NAME,iis.INDEX_NAME,(iis.stat_value*@@innodb_page_size),s.NON_UNIQUE,s.INDEX_TYPE
order by (stat_value*@@innodb_page_size) desc

#查看全表扫描或者没有使用到最优索引的语句(经过标准化转化的语句文本),默认情况下按照全表扫描次数与语句总次数百分比和语句总延迟时间(执行时间)降序排序

SELECT sys.format_statement(DIGEST_TEXT) AS query,
  SCHEMA_NAME as db,
  COUNT_STAR AS exec_count,
  sys.format_time(SUM_TIMER_WAIT) AS total_latency,
  SUM_NO_INDEX_USED AS no_index_used_count,
  SUM_NO_GOOD_INDEX_USED AS no_good_index_used_count,
  ROUND(IFNULL(SUM_NO_INDEX_USED / NULLIF(COUNT_STAR, 0), 0) * 100) AS no_index_used_pct,
  SUM_ROWS_SENT AS rows_sent,
  SUM_ROWS_EXAMINED AS rows_examined,
  ROUND(SUM_ROWS_SENT/COUNT_STAR) AS rows_sent_avg,
  ROUND(SUM_ROWS_EXAMINED/COUNT_STAR) AS rows_examined_avg,
  FIRST_SEEN as first_seen,
  LAST_SEEN as last_seen,
  DIGEST AS digest
FROM performance_schema.events_statements_summary_by_digest
WHERE (SUM_NO_INDEX_USED > 0
OR SUM_NO_GOOD_INDEX_USED > 0)
AND DIGEST_TEXT NOT LIKE 'SHOW%'
ORDER BY no_index_used_pct DESC, total_latency DESC limit 10

innodb会在表首次打开时,执行analyze table,抑或表大小发生大变化(变化超过1/16或新插入20亿都会触发)会计算索引统计信息;
#打开information_schema,show table status,show index都会触发索引统计信息的更新操作 dict_update_statistics;会直接触发读盘操作

当服务器上有大量数据时,可能是个很严重的问题,当IO很慢时,客户端或监控程序触发索引信息采样更新时可能会导致大量的锁;
#只要show index查看 就一定会触发统计信息更新;

#关闭索引统计(推荐关闭;默认on)
setl global innodb_stats_on_metadata=OFF;
#涉及的表:访问即触发索引统计:
information_schema:tables;statistics;partitions;key_column_usage;
table_constraints;referential_constraints;
show table status ;也会触发,但只是单表的;
#关闭索引统计后,索引统计信息会永远不变,需手动执行analyze table
但如果数据分布发生大的变化可能会出现一些糟糕的执行计划;

二级索引快速建立

索引建立过程:
1、扫描聚集索引(主键)数据,并将数据存储到临时表中;
2、针对这些数据进行排序
mysql8.0.19版本新增隐藏索引,降序索引,函数索引
3、加载排序数据从临时文件写入到二级索引中;

Innodb快速在线索引创建功能

先删除所有的非唯一索引,然后增加新的列,最后重新创建删除掉的索引。

操作步骤:
用需要的表结构创建一张表,但不包括索引。
载入数据到表中以构建.MYD文件。
按照需要的结构创建另外一张空表,这次要包含索引。这会创建需要的.frm和.MYI文件。
获取读锁并刷新表。
重命名第二张表的.frm和.MYI文件,让MySQL认为是第一张表文件。
释放读锁。
使用REPAIR TABLE来重建表的索引。该操作会通过排序来构建所有索引,包括唯一索引。

索引并行相关参数

1 扫描聚集索引(主键)的并行数由 innodb_parallel_read_threads 来决定,默认设置为4, 具体以cpu的核心数 - 2 到 8 来设置。 但需要注意的是即使设置了,如果无法使用并行,系统还会使用单核心的方式运行。

2 在创建并行索引是,需要注意对于并行的线程分配内存 在8.027上新添加了 innodb_ddl_buffer_size 参数(参数默认大小 1MB),具体在每个线程上的使用是 innodb_ddl_buffer_size / innodb_ddl_threads

不同的参数的变化对于添加索引的性能影响,加大 innodb_ddl_buffer_size 和 innodb_parallel_read_threads 对索引的建立的速度提升有很大的帮助。

innodb_ddl_threads:
MYSQL 8.027 引入了 innodb_ddl_threads 参数这个参数主要针对索引建立时的排序和建立阶段,同时也会应用到rebuild secondrary indexes 的操作中,默认值 4 , 可以设置的值从1 -64 。

3 innodb_online_alter_log_max_size 这个设置主要针对进行DDL操作时的临时空间的设置,默认128MB ,在索引操作时,会将读取的数据写入临时文件,临时文件的尺寸由innodb_online_alter_log_max 决定。

innodb_sort_buffer_size 在操作二级索引建立时需要对临时的文件的内容进行排序,就需要innodb_sort_buffer_size 来控制缓存。

innodb_tmpdir 临时对alter table 的语句中存储的临时数据目录的磁盘空间不足也会导致操作失败

索引-----锁关系

MYSQL避免幻读情况:

在快照读情况下,MySQL通过mvcc来避免幻读。
在当前读情况下,MySQL通过next-key来避免幻读(加行锁和间隙锁来实现的)。

next-key包括两部分:行锁和间隙锁。行锁是加在索引上的锁,间隙锁是加在索引之间的。

索引使用情况查询工具推荐-----pt-index-usage

从日志里面读取查询,并且分析如何使用索引的

二阶段原理:
一:该工具将对数据库中的所有表和索引进行清点,以便将现有索引与日志中查询实际使用的索引进行比较。
二:在查询日志中的每个查询上运行EXPLAIN。它使用单独的数据库连接来清点表并运行EXPLAIN,因此它每次都要打开两个连接。如果操作是update等非select操作,会自动转化为等价的select操作。

分析查询会在slow.log里面并且打印报告:
pt-index-usage /path/to/slow.log --host localhost
or
不打印出来,而且对于后来的分析把结果存入percona数据库里面:
pt-index-usage slow.log --no-report --save-results-database percona

./pt-index-usage  mysqldata/mysqlslowlog/slowquery.log -h192.168.226.131 -uroot -p6yhn^YHN -decology

./pt-index-usage  mysqldata/mysqlslowlog/slowquery.log -h192.168.226.131 -uroot -p6yhn^YHN 

冗余索引查询工具-----pt-duplicate-key-checker

用于为mysql表中找出重复的索引和外键。索引会更查询带来好处,但是过量的索引反而可能会使数据库的性能降低,
pt-duplicate-key-checker会将重复的索引和外键都列出来,并生成了删除重复索引的语句,非常使用。

pt-duplicate-key-checker --host=localhost --user=root --password=*********  --databases=hhrppdb

你可能感兴趣的:(mysql,数据库)