mysql索引详解
1.什么是索引
索引是一种数据结构,会对添加索引的字段的值进行排序存放,提高查询效率;一张表中可以添加多个索引;innodb存储引擎默认使用的是b+tree索引结构,也支持哈希、全文索引。
2.索引的优缺点
2.1索引的优点
①提高数据库查询效率
②减少锁等待和死锁的产生(行锁是基于索引创建的)
③减少主从复制从库的延迟时间(sql thread回放sql时会应用索引)
2.2索引的缺点
①索引维护成本高(可通过insert buffer,change buffer提升DML语句效率)
②占用更多的存储空间(磁盘和内存)
③索引过多会造成优化器负担
3.b+tree数据结构
3.1 b+tree数据结构概括:b+tree是平衡多叉树的数据结构,是基于页进行管理数据;
3.2b+tree高度:2 (一般为2-4层);
影响索引树高度因素:
。索引长度
。记录数
索引树高度不同消耗时间不同:
如sata磁盘(ssd固态硬盘同样计算):300iops,0.0033 /io
2层:0.0033*2 单位是秒
3层:0.0033*3 单位是秒
3.3非叶子节点
保存键值(添加索引的字段的值)和指针
3.4指针
指针与数据页是一种映射的关系,通过指针就可以找到对应的数据页
3.5叶子节点
用于保存数据,保存所有记录的值,并经过排序
3.6双向指针(双向链表)
用于保存相邻页的指针,提升范围查询效率
4.b+tree管理
4.1 b+tree插入操作:(页旋转操作)
情况一:b+tree插入数据时,叶子节点没有满
直接插入到对应的数据页
情况二:b+tree插入数据时,叶子节点已满(产生页分裂 split)
先取出中间值,存放到上一层非叶子节点中;
情况三:b+tree插入数据时,叶子节点和上一层的非叶子节点都已满(产生两次页分裂操作)
4.2 b+tree删除操作
当叶子节点小于填充因子50%,就会发生页合并操作
5.相辅相成的聚集索引和辅助索引
5.1 b+tree索引:索引的本质就是b+tree在数据库中的实现
5.2 索引的分类:从物理存储角度分类
聚集索引和辅助索引
5.3 聚集索引的选择:
①优先选择显示创建的主键索引来作为聚集索引
②如没有主键索引就会选择第一个创建的非空的唯一索引作为聚集索引
③系统自动创建一个实例级rowid作为聚集索引(6字节)
5.4 聚集索引的特点:
①叶子节点存放的是整行数据
②一张表只能有一个聚集索引,因为实际的数据页只能按一颗b+tree进行排序
③聚集索引的顺序决定表数据行的物理顺序
5.5 聚集索引的优势:
①不用回表查询就可以直接找到数据,节省更多的查询时间
②范围查询性能得到提升 where 4<o_orderkey<10
③排序性能提升
5.6 辅助索引:内部也是b+tree
①辅助索引存放的是键值和主键值
②每张表中可以存放多个辅助索引
6.覆盖索引与回表查询
6.1 回表查询
指回到聚集索引构建的b+tree去搜索的过程,就称为回表;回表查询要多经历几次io,消耗时间更多,性能相对较差
6.2 覆盖索引
在一个查询语句中,使用到的b+tree上面的数据就覆盖我要查询需求,称为覆盖索引;可以减少对b+tree的搜索次数(减少io的消耗,不用回表查询)
7.创建高性能的主键索引
7.1 主键索引创建的原则
①使用自增列作为主键 id int/bigint auto_increment primary key;
②主键与业务不相关,不受业务变化影响
③主键尽量不要修改、删除
7.2 主键索引的特点
①值不能为空,也不能重复
②一张表只能有一个主键
③创建辅助索引时,会隐式的将主键值保存,(name,pk)5.7自动识别里面的主键
where name=? and pk=?
where name=? order by pk
7.3 为什么建议使用自增列作为主键
①读;显示创建的主键会被作为聚集索引,在数据页上存整行数据,无论读记录任何的列,我们都不用回表查询,直接在主键构建的b+tree就可以找到。
②写;写性能非常高,顺序获取页;离散获取页;insert buffer , change buffer
③节省更多的内存
8.唯一索引与普通索引的性能差距
8.1 唯一索引特点:
- 值不能重复,可以为空
- 一张表可以创建多个唯一索引
- 如果表中已有数据,添加唯一索引时,该字段的值,不能重复,如果有重复的,就会报错
select count(b) from t;
select count(distinct b) from t;
8.2 普通索引特点
- 值可以重复,可以为空
- 一张表可以创建多个普通索引
8.3 唯一索引与普通索引的性能差距
- 读性能差距:
唯一索引:由于唯一性约束,查找到第一个满足条件的记录后,就会停止继续匹配
普通索引:值不是唯一,可能会有重复值,需要继续查找
总结:在读性能上,唯一索引的性能高于普通索引,性能差距非常小
- 写性能差距:
唯一索引:在进行写操作时要判断这个操作是否违反了唯一性约束。这个判断必须是在将页加载到内存后,才能进行判断,无法使用change buffer
普通索引:在进行写操作时,如果数据页不在内存中,会将写操作放到change buffer
总结:普通索引的写性能高于唯一索引
9.前缀索引带来的性能影响
9.1 前缀索引作用:
- 索引长度影响b+tree高度,索引长度越短越好
- 节省磁盘空和内存空间
- 建前缀索引时,如果合理的定义前缀索引的长度,会对查询性能带来好的影响
9.2 前缀索引长度创建不合理会带来哪些不好的影响?
- 不合理的长度,会带来更多回表查询
总结:建前缀索引时,最重要的就是指定合理的长度
9.3 合理长度判断:
select count(distinct a) from t;去除重复的值,总共有多少条记录
select count(distinct left(a, 3)) from t;
80%-90%就是合理的
9.4 前缀索引缺点:
- 无法使用覆盖索引
- 无法进行order by和group by,会产生额外排序和产生临时表
10.生产中索引的管理
①建表时创建索引
主键索引
create table t1(id int auto_increment primary key);
create table t2(
id int auto_increment,
primary key(id)
);
唯一索引
create table t1(name varchar(10) not null unique);
create table t2(
name varchar(10) not null,
unique key i_name(name)
);
前缀索引
create table t1(
name varchar(10) not null,
key i_name(name(5))
);
联合索引
create table t2(
name varchar(10) not null,
o_date datetime,
key i_name_date(name,o_date)
);
普通索引
create table t1(
name varchar(10) not null,
key i_name(name)
);
②建表后创建索引
create table t1(
id int ,
name varchar(10) not null,
o_date datetime,
title varchar(30) not null
);
建表后创建索引
主键索引
alter table t1 add primary key(id);
唯一索引
alter table t1 add unique index i_name(name);
前缀索引
alter table t1 add index i_title(title(10));
联合索引
alter table t1 add index i_name_date(name, o_date);
普通索引
alter table t1 add index i_o_date(o_date);
删除索引:
alter table t1 drop index 索引名称
查看索引:
show create table t1;
show index from t1;
11.SQL语句无法使用索引的情况
①where条件:
列进行计算:
explain select * from orders where o_custkey=o_custkey+1;
列使用函数:
explain select * from orders where o_custkey=ceil(o_custkey);
列进行隐式转换:
explain select * from emp where ename=007;
②联合索引:用到范围查询,只能用到部分索引
③联表查询:
关联条件字符集不同,不走索引
关联条件的列类型不同,不走索引
④其他情况:
。select * from emp;
。查询结果集大于数据量的30%,不走索引
explain select * from emp where empno > 7000;
。索引本身失效
。like '%s'
explain select * from emp where ename like '%s';
。not in(111,9999) 普通索引,如果是主键索引,会被优化为范围查询,可以利用索引
explain select * from emp where empno not in(111, 9999);
。!=
explain select * from emp where empno != 9999;