和其它数据库有所不同,MySQL 的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,『插件式』的『存储引擎架构』将查询和其它的系统任务以及数据的存储提取相分离。
这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
最上层是一些客户端和连接服务,包含本地 sock 通信和大多数基于客户端/服务端工具实现的类似于 TCP/IP 的通信。
主要任务是连接处理、授权认证、及相关的安全方案等。
该层引入了线程池,为通过认证、安全接入的客户端提供线程处理任务。
同样在该层上可以实现基于 SSL 的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
第二层架构主要完成大多数的核心服务功能。如 SQL 接口、并完成缓存的查询、SQL 的分析和优化及部分内置函数的执行。
所有跨存储引擎的功能也在这一层实现,如过程、函数等。
在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化:如确定查询表的顺序,是否利用索引等,最后生成相应的执行操作。
如果是 select 语句,服务器还会查询内部的缓存。如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。
模块名称 | 作用 |
---|---|
Management Serveices & Utilities | 系统管理和控制工具 |
SQL Interface | 接受用户的 SQL 指令,并且返回查询的结果。比如 select from 就是调用SQL Interface |
Parser | SQL 命令传递到解析器的时候会被解析器验证和解析。 |
Optimizer | 查询优化器 SQL 语句在查询之前会使用查询优化器对查询进行优化。 例如: select uid,name from user where gender= 1; 查询优化器来决定先投影还是先过滤。 |
Cache和Buffer | 查询缓存,存储 SELECT 语句以及相应的查询结果集 如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。 这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key 缓存,权限缓存等 |
存储引擎层,存储引擎真正的负责了 MySQL 中数据的存储和提取,服务器通过 API 与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要选取。后面介绍 MyISAM 和 InnoDB。
数据存储层,主要是将数据存储在运行于文件系统上,并完成与存储引擎的交互。
MySQL 客户端通过协议与 MySQL 服务器建连接,发送查询语句。
检查查询缓存中是否存在要查询的数据。
命中:直接返回结果。
不会再对查询进行解析、优化、以及执行。它仅仅将缓存中的结果返回给用户即可,这将大大提高系统的性能。
未命中:解析 SQL 语句。
MySQL 首先根据语法规则验证 SQL 语句的语法正确性,然后通过关键字将 SQL 语句进行解析,并生成一棵对应的『解析树』。预处理器则根据一些 MySQL 规则进一步检查解析树是否正确。
当解析树被确认为正确,接下来就会由查询优化器将解析树转化成执行计划(profile)。一条查询可以有很多种执行方式,最后都返回相同的结果。优化器的作用就是找到这其中最好的执行计划。
MySQL 默认使用 B+Tree 索引,并且一个大致方向是:无论怎么调整 SQL,至少在目前来说,MySQL 最多只用到表中的一个索引。
MySQL 从 5.5 版本之后,就开始默认采用 InnoDB 引擎。
MySQL 在 5.5 版本之前,默认使用 MyISAM 引擎。
MyISAM 提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等。但 MyISAM 不支持事务和行级锁,有一个毫无疑问的缺陷就是崩溃后无法安全恢复。
Federated 引擎是访问其他 MySQL 服务器的一个代理,尽管该引擎看起来提供了一种很好的跨服务器的灵活性,但也经常带来问题,因此默认是禁用的。
下面我们将最常见的 InnoDB 和 MyISAM 两个引擎详细对比一下:
对比项 | MyISAM | InnoDB |
---|---|---|
外键 | 不支持 | 支持 |
事务 | 不支持 | 支持 |
行表锁 | 支持表锁 即使操作一条记录也会锁住整个表, 不适合高并发操作 |
支持行锁 操作时只锁某一行,不对其它行有影响, 适合高并发操作 |
缓存 | 只缓存索引,不缓存真实数据 | 不仅缓存索引还要缓存真实数据, 对内存要求较高, 而且内存大小对性能有决定性的影响 |
系统提供预创建数据库表 给用户使用 |
是 | 否 |
关注点 | 性能:节省资源、消耗少、简单业务 | 事务:并发写、事务、更大资源 |
默认安装 | 是 | 是 |
默认使用 | 否 | 是 |
索引类型 | 索引特点 |
---|---|
单列索引 | 即一个索引是只根据一个字段创建的,里面只包含单个列, 一个表可以有多个单值索引(也叫单列索引) |
联合索引 | 即一个索引包含多个列 |
唯一索引 | 索引列的值必须唯一,但允许有空值,空值可以有多个 |
主键索引 | 设定为主键后数据库会自动建立索引,InnoDB 为聚簇索引 |
当我们平时谈到『主键』时,这个概念包含三个方面的具体含义:
相关语法可以参考 W3CSchool 教程:https://www.w3school.com.cn/sql/sql_create_index.asp
create database db_shop;
use db_shop;
create table t_customer(
pk_id int auto_increment primary key,
customer_no varchar(200),
customer_name varchar(200));
# create index 索引名称 on 要建立索引的字段所在的表(要建立索引的字段);
create index idx_customer_name on t_customer(customer_name);
# create unique index 索引名称 on 要建立索引的字段所在的表(要建立索引的字段);
create unique index idx_customer_no on t_customer(customer_no);
# create index 索引名称 on 要建立索引的字段所在的表(要建立索引的字段,...,要建立索引的字段);
create index idx_customer_no_name on t_customer(customer_no,customer_name);
索引创建好可以如下图所示方式查看:
# drop index 要删除的索引名称 on 索引所在的表;
drop index idx_customer_no_name on t_customer;
主键自动建立唯一索引
频繁作为查询条件的字段应该创建索引,也就是经常出现在 where 子句中的字段,尤其是数据表大的时候
关联查询
单键/组合索引的选择问题,who?(在高并发下倾向创建组合索引)
说明:同样是 A、B、C 三个字段,是分别创建三个索引,还是创建一个组合索引?
答案:此时通常来说我们更倾向于创建组合索引。因为基本上来说不管怎么优化,MySQL 里一条 SQL 语句中只有一个字段的索引能够生效。所以我们建立组合索引并参照最左原则能够让更多的索引起到作用。
查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
查询中统计或者分组的字段
索引并不是无条件创建、越多越好,而是要根据实际情况恰到好处的创建。不再使用或很少使用的索引要删除掉。
假设有一张数据库表:
emp_id(主键) | emp_age(非主键) | emp_name(非主键) |
---|---|---|
1 | 21 | tom |
2 | 22 | jerry |
3 | 23 | bob |
4 | 24 | harry |
5 | 25 | lily |
…… | …… | …… |
对于 InnoDB 存储引擎来说,最小的存储单位就是:页。那么存放原始数据的页就称为数据页。
一个数据页默认的大小是:16KB。如果我们假设一条记录所占空间的字节数是 1KB,那么这个数据页大致能够存储 16 条记录。那么如果这个数据库表总共有 100 万条记录,那么肯定是需要很多数据页来存放这些数据。
数据页内部对主键进行了排序,所以当我们在一个数据页内部根据主键查找记录时,会根据二分法进行查找。找到主键就能够很快找到这条记录。
为了在众多数据页中,定位每一个数据页,数据页需要有编号。所以这个编号也相当于是这个页的地址。
根据主键值对所有数据页进行排序。
把所有数据页组成环状双向链表。
好处是:假设搜索主键值大于 30 的记录,那么找到主键值为 30 的记录之后,后面所有的记录就都可以快速找到和返回了。
就目前的数据存储方式来说,如果要找到某一个主键值所在的数据页,需要一页一页的查找,这无疑是非常缓慢的。所以肯定需要做进一步优化。
现在我们在 4 号目录页中有如下三个区间:
情景一:搜索主键值为 11 的记录
select emp_id,emp_name,emp_age from t_emp where emp_id=11
情景二:搜索主键值为 15 的记录
select emp_id,emp_name,emp_age from t_emp where emp_id=15
为了更进一步方便查找,命中我们要找的目录页,我们可以进一步给目录再设置目录。
用户搜索的主键值是:60。现在 13 号目录页中包含的区间:
所以 60 会落在 [51,150) 区间,所以继续查找页码为 8 的目录页:
所以 60 会落在 [51,81) 区间,所以继续查找页码为 5 的数据页,根据主键执行二分法查找。
如果有需要,就可以给目录的目录再设置目录……直到向上汇总到一个唯一的根节点。
B+Tree:其实这就是 B+Tree。在我们这里例子中,数据的主键就是 B+Tree 里面的索引(或者说是用主键作为索引),页码值就是指向数据存储位置的指针。只不过指针有可能指向数据页,也有可能指向目录页。
B+Tree 节点:目录页或数据页都算是 B+Tree 中的节点。
聚簇索引:索引和原始数据存放在一起的存储形式。聚簇索引都是由主键作为索引值,所以一个表中只有一个聚簇索引。
这里我们可以说:主键索引其实就是聚簇索引,这两个名字是从不同的角度来说的。 ——> 主键索引:是指这个索引是根据主键字段来创建的 ——> 聚簇索引:是指这个索引的树形结构中是否同时包含了『原始数据』和『索引目录』 不仅『主键索引』和『聚簇索引』这个两个概念是等同的,而且它们其实也就是数据表本身。也就是说其实我们并没有在原始数据库表之外再另外创建索引表,而是应该说:数据库表本身就是按照 B+Tree 的结构组织起来的,底层就是这么存储的。
非聚簇索引:索引和原始数据没有存放在一起的存储形式。非聚簇索引都算由非主键字段创建的,所以可以有多个。
索引值:在当前例子中,我们是拿主键字段的值作为索引。
结论:根据下面的推算使用 B+Tree 的形式来组织数据库表中数据的存储方式,只需要 2~4 层就足够了。
BTree 将所有主键排序,对于父节点来说:
所以在 BTree 中,在父节点出现过的主键不会在子节点中出现。反过来说:每一个主键只在一个节点中出现。所以主键关联的数据只能在主键所在的节点中保存。
这要看索引表具体指的是哪种类型的索引。
但其实我们应该更进一步,更精确的来说:数据库底层维护表中的数据其实并没有『表』这个概念,而全部都是以索引的形式保存的。所以说数据库底层存储数据并不是我们从逻辑上看到的『行』和『列』的形式,而是树形结构。
结论:
前面我们所论述的,都是从主键出发建立索引,方便 SQL 语句中根据主键查询数据。
而用户在 SQL 语句中很多时候不是根据主键查询的。所以非主键字段也应该在适合的情况下创建索引,提升查询效率。
select emp_id,emp_name,emp_age from t_emp where emp_age=20;
select emp_id,emp_name,emp_age from t_emp where emp_name='tom';
select emp_id,emp_name,emp_age from t_emp where emp_decs like "I come from UK%";
对于目录页来说,它的结构固定就是下图所示:
那么我们用什么值做索引呢?
假设还是使用前面的数据库表:
emp_id(主键) | emp_age(非主键) | emp_name(非主键) |
---|---|---|
1 | 21 | tom |
2 | 22 | jerry |
3 | 23 | bob |
4 | 24 | harry |
5 | 25 | lily |
…… | …… | …… |
现在我们为 emp_age 字段建立索引。此时因为我们只用到了这一个字段,所以叫单列索引。
和前面聚簇索引对比,也就是数据页存储的结构不同:
其实我们可以这么理解:用哪个字段创建索引,就拿这个字段的值作为索引值。
●根据『主键字段』创建索引,那就是用『主键字段的值』作为『索引值』
●根据『非主键字段』例如 emp_age,创建索引,那就是用这个『非主键字段的值』,例如 emp_age 字段的值作为『索引值』
下面咱们拿实际数据举个例子:
B+Tree 要求作为索引值的数据必须经过排序,这样方便执行二分法查找。而字符串可以根据它底层的 Unicode 码进行排序。因为 Unicode 码是二进制数据,二进制数据(或十六进制数据)也是整数,所以当然可以比较大小,可以排序。
也叫:组合索引、联合索引。顾名思义,这种索引就是在数据页包含了多个字段的值。
多列索引是使用『多个字段值的组合』作为『索引值』。
我们根据这个以非聚簇索引的索引列大小排序的 B+ 树只能确定我们要查找记录的主键值,所以如果我们想根据非聚簇索引的索引列的值查找到完整的用户记录的话,仍然需要回到聚簇索引中再查一遍,这个过程称为回表。也就是根据非聚簇索引的索引列的值查询一条完整的用户记录需要使用到两棵B+树!
问题:为什么我们还需要一次回表操作呢?直接把完整的用户记录放到叶子节点不OK吗?
回答: 如果把完整的用户记录放到叶子节点是可以不用回表。但是太占地方
了,相当于每建立一棵B+树都需要把所有的用户记录再都拷贝一遍,这就有点太浪费存储空间了。
另外还有一个问题是:数据不一致。如果在数据保存两份的情况下,我们执行了 update 操作,那么就不能只改一边,应该两边都改,这样还是浪费性能。
我们取舍的依据:趋利避害。 回表操作所需要付出的代价小于在非聚簇索引中保存原始数据要付出的代价。此时回表操作性价比高。
因为这种按照非主键列
建立的B+树需要一次回表操作才可以定位到完整的用户记录,所以这种B+树也被称为二级索引
(英文名secondary index
),或者辅助索引
。如果我们使用的是 emp_age 列的大小作为 B+ 树的排序规则,那么我们也可以把这个B+树叫做:为 emp_age 列建立的索引。
非聚簇索引的存在不影响数据在聚簇索引中的组织,所以一张表可以有多个非聚簇索引。
如果最终需要使用的数据,在非聚簇索引中直接就能够拿到,那就不需要回表再查询聚簇索引。
create index idx_emp_name on t_emp(emp_name);
select emp_id,emp_name from t_emp where emp_name like 'tom%';
聚簇索引与非聚簇索引的原理不同,在使用上也有一些区别:
叶子节点
存储的就是我们的数据记录,非聚簇索引的叶子节点存储的是数据位置——主键。非聚簇索引不会影响数据表的物理存储顺序。只能有一个聚簇索引
,因为原始数据只能有一种排序存储的方式,但可以有多个非聚簇索引
,也就是多个索引目录提供数据检索。MySQL的索引包括普通索引、唯一性索引、全文索引、单列索引、多列索引和空间索引等。
功能逻辑
上说,索引主要有 4 种,分别是普通索引、唯一索引、主键索引和全文索引。物理实现方式
,索引可以分为 2 种:聚簇索引和非聚簇索引。字段个数
进行划分,分成单列索引和联合索引。在创建普通索引时,不附加任何限制条件,主要用于提高查询效率。这类索引可以创建在任何数据类型
中,其值是否唯一和非空,要由字段本身的完整性约束条件决定。建立索引以后,可以通过索引进行查询。例如,在表t_student
的字段stuid
上建立一个普通索引,查询记录时就可以根据该索引进行查询。
使用UNIQUE参数
可以设置索引为唯一性索引,在创建唯一性索引时,限制该索引的值必须是唯一的,但允许有空值。在一张数据表里可以有多个
唯一索引。
例如,在表t_student的字段name中创建唯一性索引,那么字段name的值就必须是唯一的。通过唯一性索引,可以更快速地确定某条记录。
主键索引就是一种特殊的唯一性索引
,在唯一索引的基础上增加了不为空的约束,也就是 NOT NULL+UNIQUE,一张表里最多只有一个
主键索引。
Why?
这是由主键索引的物理实现方式决定的,因为数据存储在文件中只能按照一种顺序进行存储。
在表中的单个字段上创建索引。单列索引只根据该字段进行索引。单列索引可以是普通索引,也可以是唯一性索引,还可以是全文索引。只要保证该索引只对应一个字段即可。一个表可以有多个
单列索引。
多列索引是在表的多个字段组合上创建一个索引。该索引指向创建时对应的多个字段,可以通过这几个字段进行查询,但是只有查询条件中使用了这些字段中的第一个字段时才会被使用。例如,在表中的字段id、name和gender上建立一个多列索引idx_id_name_gender
,只有在查询条件中使用了字段id时该索引才会被使用。使用组合索引时遵循最左前缀集合
。
全文索引(也称全文检索)是目前搜索引擎
使用的一种关键技术。它能够利用【分词技术
】等多种算法智能分析出文本文字中关键词的频率和重要性,然后按照一定的算法规则智能地筛选出我们想要的搜索结果。
使用参数FULLTEXT
可以设置索引为全文索引。在定义索引的列上支持值的全文查找,允许在这些索引列中插入重复值和空值。全文索引只能创建在CHAR
、VARCHAR
或TEXT
类型及其系列类型的字段上,**查询数据量较大的字符串类型的字段时,使用全文索引可以提高查询速度。**例如,表t_student
的字段information
是TEXT
类型,该字段包含了很多文字信息。在字段information上建立全文索引后,可以提高查询字段information的速度。
默认情况下,或者使用 in natural language mode 修饰符时,match() 函数对文本集合执行自然语言搜索。
自然语言搜索引擎将计算每一个文档对象和查询的相关度。这里,相关度是基于匹配的关键词的个数,以及关键词在文档中出现的次数。**在整个索引中出现次数越少的词语,匹配时的相关度就越高。**相反,非常常见的单词将不会被搜索,如果一个词语的在超过 50% 的记录中都出现了,那么自然语言的搜索将不会搜索这类词语。
这个机制也比较好理解,比如说,一个数据表存储的是一篇篇的文章,文章中的常见词、语气词等等,出现的肯定比较多,搜索这些词语就没什么意义了,需要搜索的是那些文章中有特殊意义的词,这样才能把文章区分开。
在布尔搜索中,我们可以在查询中自定义某个被搜索的词语的相关性,当编写一个布尔搜索查询时,可以通过一些前缀修饰符来定制搜索。
MySQL 内置的修饰符,上面查询最小搜索长度时,搜索结果 ft_boolean_syntax 变量的值就是内置的修饰符,下面简单解释几个,更多修饰符的作用可以查手册。
限制: MySQL数据库从3.23.23版开始支持全文索引,但MySQL5.6.4以前只有Myisam支持
,5.6.4版本以后innodb才支持
,但是官方版本不支持中文分词
,需要第三方分词插件。在5.7.6版本,MySQL内置了ngram全文解析器
,用来支持亚洲语种的分词。测试或使用全文索引时,要先看一下自己的 MySQL 版本、存储引擎和数据类型是否支持全文索引。
随着大数据时代的到来,关系型数据库应对全文索引的需求已力不从心,逐渐被 solr
、elasticSearch
等专门的搜索引擎所替代。
使用参数SPATIAL
可以设置索引为空间索引
。空间索引只能建立在空间数据类型上,这样可以提高系统获取空间数据的效率。MySQL中的空间数据类型包括GEOMETRY
、POINT
、LINESTRING
和POLYGON
等。目前只有MyISAM存储引擎支持空间检索,而且索引的字段不能为空值。对于初学者来说,这类索引很少会用到。
小结:不同的存储引擎支持的索引类型也不一样 **InnoDB :**支持 B-tree、Full-text 等索引,不支持 Hash 索引; MyISAM : 支持 B-tree、Full-text 等索引,不支持 Hash 索引; **Memory :**支持 B-tree、Hash 等索引,不支持 Full-text 索引; **NDB :**支持 Hash 索引,不支持 B-tree、Full-text 等索引; **Archive :**不支持 B-tree、Hash、Full-text 等索引;