目录
- 索引的理解
- 索引的作用
- MySQL与磁盘的IO
- Page
- 单个Page的分类
- 多个Page的组织
- B+树的特点
- B+树和B树的区别
- 聚簇索引 VS 非聚簇索引
- 聚簇索引的优缺点
- 非聚簇索引的优缺点
- 创建索引
- 常见索引分为:
- 主键索引
- InnoDB主键索引的生成过程
- (1)初始化
- (2)插入数据
- (3)查询数据
- (4)删除数据
- 创建主键的三种方式语法:
- 主键索引的特点
- InnoDB存储引擎中的主键索引及特点
- MyISAM存储引擎中的主键索引
- 总结
- 主键索引的性能优化
- 选择合适的主键:
- 主键索引的注意事项
- 唯一索引
- 三种创建语法:
- MyISAM存储引擎下的创建一个唯一索引工作原理
- InnoDB存储引擎下的创建一个唯一索引的工作原理
- 唯一索引的特点
- 普通索引
- 语法(同样三种方式):
- MyISAM存储引擎全文索引
- explain工具
- 查看索引
- 语法:
- 删除索引
- 语法:
- 索引创建原则
索引的理解
索引的作用
- 索引的引入会提高查询速度,但会降低插入、更新和删除操作的性能,因为这些写操作需要维护索引结构,增加了额外的IO开销。
关于存储引擎的介绍见下篇
MySQL与磁盘的IO
- 多数磁盘的每个扇区存储512B,为了提高效率,操作系统与磁盘之间的交互以 块 为单位(通常为4KB)。这种连续访问方式可以减少磁头移动,提高IO效率。
- MySQL作为应用层软件,通过操作系统与磁盘进行交互。为了提高IO效率,以 page 为基本单位进行IO操作,InnoDB存储引擎的每个page的大小通常为16KB。
- MySQL中的数据文件,是以page为单位保存在磁盘当中的。
- MySQL的CURD操作,都需要通过计算,找到对应的插入位置,或者找到对应要修改或者查询的数据。
- 而只要涉及计算,就需要CPU参与,而为了便于CPU参与,一定要能够先将数据移动到内存当中。所以在特定时间内,数据一定是磁盘中有,内存中也有。后续操作完内存数据之后,以特定的刷新策略,刷新到磁盘。而这时,就涉及到磁盘和内存的数据交互,也就是IO了。而此时IO的基本单位就是Page。
- 为了更好的进行上面的操作,MySQL服务器在内存中运行的时候,在服务器内部,就申请了被称为 Buffer Pool 的大内存空间,来进行各种缓存。其实就是很大的内存空间,来和磁盘数据进行IO交互。
Page
MySQL要管理很多数据表文件,而要管理好这些文件就需要做好数据的存储与组织。前面我们知道MySQL进行IO的基本单位是page,而这些page就是用于数据存储与组织的载体。可以简单的理解为MySQL中一个个独立的文件就是由一个或多个page组成的。它们之间的组织结构就像本书一样。
单个Page的分类
-
普通页:
- 包含prev和next指针,与其他page构成双向链表。
- 大部分空间用于存储表的结构与数据,小部分空间作为页内目录,类似于书的目录,用于记录不同数据的大概位置,方便后续查找。
- 页内目录管理的是一项一项的数据。
-
目录页:
- 内部空间全部用于管理普通页。
- 每个目录项指向一个普通页,并存储该普通页中最小数据的键值。
- 与页内目录不同,目录页管理的级别是页,而页内目录管理的级别是行。
多个Page的组织
- 多个page间使用prev和next构成双向链表,同时通过普通页与目录页之间的组织。
- 即使为页目录建立了更高级别的页目录,但随着数据量的增加,页目录的数量也会不断增多。如果仅依赖线性遍历页目录来寻找目标Page,效率仍然较低。
- 可以不断在页目录之上再创建页目录,最终形成一个入口页目录。在查询数据时,可以从入口页目录开始不断查询页目录,最终找到目标数据所在的Page,然后再在该Page内部找到目标数据。
最终形成一颗胖而矮的树,整体结构如图:
这就是一颗 B+树,也是InnoDB的索引结构。
B+树的特点
- 只有叶子结点才用来保存数据,而非叶子结点只保存目录项,不保存数据。
- 非叶子结点page不保存数据,就可以存储更多的目录项,从而管理更多的叶子page。
- 叶子结点全部用链表关联起来,便于范围查找。
- 目录页只放各个下级Page的最小键值。
- 查找的时候,自顶向下找,只需要加载部分目录页到内存,即可完成算法的整个查找过程,大大减少了IO次数。
B+树和B树的区别
- B+树是B树的改进版。
- B树:所有结点既保存数据,又保存目录信息(Page指针)。
- B+树:
- 只有叶子结点存储数据,非叶子结点仅存储键值和Page指针。
- 叶子结点通过链表关联,便于范围查找,而B树没有这种设计。
- 非叶子结点不存储数据,可以用来存储更多的目录项,使得树变得更矮,减少IO次数,最终实现更高的效率。
聚簇索引 VS 非聚簇索引
-
MyISAM存储引擎:
- 索引结构同样是B+树,但它的叶子结点并不直接存储数据,而是存储数据的地址。
- 数据内容与索引结构是分离的。
- 这种用户数据与索引数据分离的索引方案,叫做 非聚簇索引。
-
InnoDB存储引擎:
- 索引结构是B+树,且叶子结点直接存储数据。
- 这种用户数据与索引数据存储在一起的索引方案,叫做 聚簇索引。
聚簇索引的优缺点
- 优点:
- 数据与索引存储在一起,查询效率高,尤其是范围查询。
- 缺点:
- 插入和更新操作需要维护索引结构,可能导致性能下降。
非聚簇索引的优缺点
- 优点:
- 数据与索引分离,插入和更新操作对索引影响较小。
- 缺点:
- 查询时需要额外访问数据文件,效率较低。
创建索引
常见索引分为:
MySQL默认会创建主键索引
- 主键索引(primary key)
- 唯一索引(unique)
- 普通索引(index)
- 全文索引(fulltext)–解决中子文索引问题。
主键索引
InnoDB主键索引的生成过程
当创建主键索引时,MySQL会按照以下步骤生成B+树结构:
(1)初始化
表被创建时,如果没有显式定义主键,InnoDB会隐式创建一个隐藏的主键(通常是一个6字节的自增字段)。
如果定义了主键,MySQL会为主键列创建B+树索引。
(2)插入数据
当插入一行数据时,MySQL会根据主键值将数据插入到B+树的合适位置。
如果B+树的节点已满,会触发 节点分裂:将节点一分为二,并将中间键值提升到父节点。
数据按照主键的顺序存储在叶子节点中。
(3)查询数据
当查询数据时,MySQL会从根节点开始,根据主键值逐层查找,直到定位到目标叶子节点。
由于B+树的高度较低,查询效率非常高。
(4)删除数据
当删除数据时,MySQL会从B+树中移除对应的叶子节点记录。
如果节点变得过空,可能会触发 节点合并:将相邻节点合并,以保持B+树的平衡。
值得注意的是在InnoDB 存储引擎中插入数据是会默认进行排序的。这种行为主要是由于 InnoDB存储引擎 的 聚簇索引 设计所导致的。
由于InnoDB存储引擎的数据结是B+树,而B+树又是聚簇索引
所以数据和索引存储在一起而他们又作为B+树的叶子节点,B+树为了保证平衡性和有序性,就会对新插入的数据按照主键进行排序。
创建主键的三种方式语法:
#第一种创建主键方式
#在创建表的时候,直接在字段名后指定 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
- 每个表只能有一个主键,但主键可以由多个列组成(复合主键),每个表只能有一个主键索引。
InnoDB存储引擎中的主键索引及特点
在InnoDB存储引擎中,主键索引是聚簇索引。
特点如下:
- 数据行按照主键的顺序物理存储。
- 因为物理存储的连续提升范围查找的速度,同时减少IO次数。查找速度飞快
- B+树的高度较低,查询时间复杂度为O(log n),效率非常高
- 查询主键时可以直接访问数据,无需额外的查找操作。
- 如果表没有显式定义主键,InnoDB会选择一个唯一的非空索引作为聚簇索引;如果没有这样的索引,InnoDB会隐式创建一个隐藏的主键。
MyISAM存储引擎中的主键索引
特性 | MyISAM 主键索引 | InnoDB 主键索引 |
---|---|---|
索引类型 | 非聚簇索引 | 聚簇索引 |
数据存储位置 | 索引和数据分开存储(.MYI 和.MYD 文件) | 索引和数据存储在一起(.ibd 文件) |
叶子节点存储内容 | 指向数据行的指针 | 完整的行数据 |
数据排序 | 数据按照插入顺序存储 | 数据按照主键顺序存储 |
插入性能 | 较高(数据追加到文件末尾) | 较低(需要维护B+树的有序性) |
查询性能 | 较低(需要额外查找数据文件) | 较高(直接访问数据) |
事务支持 | 不支持 | 支持 |
行级锁 | 不支持(表级锁) | 支持 |
总结
MyISAM存储引擎中的主键索引是 非聚簇索引,与InnoDB都是B+树,索引和数据分开存储,叶子节点存储指向数据行的指针。这种设计使得MyISAM在插入性能上有优势,但在查询性能、事务支持和并发写入方面存在不足。在选择存储引擎时,需要根据应用场景的需求权衡MyISAM和InnoDB的优缺点。
主键索引的性能优化
选择合适的主键:
- 主键应尽可能短且唯一。通常使用INT或BIGINT类型的主键,并配合AUTO_INCREMENT自动生成唯一值。
- 避免使用大字段作为主键:如果主键列是VARCHAR或TEXT等大字段类型,索引会占用更多空间,影响性能。
- 复合主键的使用:复合主键适用于需要多列唯一标识的场景,但会增加索引的复杂性。
主键索引的注意事项
- 主键的值一旦插入,通常不建议修改,因为修改主键可能会导致数据完整性问题。
- 如果表中有外键关联,修改主键可能会影响关联表的数据。
- 主键索引的选择对数据库性能有重要影响,应谨慎设计。
唯一索引
三种创建语法:
#第一种方式:
#在表定义时,在某列后直接指定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);
MyISAM存储引擎下的创建一个唯一索引工作原理
- 它会重新独立创建一个B+树(与主键索引的树一样为非聚簇索引树,只不过键值不同)并进行维护。
- 在主键索引存在的情况下,数据存储在 .MYD 文件中,而索引存储在 .MYI 文件中。无论是主键索引还是唯一索引,它们都存储指向 .MYD 文件中数据行的指针(即行偏移量)。
- 唯一索引会直接指向已经存储好的表数据,而不是通过主键索引间接查找数据。
- 当有数据变动时(插入或删除等)它需要同时维护更新多个已经创建的索引树
InnoDB存储引擎下的创建一个唯一索引的工作原理
- 它同样创建一个B+树,只不过是个辅助索引树。依赖于主键索引。
- 叶子节点存储内容:存储的是 主键值(Primary Key Value)。
- 唯一索引的B+树存储的是唯一列的值和对应的主键值,通过主键值在聚簇索引中查找实际的行数据。
所以通过辅助(普通)索引,找到目标记录,需要两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。这种过程,就叫做回表查询。
为何 InnoDB 针对这种辅助(普通)索引的场景,不给叶子节点也附上数据呢?因为那样太浪费空间了。
唯一索引的特点
- 索引值同样不能重复
- 一个表可以存在多个唯一索引
- 如果一个唯一索引上指定not null,等价于主键索引(也就是唯一索引允许一个null)
- 查找效率高
普通索引
语法(同样三种方式):
#第一种方式
#在表的定义最后,指定某列为索引
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
); --创建完表以后指定某列为普通索引
#第三种方式
#创建一个索引名为 idx_name1 的索引
create table user10(
id int primary key,
name1 varchar(20), email
varchar(30)
);
create index idx_name on user10(name1);
普通索引的特点:
- 一个表中可以有多个普通索引,普通索引在实际开发中用的比较多
- 如果某列需要创建索引,但是该列有重复的值,那么我们就应该使用普通索引
- 普通索引 和 唯一索引 的创建过程几乎相同,主要区别在于唯一索引有 唯一性约束。
MyISAM存储引擎全文索引
当对文章字段或有大量文字的字段进行检索时,会使用到全文索引。MySQL提供全文索引机制,但是有要求,要求表的存储引擎必须是MyISAM,而且默认的全文索引支持英文,不支持中文。如果对中文进
行全文检索,可以使用sphinx的中文版(coreseek)
语法:
#第一种方式:(关键句FULLTEXT (title,body))
CREATE TABLE articles (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
title VARCHAR(200),
body TEXT,
FULLTEXT (title,body)
)engine=MyISAM;
#起全文索引名为idx_content:
CREATE TABLE articles (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200),
content TEXT,
FULLTEXT INDEX idx_content (content)
);
#第二种方式 在已有表上创建全文索引
ALTER TABLE articles
ADD FULLTEXT INDEX idx_title_content (title, content);
全文索引的使用:
使用 MATCH ... AGAINST
语法进行自然语言搜索
#示例:
SELECT * FROM articles
WHERE MATCH(content) AGAINST('database');
explain工具
可以用explain工具看是否使用到索引
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
关键字段解析
- type: fulltext:表示查询使用了全文索引。
- possible_keys: title:表示查询可能使用的索引是 title。
- key: title:表示查询实际使用的索引名是 title。
- key_len: 0:对于全文索引,key_len 的值通常为 0,因为全文索引的计算方式与普通索引不同。
- rows: 1:表示 MySQL 预计需要扫描 1 行数据来返回结果。
- Extra: Using where:表示 MySQL 使用了 WHERE 条件来过滤数据。
查看索引
语法:
show keys from 表名;
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)
show index from 表名;
- (信息比较简略):
desc 表名;
删除索引
语法:
- 删除主键索引:
alter table 表名 drop primary key;
- 其他索引的删除:
alter table 表名 drop index 索引名;
索引名是show keys from
表名中的Key_name
字段 drop index 索引名 on 表名
索引创建原则
- 比较频繁作为查询条件的字段应该创建索引
- 唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
- 更新非常频繁的字段不适合作创建索引
- 不会出现在where子句中的字段不该创建索引
其他概念
- 复合索引
- 索引最左匹配原则
- 索引覆盖