目录
为什么要有索引
InnDB中的数据也是存储数的基本单位,多个页之间通过双向链表连接,每个数据也中的记录都按照主键从小到大排成一个单向链表。每个页有一个页目录,用来加快查找速度
索引数据查找过程
1.再数据页中找一个记录
- 首先按照主键查找,通过页目录(页目录中存放有这个页中记录的最大的主键值)使用二分法找到对应的槽,随后在槽的记录租里面逐条查找目标目录
- 如果不是根据主键值查找,因为没有根据主键值建立页目录,所以只能遍历,直到找到符合情况的记录
2.在多个数据页中查找
- 首先找到记录所处的页
- 随后在页中找具体的记录
总的来说,如果没有索引,不管是主键还是其他列来找都要从第一页开始顺着双向链表查找,也就是全表扫描
基本的索引方案
索引可以帮助我们快速找到数据的位置(类似目录),在数据库与中,每一个数据也都有一个“页目录”,用于快速定位记录。而且后一个页中的所有记录的主键值必须大于前一个页中的最大主键值。
例子如下
插入三个数据
-- 插入三条测试数据
INSERT INTO test VALUES(1, 10, 'aa');
INSERT INTO test VALUES(2, 20, 'bb');
INSERT INTO test VALUES(4, 40, 'dd');
插入后一个页的数据类似这样
一个页的数据就是类似这样的主键从小到大的
随后如果我们再次插入一条数据
INSERT INTO test VALUES(3, 30, 'cc');
倘若每页只能放三个数据,如果要放入四条数据,就必须要新建一个页。而且新页中的主键值必须大于前一页的最大值4,在这个过程中,不仅仅要放入新的数据进入新的页,而且原本的页中的数据也要进行调整
也就是我们要把主键4的记录放入新的一页,而且把主键3的记录存入前一页
这个页的过程就是页分裂,所以实际存储中,数据也的物理位置可能是不连续的
所以为了快速找到特定主键的记录,我们必须为这些页建立目录。目录项包括每一页的最小主键值和页号。类似这样
查找过程示例(找主键5的记录):
(1) 用二分法在目录中定位:4 < 5 < 7 → 目录项2 → 页23
(2) 在页23中查找具体记录
关键结论:这个目录结构就是我们说的索引。
InnoDB索引方案
InnoDB使用数据页来存储目录项,为了区分:
- 普通用户记录
- 目录项记录(使用record_type字段区分)
当目录项太多时,会使用多级目录结构,也就是会再多整一个存储目录项记录的页。
所以如果此时我们再向上图中插入一条主键值为10的用户记录的话:
这种结构就像:是一个多级目录一样,大目录里嵌套小目录,小目录里才是实际的数据
- 最上层:根节点
- 中间层:非叶子节点(内节点)
- 最下层:叶子节点(存储实际数据)
索引类型
聚簇索引
聚簇索引的特点:
- 按照主键值排序的记录和页
- 同时叶子节点存储完整的用户记录
- 而且是由InnoDB自动创建,无需手动建立
- 索引和数据存储在一起
- 叶子节点包含所有列的值
二级索引
二级索引和聚簇索引的区别
排序的依据不同
聚簇索引按照主键排序
二级索引是按照某个普通列排序
叶子节点存储的数据不同
二级索引存储的数据是索引列的值和对应的主键,而不是整条记录
目录项的内容不同
聚簇索引:目录项的内容是:主键+页号
二级索引:目录项里面是索引列+页号
利与弊
聚簇索引查找效率高,因为数据就在叶子节点
二级索引节省空间,但是需要回表查询才能获取到完整的数据
索引的代价
索引能加速查询,但是
- 占用更多的空间·,每一个索引都对应一个B+树,而B+树每个节点都是一个数据页。索引越多,占用的存储越大
- 影响写入数据,每次新增,删除,修改数据的时候,不仅仅要操作表本身,而且还要同步更新相关的索引。为了保持排序
-
- 记录移位
- 页面分裂
- 页面回收
全表扫描、 索引扫描、 回表查询、索引覆盖扫描的对比
当执行一个 SQL 查询语句时,Mysql 会根据优化器的选择,使用不同的执行计划来执行。
其中,最常见的执行计划有以下几种:
(1) 全表扫描
(2) 索引扫描
(3) 回表查询
(4) 索引覆盖扫描
其中特别说明一个索引覆盖扫描
这种就是根据查询条件在索引上查找,并且索引能直接返回条件的记录,但是不需要再访问主键的索引树,因为查询需要的所有字段都在索引。(通常在有合适的索引并且查询字段比较少的时候使用)
性能对比
回标查询的坏处
回标查询:回标导致增加磁盘I/O次数(因为要会主键索引树查找),降低查询效率,在大数据量或者高并发场景下最为明显。
索引失效十大场景
(1)使用 != 或 <> 操作符
(2)使用 OR 连接多个条件,当查询中用了 OR
,如果其中一个条件没有索引,整个查询可能就不会走索引。
(3)在字段上使用函数或表达式
(4)模糊查询以 % 开头
(5)类型转换导致索引失效,比如字段是字符串类型,但你传的是数字
(6)联合索引没用最左前缀原则联合索引 (a, b, c)
,如果你只查 b
或 c
,或者跳过前面的字段,索引也不会生效
(7)查询返回太多数据(超过一定比例),如果一个查询返回的数据太多,比如占整张表的大部分,数据库会觉得直接扫表更快,就不走索引了。
(8)使用 NOT IN 或 NOT EXISTS
(9)字段允许为 NULL,且查询未处理 NULL 值
(10)统计信息不准或索引损坏
索引创建原则?
(1) 数据量大、查询频繁的表才加索引。比如单张表超过10万条数据时,加索引能明显提升查询速度,改善用户体验。
(2) 对经常用来做查询条件(where)、排序(order by)、分组(group by)的字段加索引,这样可以加快这些操作的速度。
(3) 优先选择区分度高的字段建索引。区分度越高,查找越快。最好能建唯一索引,效率更高。
(4) 如果字段是字符串且比较长,可以考虑前缀索引。也就是只取字段的前几个字符建立索引,节省空间又不影响性能。
(5) 尽量用联合索引,少用单列索引。联合索引很多时候可以覆盖查询字段,减少回表查询,提高效率,还省空间。
(6) 不要随便加太多索引。索引多了虽然查得快,但会影响插入、更新、删除的速度,维护成本高。
(7) 如果字段不需要为空,建表时要加上NOT NULL约束。这样优化器能更好地判断该不该用这个索引。