详解MySQL的逻辑架构和SQL语句执行流程

Posted Code0cean

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了详解MySQL的逻辑架构和SQL语句执行流程相关的知识,希望对你有一定的参考价值。

文章目录

1. 逻辑架构

mysql的逻辑架构如下图所示:

MySQL逻辑结构中的各组件的作用说明:

  • Connectors:指的是不同语言中与SQL的交互。MySQL在TCP中定义了自己的应用层协议,提供了Native C API、JDBC、php等各语言的MySQL Connector或者ODBC,让客户端可方便与MySQL进行交互。

  • Management Services & Utilities:管理服务和工具组件,从备份和恢复的安全性、复制、集群、管理、配置、迁移和元数据等方面管理数据库。

  • Connection Pool:连接池,是为解决资源的频繁分配、释放所造成的问题而为数据库建立的一个“缓冲池”。原理:预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕后再放回去。它的作用是进行身份验证、线程重用、连接限制、管理用户的连接、线程处理等需要缓存的需求。

  • SQL Interface(SQL接口):接受用户的SQL命令,并且返回用户需要查询的结果。MySQL支持DML、DDL、存储过程等多种SQL接口

  • Parser解析器:在解析器中对SQL语句进行语法分析语义分析。将SQL语句分解成数据结构,并将这个结构传递到后续步骤,以后SQL语句的传递和处理就是基于这个结构。在SQL命令传到解析器的时候会被解析器验证和解析,并为其创建语法树,并根据数据字典丰富查询语法树,会验证该客户端是否具有执行该查询的权限。创建好语法树后,MySQL还会对SQL查询进行语法上的优化,进行查询重写。

  • Optimizer查询优化器:SQL语句在查询执行之前,会使用查询优化器对查询进行优化,得出一个最优的策略。SQL语句在语法解析之后、查询执行之前会使用查询优化器确定SQL语句的执行路径,生成一个执行计划。这个计划表明应该使用那些索引进行查询(全表检索或索引检索),表之间的连接顺序,最后会按照计划中的步骤调用存储引擎提供的方法来真正执行查询,并将结果返回给用户

  • Cache和Buffer:MySQL内部维持着一些Cache和Buffer,比如Query Cache用来缓存一条SELECT语句的执行结果,如果能够在其中找到对应的查询结果,那么就不必再进行查询解析、优化和执行的整个过程,直接将结果反馈给客户端。这个缓存机制是由一系列的小缓存组成,比如,表缓存、记录缓存、key缓存、权限缓存等。这个查询缓存可以在不同客户端共享。从MySQL5.7.20开始,不推荐使用查询缓存,并在MySQL8中删除。

  • Pluggable Storage Engines:可插拔存储引擎。

  • File System:数据存储在运行于裸设备的文件系统上,支持的文件类型有EXT3、EXT4 、NTFS、NFS。

  • Files&Logs :数据文件以及redo、undo等各种日志文件。

将MySQL的逻辑架构进行分层划分,可以分为四层,分别是:连接层,服务层,引擎层,存储层。

1.1 连接层

连接层的主要功能是:管理连接和权限验证。连接层包含本地 Socket 通信和大多数基于客户端/服务端工具实现的类似于TCP/IP 的通信。主要完成一些类似于连接处理、授权认证及相关的安全方案。

客户端访问MySQL服务器前,做的第一件事就是建立TCP连接。经过三次握手建立连接成功后,MySQL服务器对TCP传输过来的账号密码做身份认证、权限获取。

  • 用户名或密码不对,会收到一个Access denied for user错误。

  • 用户名密码认证通过,会从权限表查出账号拥有的权限与连接关联,之后的权限判断逻辑,都将依赖于此时读到的权限.

为了避免连接无限创建和TCP频繁创建销毁带来的资源耗尽、性能下降问题。MySQL服务器有专门的TCP连接池限制连接数,并且采用长连接模式复用TCP连接。

另外,建立TCP连接成功后,必须分配给一个线程专门与这个客户端的交互,所在该层上引入了线程池的概念,为通过安全认证接入的客户端提供线程。每一个连接从线程池中获取线程,省去了创建和销毁线程的开销。同样在该层上可以实现基于 SSL 的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。

1.2 服务层

SQL Interface: SQL接口接收用户的SQL命令,并且返回用户需要查询的结果。比如SELECT ... FROM就是调用SQL Interface。MySQL支持DML(数据操作语言)、DDL(数据定义语言)、存储过程、视图、触发器、自定义函数等多种SQL语言接口。

缓存: 查询缓存组件 MySQL内部维持着一些Cache和Buffer,比如Query Cache用来缓存一条SELECT语句的执行结果,如果能够在其中找到对应的查询结果,那么就不必再进行查询解析、优化和执行的整个过程了,直接将结果反馈给客户端。 这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等 。 这个查询缓存可以在不同客户端之间共享 。== 从MySQL 5.7.20开始,不推荐使用查询缓存,并在 MySQL 8.0中删除== 。

Parser: 解析器 在解析器中对 SQL 语句进行语法分析、语义分析。将SQL语句分解成数据结构,并将这个结构传递到后续步骤,以后SQL语句的传递和处理就是基于这个结构的。如果在分解构成中遇到错误,那么就说明这个SQL语句是不合理的。 在SQL命令传递到解析器的时候会被解析器验证和解析,并为其创建语法树 ,并根据数据字典丰富查询语法树,会验证该客户端是否具有执行该查询的权限 。创建好语法树后,MySQL还 会对SQl查询进行语法上的优化,进行查询重写。

Optimizer: 查询优化器 SQL语句在语法解析之后、查询之前会使用查询优化器确定 SQL 语句的执行路径,生成一个 执行计划 。 这个执行计划表明应该 使用哪些索引 进行查询(全表检索还是使用索引检索),表之间的连接顺序如何,使用“ 选取-投影-连接 ”策略最后会按照执行计划中的步骤调用存储引擎提供的方法来真正的执行查询,并将查询结果返回给用户。

例如:

SELECT id,name FROM student WHERE gender = '女';

这个SELECT查询先根据WHERE语句进行选取 ,而不是将表全部查询出来以后再进行gender过滤。 然后再根据id和name进行属性投影 ,而不是将属性全部取出以后再进行过滤,将这两个查询条件 连接起来生成最终查询结果。

1.3 引擎层

底层数据存取操作实现的部分,由多种存储引擎共同组成。它们负责存储和获取所有存储在MySQL中的数据,类似Linux的众多文件系统。

存储引擎层,存储引擎真正的负责了 MySQL 中数据的存储和提取,服务器通过 API 与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。

1.3.1 InnoDB 存储引擎

InnoDB是从MySQL 5.5.5版本开始成为了 默认存储引擎,也是最重要、使用最广泛的存储引擎。

InnoDB采用MVCC来支持高并发, 并且实现了四个标准的隔离级别。其默认级别是REPEATABLE READ (可重复读),并且通过间隙锁(next-key locking)策略防止幻读的出现。间隙锁使得InnoDB不仅仅锁定查询涉及的行,还会对索引中的间隙进行锁定,以防止幻影行的插入。

InnoDB表是基于聚簇索引建立的,聚簇索引对主键查询有很高的性能,不过它的二级索引(非主键索引)中必须包含主键列,所以如果主键列很大的话,其他的所有索引都会很大。因此如果表上的索引较多的话,主键应当尽可能小。

InnoDB内部做了很多优化,包括从磁盘读取数据时次啊用的可预测性预读,能够自动在内存中创建hash索引以加速读操作的自适应哈希索引,以及能够加速插入操作的插入缓冲区等。

1.3.2 MyISAM 存储引擎

MyISAM 存储引擎的特性:

  • 不支持事务、不具备AICD特性(原子性、一致性、分离性、可持续性)
  • 表级别锁定形式(更新数据时锁定整个表、这样虽然可以让锁定的实现成本很小但是同时大大降低了其并发的性能)
  • 读写相互阻塞(不仅会在写入的时候阻塞读取、还会在读取的时候阻塞写入、但是读取不会阻塞读取)
    只会缓存索引(myisam通过key_buffer_size来设置缓存索引,提高访问性能较少磁盘IO的压力、但是只缓存索引、不缓存数据)
  • 读取速度快、占用资源比较少
  • 不支持外键约束、只支持全文检索
  • MyISAM表支持数据压缩

InnoDB和 MyISAM 的比较

查看所有的数据库引擎的命令

show engines;


查看默认的数据库引擎的命令

show variables like '%storage_engine%';

1.3.3 其他存储引擎

  • Archive引擎

Archive存储引擎只支持INSERT和SELECT操作,在MySQL5.1之前不支持索引。

Archive表适合日志和数据采集类应用。适合低访问量大数据等情况。
根据英文的测试结论来看,Archive表比MyISAM表要小大约75%,比支持事务处理的InnoDB表小大约83%。

  • Blackhole引擎

Blackhole引擎没有实现任何存储机制,它会丢弃所有插入的数据,不做任何保存。但服务器会记录Blackhole表的日志,所以可以用于复制数据到备库,或者简单地记录到日志。但这种应用方式会碰到很多问题,因此并不推荐。

  • CSV引擎

CSV引擎可以将普通的CSV文件作为MySQL的表来处理,但不支持索引。
CSV引擎可以作为一种数据交换的机制,非常有用。

CSV存储的数据直接可以在操作系统里,用文本编辑器,或者excel读取。

  • Memory引擎

如果需要快速地访问数据,并且这些数据不会被修改,重启以后丢失也没有关系,那么使用Memory表是非常有用。Memory表至少比MyISAM表要快一个数量级。(使用专业的内存数据库更快,如redis)

  • Federated引擎

Federated引擎是访问其他MySQL服务器的一个代理,尽管该引擎看起来提供了一种很好的跨服务器的灵活性,但也经常带来问题,因此默认是禁用的。

1.4 存储层

所有的数据,数据库、表的定义,表的每一行的内容,索引,都是存在文件系统上,以文件的方式存在的,并完成与存储引擎的交互。

当然有些存储引擎比如InnoDB,也支持不使用文件系统直接管理裸设备,但现代文件系统的实现使得这样做没有必要了。在文件系统之下,可以使用本地磁盘,可以使用 DAS、NAS、SAN等各种存储系统。

3. SQL语句执行流程

SQL语句在MySQL中的流程:查询缓存-》解析器-》优化器-》执行器

3.1 查询缓存

MySQL 客户端通过协议与 MySQL服务器建连接,发送查询语句,先检查查询缓存,如果命中,直接返回结果,否则进行语句解析,也就是说,在解析查询之前,服务器会先访问查询缓存(query cache)——它存储 SELECT 语句以及相应的查询结果集。如果某个查询结果已经位于缓存中,服务器就不会再对查询进行解析、优化、以及执行。它仅仅将缓存中的结果返回给用户即可,这将大大提高系统的性能。

但是因为查询缓存往往效率不高,所以在 MySQL8.0 之后就抛弃 了这个功能。这是为什么呢?

  1. 这是查询缓存是提前把查询结果缓存起来,这样下次不需要执行就可以直接拿到结果。需要说明的是,在 MySQL 中的查询缓存,不是缓存查询计划,而是查询对应的结果。这就意味着查询匹配的可用性大大降低 ,只有相同的查询操作才会命中查询缓存 。两个查询请求在任何字符上的不同(例如:空格、注释、 大小写),都会导致缓存不会命中。因此 MySQL 的查询缓存命中率不高 。

  2. 如果查询请求中包含某些系统函数、用户自定义变量和函数、一些系统表,如 mysql 、 information_schema、 performance_schema 数据库中的表,那这个请求就不会被缓存。以某些系统函数举例,可能同样的函数的两次调用会产生不一样的结果,比如函数 NOW ,每次调用都会产生最新的当前 时间,如果在一个查询请求中调用了这个函数,那即使查询请求的文本信息都一样,那不同时间的两次查询也应该得到不同的结果,如果在第一次查询时就缓存了,那第二次查询的时候直接使用第一次查询 的结果就是错误的!

  3. 此外,既然是缓存,那就有它缓存失效的时候 。MySQL的缓存系统会监测涉及到的每张表,只要该表的 结构或者数据被修改,如对该表使用了 INSERT 、 UPDATE 、 DELETE 、 TRUNCATE TABLE 、 ALTER TABLE 、 DROP TABLE 或 DROP DATABASE 语句,那使用该表的所有高速缓存查询都将变为无效并从高速缓存中删除!对于 更新压力大的数据库 来说,查询缓存的命中率会非常低。

3.2 解析器

解析器的主要作用是SQL的词法分析和语法分析。首先MySQL通过关键字将 SQL 语句进行解析,并生成一颗对应的“解析树”。SQL语句的分析分为词法分析与语法分析。MySQL解析器将使用 MySQL语法规则验证和解析查询;预处理器则根据一些 MySQL规则进一步检查解析数是否合法。

分析器先做“ 词法分析 ”。你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面 的字符串分别是什么,代表什么。 MySQL 从你输入的"select"这个关键字识别出来,这是一个查询语 句。它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”。

接着,要做“ 语法分析 ”。根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法 。

例如如下SQL语句:

select username,ismale from userinfo where age>20 and level>5 and 1=1;

如果SQL语句正确,则会生成一个这样的语法树:

3.3 优化器

优化器的主要作用是生成执行计划和选择索引。 查询优化器当解析树被认为是合法的了,并且由优化器将其转化成执行计划。一条查询可以有很多种执行方式,最后都返回相同的结果。优化器的作用就是找到这其中最好的执行计划。比如是根据全表检索 ,还是根据索引检索等。

在查询优化器中,可以分为逻辑查询优化阶段和物理查询优化阶段。

  • 逻辑查询优化:逻辑查询优化就是通过改变SQL语句的内容来使得SQL查询更加高效,同时为物理查询优化提供更多的候选执行计划。通常采用的方式是对SQL语句进行等价变换,对查询进行重写,而查询重写的数学基础就是关系代数。对条件表达式进行等价谓词重写、条件简化,对视图进行重写,对子查询进行优化,对连接语句进行了外连接消除、嵌套连接消除等。

  • 物理查询优化:物理查询优化基于关系代数进行的查询重写,而关系代数的每一步都对应着物理计算。在这个阶段里,对于单表和多表的连接操作,需要高效地使用索引,提升查询效率。

3.4 执行器

执行器的主要功能是操作存储引擎和返回结果。执行器在执行之前需要判断该用户是否具备权限,如果没有就会返回权限错误。如果有权限,就打开表继续执行,执行器会根据表的引擎定义,调用存储引擎API对表进行读写。存储引擎API只是抽象接口,下面还有个存储引擎层。最终从存储引擎层返回结果给到客户端。

4. SQL语句的执行顺序

一条SQL语句的模板如下所示:

随着 MySQL版本的更新换代,其优化器也在不断的升级,优化器会分析不同执行顺序产生的性能消耗不同而动态调整执行顺序。下面是经常出现的查询顺序:

(8) SELECT (9)DISTINCT<select_list>
(1) FROM <left_table>
(3) <join_type> JOIN <right_table>
(2)         ON <join_condition>
(4) WHERE <where_condition>
(5) GROUP BY <group_by_list>
(6) WITH CUBE|ROLLUP
(7) HAVING <having_condition>
(10) ORDER BY <order_by_list>
(11) LIMIT <limit_number>

一共有十一个步骤,最先执行的是FROM操作,最后执行的是LIMIT操作。每个操作都会产生一个虚拟表,该虚拟表作为一个处理的输入,具体执行顺序如下:
(1) FROM:对FROM子句中的左表<left_table>和右表<right_table>执行笛卡儿积,产生虚拟表VT1;
(2) ON: 对虚拟表VT1进行ON筛选,只有那些符合<join_condition>的行才被插入虚拟表VT2;
(3) JOIN: 如果指定了OUTER JOIN(如LEFT OUTER JOIN、RIGHT OUTER JOIN),那么保留表中未匹配的行作为外部行添加到虚拟表VT2,产生虚拟表VT3。如果FROM子句包含两个以上的表,则对上一个连接生成的结果表VT3和下一个表重复执行步骤1~步骤3,直到处理完所有的表;
(4) WHERE: 对虚拟表VT3应用WHERE过滤条件,只有符合<where_condition>的记录才会被插入虚拟表VT4;
(5) GROUP By: 根据GROUP BY子句中的列,对VT4中的记录进行分组操作,产生VT5;
(6) CUBE|ROllUP: 对VT5进行CUBE或ROLLUP操作,产生表VT6;
(7) HAVING: 对虚拟表VT6应用HAVING过滤器,只有符合<having_condition>的记录才会被插入到VT7;
(8) SELECT: 执行SELECT操作,选择指定的列,插入到虚拟表VT8中;
(9) DISTINCT: 去除重复数据,产生虚拟表VT9;
(10) ORDER BY: 将虚拟表VT9中的记录按照<order_by_list>进行排序操作,产生虚拟表VT10;
(11) LIMIT: 取出指定行的记录,产生虚拟表VT11,并返回给查询用户

5. profile功能

5.1 开启查询缓存

(1)修改配置文件/etc/my.cnf,新增一行:query_cache_type=1


重启MySQL服务

systemctl restart mysqld

参考:
https://blog.csdn.net/weixin_50616848/article/details/125583895

5.2 开启profile

查看 profile 是否开启的命令

show variables like '%profiling%';


profiling为OFF,则为没有开启。如果没有开启,可以执行以下命令开启!

或者使用以下命令:

select @@profiling;

开启profile功能的命令

 set profiling=1;

5.3 使用profile

执行 show profiles 命令,可以查看最近的几次查询和执行的命令。

 show profiles;


show profile命令说明

show profile [type,type,..] for query n limit row_count [offset offset]

type的可选值:

  • ALL --显示所有参数的开销信息
  • BLOCK IO --显示IO相关开销
  • CONTEXT SWITCHES --上下文切换相关开销
  • CPU --显示CPU相关开销信息
  • IPC --显示发送和接收相关开销信息
  • MEMORY --显示内存相关开销信息
  • PAGE FAULTS --显示页面错误相关开销信息
  • SOURCE --显示和Source_function,Source_file,Source_line相关开销信息
  • SWAPS --显示交换次数相关开销信息

根据 Query_ID,可以进一步执行 show profile cpu,block io for query Query_id 来查看 sql 的具体执行步骤。

 select * from mydb.user where id=1;

根据 Query_ID,查看SQL语句的具体执行步骤。

 show profiles;
show profile cpu,block io for query 4;

当开启了查询缓存,并且命中缓存(SQL语句必须一致才可以缓存命中,如果对表进行了insert,update,delete等操作,缓存则会失效),SQL语句的大致执行步骤如下所示:

6. 数据库缓冲池

InnoDB存储引擎是以页为单位来管理存储空间的,我们进行的增删改查操作其实本质上都是在访问页面。而磁盘I/O需要消耗的时间很多,而在内存中进行操作,效率则会高很多,为了能让数据表或者索引数据被我们所用,DBMS会申请占用内存来作为数据缓冲池,在真正访问页面之前,需要把磁盘上的页缓存到内存中的buffer pool之后才能访问,从而让磁盘活动最小化,减少与磁盘直接进行I/O的时间。

缓冲池用来存储各种数据的缓存,如下图所示:

InnoDB缓冲池包括了数据页,索引页,插入缓存,锁信息,自适应Hash和数据字典信息等。

  • 缓存原则
    位置 * 频次这个原则,可以帮我们对I/O访问效率进行优化。首先位置决定效率,提供缓冲池就是为了在内存中可以直接访问数据。其次,频次决定优先级顺序。因为缓冲池的大小是有限的,会优先对使用频次高的热数据进行加载

  • 缓冲池的预读特性
    缓冲池的作用就是提升I/O效率,而我们进行读取数据的时候存在一个局部性原理,也就是说我们使用了一些数据,大概率还会使用它周围的一些数据,因此采用预读机制提前加载,可以减少未来可能的磁盘I/O操作

6.1 缓存池如何读取数据

缓冲池管理器会尽量将使用的数据保存起来,在数据库进行页面操作读操作的时候,首先会判断该页是否存在缓冲池中,如果存在就直接读取,如果不存在,就会通过内存或磁盘将页面放到缓冲池中再进行读取。

如果我们执行SQL语句的时候更新了缓冲池中的数据,那么这些数据会马上同步到磁盘吗?
实际上,当我们对数据库中的记录进行修改的时候,首先会修改缓冲池中页里面的记录信息,然后数据库会以一定的频率刷新到磁盘。缓冲池会采用一种叫做checkpoint的机制将数据回写到磁盘上。
当缓冲池不够用时,需要释放掉一些不常用的页,此时就可以强行采用checkpoint的方式,将不常用的脏页回写到磁盘上,然后再从缓冲池中将这些页释放掉。

6.2 操作缓冲池

通过innodb_buffer_pool_size变量来查看缓冲池的大小

show variables like 'innodb_buffer_pool_size';

修改缓冲池的大小的命令

set global innodb_buffer_pool_size =

Buffer Pool的本质是InnoDB向操作系统申请的一块连续的内存空间,在多线程环境下,访问Buffer pool中的数据都需要加锁处理。在Buffer pool特别大而且多线程并发访问特别高的情况下,单一的Buffer Pool可能影响处理的请求速度。所以在Buffer Pool特别大的时候,我们可以把它们差分成若干个小的Buffer Poll,它们独立出去,独立申请内存空间,独立的管理各种链表,所以在多线程并发访问时并不会相互影响,从而提高并发处理能力。

我们可以在服务器启动的时候设置innodb_buffer_pool_instances的值来修改Buffer Pool实例的个数。

[server]
#创建2个Buffer Pool实例
innodb_buffer_pool_instance = 2;

查看缓冲池个数的命令

show variables like 'innodb_buffer_pool_instances';


默认情况下的Buffer Pool只有1。每个Buffer Pool实例占用的内存大小等于innodb_buffer_pool_size/innodb_buffer_pool_instance 。因为管理各个Buffer Pool也是需要性能开销的,所以并不是Buffer Pool实例创建越多越好。InnoDB规定:当innodb_buffer_pool_size的值小于1G的时候设置多个实例是无效的,InnoDB会默认把innodb_buffer_pool_instance的值修改为1.当innodb_buffer_pool_size的值大于或等于1G的时候,推荐设置多个Buffer Pool实例。

参考:
https://blog.csdn.net/qq_27495855/article/details/124326854
https://blog.csdn.net/weixin_51538712/article/details/124586938
https://blog.csdn.net/qq_38327769/article/details/12421975

MySQL存储引擎 lnnoDB的逻辑架构详解 InnoDB存储引擎执行流程

MySQL组织架构

在这里插入图片描述

连接层

  1. 验证用户的身份,用户名密码是否匹配
  2. 提供两种连接方式(TCP/IP连接、socket连接)
  3. 连接层提供了一个与sql层交互的线程

SQL层

  1. 接收连接层传过来的SQL语句
  2. 验证执行的SQL语法
  3. 验证SQL的语义(DDL,DML,DQL,DCL)
  4. 解析器:解析SQL语句,生成执行计划
  5. 优化器:将解析器传来的执行计划选择最优的一条执行
  6. 执行器:将最优的一条执行
    6.1 与存储引擎层建立交互的线程
    6.2 将要执行的sql发给存储引擎层
  7. 如果有缓存,则走缓存
  8. 记录日志(如binlog)

存储引擎层

  1. 接收SQL层传来的语句

  2. 与磁盘交互,获取数据,返回给sql层

  3. 建立与sql层交互的线程

     MySQL数据库管理软件服务端  =====>  存储引擎innodb 
     				操作系统  =====>  文件系统 
     			  计算机硬件  =====>  硬盘
    

查看存储引擎信息

# 查看所有支持的存储引擎
show engines;
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine             | Support | Comment                                                        | Transactions | XA   | Savepoints |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| PERFORMANCE_SCHEMA | YES     | Performance Schema                                             | NO           | NO   | NO         |
| CSV                | YES     | CSV storage engine                                             | NO           | NO   | NO         |
| MRG_MYISAM         | YES     | Collection of identical MyISAM tables                          | NO           | NO   | NO         |
| BLACKHOLE          | YES     | /dev/null storage engine (anything you write to it disappears) | NO           | NO   | NO         |
| MyISAM             | YES     | MyISAM storage engine                                          | NO           | NO   | NO         |
| FEDERATED          | NO      | Federated MySQL storage engine                                 | NULL         | NULL | NULL       |
| ARCHIVE            | YES     | Archive storage engine                                         | NO           | NO   | NO         |
| MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables      | NO           | NO   | NO         |
| InnoDB             | DEFAULT | Supports transactions, row-level locking, and foreign keys     | YES          | YES  | YES        |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+

innodb存储引擎支持的核心特性

  1. 事务
  2. 行级锁:innodb支持行级锁,myisam是表级锁,锁的粒度越小并发能力越强
  3. 外键
  4. MVCC 多版本并发控制
  5. 备份和恢复 innodb支持支持热备,myisam不支持
  6. 自动故障恢复 (CSR) Crash Safe Recovery
    6.1 对硬盘上的数据进行修改, 当修改过程中,突然断电或者由于其它外界因素导致数据丢失。
    6.2 内存里面的数据先记录在日志(如binlog),再写入硬盘。innodb搜索引擎自带的故障恢复功能,redo日志。
  • MySQL 5.5.8 版本开始是默认的存储引擎:innodb
  • MySQL 5.5.8 版本之前是默认的存储引擎:myisam

myisam搜索引擎的弊端

  1. 表级锁:对表中任意一行数据修改类操作时,整个表都会锁定,对其他行的操作都不能同时进行。
  2. 不支持故障自动恢复(CSR):当断电时有可能会出现数据损坏或丢失的问题。
# 查看正在使用的存储引擎
show variables like 'storage_engine%';
或者
ELECT @@default_storage_engine;

# 查看innodb的表有哪些,通过查表information_schema.tables来获取
# table_schema字段的值即表所在的库
select table_schema,table_name,engine from information_schema.tables where engine='innodb';

# 查看myisam的表有哪些,通过查表information_schema.tables来获取
select table_schema,table_name,engine from information_schema.tables where engine='myisam';

# 查看表的存储引擎
SHOW CREATE TABLE db1.t1\\G
或者
SELECT TABLE_NAME, ENGINE FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 't1' AND TABLE_SCHEMA = 'db1'\\G

修改存储引擎

配置文件修改存储引擎

vim /etc/my.cnf
[mysqld]
default-storage-engine=innodb
# 每个表都有自己独立的.ibd(数据文件)文件。innodb搜索引擎5.5之前的版本中,不加该选项,默认为数据文件都是存在mysql/ibdata1(共享表空间)文件中
innodb_file_per_table=1	

临时修改存储引擎

#在MySQL命令行中临时设置
SET @@storage_engine=myisam;

#查看
SELECT @@default_storage_engine;

建表时修改存储引擎

CREATE TABLE egon(id INT) ENGINE=myisam;

将Myisam存储引擎替换为Innodb存储引擎,并实现数据迁移

实验环境

# 将当前使用的数据库搜索引擎改成mysiam存储引擎
mysql> SET @@storage_engine=myisam;
Query OK, 0 rows affected, 1 warning (0.00 sec)

# 查看当前数据库默认使用的存储引擎
mysql> SELECT @@default_storage_engine;
+--------------------------+
| @@default_storage_engine |
+--------------------------+
| MyISAM                   |
+--------------------------+

# 使用Myisam存储引擎创建数据
mysql> create database db01;
Query OK, 1 row affected (0.00 sec)

mysql> use db01;
Database changed
mysql> create table t1(
    -> id int primary key auto_increment,
    -> name varchar(16)
    -> );
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t1(name) values("nana"),("dada");
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

# 查看t1表的存储引擎
mysql> SHOW CREATE TABLE t1\\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(16) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

替换存储引擎,实现数据迁移

  1. 导出Mysiam存储引擎的存储的库文件
  2. 修改导出库的配置文件,将配置文件的存储引擎myisam该成innodb。
  3. 将导出的库配置文件传给新的数据库服务器
  4. 在新的数据库服务器中导入库配置文件
# 激活服务器上的Binlogging
vim /etc/my.cnf
[mysqld]
log-bin=mysql-bin

# 在旧机器上备份系统库以外的生产库数据
# --triggers (默认导出触发器,使用–skip-triggers屏蔽导出)
# -R:–routines,导出存储过程以及自定义函数
mysqldump -uroot -p123 -B db01 --triggers -R --master-data=2 >/tmp/db01.sql

# 对备份数据进行处理(将engine字段替换)
sed -i 's#ENGINE=MYISAM#ENGINE=INNODB#gi' /tmp/db01.sql

# 将备份的数据传到新的数据库服务器上
scp -r /tmp/db01.sql root@192.168.15.7:/tmp

# 将修改后的备份恢复到新库
mysql -uroot -p123 < /tmp/db01.sql

lnnoDB的逻辑架构

如下图所示,InnoDB的逻辑架构主要分为三个大的组成部分:

  1. 在内存中的架构(In-Memory Structures):用户空间
  2. 操作系统缓存(Operating System Cache) :内核空间
  3. 在硬盘上的架构(On-Disk Structures):硬盘

在这里插入图片描述

innodb在内存上的架构

InnoDB的内存架构分为4个部分:

  1. 缓冲池(Buffer Pool)

    缓冲池是一块用于缓存被访问过的表和索引数据的内存区域,缓冲池允许在内存中处理一些被用户频繁访问的数据。
    在某一些专用的服务器上,甚至有可能使用80%的物理内存作为缓冲池。
    缓冲池主要是为了通过降低磁盘IO的次数来提升数据的读性能。
    
  2. 写缓冲(Change Buffer)

    写缓冲是为了缓存缓冲池(Buffer Pool)中不存在的二级索引(Secondary Index)页的变更操作的一种特殊的内存数据结构。
    这些变更通常是一些Insert、Update、Delete等DML操作引发的。
    如果有一些其它的读操作将这些被变更的二级索引页加进了缓冲池(Buffer Pool),则这些变更的数据会被马上合并至缓冲池中以保证用户读取数据的一致性。
    
  3. 日志缓冲(Log Buffer)

    InnoDB将数据的每次写优化为了批量写,这便以降低磁盘IO的次数,为了防止一些数据尚未写入硬盘就断电了,需要记录日志。
    而日志缓冲就是用来缓存一些即将要被写入磁盘日志文件(log files)中的数据。
    
  4. 自适应hash索引(Adaptive Hash Index)

    在Innodb中,用户是不可以直接去创建哈希索引的,这个自适应哈希索引是InnoDB为了加速查询性能。
    会根据实际需要来决定是否对于一些频繁需要被访问的索引页构建哈希索引,它会利用key的前缀来构建哈希索引。
    只适用于等值查询,例如:select * from db01.t1 where id = 1;
    

innodb在操作系统上的架构

write 和 fsync的操作是操作系统中的调用函数

在这里插入图片描述

  • write操作

    将数据写到操作系统缓存后立即返回。
    后面依靠操作系统的调度机制将缓存数据刷到磁盘中去,其顺序是user buffer——>page cache——>disk。
    
  • fsync操作

    将数据提交到硬盘中,强制硬盘同步。
    将一直阻塞到写入硬盘完成后返回,大量进行fsync操作就有性能瓶颈。
    

    o_direct选项:是在Linux系统中的选项,使用该选项后,对文件进行直接IO操作,不经过文件系统缓存,直接写入磁盘。

innodb在硬盘上的架构

InnoDB在硬盘上总共分为六个部分,也就是:

  1. 表(Tables);

    1.如果已经指定了数据的默认存储引擎,那么创建表的时候,无需指定再指定存储引擎。
    
    2.默认情况下,创建InnoDB表的时候innodb_file_per_table参数是开启的,它表明用户创建的表和索引,会被以单表单文件的形式放入到file-per-table表空间中。
    mysql> show variables like "innodb_file_per_table";
    +-----------------------+-------+
    | Variable_name         | Value |
    +-----------------------+-------+
    | innodb_file_per_table | ON    |
    +-----------------------+-------+
    1 row in set (0.00 sec)
    
    3.如果禁用了该参数innodb_file_per_table,那么表及索引会被放入系统表空间(system tablespaces)中。
    
    4.如果创建表的时候,想要把表创建在通用表空间(general tablespaces)中,那么需要用户使用create table … tablespace语法来创建表结构。
    
  2. 表空间(Tablespaces),指的就是.ibd文件;

    在InnoDB中,表空间总共分为:
    
    1.系统表空间(System Tablespaces : ibdata1)
    系统表空间主要用于存储双写缓冲、写缓存以及用户创建的表和索引(当innodb_file_per_table被禁用的情况下)
    
    2.file-per-table表空间(file-per-tableTablespaces : 每个table对应的.ibd文件)
    存储用户创建的表和索引数据,默认情况下(innodb_file_per_table参数是启用的)
    
    3.通用表空间(General Tablespaces)
    General Tablespaces是一种共享的 innodb 表空间,类似系统表空间,可以存储多个schema(架构)下的多张表。与系统表空间相比,这种方式可以将存储表空间的元数据到内存中,可以减小内存消耗。
    General Tablespaces可以将数据文件存放在其它盘上。
    这样做的好处是,比如说我需要将几张热点表放到更快的存储盘上,就可以在更快的存储盘上创建 general tablespace ,将热点表放过去。
    数据表可以在 general tablespace 和 per_file_table tablespace 之间来回移动。
    
    4.回滚表空间(Undo Tablespaces) 
    回滚表空间是为了存储回滚日志,通常回滚日志在表空间会以回滚段(Undo Segments)的形式存在。
    
    5.临时表空间(Temporary Tablespaces) 
    临时表空间用于存储用户创建的临时表,或者优化器内部自己创建的临时表。
    
  3. 索引(Indexes);

    按键的类别划分:主键索引和二级索引/辅助索引;
    按索引的类型分:BTree索引和自适应哈希索引;
    按存储结构划分:聚集索引和非聚集索引。
    
    索引存在的目的主要是为了加速数据的读取速度,InnoDB采用BTree(实际为优化改进后的B+树索引)。
    
    主键索引也是聚集索引,二级索引都是非聚集索引。
    
    自适应哈希索引是InnoDB为了加速查询性能,它自己按需在内存中对加载进内存的BTree索引优化为哈希索引的一种手段。
    
  4. 双写缓冲(Doublewrite Buffer);

    双写缓冲是一个在系统表空间System Tablespaces中存储区,在这个存储区中,在InnoDB将页面写入InnoDB数据文件中的适当位置之前,会先从缓冲池中刷新页面 。
    如果在页面写入过程中发生操作系统,存储子系统或mysqld进程崩溃,则InnoDB可以在崩溃恢复期间从双写缓冲中找到页面的原来的数据。
    
  5. Redo日志:记录的是尚未完成的操作,断电则用其重做

    redo即redo日志,是用于记录数据库中数据变化的日志,只要你修改了数据块那么就会记录redo信息,当然nologging除外了。
    
    你的每次操作都会先记录到redo日志中,当出现实例故障(像断电),导致数据未能更新到数据文件,则数据库重启时须redo,重新把数据更新到数据文件。
    
  6. Undo日志:记录的改动之前的旧数据,一旦改错,可以回滚

    undo即undo段,是指数据库为了保持读一致性,存储历史数据在一个位置。
    
    用于记录更改前的一份copy,用于回滚、撤销还原
    

innodb存储引擎执行流程

在这里插入图片描述

执行一条更新sql语句,存储引擎执行流程可以分为三大阶段,8个小步骤

三大阶段:

  1、执行阶段(1、2、3、4)
  	 数据加载到内存,写undo log,更新内存中数据,写redo log buffer

  2、事务提交阶段(5、6、7)
  	  redo log(修改的数据)和binlog(修改的操作)刷盘,commit标记写入redo log中
  	  
  3、最后(8)
  	  后台io线程随机把内存中脏数据(更新之后的数据)刷到磁盘上

8个小步骤:

  1. 把该行数据从磁盘加载到buffer pool中,并对该行数据进行加锁
  2. 写undo log
  3. 在buffer pool中的数据更新,得到脏数据(更新之后的数据)
  4. 把所作的修改写入到redo log buffer当中
  5. 准备提交事务redo log刷入磁盘
  6. 准备提交事务binlog写入磁盘
  7. 把binlog的文件名和位置写入commit标记,commit标记写入redolog中,事务才算提交成功;否则不会成功
  8. IO线程Buffer Pool中的脏数据刷入磁盘文件,完成最终修改
  1. 缓冲池 buffer pool(更新后的数据)

    1. 会把一些磁盘上的数据加载到该内存当中 
    2. 查询数据的时候不从磁盘查,从该内存里查
    
  2. undo log(回滚日志)

    1. 逻辑日志,可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录 
    2. 用于数据回滚
    3. 实现mvcc
    
  3. redo log(修改的数据)

    1. 存储引擎层日志 
    2. 物理日志(类似于"对哪个数据页中的什么记录,做了个什么修改") 
    3. 记录对数据做了什么修改,防止已提交事务的数据丢失。
       因为数据不是实时刷盘的,数据是在buffer pool当中,如果数据库宕机了并且buffer pool中的数据还没有刷盘,修改过的数据就丢失了,redo log解决这一问题。 
    4. redo log buffer是redo log的缓冲区,数据做了什么修改,首先会写入到redo log buffer中,再刷盘写入redo log中。
    
  4. binlog(修改的操作)

    归档日志,属于mysql server层,不属于存储引擎层 
    逻辑性日志(类似于"对users表中的id=10的一行数据做了更新操作,更新以后的值是什么")
    

常见面试题

问题1:事务还没有提交,mysql宕机了怎么办?

  • buffer pool和redo log buffer中的数据都会丢失,数据库返回异常,提示事务失败。磁盘数据并不会发生任何改变,不影响。

问题2:事务提交了,mysql突然宕机了怎么办?

  • 分两种情况
  1. 第一种情况: 事务提交失败了,那么对磁盘原来的数据没有任何影响。
  2. 第二种情况: 事务提交成功了,buffer pool和redo log buffer中的数据都会丢失,但事务提交成功意味着更新后的数据已经写入了redo日志文件,重启数据库之后,数据库会根据redo日志文件自动恢复数据。

commit标记的意义

  • commit命令用于把事务所做的修改保存到数据库,它把上一个commit或rollback(回滚)命令之后的全部事务都保存到数据库。

commit写入redo log,才能判定事务成功;因为此时,redo log中有这次更新记录,binlog也有这次更新记录。
redo log和binlog保持了一致,否则:

1. redo log刷盘成功,binlog还没刷盘   
   数据库宕机,没有commit标记写到redo log中,事务判定为失败。   
   因为redolog中有这次更新日志,binlog中没有这次更新日志,会出现数据不一致问题。

2. redo log刷盘成功,binlog刷盘成功   
   commit标记还没来得及写入redo log中,数据库宕机,同样判定事务提交失败。

我们需要知道的一个概念: commit标记成功之前,redo log和binlog都是在内存空间里面的。commit标记成功之后,才开始刷盘。
只有同时满足

redo log刷盘成功,
binlog刷盘成功,
commit标记写入redo log日志文件。

这样的条件下,才能判定事务提交成功。
innodb存储引擎每提交一条sql语句,默认是开启一个事务的。

redo log刷盘策略

当提交事务的时候,redo log buffer里的数据会根据一定规则刷到磁盘上
通过innodb_flush_log_at_trx_commit参数来配置

0  提交事务的时候,不立即把 redo log buffer 里的数据刷入磁盘文件的,而是依靠 InnoDB 的主线程每秒执行一次刷新到磁盘。
此时可能你提交事务了,结果 mysql 宕机了,然后此时内存里的数据全部丢失。

1 (默认值,建议)提交事务的时候,就必须把 redo log 从内存刷入到磁盘文件里去。
只要事务提交成功,那么 redo log 就必然在磁盘里了。

2  提交事务的时候,把 redo 日志写入磁盘文件对应的 os cache 操作系统缓存里去,而不是直接进入磁盘文件。
可能 1 秒后才会把 os cache 操作系统缓冲里的数据写入到磁盘文件里去。此时mysql宕机,数据不会丢失;如果机器宕机,数据会丢失。

binlog刷盘策略

当提交事务的时候,binlog也会刷到磁盘上去
通过sync_binlog参数来配置

0  默认值。事务提交后,将二进制日志写入了操作系统缓存,若操作系统宕机则会丢失部分二进制日志。

1 (推荐)事务提交后,将二进制文件写入磁盘并立即执行刷新操作,相当于是同步写入磁盘,不经过操作系统的缓存。

内存(buffer pool)中更新过脏数据什么时候刷盘

  1. redo log满的情况下才会主动刷入磁盘
  2. 系统内存不足时,需要将一部分数据页淘汰掉,如果淘汰的是脏页,需要先将脏页同步到磁盘;
  3. MySQL 认为空闲的时间,这种情况没有性能问题;
  4. MySQL 正常关闭之前,会把所有的脏页刷入到磁盘,这种情况也没有性能问题

LRU算法淘汰策略

  • 常用的的索引页和数据页都会缓存在内存缓冲池(Buffer Pool)中,在查询数据时,只要在内存缓冲池中存在该数据,InnoDB就不用每次都去磁盘中读取页,从而提高数据库的查询性能。
  • 缓冲池需要通过 LRU 算法将最近且经常查询的数据缓存在其中,而不常查询的数据就淘汰出去。

以上是关于详解MySQL的逻辑架构和SQL语句执行流程的主要内容,如果未能解决你的问题,请参考以下文章

MySql基础架构以及SQL语句执行流程

MySQL逻辑架构——SQL执行流程

Mysql语句执行逻辑

Mysql 逻辑架构图及日志系统

MySQL逻辑架构

MySQL架构与执行流程