目录
1. MySQL是如何处理数据的
2. MySQL与磁盘的关系
3. MySQL与磁盘交互的基本单位
4.MySQL和磁盘之间联系的总结
5.索引的理解
5.1 理解单个page
5.2 理解多个page
5.3 为什么采用B+树
5.4 聚簇索引 和 非聚簇索引
5.5 聚簇索引 和 非聚簇索引下的普通索引
6.索引操作
6.1 创建主键索引
6.2 创建唯一索引
6.3 创建普通索引
6.4 查询索引
6.5 删除索引
6.6 全文索引
7.复合索引
7.1 最左匹配原则
8.索引覆盖
1. MySQL是如何处理数据的
MySQL 中的数据文件,是以page为单位保存在磁盘当中的。找到了对应的扇区之后呢?
MySQL 的 CURD 操作,都需要通过计算,找到对应的插入位置,或者找到对应要修改或者查询的数据,谁又可以帮助计算呢?
2. MySQL与磁盘的关系
mysqld本质上就是一个在应用层的进程,所以他一定在操作系统之上运行的。当MySQL对内部的数据进行操作(CURD)的时候,本质上就是操作文件内容。所以文件必须先被打开,在哪里打开呢?难道是磁盘吗?肯定不是,文件需要被加载到内存当中,才能被同在内存中的进程获取到。
如果数据不在内存中呢?换入换出。换出来的内容放在哪里呢?所以mysql内部一定管理有一块空间,用来读取存放在内核缓冲区中的数据,不然每次都要开辟空间。所以在MySQL启动的时候,要预先申请一批空间。
3. MySQL与磁盘交互的基本单位
MySQL本身就是用来处理数据的,所以少不了和磁盘进行接触。磁盘扇区大小为512字节,为了避免大量IO,以及减少耦合,文件系统读取以块为单位读取磁盘数据,单位是4KB。而MySQL进行IO的基本单位是16KB,这个基本数据单元称为page(和系统的page不一样)。
所以MySQL一次读取,就会至少读4个linux kernel,也就是4*8个扇区。
为何MySQL和磁盘进行IO交互的时候,要采用Page的方案进行交互呢?用多少,加载多少可以吗?
如过有5条记录,如果MySQL要查找id=2的记录,第一次加载id=1,第二次加载id=2,一次一条记录,那么就需要2次IO。如果要找id=5,那么就需要5次IO。
但如果这5条都被保存在一个Page中(16KB,能保存很多记录),那么第一次IO查找id=2的时
候,整个Page会被加载到MySQL的Buffer Pool中,这里完成了一次IO。但是往后如果在查找id=1,3,4,5等,完全不需要进行IO了,而是直接在内存中进行了。所以,就在单Page里面,大大减少了IO的次数。
MySQL申请的缓冲区中基本IO单位为page,说明内部一定有很多page,那么也就需要他来管理这些数据,用什么样的数据结构管理这些数据,也就是建立索引之后搜索变快的基础。
4.MySQL和磁盘之间联系的总结
- MySQL 中的数据文件,是以page为单位保存在磁盘当中的。
- MySQL 的 CURD 操作,都需要通过计算,找到对应的插入位置,或者找到对应要修改或者查询的数据。而只要涉及计算,就需要CPU参与,而为了便于CPU参与,一定要能够先将数据移动到内存当中。
- 所以在特定时间内,数据一定是磁盘中有,内存中也有。后续操作完内存数据之后,以特定的刷新策略,刷新到磁盘。而这时,就涉及到磁盘和内存的数据交互,也就是IO了。而此时IO的基本单位就是Page。
- 为了更好的进行上面的操作, MySQL 服务器在内存中运行的时候,在服务器内部,就申请了被称为 Buffer Pool 的的大内存空间,来进行各种缓存。其实就是很大的内存空间,来和磁盘数据进行IO交互。
- 为何更高的效率,一定要尽可能的减少系统和磁盘IO的次数
5.索引的理解
建立一张有主键的表,然后随便插入几条数据。
mysql> desc t1;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| age | int(11) | YES | | NULL | |
| name | varchar(10) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
mysql> insert into t1 values(3,18,'杨过');
mysql> insert into t1 values(4, 16, '小龙女');
mysql> insert into t1 values(2, 26, '黄蓉');
mysql> insert into t1 values(5, 36, '郭靖');
mysql> insert into t1 values(1, 56, '欧阳锋');
mysql> select * from t1;
+----+------+-----------+
| id | age | name |
+----+------+-----------+
| 1 | 56 | 欧阳锋 |
| 2 | 26 | 黄蓉 |
| 3 | 18 | 杨过 |
| 4 | 16 | 小龙女 |
| 5 | 36 | 郭靖 |
+----+------+-----------+
现象:我并没有按照主键大小的顺序进行插入,但是在表中这些数据已经按照一定顺序帮我排列好了。
谁做的?mysql自己做的。
为什么做?只有page里面的数据是有序的,才能方便的引入页内目录(后面说),才能加快查找速度。
5.1 理解单个page
先描述,在管理
但是当我单个page内的数据非常多,每次查找一个就要遍历一遍链表吗?使用二分?
加一个页内目录即可,就像书籍前面的目录一样,方便读者查找文章内容。
在单个页中存放了多条数据信息,从而避免了频繁的IO操作,大大加快了搜索速度。可我怎么知道我需要的数据就在这个page,而不是前一个或者后一个呢?
5.2 理解多个page
如果真的采用上述双链表样的结构存储数据,由于链表的遍历是O(n)的,找对应的page需要遍历一遍,那效率也太低了。
所以我们需要将页内目录也扩展到page与page之间。
- 使用一个目录项来指向某一页,而这个目录项存放的就是将要指向的页中存放的最小数据的键值。
- 和页内目录不同的地方在于,这种目录管理的级别是页,而页内目录管理的级别是行。
- 其中,每个目录项的构成是:键值+指针。
存在一个目录页来管理页目录,目录页中的数据存放的就是指向的那一页中最小的数据。有数据,就可通过比较,找到该访问那个Page,进而通过指针,找到下一个Page。
目录页的本质也是页
普通页中存的数据是用户数据,而目录页中存的数据是普通页的地址。
目录页所在结构无需加前后指针,因为
只有相邻的普通页由于连续存储,一个page存不下了,需要创建第二个page,这样两个page使用前后指针方便了查找,而无需再从类似根节点的地方再走一遍。
目录页不存数据,因为
虽然存数据可以方便某一些查找情况下直接返回数据,但是如果页目录没有数据,就意味着16KB的空间全部可以用来保存(id,page_address),会让这颗树变得矮胖,所以走到叶子节点所经历的中间节点就少了,也就意味着IO的次数少了,所以更快了。
综上,存储的结构已经显现,就是B+树。
5.3 为什么采用B+树
链表——线性遍历,太慢。
二叉搜索树——退化问题,而且是二叉,树层高。
AVL、红黑树——再平衡也是二叉树,树层高。
Hash——MySQL其实支持Hash,不过 InnoDB 和 MyISAM 并不支持,在面对范围查找就明显不行
B树
可以发现B树会在近似页目录的机构中添加数据。即B树的节点既有数据、又有page指针,叶子节点有键值和数据。
B+树
而B+树只有叶子节点有数据,其他目录页,只有键值和page指针,可以使树更矮,减少了IO次数。而且叶子节点相连,更有利于进行范围查找。
5.4 聚簇索引 和 非聚簇索引
二者底层存储结构都是B+树,但是存储的数据内容不一样。
MyISAM存储引擎,叶节点的数据域存放的是数据记录的地址。
MyISAM最大的特点就是将索引page和数据page分离,叶节点只有对应数据的地址。
而InnoDB是将索引和数据放在一起的。
MyISAM 这种用户数据与索引数据分离的索引方案,叫做非聚簇索引
mysql> create table mtest(id int primary key,
name varchar(11) not null)engine=MyISAM;
mtest.frm 表的数据结构
mtest.MYD 该表对应的数据,现在没插入数据 所以是0
mtest.MYI 该表对应的主键索引
InnoDB 这种用户数据与索引数据在一起索引方案,叫做聚簇索引
mysql> create table itest(id int primary key,
name varchar(11) not null)engine=InnoDB;
itest.frm 表结构数据
itest.ibd 该表对应的主键索引和用户数据,虽然没有数据 但是有主键索引,所以不是0
5.5 聚簇索引 和 非聚簇索引下的普通索引
MyISAM ,建立普通索引和主键索引没有差别,无非就是主键不能重复,而非主键可重复。以第二列建立索引。
InnoDB 除了主键索引,用户也会建立普通索引
InnoDB 的非主键索引中叶子节点并没有数据,而只有对应记录的key值。
所以通过普通索引,找到目标记录,需要两遍索引:首先检索辅助索引获得主键,然后用主键
到主索引中检索获得记录。这种过程,就叫做回表查询。
6.索引操作
6.1 创建主键索引
在建表的时候,创建主键,自动建立主键索引。
create table user1(id int primary key, name varchar(30));
create table user3(id int, name varchar(30));
alter table user3 add primary key(id)
主键索引名字为PRIMARY
6.2 创建唯一索引
创建唯一键时自动建立唯一索引
create table user4(id int primary key, name varchar(30) unique);
create table user6(id int primary key, name varchar(30));
alter table user6 add unique(name);
唯一索引名为列名
6.3 创建普通索引
在表的定义最后,指定某列为索引
create table user8(id int primary key,
name varchar(20),
email varchar(30),
index(name));
创建完表以后指定某列为普通索引
create table user9(id int primary key, name varchar(20),
email varchar(30));
alter table user9 add index(name);
mysql> desc user9;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | varchar(20) | YES | MUL | NULL | |
| email | varchar(30) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
创建一个索引名为 idx_name 的索引
create table user10(id int primary key, name varchar(20),
email varchar(30));
create index idx_name on user10(name);
注意:
- 建立普通索引默认,索引名字和列名字相同。
- 如果给普通索引起别名,删除时需要指定该别名。
6.4 查询索引
show index from 表名;
mysql> show index from user10;
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| user10 | 0 | PRIMARY | 1 | id | A | 0 | NULL | NULL | | BTREE | | |
| user10 | 0 | email | 1 | email | A | 0 | NULL | NULL | YES | BTREE | | |
| user10 | 1 | name | 1 | name | A | 0 | NULL | NULL | YES | BTREE | | |
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
6.5 删除索引
删除主键索引
alter table 表名 drop primary key;
其他索引的删除
alter table 表名 drop index 索引名;
其他写法
drop index 索引名 on 表名;
6.6 全文索引
当对文章字段或有大量文字的字段进行检索时,会使用到全文索引。MySQL提供全文索引机制,但是有要求,要求表的存储引擎必须是MyISAM,而且默认的全文索引支持英文,不支持中文。
查询有没有database数据
如果采用以下查询方式,虽然有结果但是没有用到全文索引。
mysql> select * from articles where body like '%database%';
使用全文索引
mysql> SELECT * FROM articles
-> WHERE MATCH (title,body) AGAINST ('database');
+----+-------------------+------------------------------------------+
| id | title | body |
+----+-------------------+------------------------------------------+
| 5 | MySQL vs. YourSQL | In the following database comparison ... |
| 1 | MySQL Tutorial | DBMS stands for DataBase ... |
+----+-------------------+------------------------------------------+
7.复合索引
相当于在键值中比较的是(col1,col2),名字和col1的列名相同。先比较col1,在比较col2。
7.1 最左匹配原则
CREATE INDEX IDX_XXX ON TABLE(COL3, COL2);
详见
复合索引的底层数据结构——最左原则_复合索引在底层的数据结构是什么_长不大的大灰狼的博客-CSDN博客
8.索引覆盖
通俗来说,就是select的数据列只用从索引中就能够取得,不必从数据表中读取,换句话说查询列要被所使用的索引覆盖。
比如
id为聚集索引,name为非聚集索引:
select name, age from t where name = 'chy';
由于select后面有name 和 age,此时就需要回表查询。
但是如果在SQL中只查询name字段。这样name的索引就覆盖到了所有的查询列。
select name from t where name = 'chy';
或者将name的索引修改为联合索引(name, age )
select name, age from t where name = 'chy';
这样也覆盖到了所有的查询列。