MySQL学习:
https://blog.csdn.net/2301_80220607/category_12971838.html?spm=1001.2014.3001.5482
前言:
在前面我们基本上已经把MySQL的基础知识都进行了学习,但是我们之前处理的数据都是十分少的,但是如果当我们的数据量很大的时候,比如一张表中有一百万个数据,我们要对其中一个进行查询的时候,效率就会很慢了,这个时候我们就可以借助索引来帮我们完成提高效率的工作,索引简单点来说就是将数据以特定的数据结构组织起来,从而方便查询和管理
1. 索引的概念和作用
1.1 索引的概念
索引是数据库中一种特殊的数据结构,它类似于书籍的目录,能够帮助数据库系统快速定位到表中的特定数据,而不必扫描整个表。索引本质上是通过额外的数据结构(如B+树、哈希表等)对表中的一个或多个列的值进行排序和组织,从而加速数据检索操作。
1.2 索引的作用
索引可以提高数据库的性能,而且索引不用加内存,不用改程序,不用调sql,只需要执行正确的创建索引语句,就可以很方便的帮助我们在大量的数据中进行查找工作,它的本质就是通过特定的数据结果对数据库中的数据进行管理,主要是通过B+树
1.3 索引的局限
没有什么东西是百利而无一弊的,索引虽然可以提高我们的查询速度,但是插入、更新、和删除的速度降低为代价的,因为本来这些操作就涉及大量的IO操作,索引的创建会增加IO操作次数,IO操作会大大影响这些操作的效率。同时索引的创建还会消耗额外的内存空间。
但是对于拥有海量数据的数据库,索引的创建仍是很有必要的。
2. 索引的底层数据结构
关于索引的使用其实还是比较简单的,关键是我们需要明白索引的实现原理是什么,这里我们就讲一下索引的底层实现
2.1 MySQL与磁盘交互基本单位
在计算机硬盘的实现中,硬盘是由多个扇面组成的,每个扇面上又被划成不同的扇区,每个扇区的大小都是512字节,即我们存储在磁盘中的内容都是以512字节作为存储单元的。
那这是不是意味着我们所有的I/O操作都是以512字节为单位的呢?
答案其实是否定的。不同的服务进行I/O操作的单位其实是不同的,比如我们的MySQL的操作单位是16KB,那为什么不是512字节而是16KB呢?这其实与MySQL服务的所在层级有关
如图,MySQL服务实际上是作为一个服务进程在应用层跑动的,所以MySQL并不是直接与磁盘或内存进行交互的,它是通过操作系统(OS)提供的接口与磁盘进行数据的传送的,虽然磁盘的操作单位为512字节,但是MySQL服务综合考虑速度、容量等各方面因素,它所选择的操作单位是16KB
实现方法就是:在操作系统层次和MySQL服务应用层上实际上都有一个文件缓冲区的存在,MySQL服务写入的内容在将buffer pool写满之后传给操作系统,操作系统再将这部分内容传给磁盘;同理,磁盘的操作也是这样的,磁盘将MySQL服务所需数据传给操作系统,操作系统再将数据传给MySQL服务
这个基本的存储单元就叫做MySQL的页
还遗漏了一个重要的知识点是在MySQL的底层实现中,不同的存储引擎的实现是不同的,但是不同的地方主要体现在页的管理上,上面的内容基本上实现还都是一样的
2.2 建立共识
- MySQL中的数据文件,是以page为单位保存在磁盘中的
- MySQL的CURD操作,都是通过计算,找到对应的插入位置,或者找到对应的要查询或修改的数据
- 只要涉及到运算,就需要CPU参与,为了方便CPU的参与,一定要先将数据移动到内存中
- 所以在特定的时间内,数据一定是磁盘中有,内存中也有。后续操作完内存数据之后,以特定的刷新策略,刷新到磁盘。而这时,就涉及到磁盘和内存的数据交互,也就是IO了。而此时IO的基本单位就是Page
- 为了更好的进行上面的操作, MySQL 服务器在内存中运行的时候,在服务器内部,就申请了被称为 BufferPool 的的大内存空间,来进行各种缓存。其实就是很大的内存空间,来和磁盘数据进行IO交互。
- 为了提高效率,一定要尽可能的减少系统和磁盘IO的次数
2.3 索引的理解
建立测试表:
create table if not exists user (
id int primary key, --一定要添加主键哦,只有这样才会默认生成主键索引
age int not null,
name varchar(16) not null
);
插入多条测试记录:
--插入多条记录,注意,我们并没有按照主键的大小顺序插入哦
mysql> insert into user (id, age, name) values(3, 18, '杨过');
Query OK, 1 row affected (0.01 sec)
mysql> insert into user (id, age, name) values(4, 16, '小龙女');
Query OK, 1 row affected (0.00 sec)
mysql> insert into user (id, age, name) values(2, 26, '黄蓉');
Query OK, 1 row affected (0.01 sec)
mysql> insert into user (id, age, name) values(5, 36, '郭靖');
Query OK, 1 row affected (0.00 sec)
mysql> insert into user (id, age, name) values(1, 56, '欧阳锋');
Query OK, 1 row affected (0.00 sec)
查看插入结果:
select * from user;
观察这个插入结果,我们可以发现,我们并没有按照id顺序来插入数据,但是最后却发现插入后的数据是按照id来排序的,id是主键,其实原因是建表时被设置为主键的列会默认建立索引,索引通过这样排序的方式就可以帮助我们更快的查找到我们想要的数据
但是仅仅知道这些还是不够的,我们需要知道索引工作的原理,下面我们就来看一下
2.4 索引的底层实现
关于page的认识
上面我们讲过MySQL与磁盘的交互单位为page(16KB),但是为什么呢?为什么不用多少,加载多少呢?
这其实是为了提高效率,比如上面我们插入的数据,我们现在如果要查找id=1的记录,按照用多少取多少的方式,我们就需要直接把它从磁盘加载到MySQL服务端,进行依次I/O操作,如果又想查id=2的记录,就有需要再进行这样一次的I/O
需要注意的是这样的I/O操作在计算机运行中,会消耗大量的空间,所以为了提高效率我们必须想办法减少I/O操作的次数,所以我们就可以一次直接I/O更多的数据(page),比如把五条记录全部取了,这样不管要哪条记录,我们的服务端都可以直接在自己的缓冲区中找就可以了,这样就节省的大量的时间
当然并不是每次要取的数据都能在同一个page页,但是根据局部性原理,还是能够保证我们在大部分情况下都是效率更高的
理解单个page
上面讲了page的概念后,实际上我们就应该认识到page作为MySQL的存储单元一定会伴随着许多的设计的。
MySQL中有很多表,这些表中存放着大量的数据,我们可以理解成这些表中的数据是存放在一个或多个page中的,由于大量page表的存在,所以我们需要对page进行组织管理
如上,就是page的基本构成,page中的数据是以链表的形式存放的,同时page自身也是通过链表的形式进行组织的,它里面有两个指针分别指向前一个page和后一个page
理解多个page
- 通过上面的分析,我们知道,上面页模式中,只有一个功能,就是在查询某条数据的时候直接将一整页的数据加载到内存中,以减少硬盘IO次数,从而提高性能。但是,我们也可以看到,现在的页模式内部,实际上是采用了链表的结构,前一条数据指向后一条数据,本质上还是通过数据的逐条比较来取出特定的数据。
-
如果有 1 千万条数据,一定需要多个 Page 来保存 1 千万条数据,多个 Page 彼此使用双链表链接起来,而且每个 Page内部的数据也是基于链表的。那么,查找特定一条记录,也一定是线性查找。这效率也太低了。

页目录
现在有一本书,我们找到其中某些内容的时候,一定是先看目录,找到这些内容对应的页数,然后再根据页数再去找这部分内容,这样可以帮助我们节省很多时间。
页目录会占用几页,这是一种空间换时间的做法,但是仍然是十分值得去做的
page页目录
为了方便我们的page进行高效查找,我们可以对page页也进行添加目录操作,即可以添加在page内对page中内容进行管理,也可以添加在page外对page进行管理
添加在page内
那么当前,在一个Page内部,我们引入了目录。比如,我们要查找id=4记录,之前必须线性遍历4次,才能拿到结果。现在直接通过目录2[3],直接进行定位新的起始位置,提高了效率。现在我们可以再次正式回答上面的问题了,为何通过键值 MySQL 会自动排序?
- 可以很方便引入目录
添加在page外
MySQL 中每一页的大小只有 16KB ,单个Page大小固定,所以随着数据量不断增大, 16KB 不可能存下所有的数据,那么必定会有多个页来存储数据。
我们前面讲过page页中会有两个指针的,这两个指针就是帮助我们建立这样的双链表的结构的
在单表数据不断被插入的情况下, MySQL 会在容量不足的时候,自动开辟新的Page来保存新的数据,然后通过指针的方式,将所有的Page组织起来。
需要注意,上面的图,是理想结构,大家也知道,目前要保证整体有序,那么新插入的数据,不一定会在新Page上面,这里仅仅做演示。
这样,我们就可以通过多个Page遍历,Page内部通过目录来快速定位数据。可是,貌似这样也有效率问题,在Page之间,也是需要 MySQL 遍历的,遍历意味着依旧需要进行大量的IO,将下一个Page加载到内存,进行线性检测。这样就显得我们之前的Page内部的目录,有点杯水车薪了。
那么解决方法是什么呢?解决方法,其实就是我们之前的思路,给page也带一个目录
存在一个目录页来管理页目录,目录页中的数据存放的就是指向的那一页中最小的数据。有数据,就可通过比较,找到该访问那个Page,进而通过指针,找到下一个Page。
其实目录页的本质也是页,普通页中存的数据是用户数据,而目录页中存的数据是普通页的地址。
可是,我们每次检索数据的时候,该从哪里开始呢?虽然顶层的目录页少了,但是还要遍历啊?不用担心,可以在加目录页
我们观察就可以发现这就是数据结构中的B+树啊!!至此,我们就给我们的user表建立了主键索引。现在随便找一个id=?的数据,我们会发现查询速度会快很多。
复盘一下
- Page分为目录页和数据页。目录页只放各个下级Page的最小键值。
- 查找的时候,自顶向下找,只需要加载部分目录页到内存,即可完成算法的整个查找过程,大大减少了IO次数
以上就是索引的底层实现,但是需要注意的是我们讲的这种B+树的底层实现方式,适用的主要是MyISAM存储引擎,不同的存储引擎的底层实现可能是不同的,比如我们还常用的另一种存储引擎InnoDB就是常用B树来作为底层数据结构,用B树实现的存储引擎它的用户数据和索引数据不会分离,被称为聚簇索引;而用B+树实现的索引类型一般为非聚簇索引
3. 索引操作
3.1 创建索引
3.1.1 创建主键索引
- 第一种方法
-- 在创建表的时候,直接在字段名后指定 primary key
create table user1(id int primary key, name varchar(30));
- 第二种方法
-- 在创建表的最后,指定某列或某几列为主键索引
create table user2(id int, name varchar(30), primary key(id));
- 第三种方法
create table user3(id int, name varchar(30));
-- 创建表以后再添加主键
alter table user3 add primary key(id);
主键索引的特点:
- 一个表中,最多有一个主键索引,当然可以使符合主键
- 主键索引的效率高(主键不可重复)
- 创建主键索引的列,它的值不能为null,且不能重复
- 主键索引的列基本上是int
3.1.2 创建唯一索引
- 第一种方式
-- 在表定义时,在某列后直接指定unique唯一属性。
create table user4(id int primary key, name varchar(30) unique);
- 第二种方式
-- 创建表时,在表的后面指定某列或某几列为unique
create table user5(id int primary key, name varchar(30), unique(name));
- 第三种方式
create table user6(id int primary key, name varchar(30));
alter table user6 add unique(name);
唯一索引的特点:
- 一个表中,可以有多个唯一索引
- 查询效率高
- 如果在某一列建立唯一索引,必须保证这列不能有重复数据
- 如果一个唯一索引上指定not null,等价于主键索引
3.1.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); --创建完表以后指定某列为普通索引
- 第三种方式
create table user10(id int primary key, name varchar(20), email varchar(30));
-- 创建一个索引名为 idx_name 的索引
create index idx_name on user10(name);
普通索引的创建:
- 一个表中可以有多个普通索引,普通索引在实际开发中用的比较多
- 如果某列需要创建索引,但是该列有重复的值,那么我们就应该使用普通索引
3.1.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)
)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数据
如果使用如下查询方式,虽然查询出数据,但是没有使用到全文索引
select * from articles where body like '%database%';
可以用explain工具看一下,是否使用到索引
explain select * from articles where body like '%database%'\G
- 如何使用全文索引
SELECT * FROM articles
-> WHERE MATCH (title,body) AGAINST ('database');
通过explain来分析这个sql语句
explain SELECT * FROM articles WHERE MATCH (title,body) AGAINST ('database')\G