create table t1 (
a int primary key,
b int,
c int,
d int,
e varchar(20)
) engine = innodb;
insert into t1 value(4, 3, 1, 1, 'd');
insert into t1 value(1, 1, 1, 1, 'a');
insert into t1 value(8, 8, 8, 8, 'h');
insert into t1 value(2, 2, 2, 2, 'b');
insert into t1 value(5, 2, 3, 5, 'e');
insert into t1 value(3, 3, 2, 2, 'c');
insert into t1 value(7, 4, 5, 5, 'g');
insert into t1 value(6, 6, 4, 4, 'f');
1、页
页是InnoBD存储引挚管理数据库的最小磁盘单位(逻辑单位),遵循计算机的局部性原理
1、局部性原理
局部性原理又表现为:时间局部性和空间局部性。
- 时间局部性是指如果程序中的某条指令一旦执行,则不久之后该指令可能再次被执行;如果某数据被访问,则不久之后该数据可能再次被访问。
- 空间局部性是指一旦程序访问了某个存储单元,则不久之后,其附近的存储单元也将被访问。
2、磁盘IO与预读
考虑到磁盘IO是非常高昂的操作,计算机操作系统做了一些优化,当一次IO时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,因为局部预读性原理告诉我们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。每一次IO读取的数据我们称之为一页(page)。具体一页有多大数据跟操作系统有关,一般为4k或8k,也就是我们读取一页内的数据时候,实际上才发生了一次IO,这个理论对于索引的数据结构设计非常有帮助。(在InnoDB一页的大小是16KB)
3、InnoDB数据页由以下7个部分组成
- FIle Header(文件头)
- Page Header(页头)
- Infimun 和 Supremum Records
- User Records(用户记录,即行记录)
- Free Space(空闲空间)
- Page Directory(页目录)
- File Trailer(文件结尾信息)

特别说明:
①:主键索引,b+树中会存储完整记录,即每一个页的每个数据项记录的是某一个行的完整信息
②:二级索引,b+数中不会存在完整记录,即每一页的每个数据项记录的是某一行的主键索引(注意,这里记录的是一个索引,还需要回表到主键索引对应的B+树去查)
③:页目录的作用:通过槽对用户数据区域的数据项进行划分,以多少个数据项为一组,而槽记录的是该组第一个数据项的地址偏移量。当查找一个数据时,具体的执行流程在下面
④:用户数据区域的数据项是通过某个值进行排序的,主键索引通过主键值排序,其他二级索引通过特定的索引值排序
4、在某一页中的查找数据的执行流程
通过某个索引值去查找数据时,找到该页时,通过二分法在页目录中找到对应的槽,在槽维护的区间中逐个遍历寻找,找到后返回
例如:
select * from t1 where a = 3

插入操作
在图中,加入用户数据区域只能存4条数据,当插入(5,2,3,5,'e')时,会把(5,2,3,5,'e')插入到该页中,而将(8,8,8,8,'h')调整到下一页

两页数据的存储情况

2、InnoDB 存储引擎使用B+树结构

如果数据中数据极少,只有一两条记录,完全没有必要用B+树,B+树是需要去维护的,需要一定的成本,占用一定的内存,B+树是随着数据的增加慢慢生成出来的,当数据进行插入的时候,是根据B+树的特性进行插入,慢慢呈现出一棵B+树
其中,每个大框框都是代表一个页,指针指向的是一个页
1、索引的本质是什么
索引是mysql高效去获取数据并排好序的数据结构,数据结构指的是组织数据的方式。例如,主键值数据是以B+树进行组织的,因此这个B+树就是一个主键索引。
每一个索引在 InnoDB 里面对应一棵 B+ 树。
**,该B+树的叶结点是存放具体数据的,
2、聚簇索引 与 非聚簇索引
(1)聚簇索引
1、以 InnoDB 作为存储引擎的表,表中的数据都会有一个主键,即使你不创建主键,系统也会帮你创建一个隐式的主键。这是因为 InnoDB 是把数据存放在 B+ 树中的,而 B+ 树的键值就是主键,在 B+ 树的叶子节点中,存储了表中所有的数据。
2、即每个表都会有一棵通过主键值建立出来的B+树(主键索引),主键索引的叶子节点存的是整行数据。在 InnoDB 里,主键索引也被称为聚簇索引
(2)非聚簇索引
以主键以外的列值作为键值构建的 B+ 树索引,且非主键索引的叶子节点内容是主键的值,在 InnoDB 里,我们称之为非聚集索引。也称 非主键索引 或者 二级索引
3、当某个表中如果没有主键怎么办?
如果没有主键索引,就把唯一索引作为主键索引;如果没有唯一索引,则会对每一行生成一个隐式字段row_id,通过他们作为主键索引
注意:必须需要一个唯一确定该表的主键索引,整棵B+树是通过主键索引的排序规律进行插入,寻找的
4、聚簇索引(主键索引)例子
(1)精确查询
explain select * from t1 where a = 1;
分析:由于a是主键,因此可以通过主键索引进行查询
(2)范围查询
explain select * from t1 where a > 1;
分析:也是通过主键索引进行查询,查到a = 1的位置对应的叶结点,通过叶结点继续往后找就可以了
5、非聚簇索引(主键索引)例子
(1)在只有主键索引的情况下,查找如下
explain select * from t1 where b = 2 and c = 3 and d = 5;
分析:会通过全表扫描的方式查询,即通过主键索引搜到最小的页,从左往右扫描
(2)如果上面的查询语句是我们经常使用的查询,我们可以添加一个索引,再通过该索引idx_t1_bcd进行查询,该索引idx_t1_bcd也会生成一棵B+树,存的数据是该行的主键。同时数据的排序规则是,先对b排序,相同再对c排序,相同再对d排序。如图所示
create index idx_t1_bcd on t1(b, c, d);//建立一个bcd索引
explain select * from t1 where b = 2 and c = 3 and d = 5;

分析:通过索引idx_t1_bcd找到b = 2,c = 3,d = 5对应是数据是主键值是5,即a = 5,再通过该主键值继续通过主键索引去寻找具体的行
注意:非聚簇索引存储的是索引的数据(b,c,d)以及主键是数据(a),要寻找其他的数据需要继续通过聚簇索引(主键索引)查找,这样的操作叫做回表(特别重要)
覆盖索引
InnoDB存储引擎支持覆盖索引(covering index,或称索引覆盖),即从辅助索引中就可以得到查询记录,而不需要查询聚集索引中的记录。
使用覆盖索引的一个好处是:辅助索引不包含整行记录的所有信息,故其大小要远小于聚集索引,因此可以减少大量的IO操作
(3)如果条件也是b,c,d,只不过顺序不同,会怎么样查询呢?
explain select * from t1 where c = 1 and d = 1 and b = 1;
分析:也是会通过索引idx_t1_bcd进行查询,因为查询的时候在Server层会经过一个优化器,该优化器会对条件c,d,b进行调优再决定使用哪个索引去查询,最终选择索引idx_t1_bcd进行查询(例如,可以通过条件顺序调整成固定顺序b,c,d的形式)
(4)如果条件只是c和d,会怎么样查询呢??
explain select * from t1 where c = 1 and d = 1 ;
分析:会通过全表扫描进行查询,原因是因为b不知道,导致如果用索引idx_t1_bcd去查询的时候,不知道是向左边找还是向右边找,因此只能全表扫描
这就是最左前缀原则
B+ 树这种索引结构,可以利用索引的“最左前缀”,来定位记录。如果想使用哪个索引,一定要提供这个字段的最左边的字段,因为是按照最左边的字段顺序排序的
(5)如果条件只是b和d,会怎么样查询呢??
explain select * from t1 where b = 1 and d = 1 ;
分析:先通过索引idx_t1_bcd寻找到b=1的第一个页,因为哪些符合还不知道,因此在b=1的第一个页中开始往后遍历,当每一次都满足d也等于1(例如111),则拿到该数据项的主键值去回表查询,直到b不等于1为止就停止
这就是索引下推
在找到最左前缀对应的ID时,在非聚簇索引中开始往后找,满足条件的则进行回表(并不是每个都回表,只有满足条件才回表查询)
在 MySQL 5.6 之前,只能从 ID3 开始一个个回表。到主键索引上找出数据行,再对比字段值。
而 MySQL 5.6 引入的索引下推优化(index condition pushdown), 可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。
(6)如果条件只是b和e,会怎么样查询呢??
explain select * from t1 where b = 1 and e = 1 ;
分析:先通过索引idx_t1_bcd寻找到 b=1 的第一个页的第一个主键值,然后再在索引idx_t1_bcd中往后扫,每当满足b = 1的就回表查询,回表查询后发现e=1则返回数据,
下面的分析是错误的,由于主键索引是以a为键值,b = 1并不是在主键索引中是连续的,因此是错误的
先通过索引idx_t1_bcd寻找到 b=1 的第一个页的第一个主键值,再进行回表,找到主键索引中第一个满足条件的页的第一项,从左往右扫描,直到b不等于1为止
(7)如果条件只是b > 1,会怎么样查询呢??
explain select * from t1 where b > 1;
分析:优化器会分析情况一和情况二的性能,再做出决策
情况一:
先通过索引idx_t1_bcd寻找到b=1的第一个页的第一个主键值,再在索引idx_t1_bcd中往后扫,每当满足b > 1的就回表查询,(和6一样)
情况二:
可以通过主键索引进行全表查询
(8)如果条件如下
explain select c from t1 where b = 1;
分析:直接在索引idx_t1_bcd找到第一个b = 1的数据项,从左往右扫描,然后直接返回即可(覆盖索引)
(9)如果条件如下
create index idx_t1_e on t1(e);//建立一个e索引
explain select * from t1 where a = 1; (1)
explain select * from t1 where a = '1'; (2)
explain select * from t1 where e = 1; (3)
explain select * from t1 where e = '1'; (4)
可以很明确指定(1)会走a的索引,(4)会走e的索引
引理:字符串可以自动转数字,而数字不能自动转字符串
因此a = '1'会自动转换成a = 1,因此(2)也是走a的索引
然而e = 1不会自动转成‘1’,因此(4)只能e = 1去全表扫描,将查到的字符串e先将它转成数字,再和1比较
字符串作为索引去建一棵B+树,是通过某一个特定的规律进行字符串排序的,也就是说e = 1不能走idx_t1_e索引去搜,可能idx_t1_e索引的顺序是1,3,2,并非按照1,2,3的顺序排列