赞
踩
目录
InnoDB在建立索引结构来管理数据的时候,为什么其他数据结构不行?
前言
Q:没有索引,可能会有什么问题?
索引:提高数据库性能,且不需要加内存改程序,就能够让查询速度提高成百上千倍数。 但是查询速度的提高是以插入、更新、删除的速度为代价的,这些操作增加了大量的IO。
所以它的价值在于提高一个海量数据的检索速度。
索引
常见索引分为:
案例:
先制造一个海量表,在查询的时候看看没有索引的时候会有什么问题?
- --构建一个8000000条记录的数据
- --构建的海量表数据需要有差异性,所以使用存储过程来创建, 拷贝下面代码就可以了,暂时不用理解
- -- 产生随机字符串
- delimiter $$
- create function rand_string(n INT)
- returns varchar(255)
- begin
- declare chars_str varchar(100) default
- 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
- declare return_str varchar(255) default '';
- declare i int default 0;
- while i < n do
- set return_str =concat(return_str,substring(chars_str,floor(1+rand()*52),1));
- set i = i + 1;
- end while;
- return return_str;
- end $$
- delimiter ;
- --产生随机数字
- delimiter $$
- create function rand_num()
- returns int(5)
- begin
- declare i int default 0;
- set i = floor(10+rand()*500);
- return i;
- end $$
- delimiter ;
- --创建存储过程,向雇员表添加海量数据
- delimiter $$
- create procedure insert_emp(in start int(10),in max_num int(10))
- begin
- declare i int default 0;
- set autocommit = 0;
- repeat
- set i = i + 1;
- insert into EMP values ((start+i)
- ,rand_string(6),'SALESMAN',0001,curdate(),2000,400,rand_num());
- until i = max_num
- end repeat;
- commit;
- end $$
- delimiter ;
- -- 执行存储过程,添加8000000条记录
- call insert_emp(100001, 8000000);

这样就创造出一张海量表了
查询标号为998866的员工
select * from EMP where empno=998877;
一共耗时将近5s,这还是在本地单人操作,在实际项目中会有很多人并发查询,这时候就得死机了。
解决方法 = 创建索引:
alter table EMP add index(empno);
认识磁盘:
MySQL 给用户提供存储服务,而存储的都是数据,数据在磁盘这个外设当中。磁盘是计算机中的一个机械设备,相比于计算机其他电子元件,磁盘效率是比较低的,在加上IO本身的特征,可以知道,如何提交效率,是 MySQL 的一个重要话题
看看磁盘什么样:

其中的一个盘片:

数据库文件,本质其实就是保存在磁盘的盘片当中。也就是上面的一个个小格子中,就是我们经常所说的扇区。当然,数据库文件很大,也很多,一定需要占据多个扇区
TIPS:
所以最基本的,找到一个文件的全部,本质就是在磁盘找到所有保存文件的扇区。
而我们能够定位任何一个扇区,那么就能找到所有扇区,因为查找方式都一样(磁头疯狂摇摆)

我们现在已经能够在硬件层面定位,任何一个基本数据块(扇区),就直接按照扇区(512字节,部分4096字节),进行IO交互吗?
不是!
故,系统读取磁盘,是以块为单位的,基本单位是 4KB 。
随机访问:
本次IO所给出的扇区地址和上次IO给出扇区地址不连续,这样的话磁头在两次IO操作之间需要作比较大的移动动作才能重新开始读/写数据
连续访问:
如果当次IO给出的扇区地址与上次IO结束的扇区地址是连续的,那磁头就能很快的开始这次
IO操作,这样的多个IO操作称为连续访问
MySQL与磁盘交互的基本单位:
MySQL 作为一款应用软件,可以想象成一种特殊的文件系统。它有着更高的IO场景,所以,为了提高基本的IO效率, MySQL 进行IO的基本单位是 16KB
(本篇分享默认存储引擎为innodb)
- mysql> SHOW GLOBAL STATUS LIKE 'innodb_page_size';
- +------------------+-------+
- | Variable_name | Value |
- +------------------+-------+
- | Innodb_page_size | 16384 | -- 16*1024=16384
- +------------------+-------+
- 1 row in set (0.01 sec)
也就是说,磁盘这个硬件设备的基本单位是 512 字节,而 MySQL InnoDB引擎 使用 16KB 进行IO交互。即, MySQL 和磁盘进行数据交互的基本单位是 16KB 。这个基本数据单元,在 MySQL 这里叫做page(注意和系统的page区分)
建立共识
索引的理解:
- create table if not exists user (
- id int primary key, --一定要添加主键哦,只有这样才会默认生成主键索引
- age int not null,
- name varchar(16) not null
- );
- mysql> show create table user \G
- *************************** 1. row ***************************
- Table: user
- Create Table: CREATE TABLE `user` (
- `id` int(11) NOT NULL,
- `age` int(11) NOT NULL,
- `name` varchar(16) NOT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8 --默认就是InnoDB存储引擎
- 1 row in set (0.00 sec)
这里要记得设置主键,这样就有默认的主键索引
那么上面说了MySQL和磁盘IO交互的时候是采用Page(16K ,蛮大的)。为什么不按需加载呢?
比如我像查找id = 1的我就跟磁盘IO一次,查找id = 2也IO一次, 不累吗?
如果我要查找5条记录我还要跟硬盘IO五次太麻烦了,我直接一次IO就加载上来Page大小(16K),下次再查找该数据的就从内存找
但我们也不能保证一定能把想要的数据成功加载到内存,但有很大概率,因为局部性原理
打个比方:你有个硬币存钱罐;你准备去买菜要 7块6毛5分。你从零钱罐里一次抓一大把,那肯定是够钱的,至于能不能抓到6毛和5分只能说有很大概率能抓到。
MySQL中要管理很多数据表文件,我们可以简单理解成一个个独立文件是由很多个Page构成的
那么如何管理多个Page的结构呢?

不同的Page在MySQL中都是16KB,使用prev和next构成双向链表
因为有主键的问题,MySQL会默认按照主键给我们的数据进行排序

那么当前,在一个Page内部,我们引入了目录。比如,我们要查找id=4记录,之前必须线性遍历4次,才能拿到结果。现在直接通过目录2[3],直接进行定位新的起始位置,提高了效率。现在我们可以再次正式回答上面的问题了,为何通过键值 MySQL 会自动排序?
MySQL 中每一页的大小只有 16KB ,单个Page大小固定,所以随着数据量不断增大, 16KB 不可能存下所有的数据,那么必定会有多个页来存储数据。
需要注意,上面的图,是理想结构,大家也知道,目前要保证整体有序,那么新插入的数据,不一定会在新Page上面,这里仅仅做演示
这样,我们就可以通过多个Page遍历,Page内部通过目录来快速定位数据。可是,貌似这样也有效率问题,在Page之间,也是需要 MySQL 遍历的,遍历意味着依旧需要进行大量的IO,将下一个Page加载到内存,进行线性检测。这样就显得我们之前的Page内部的目录,有点杯水车薪了
那么解决方案也就是给Page也带上目录:

存在一个目录页来管理页目录,目录页中的数据存放的就是指向的那一页中最小的数据。有数据,就可通过比较,找到该访问那个Page,进而通过指针,找到下一个Page
其实目录的本质也是也,不同页中存的是用户数据,这里的页存的就是普通页的地址
可是,我们每次检索数据的时候,该从哪里开始呢?虽然顶层的目录页少了,但是还要遍历啊?不用担心,可以在加目录页

这就是传说中的b+树,没错致辞就给我们的表构建好索引了
随便找一个id我们需要查找的Page数一定减少了,也就意味着IO减少了
俄罗斯套娃一般的操作,首先我们需要查找单个页中的数据,单个页大小16K,遍历慢,那么给单个页加上目录,这样起码在单个页中查找没有问题
第二步:当我们的页多了起来,发现我们需要遍历很多页的目录来知道该成员到底在哪里,所以这时候就把这多个页管理起来,让我们以O(1)的时间能够找到元素所在的页
第三部:我们对页目录管理起来,一次就能找到对应的页目录
这样一层一层管理起来之后只需要置顶向下找,并加载部分目录到内存,就可以完成查找过程,大大减少了IO次数
那么为什么不用B数而是用B+树? B树:
B+数:
这两棵树的对我们最有意义的区别就是:
为何选择B+树:
MyISAM这种索引数据分离的索引方案,叫做非聚簇索引
MyISAM 存储引擎-主键索引
MyISAM 引擎同样使用B+树作为索引结果,叶节点的data域存放的是数据记录的地址。下图为 MyISAM表的主索引, Col1 为主键。
其中, MyISAM 最大的特点是,将索引Page和数据Page分离,也就是叶子节点没有数据,只有对应数据的地址。
相较于 InnoDB 索引, InnoDB 是将索引和数据放在一起的就是聚簇索引
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);
主键索引的特点:
同上跟创建主键索引的模式一模一样
把primary key关键字改成 unique即可;
唯一索引的特点:
- 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),
- emailvarchar(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);
普通索引的特点:
当对文章字段或有大量文字的字段进行检索时,会使用到全文索引。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 ...');
如果使用如下查询方式,虽然查询出数据,但是没有使用到全文索引
- mysql> select * from articles where body like '%database%';
- +----+-------------------+------------------------------------------+
- | id | title | body |
- +----+-------------------+------------------------------------------+
- | 1 | MySQL Tutorial | DBMS stands for DataBase ... |
- | 5 | MySQL vs. YourSQL | In the following database comparison ... |
- +----+-------------------+------------------------------------------+
可以用explain工具看一下,是否使用到索引:
- mysql> explain select * from articles where body like '%database%'\G
- *************************** 1. row ***************************
- id: 1
- select_type: SIMPLE
- table: articles
- type: ALL
- possible_keys: NULL
- key: NULL <== key为null表示没有用到索引
- key_len: NULL
- ref: NULL
- rows: 6
- Extra: Using where
- 1 row in set (0.00 sec)
- 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 ... |
- +----+-------------------+------------------------------------------+
通过explain分析一下这个sql语句:
- mysql> explain SELECT * FROM articles WHERE MATCH (title,body) AGAINST
- ('database')\G
- *************************** 1. row ***************************
- id: 1
- select_type: SIMPLE
- table: articles
- type: fulltext
- possible_keys: title
- key: title <= key用到了title
- key_len: 0
- ref:
- rows: 1
- Extra: Using where
- mysql> show keys from goods\G
- *********** 1. row ***********
- Table: goods <= 表名
- Non_unique: 0 <= 0表示唯一索引
- Key_name: PRIMARY <= 主键索引
- Seq_in_index: 1
- Column_name: goods_id <= 索引在哪列
- Collation: A
- Cardinality: 0
- Sub_part: NULL
- Packed: NULL
- Null:
- Index_type: BTREE <= 以二叉树形式的索引
- Comment:
- 1 row in set (0.00 sec)
比较频繁作为查询条件的字段应该创建索引
唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
不会出现在where子句中的字段不该创建索引
更新非常频繁的字段不适合作创建索引
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。