续上文:
一、理解page
1.1 理解单个page
MySQL 中要管理很多数据表文件,而要管理好这些文件,就需要 先描述,在组织 , 我们目前可以简单理解 成一个个独立文件是有一个或者多个Page 构成的。

不同的 Page ,在 MySQL 中,都是 16KB , 使用 prev 和 next 构成双向链表
因为有主键的问题, MySQL 会默认按照主键给我们的数据进行排序,从上面的 Page 内数据记录可以看出,数据是有序且彼此关联的。

为什么数据库在插入数据时要对其进行排序呢?我们按正常顺序插入数据不是也挺好的吗?
- 插入数据时排序的目的,就是优化查询的效率。
- 页内部存放数据的模块,实质上也是一个链表的结构,链表的特点也就是增删快,查询修改慢,所以优化查询的效率是必须的。
- 正式因为有序,在查找的时候,从头到后都是有效查找,没有任何一个查找是浪费的,而且,如果运气好,是可以提前结束查找过程的。
1.2 理解多个****Page
- 通过上面的分析,我们知道,上面页模式中,只有一个功能,就是在查询某条数据的时候直接将一整页的数据加载到内存中,以减少硬盘IO次数,从而提高性能。
- 但是,我们也可以看到,**现在的页模式内部,实际上是采用了链表的结构,**前一条数据指向后一条数据,本质上还是通过数据的逐条比较来取出特定的数据。
- 如果有1千万条数据,一定需要多个Page来保存1千万条数据,多个Page彼此使用双链表链接起来,而且每个Page内部的数据也是基于链表的。那么,查找特定一条记录,也一定是线性查找。这****效率也太低了。

如何提高page内部链式的遍历效率? ---> 引入业内目录
多page下,如何在page键查找的效率好?
二、页目录

通过目录查找内容 , 是现实中普遍存在的,那么就说明他一定能提高我们的效率【既定事实】。
2.1 单页情况
通过目录,索引到起始位置!提高对数据的检索速度

那么当前,在一个 Page 内部,我们引入了目录。比如,我们要查找 id=4记录,之前必须线性遍历4次, 才能拿到结果。 现在直接通过目录2[3] ,直接进行定位新的起始位置,提高了效率。 现在我们可以再次正式回答上面的问题了,为何通过键值 MySQL 会自动排序?
可以很方便引入页内目录
2.2 多页情况
MySQL 中每一页的大小只有 16KB ,单个Page大小固定 ,所以随着数据量不断增大, 16KB 不可能存下所有的数据,那么必定会有多个页来存储数据。

- 在单表数据不断被插入的情况下, MySQL 会在容量不足的时候,自动开辟新的****Page来保存新的数据,然后通过指针的方式,将所有的Page组织起来。
- 需要注意,上面的图,是理想结构,大家也知道,目前要保证整体有序,那么新插入的数据,不一定会在新Page上面,这里仅仅做演示。
- 这样,我们就可以通过多个****Page 遍历,Page内部通过目录来快速定位数据。可是,貌似这样也有效率问 题,在****Page之间,也是需要 MySQL 遍历的,遍历意味着依旧需要进行大量的IO,将下一个Page加载到 内存,进行线性检测。这样就显得我们之前的Page内部的目录,有点杯水车薪了。
那么如何解决呢? 解决方案,其实就是我们之前的思路,给 Page也带上目录。
- 使用一个目录项来执向某一页 , 而这个目录项存放的就是指将要指向的页中存放的最小数据的键值。
- 和内页目录不同之处在于 , 这种目录管理的级别是页 , 而内页目录管理的级别是行。
- 其中 , 每个目录项的构成是 : 指针 + 键值!


其实 目录页的本质也是页,普通页中存的数据是用户数据,而目录页中存的数据是普通页的地址。


这里我们做一个简要说明 , 为什么这样做 , 效率高?
- 叶子节点保存数据, 路上节点没有 ;非叶子结点 , 不需要数据, 只需要目录项 ;
- 非叶子结点 ,不存储数据 , 可以存储更多的目录项 、 目录页, 可以管理更多的叶子page ,这棵树 , 一定是个 "矮胖型" 的树 。所以 , 途径路上的结点 , 大大减少 , 找到目标数据只需要更少的page , IO次数少了 ,在IO层面上提高效率!
- 每个结点,都有目录项 , 可以大大提高搜索效率
这是传说中的 B+ 树啊!
B+树特点:
只有叶子节点存放真实数据(或指向数据的指针)。
非叶子节点只存放键值和指向子节点的指针。
叶子节点之间通过指针相连,形成一个有序链表,便于范围查询。
树的高度通常为2~4层,即使亿级数据,也只需少数几次IO。
没错,至此,我们已经给我们的表 user 构建完了主键索引。
随便找一个 id= ?我们发现,现在查找的 Page 数一定减少了,也就意味着 IO次数减少了,那么效率也就提高了。
我们把这个构建好的数据结构 , 称为 MySQL innodb 的索引结构!
InnoDB 在建立索引结构来管理数据的时候,其他数据结构为何不行?
明确一点 , 是MySQL选择了这个B+的数据结构 , 因为B+树的结构优势 , 例如 , 叶子结点的级联 , 就可以支持我们的范围查找 。 例如 , 想要查找10到20的数据 , 不需要从头全部去遍历 ,而是找到 10 这个起点 , 20这个重点即可!
| 数据结构 | 缺点 |
|---|---|
| 链表 | 线性查找,太慢 |
| 二叉搜索树 | 可能退化为链表 |
| AVL/红黑树 | 虽然平衡,但仍是二叉树,树高较大,IO次数多 |
| 哈希表 | 只支持等值查询,不支持范围查询;有哈希冲突问题 |
| B树 | 非叶子节点也存数据,导致单个节点能存的键值少,树更高;叶子节点不相连,范围查询效率低 |
2.3 B树 VS B+树
数据结构演示链接: https://www.cs.usfca.edu/\~galles/visualization/Algorithms.html
B 树

B+树

- B树节点,既有数据,又有Page指针,而B+,只有叶子节点有数据,其他目录页,只有键值和 Page指针
- B+叶子节点,全部相连,而B没有
为何选择B+
- 节点不存储data,这样一个节点就可以存储更多的key。可以使得树更矮,所以IO操作次数更少。
- 叶子节点相连,更便于进行范围查找
索引的本质,就是数据结构 --- B+
2.4 聚簇索引VS非聚簇索引
2.4.1 InnoDB的主键索引(聚簇索引)
在InnoDB中,表数据本身就是按B+树组织的,这棵树的叶子节点存放整行数据。这种索引叫做聚簇索引。
-
每个表只能有一个聚簇索引(通常是主键)。
-
如果没有定义主键,InnoDB会选择一个唯一的非空索引代替;如果没有,则隐式生成一个6字节的rowid作为主键。
-
数据文件和索引文件是同一个(.ibd文件)。
查看InnoDB表的数据文件:
[root@host]# ls innodb_test/
db.opt itest.frm itest.ibd
其中 itest.ibd 既存储索引,也存储数据。




2.4.2 MyISAM的主键索引(非聚簇索引)
MyISAM的索引和数据是分离的:
-
索引文件(.MYI)中叶子节点存放的是数据的地址(或指向数据文件的指针)。
-
数据文件(.MYD)单独存放行数据。
-
这种索引方案称为非聚簇索引。

.frm : 表结构数据
.MYD:该表对应的数据
.MYI : 该表对应的主键索引数据


三、索引操作
3.1 创建主键索引
-- 方式1:建表时指定
create table user1(id int primary key, name varchar(30));
-- 方式2:建表最后指定
create table user2(id int, name varchar(30), primary key(id));
-- 方式3:表后添加
alter table user3 add primary key(id);
主键索引的特点:
- 一个表中,最多有一个主键索引,当然可以使符合主键
- 主键索引的效率高(主键不可重复)
- 创建主键索引的列,它的值不能为null,且不能重复
- 主键索引的列基本上是int
3.2 唯一索引的创建
-- 方式1:建表时指定
create table user8(id int primary key, name varchar(20), index(name));
-- 方式2:表后添加
alter table user9 add index(name);
-- 方式3:指定索引名
create index idx_name on user10(name);
- 一个表中,可以有多个唯一索引
- 查询效率高
- 如果在某一列建立唯一索引,必须保证这列不能有重复数据
- 如果一个唯一索引上指定not null,等价于主键索引
3.3 普通索引的创建
-- 方式1:建表时指定
create table user8(id int primary key, name varchar(20), index(name));
-- 方式2:表后添加
alter table user9 add index(name);
-- 方式3:指定索引名
create index idx_name on user10(name);
普通索引的特点:
- 一个表中可以有多个普通索引,普通索引在实际开发中用的比较多
- 如果某列需要创建索引,但是该列有重复的值,那么我们就应该使用普通索引
3.4 全文索引的创建
当对文章字段或有大量文字的字段进行检索时,会使用到全文索引。 MySQL 提供全文索引机制,但是有要求,要求表的存储引擎必须是MyISAM ,而且默认的全文索引支持英文,不支持中文。如果对中文进行全文检索,可以使用sphinx 的中文版 (coreseek) 。
create table articles (
id int unsigned auto_increment not null primary key,
title varchar(200),
body text,
fulltext (title, body) -- 在title和body上创建联合全文索引
) engine=MyISAM;
insert into articles (title, body) values
('MySQL Tutorial', 'DBMS stands for DataBase ...'),
('How To Use MySQL well', 'After you went through a ...'),
('Optimizing MySQL', 'In this tutorial we will show ...'),
('1001 MySQL Tricks', '1. Never run mysqld as root. 2. ...'),
('MySQL vs. YourSQL', 'In the following database comparison ...'),
('MySQL Security', 'when configured properly, MySQL ...');

- 查询有没有database数据

- 可以用explain工具看一下,是否使用到索引

- 如何使用全文索引?

- 可以用explain工具看一下,是否使用到索引

3.5 查询索引
- 方法一 : show index from 表名

- 方法二:show keys from 表名\G

- 方法三:desc 表名

3.6 删除索引
- 第一种方法-删除主键索引: alter table 表名 drop primary key;

- 第二种方法-其他索引的删除: alter table 表名 drop index 索引名;索引名就是show keys from 表名中的 Key_name 字段

- 第三种方法方法: drop index 索引名 on 表名
3.7 索引创建原则
-
频繁作为查询条件的字段应创建索引。
-
唯一性太差的字段(如性别)不适合单独建索引,过滤效果差。
-
更新非常频繁的字段不适合建索引,因为更新索引代价高。
-
不会出现在where子句中的字段无需索引。
-
尽量使用覆盖索引(索引中已包含所需字段,避免回表)。
-
复合索引要注意最左匹配原则。