by shihang.mai
1. 索引存放位置
选择不同的存储引擎,数据和索引以不同的文件格式,存放在不同的位置。
存储引擎 | 位置 | 文件格式 | 索引类型 |
---|---|---|---|
Innodb | 磁盘 | .frm:表结构 .idb:数据文件和索引文件 |
聚簇索引 |
Myisam | 磁盘 | .frm:表结构 .myi:索引文件 .myd:数据文件 |
非聚簇索引 |
Memory | 内存 |
2. Mysql索引数据结构
mysql存储索引数据结构用的B+树
2.1 如果用哈希表存储
Data:(key,value)=(列值,一行数据或者是某个列的值)。每次在添加索引时,需要计算索引列的hash值,取模运算后计算下标,然后放入data即可。哈希表存储索引只适合等值查询场景。
Mysql不用哈希表原因:
- 数据是无序的,而企业中大多查询是范围查询,那么就要做遍历操作,比较浪费时间
- hash表做操作时,要全部加载到内存,比较浪费内存空间
2.2 树
树特点:左子树必须小于根节点,右子树必须大于根节点。多叉树的话,从左到右是有序的
得出结论:二叉树及其变种,都不能支撑索引的存储。因为树深度无法控制或者是插入性能差。
2.3 B树
所有的键值分布在整个树中
-
搜索可能在非叶子结点结束,性能逼近2分查找
图中,黄色-索引列值,红色-索引对应的数据,绿色-指针,例如查找28
- 第一次IO,读入4k,16<22<34,寻找p2
- 第二次IO,读入4k,25<28<31,寻找p2
- 第三次IO,读入4k,找到28返回数据
假如:data-1k,那么每个磁盘块存4条数据 ,一共存储4*4*4=64条数据,3次IO
缺点:
- data数据的大小并不能控制,一个磁盘块内,随着data数据下降,索引量下降
- 当存储数据量大时,树深度过深,增大查询IO次数,进而影响性能
2.4 B+树
非叶子结点只存储key,叶子节点存储key-value
非叶子结点能存放更多key,降低树的高度。将数据范围变为更多的区间,区间越多检索越快
-
叶子结点间还是双向链表,顺序查询性能更高
图中,黄色-索引列值,红色-索引对应的数据,绿色-指针,例如查找28
- 第一次IO,读入4k,16<22<34,寻找p2
- 第二次IO,读入4k,25<28<31,寻找p2
- 第三次IO,读入4k,找到28返回数据
假如:data-1k,p+16=10byte,4k=4096byte约等于4000byte,一共存储400*400*4=640000条数据,同样3次IO
3. 总结
3.1 innodb索引
mysql innodb索引是B+树
每个节点都是一个磁盘块4k大小
非叶子节点会存放索引及指向下个磁盘块的指针,并且索引是有序排列的
叶子节点存放索引及其值,并且索引是有序排列的,每个叶子节点都有双向指针
3.2 组合索引匹配
例如建立(a,b,c)组合索引
索引由上以下分别是a b c ,并且从左往右a的索引有序,当a索引相同,那么根据b索引排序,b索引相同根据c排序
先匹配a索引,再匹配b索引,最后匹配c索引,所以有最左匹配原则,条件a ab abc均会走索引
3.3 B+树和B树对比
B树匹配有可能在非叶子节点结束,即每个磁盘块均包含索引及其数据,这样的话索引的数量会随着数据的增大而减少,导致树深过高
而使用B+树的话,叶子节点存放数据,那么非叶子节点就可以存更多的索引,降低树的深度
对于查询同一个条件,B+树读取I/O的数量比B树少
3.4 树高度计算
TINYINT(1字节)/SMALLINT(2字节)/MEDIUMINT(3字节)/INT(4字节)/BIGINT(8字节)
问:3000万数据,树高度多少?
反问:每条记录多大?用什么存储主键?有建其他索引吗?有的话是哪个,用什么存储?
如:每条记录1k,主键用bigint存储,没建其他索引
那么这里只有id索引树,树深度计算前,先计算叶子节点和非叶子节点分别能存多少条数据.指针大小默认=6k,innodb每页16k
叶子节点可存储的数据量=(16 * 1024)/(1 * 1024 + 8)约等于16
非叶子节点可存储的数据量=(16 * 1024)/(6 + 8)约等于1170
那么2层树深度可表示的数据条数=1170 * 16 = 18720
那么3层树深度可表示的数据条数=1170 * 1170 * 16 约等于2000W
那么4层树深度可表示的数据条数=1170 * 1170 * 1170 * 16>2000W
所以3000万条数据高度为4
4. Innodb回表
当索引列是普通索引时触发
表数据
Id | Name |
---|---|
1 | ma |
2 | zhou |
3 | lian |
4 | gan |
5 | ming |
6 | lu |
当name建索引,data存放的是id的主键。例如找lian,那么先找下面的B+树,找到了主键3,再找上面的B+树,找到返回。叫回表
5. Myisam树
仍然是B+树,但是因为数据和索引是分别存放在不同文件中。那么主键索引的B+树的红色存放的是文件位置,有文件位置,再去找数据文件即可
6. 索引分类
-
主键索引
唯一非null。mysql自动以主键创建索引,没主键的话,找唯一键,没唯一键的话,自行生成rowId创建索引。
-
唯一索引
唯一可为null。唯一索引不用做回表操作。
-
普通索引
不唯一可为null。要做回表操作。
-- 需要先找name b+树,再找id B+树 select * from stud where name ='msh'; -- 覆盖索引 如果需要查询的列 在索引树中可以找到对应数据,不用再查另外一个b+树,就叫覆盖索引 -- 只需要找name b+树 select id from stud where name = 'msh';
-
全文索引
可在char varchar text字段类型上建。
-
组合索引
组合索引在B+数的查找
https://blog.csdn.net/ibigboy/article/details/104571930/
多列值组成一个索引,专门用于组合
告警运维查询慢,页面需求:数据中心和设备名称 那么可以建2种情况索引: A. dcId&eqName(组合索引) + eqName(普通索引) B. eqName&dcId(组合索引)+ dcId(普通索引) name长度一定比dcId大,那么选择B方案
7. 索引下推
前提:组合索引.
建立name,age联合索引,查询下面sql
SELECT * from stud where name ='msh' and age ='28';
在mysql5.6前,通过name去(name,age)B+树找到id,然后通过id去(id)B+树找到数据返回到server层,在server层再通过age过滤数据
在5.6后加入后,通过name,age去(name,age)B+树找到id,然后通过id去(id)B+树找到数据返回
这里的下推是指,下推到存储引擎做过滤操作
8. 索引合并和组合索引
索引合并,分别对name,age建索引,然后查找时,分别去(name)B+和(age)B+树查找数据,然后结果进行合并
组合索引,将(name,age)编为一个索引,查找数据时,直接(name,age)B+树查找数据
9. 前缀索引
对于一些比较长的字段,截取前面固定长度的字符做索引。
alter table student add key(name(5));
10. 索引使用
- 全值匹配
建立字段(name,age,sex)联合索引
select * from student where name='msh' and age = 18 and sex=0
- 匹配最左前缀
建立字段(name,age,sex)联合索引
-- 只有(name,age,sex),(name,age),(name)走索引
select * from student where name='msh' and age = 18 and sex=0
select * from student where name='msh' and age = 18
select * from student where name='msh'
- 匹配列前缀
建立字段(name,age,sex)联合索引
-- 走索引
select * from student where name like 'm%'
-- 不走索引
select * from student where name like '%m%'
- 匹配范围值
建立字段(name,age,sex)联合索引
select * from student where name > 'msh'
- 精确匹配某一列,范围匹配另外一列
建立字段(name,age,sex)联合索引
-- 2个都走索引
select * from student where name='msh' and age > 18
-- sex不走索引
select * from student where name='msh' and sex > 18
-- 这个age也不走索引
select * from student where name='msh' and age = '18'
- 组合索引匹配
建立字段(a,b,c)联合索引
-- 用了a
where a=3
-- 用了a,b
where a=3 and b=4
-- 用了a,b,c 顺序mysql会自动调整的
where a=3 and c=5 and b=4
-- 用了a
where a=3 and c=5
-- 用了a,b
where a=3 and b>4 and c=5
-- 用了a
where a=3 and b like '%xx%' and c=5
- union all,in,or都能够使用索引,但是推荐使用in
- 范围列可以用到索引,但是范围列后面的列无法用到索引,索引最多用于一个范围列
- 需要join的字段,数据类型必须一致,不然不走索引
函数、隐式类型转换、字符集不走索引原因
11. 索引不适用条件
有索引,但是不走:
索引列上有函数。
不满足最左前缀。
使用了不等号
需要join的字段,数据类型不一致
不适合建立索引:
- 唯一性太差的字段,即使频繁作为查询条件
- 更新非常频繁的字段
- 不会出现在 WHERE 子句中的字段
- 表记录太少
12. 不能建很多索引原因
虽然索引可以加快查询,但是对插入,删除等等操作需要维护索引,索引也要存储空间的,而且随着表数据量的增加,索引所占用的空间也会不断增加,所以索引还会带来存储空间资源消耗的增加。会涉及页分裂和页合并
当插入数据时
向一页一页的插,满了就新开一页插入
页合并可发生在删除或更新时
当你删了一行记录时,实际上记录并没有被物理删除,记录被标记为删除并且它的空间变得允许被其他记录声明使用。当页中删除的记录达到MERGE_THRESHOLD
(默认页体积的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用,这叫页合并
页分裂可发生在插入或更新
页可能填充至100%的。当前页A无法容纳需要插入的数据,然而下一页B也无法容纳,那么就会新建一页C,将A可分的数据移到C,并把需要插入的数据也插入到C,然后维护前后指针,类似链表插入数据一样。叫页分裂.
要记住在合并和分裂的过程,InnoDB会在索引树上加写锁(x-latch)。在操作频繁的系统中这可能会是个隐患。它可能会导致索引的锁争用(index latch contention)。如果表中没有合并和分裂(也就是写操作)的操作,称为“乐观”更新,只需要使用读锁(S)。带有合并也分裂操作则称为“悲观”更新,使用写锁(X)
13. 一条sql走了索引但是依然很慢
- explain看下执行计划, 虽然走了索引,但是走的索引是不是最好的?
- 有没有进行内存排序操作?
- 是不是查询的列不在索引中而导致了回表?
- 是不是服务器压力太大,而导致的处理速度变慢?
- 是不是有查询的数据没在mysql的buffer poll中,而从磁盘加载到内存中