MySQL详解二

MySQL详解二

索引

数据库中的数据是以记录为单位的,如果一条一条进行查找,几十万数据就已经到了查找的瓶颈,如果上百万数据的话,查找就会非常的浪费时间。

索引的价值在于提高海量数据的检索速度,只要执行了正确的创建索引的操作,查询速度就可能提高成百上千倍。当一张表创建索引后,在数据库底层就会为表中的数据记录构建特定的数据结构,后续在查询表中数据时就能通过查询该数据结构快速定位到目标数据。

索引虽然提高了数据的查询速度,但在一定程度上也会降低数据增删改的效率,因为这时在对表中的数据进行增删改操作时,除了需要进行对应的增删改操作之外,可能还需要对底层建立的数据结构进行调整维护。

索引分类:主键索引、唯一索引、普通索引、组合索引、以及全文索引(elasticsearch);

主键索引

主键索引指是一个非空唯一索引,一个表只有一个主键索引,在 innodb 中,主键索引的 B+ 树包含表数据信息。

bash 复制代码
PRIMARY KEY(key1, key2)

唯一索引

bash 复制代码
UNIQUE(key)

唯一索引是某一列不会出现相同的值,但是可以是 NULL 值;

普通索引

普通索引是指某一列允许出现相同的索引内容,也可以为NULL 值;

bash 复制代码
INDEX(key)
-- OR
KEY(key[,...])

组合索引

组合索引所指的就是对表上的多个列进行索引。

bash 复制代码
INDEX idx(key1,key2[,...]);
UNIQUE(key1,key2[,...]);
PRIMARY KEY(key1,key2[,...]);

全文索引

将存储在数据库当中的整本书和整篇文章中的任意内容信息查找出来的技术,关键词 FULLTEXT,在短字符串中用 LIKE % ,在全文索引中用 match 和 against 。

主键选择

InnoDB 中的表是索引组织表,每张表有且仅有一个主键,主键的选择就会存在以下几种情况:

  • 如果显示的设置 PRIMARY KEY ,则该设置的 KEY 为该表的主键;
  • 如果没有显示的设置,则从非空唯一索引中进行选择,会出现以下两种情况:
    情况一:只有一个非空唯一索引,就会选择该索引作为主键索引;
    情况二:存在多个非空唯一索引,选择声明的第一个为主键;
  • 没有非空唯一索引,则自动生成一个 6 字节的 _rowid 作为主键。

约束

为了实现数据的完整性,对于 innodb,提供了以下几种约束,primary key,unique key,foreign key,default,not null。

  • primary key:非空唯一约束;
  • unique key:唯一约束;
  • foreign key:外键约束;
  • default:默认值约束;
  • not null:非空约束。

这儿需要特别介绍一下外键约束,他其实就是将几张表给联系起来,就像下面这样:

bash 复制代码
create table parent (
id int not null,
primary key(id)
) engine=innodb;
create table child (
id int,
parent_id int,
foreign key(parent_id) references parent(id) ON DELETE CASCADE ON UPDATE
CASCADE
) engine=innodb;
-- 被引用的表为父表,引用的表称为子表;
-- 外键定义时,可以设置行为 ON DELETE 和 ON UPDATE,行为发生时的操作可选择:
-- CASCADE 子表做同样的行为
-- SET NULL 更新子表相应字段为 NULL
-- NO ACTION 父类做相应行为报错
-- RESTRICT 同 NO ACTION
INSERT INTO parent VALUES (1);
INSERT INTO parent VALUES (2);
INSERT INTO child VALUES (10, 1);
INSERT INTO child VALUES (20, 2);

child 表跟 parent 就通过外键约束联系在一起,如果我们删除 child 表中的某一行数据,那么对应的 parent 表中的某个数据也会被删除掉,这就是外键约束。

约束是数据库为我们提供的一种安全的方式,索引和约束的不同点就在于创建主键索引或者唯一索引的时候同时创建了相应的约束,但是约束时逻辑上的概念,索引是一个数据结构既包含逻辑的概念也包含物理的存储方式。

索引实现

B+树

Innodb 中的索引的数据结构是一颗 B+ 树结构,首先我们来介绍一下 B+ 树:

B+树

B+ 树是一颗多路平衡搜索树,他的结构通过中序遍历也是有序地,B+ 树通常是用来减少磁盘访问次数,组织磁盘数据,以页为单位,物理磁盘页一般为 4K,innodb 默认页大小为 16K,对页的访问是一次磁盘 IO,缓存中会缓存常访问的页。

下面就是一颗 B+ 树索引的结构,注意,每一个索引都对应着一颗 B+ 树:

B+ 树索引结构存在一个特征,非叶子节点只存储索引信息,叶子节点存储具体数据信息,叶子节点之间互相连接,结点的大小是 16 KB,映射的连续的磁盘页,方便范围查询。

B+ 树方便范围查询的点就在于我不需要每次查询都从根节点进行遍历,而是一次查询就可以去搞定,从而来减少磁盘的 IO 次数的,就像下图一样,一次 IO 就能搞定:

为什么B+ 树映射的连续的磁盘页?

因为映射连续的磁盘页就是以顺序 IO 的形式对磁道进行访问,如果当次IO给出的扇区地址与上次IO结束的扇区地址是连续的,那磁头就能很快的开始这次IO操作,这样的多个IO操作就会加快对应的访问效率。

因此 B+ 树索引的优点就可以总结为以下几点:

聚集索引

按照主键构造的 B+ 树,叶子节点中存放数据页,数据也是索引的一部分。

比如说我们当前需要查找 id 大于并且小于 40 的所有学生,就会使用如下方式进行查询:

辅助索引

叶子节点不包含行记录的全部数据,辅助索引的叶子节点中,除了用来排序的 key 还包含一个bookmark ,该书签存储了聚集索引的 key。

比如说我们需要查找 teacher_id = 33 的老师的所有信息,就会采用如下的查找方式:

因为在辅助索引的叶子结点中存储了一个聚集索引的书签,当我们找到对应的节点以后,此时就需要进行一个回表查询,又重新回到聚集索引当中,查找对应的信息,然后再进行返回数据,相对于聚集索引来说他多出来一步回表查询的操作。

索引存储

innodb 由段、区、页组成;段分为数据段、索引段、回滚段等,区大小为 1 MB(一个区由 64 个连续页构成),页的默认值为 16k,页为逻辑页,磁盘物理页大小一般为 4K 或者 8K,为了保证区中的页连续,存储引擎一般一次从磁盘中申请 4~5 个区。

innodb 体系结构

对于 MySQL 来说,光实现以上的策略是不够的,因为每一次访问 B+ 树的结点都会触发一次 IO ,采用上面的策略了,还是会有多次磁盘 IO,所以在 MySQL 中还设计了一个 bufferpool 缓存表和索引数据;采用 LRU 算法(原理如下图)让 Buffer pool 只缓存比较热的数据 。

正常情况下我们刷盘操作是要经过 page cache 的,但是 page cache 属于内核态,我们无法去定制我们自己的一个刷盘策略,所以就需要在用户态设计一个 Buffer pool ,然后制定我们的刷盘策略,通过 Direct IO 的方式,直接将数据刷入到磁盘当中。

我们再来看一张图示结构:

从上图就可以看出,在我们的内存结构当中是存在一个 Buffer pool 的,这个 Buffer pool 就是用来缓存对应的热点数据的,也就是说,我们对于写数据并不是直接对磁盘空间进行操作的,而是先将其写进用户态的 Buffer pool 中,然后最终通过 Direct IO 的方式,将数据直接刷入到磁盘中。

我们来看一下 Buffer pool 的结构:

Buffer pool 中使用的是 LRU (最近未被使用)的淘汰策略,通过上图我们可以看出来,在 MySQL 中数据的插入的方式是选择中间的位置进行插入的,原因就在于,我们并不认为最新被插入的数据就是热点数据,所以并不会选择在开头位置进行插入,只有当我们多次对该数据进行访问以后,他就会被挪到前面的位置,而一个数据如果长时间不被访问到,就会被移动到后面的位置,最终通过 LRU 策略把这些数据输入到磁盘当中。

Buffer pool 中缓存的是聚集索引也就是表和索引的数据,对于辅助索引的数据,是存在 Change buffer 中的,当我们需要读取非唯一索引的数据时, Change buffer 当中的数据就会异步的 Merge 到 Buffer pool 当中去,同时他也会定期的同步到索引页当中去。

如下图所示,free list 组织 buffer pool 中未使用的缓存页;flush list 组织 buffer pool 中脏页,也就是待刷盘的页;lru list 组织 buffer pool 中冷热数据,当 buffer pool 没有空闲页,将从 lru list 中最久未使用的数据进行淘汰。

Redlog

Buffer pool 是存在于内存当中的,如果服务器突然宕机了,此时 Buffer pool 当中的数据就没有了,我们当然不能让这些数据都丢失,我们会将这些数据写入到 Redlog 中去,如果出现宕机,我们就会保证 MySQL 在重启以后将这些数据从 Redlog 中再重新读取回来。

我们可以看见,内存中是存在一个 Log Buffer 的,它采用的就是正常将数据刷入到 page cache 中,然后再将 page cache 中的数据刷入 Redlog 当中,Redlog 当中的数据也是一页一页连续进行存储的,这也方便我们在读取 Redlog 中数据的时候提高对应的效率。

整体总结下来就是 MySQL 的索引采用的 B+ 树的结构,保证数据访问的一个高效,然后又通过在内存中的 Buffer pool 结构,减少磁盘 IO 的操作。

最左匹配原则

对于组合索引,从左到右依次匹配,遇到 ><betweenlike 就停止匹配。

如何理解上面这句话,我们来看一张表结构:

上面这张表中普通索引设置为组合索引,其中主键索引为 id,普通索引为name,cid,最左匹配规则就是我们在查询的过程中首先会以 name 来进行比较,如果 name 一样我们才会去使用 cid。

比如下面这两句查询语句:

这一个查询语句 where 后面使用的是 name,此时就会踩到我们的索引结构,那么他首先会去辅助索引中进行查询,然后回表查询到聚集索引当中。

再看这一个查询语句,因为我们设置的最左索引为 name,此时条件为 cid了,那么就不满足最左匹配的原则,就不会踩到对应的索引,也就意味着此时会去聚集索引中进行全表查询,全表查询的速度是最慢的。

我们再看下面这句查询语句,我们可以看见当前他也是踩了索引的,因为这儿会进行优化,总是执行最左匹配的条件的,name 就为我们的最左匹配规则。

其中 type 表示的显示访问类型,采用怎么样的方式来访问数据,效率从好到坏依次为:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery >index_subquery > range > index > ALL

覆盖索引

从辅助索引中就能找到数据,而不需通过聚集索引查找,利用辅助索引树高度一般低于聚集索引树,会进行较少磁盘 IO。

覆盖索引并不表示索引,他表示的是一种选择策略。

通过下面这张表就可以看出来,第二句查询语句走的策略就是覆盖索引,因为辅助索引 B+ 树结构中除了包含普通索引信息之外还包含主键索引,我们此时需要查询的 3 个信息均可以在辅助索引中找到,就不需要进行回表查询。

索引下推

索引下推是为了减少回表次数,提升查询效率,在 MySQL 5.6 的版本开始推出的。

MySQL 架构分为 server 层和存储引擎层,没有索引下推机制之前,server 层向存储引擎层请求数据,在 server 层根据索引条件判断进行数据过滤;有索引下推机制之后,将部分索引条件判断下推到存储引擎中过滤数据,这样就可以减少回表次数,最终由存储引擎将数据汇总返回给 server 层。

索引失效

首先我们创建一张下面这样的表,然后在插入一些数据:

  • select ... where A and B 若 A 和 B 中有一个不包含索引,则索引失效;
  • 索引字段参与运算,则索引失效;例如: from_unixtime(idx) = '2021-04-30',需要改成 idx = unix_timestamp("2021-04-30")就不会索引失效
  • 索引字段发生隐式转换,则索引失效;例如:将列隐式转换为某个类型,实际等价于在索引列上作用了隐式转换函数;
  • LIKE 模糊查询,通配符 % 开头,则索引失效;例如: select * from user where name like '%Mark';
  • 在索引字段上使用 NOT <> != 索引失效,如果判断 id <> 0 则修改为 idx > 0 or idx < 0 ;
  • 组合索引中,没使用第一列索引,索引失效。

索引原则

索引原则这块儿涉及到数据类型,可以去看一下之前的一篇文章MySQL数据类型,索引设计的原则包含以下几个方面:

  • 查询频次较高且数据量大的表建立索引,索引选择使用频次较高,过滤效果好的列或者组合;
  • 使用短索引,这样可以让节点包含的信息多,进行较少磁盘 IO 操作;比如: smallint , tinyint等;
  • 对于很长的动态字符串,考虑使用前缀索引:
  • 对于组合索引,考虑最左侧匹配原则、覆盖索引;
  • 尽量选择区分度高的列作为索引,该列的值相同的越少越好;
  • 尽量扩展索引,在现有索引的基础上,添加复合索引,最多 6 个索引;
  • 不要 select * , 尽量只列出需要的列字段,方便使用覆盖索引;
  • 索引列,列尽量设置为非空;
  • 可选:开启自适应 hash 索引或者调整 change buffer。
相关推荐
想要AC的sjh29 分钟前
【MySQL】性能优化实战指南:释放数据库潜能的艺术
数据库·mysql·性能优化
极限实验室1 小时前
极限科技亮相 TDBC 2025 可信数据库发展大会,连续三年荣誉入选信通院《中国数据库产业图谱》
数据库
鱼见千寻1 小时前
Flowable31动态表单-----------------------终章
java·数据库·spring boot·flowable
木林森先生1 小时前
SQLite的可视化界面软件的安装
数据库·sqlite
ZNing_13 小时前
SQL基础操作指南:约束、表设计与复杂查询
数据库·笔记·sql·学习
smileNicky3 小时前
接口幂等性设计:用Redis避免接口重复请求
数据库·redis·缓存
奋进小子3 小时前
达梦数据库CASE_SENSITIVE大小写敏感差异比较
数据库
大大的大大4 小时前
python基础笔记
前端·数据库·python
Elastic 中国社区官方博客4 小时前
Elasticsearch 重命名索引
大数据·数据库·elasticsearch·搜索引擎·全文检索