MySQL的文件结构

  • MySQL是通过文件系统来实现对数据和索引的存储。
  • MySQL在物理结构上大致可分为两种:数据文件,索引文件。
    MySQL在Linux中的数据索引文件和日志文件通常放在/var/lib/mysql目录下。

日志文件

  1. **日志文件采用顺序IO的方式存储。**日志文件分为:错误日志,查询日志,慢查询日志,二进制日志,事物日志,中续日志。
  1. #查询当前数据库使用的日志信息
  2. show variables like 'log_%';

错误日志(error log)

  1. 默认是开启,而且从5.5.7以后无法关闭错误日志,错误日志记录了运行过程中遇到的所有严重的错误信息,以及 MySQL每次启动和关闭的详细信息。<br />默认的错误日志名称:hostname.err
  2. 错误日志所记录的信息是可以通过log-errorlog-warnings来定义的,其中log-err是定义是否启用错误日志的功能和错误日志的存储位置,log-warnings是定义是否将警告信息也定义至错误日志中。
  1. #可以直接定义为文件路径,也可以为ON|OFF
  2. log_error=/var/log/mysqld.log
  3. #只能使用1|0来定义开关启动,默认是启动的
  4. log_warings=1

二进制日志(bin log)

  1. #默认是关闭的,需要通过以下配置进行开启。
  2. log-bin=mysql-bin
  1. 其中mysql-binbinlog日志文件的basenamebinlog日志文件的完整名称:mysql-bin-000001.log
  2. binlog记录了数据库所有的ddl语句和dml语句,但不包括select语句内容,语句以事件的形式保存,描述了数据的变更顺序,binlog还包括了每个更新语句的执行时间信息。如果是DDL语句,则直接记录到binlog日志,而DML语句,必须通过事务提交才能记录到binlog日志中。<br />binlog主要用于实现mysql主从复制、数据备份、数据恢复。

通用查询日志(general query log)

  1. 由于通用查询日志会记录用户的所有操作,其中还包含增删查改等信息,在并发操作大的环境下会产生大量的信息从而导致不必要的磁盘IO,会影响mysql的性能的。如若不是为了调试数据库的目的建议不要开启查询日志。
  1. #默认关闭,查询是否开启语句
  2. show global variables like 'general_log';
  3. #启动开关
  4. general_log={ON|OFF}
  5. #日志文件变量,而general_log_file如果没有指定,默认名是host_name.log
  6. general_log_file=/PATH/TO/file
  7. #记录类型
  8. log_output={TABLE|FILE|NONE}

慢查询日志(slow query log)

  1. #默认是关闭的
  2. #开启慢查询日志
  3. slow_query_log=ON
  4. #慢查询的阈值
  5. long_query_time=10
  6. #日志记录文件如果没有给出file_name值, 默认为主机名,后缀为-slow.log。如果给出了文件名,
  7. 但不是绝对路径名,文件则写入数据目录。
  8. slow_query_log_file= file_name
  9. #查询那些SQL是慢SQL
  10. SHOW GLOBAL STATUS LIKE '%Slow_queries%';

数据文件

  1. **数据文件采用随机IO方式存储。**
  1. #查看数据文件所在位置
  2. SHOW VARIABLES LIKE '%datadir%';

InnoDB数据文件存储方式

  • .frm:存储表结构。
  • .ibd:存储数据和索引。

MyIsam文件存储方式

  • .from:存储表结构。
  • .myd:存储数据。
  • .myi:存储索引。

逻辑架构

MySQL架构解析 - 图1

Connectors

  1. 连接器:如JDBC这种内库,或者Native这种操作工具。

Management Serveices & Utilities

  1. 系统管理和控制工具

Connection Pool: 连接池

  • 管理用户连接,等待处理连接请求。
  • 负责监听对 MySQL Server 的各种请求,接收连接请求,转发所有连接请求到线程管理模块。每一个连接上 MySQL Server 的客户端请求都会被分配(或创建)一个连接线程为其单独服务。
  • 而连接线程的主要工作就是负责 MySQL Server 与客户端的通信,接受客户端的命令请求,传递Server 端的结果信息等。线程管理模块则负责管理维护这些连接线程。包括线程的创建,线程的cache 等。

SQL Interface: SQL接口

  1. 接受用户的SQL命令,并且返回用户需要查询的结果。

Parser: 解析器

  1. SQL命令传递到解析器的时候会被解析器验证和解析。
  • 将SQL语句进行词法分析和语法分析,解析成语法树,然后按照不同的操作类型进行分类,然后做出针对性的转发到后续步骤,以后SQL语句的传递和处理就是基于这个结构的。
  • 如果在分解过程中遇到错误,那么就说明这个sql语句是不正确的。

Optimizer: 查询优化器

  1. SQL语句在查询之前会使用查询优化器对查询进行优化。explain语句查看的SQL语句执行计划,就是由查询优化器生成的。

Cache和Buffer: 查询缓存

  1. 主要功能是将客户端提交给MySQL select请求的返回结果集 cache 到内存中,与该 query 的一个 hash 做一个对应。该 Query 所取数据的基表发生任何数据的变化之后, MySQL 会自动使该query Cache 失效。在读写比例非常高的应用系统中, Query Cache 对性能的提高是非常显著的。当然它对内存的消耗也是非常大的。<br /> 如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等

Pluggable Storage Engines:存储引擎

  1. MySQL有一个被称为“Pluggable Storage Engine Architecture”(可插拔的存储引擎架构)的特性,也就意味着MySQL数据库提<br />供了多种存储引擎。
  2. 而且存储引擎是针对表的,用户可以根据不同的需求为数据表选择不同的存储引擎,用户也己的需要编写自己的存储引擎。也就是说,同一数据库不同的表可以选择不同的存储引擎

MySqlServer层对象

SQL语句执行流程

MySQL架构解析 - 图2

  1. 大体来说,MySQL 可以分为 Server 层和存储引擎层两部分:
  2. Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
  3. 存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDBMyISAMMemory 等多个存储引擎。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。也就是说,你执行 create table 建表的时候,如果不指定引擎类型,默认使用的就是 InnoDB。不过,你也可以通过指定存储引擎的类型来选择别的引擎,比如在 create table 语句中使用 engine=memory,来指定使用内存引擎创建表。不同存储引擎的表数据存取方式不同,支持的功能也不同。
  1. #下面我们将对这条SQL语句进行执行流程的分析
  2. select customer_id,first_name,last_name from customer where customer_id=14;

连接器

第一步,先连接到这个数据库上,这时候接待你的就是连接器。连接器负责跟客户端建立连接、获取权限、维持和管理连接。连接命令一般是这么写的:

  1. -h$ip -P$port -u$user -p
  1. 输完命令之后,你就需要在交互对话里面输入密码。虽然密码也可以直接跟在 -p 后面写在命令行中,但这样可能会导致你的密码泄露。
  2. 如果连的是生产服务器,强烈建议不要这么做。连接命令中的 mysql 是客户端工具,用来跟服务端建立连接。在完成经典的 TCP 握手后,连接器就要开始认证身份,这个时候用的就是输入的用户名和密码。
  • 如果用户名或密码不对,就会收到一个”Access denied for user”的错误,然后客户端程序结束执行。
  • 如果用户名密码认证通过,连接器会到权限表里面查出账号拥有的权限。之后 这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。
  1. 也就是说,一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置。<br /> 连接完成后,如果你没有后续的动作,这个连接就处于空闲状态,你可以在 show processlist 命令中看到它。文本中这个图是 show processlist 的结果,其中的 Command 列显示为“Sleep”的这一行,就表示现在系统里面有一个空闲连接。

MySQL架构解析 - 图3

  1. 客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 wait_timeout 控制的,默认值是 8 小时。
  2. 建立连接的过程通常是比较复杂的,所以我建议你在使用中要尽量减少建立连接的动作,也就是尽量使用长连接。但是全部使用长连接后,你可能会发现,有些时候 MySQL 占用内存涨得特别快,这是因为 MySQL 在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放。所以如果长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM),从现象看就是 MySQL 异常重启了。

可以考虑以下两种方案:

  • 定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。
  • 如果用的是 MySQL 5.7 或更新版本,可以在每次执行一个比较大的操作后,通过执行mysql_reset_connection 来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。

查询缓存

  1. MySQL 拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果可能会以 key-value 对的形式,被直接缓存在内存中。key 是查询的语句hash之后的值,value 是查询的结果。如果你的查询能够直接在这个缓存中找到 key,那么这个 value 就会被直接返回给客户端。
  2. 如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。你可以看到,如果查询命中缓存,MySQL 不需要执行后面的复杂操作,就可以直接返回结果,就不走接下来的流程了,这个效率会很高。

需要注意的是,MySQL 8.0 版本直接将查询缓存的整块功能删掉了,也就是说 8.0 开始彻底没有这个功能了。

分析器

  1. 这个阶段就是 MySQL Parser 解析器和 Preprocessor预处理模块的功能。
  2. 客户端程序发送过来的请求事实上只是一段文本而已,所以MySQL服务器程序首先需要对这段文本做分析,判断请求的语法是否正确,然后从文本中将要查询的表、列和各种查询条件都提取出来,本质上是对一个SQL语句编译的过程,涉及词法解析、语法分析 等阶段。

解析器先会做“词法分析”。

词法分析就是把一个完整的 SQL 语句分割成一个个的字符串,比如这条简单的SQL语句

  1. select customer_id,first_name,last_name from customer where customer_id=14;
  2. #会被分割成10个字符串
  3. selectcustomer_id,first_name,last_namefromcustomerwherecustomer_id,=,14
  1. MySQL 同时需要识别出这个SQL语句中的字符串分别是什么,代表什么。MySQL "select"这个关键字识别出来,这是一个查询语句,把字符串“customer”识别成“表名 customer”,把字符串“customer_id识别成“列 customer_id

做完了这些识别以后,解析器就要做“语法分析”。

  1. 分析器的第二步是根据词法分析的结果,语法分析器会根据语法规则做语法检查,判断输入的这个SQL 语句是否满足 MySQL 语法。
  2. 如果语句不对,就会收到“You have an error in your SQL syntax”的错误提醒,比如下面这个语句 select 少打了开头的字母“s”。
  1. elect customer_id,first_name,last_name from customer where customer_id=14;
  2. ERROR 1064 (42000): You have an error in your SQL syntax; check the manual
  3. that corresponds to your MySQL server version for the right syntax to use
  4. near 'elect * from customer where customer_id=14' at line 1
  1. 一般语法错误会提示第一个出现错误的位置,所以要关注的是紧接“use near”的内容。
  2. 如果语法正确,就会根据 MySQL 定义的语法规则,根据 SQL 语句生成一个数据结构,这个数据结构我们把它叫做解析树。

MySQL架构解析 - 图4

预处理器

  1. 预处理器则会进一步去检查解析树是否合法,比如表名是否存在,语句中表的列是否存在等等,在这一步MySQL会检验用户是否有表的操作权限。预处理之后会得到一个新的解析树。

优化器

  1. 经过了解析器和预处理器,得到了解析树后,MySQL已经知道你要做什么了,那是不是这时就可以执行语句了呢?
  2. 这里我们有一个问题,一条 查询 语句是不是就只有一种执行方式?数据库最终执行的 SQL 是不是就是我们发送的 SQL?答案是否定的。一条 SQL 语句是可以有多种执行方式的,它们最终返回结果是相同的。但是在这么多种执行方式,我们最终选择哪一种去执行?选择的判断标准是什么呢? 这个是就是优化器的作用。

优化器一般会做如下处理:

  • 当有多个索引可用的时候,决定使用哪个索引;
  • 在一个语句有多表关联(join)的时候,决定各个表的连接顺序,以哪个表为基准表

执行器

  1. MySQL 通过分析器知道了要做什么,通过优化器知道了该怎么做,得到了一个查询计划。于是就进入了执行器阶段,开始执行语句。
  2. 开始执行的时候,要先判断一下你对这个表customer有没有执行查询的权限,如果没有,就会返回没有权限的错误。 (在工程实现上,如果命中查询缓存,会在查询缓存返回结果的时候,做权限验证。)。
  3. 比如我们新建一个用户lesson1_test,只有表actor的查询权限,没有表customer的查询权限。
  1. CREATE USER `lesson1_test`@`localhost` IDENTIFIED BY '123456';
  2. GRANT Select ON TABLE `sakila`.`actor` TO `lesson1_test`@`localhost`;
  1. 使用这个用户lesson1_test连接mysql,执行下面的查询语句,就会返回没有权限的错误。
  1. select * from customer where customer_id=14;
  2. ERROR 1142 (42000): SELECT command denied to user 'lesson1_test'@'localhost'
  3. for table 'customer'
  1. 如果有权限,就使用指定的存储引擎打开表开始查询。执行器会根据表的引擎定义,去使用这个引擎提供的查询接口,提取数据。
  2. 比如我们这个例子中的表 customer中,customer_id 字段是主键,那么执行器的执行流程是这样的:
  • 调用 InnoDB 引擎接口,从主键索引中检索customer_id=14的记录。
  • 主键索引等值查询只会查询出一条记录,直接将该记录返回客户端。至此,这个语句就执行完成了。InnoDB主键索引和非主键索引的检索流程,我们在后面的索引部分内容会展开说明,这里就不做过多说明了。
    假设customer_id 字段不是索引,这时查询只能全表扫描。那么执行器的执行流程是这样的:
  • 调用 InnoDB 引擎接口取这个表的第一行,判断customer_id 值是不是 14,如果不是则跳过,如果是则将这行缓存在结果集中。
  • 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
  • 执行器将上述遍历过程中所有满足条件的行组成的结果集返回给客户端。至此,这个语句就执行完成了

详细的执行流程图:

MySQL架构解析 - 图5

存储引擎分类

存储引擎 描述
InnoDB 支持事物,行锁,外键,MVCC,拥有回滚能力。5.5版本以后默认的数据库引擎
MyISAM 高速引擎,拥有较高的插入,查询速度,不支持事务
Memory 内存存储引擎,拥有极高的插入,更新和查询效率。但是会占用和数据同比例的内存。存储在内存的数据意外着数据可能会丢失
ISAM MyISAM的前身,MySQL5.0以后不再默认安装
Falcon 一种新的存储引擎,支持事物处理,传言可能是InnoDB的替代者
Archive 将数据压缩后进行存储,非常适合存储大量的独立的,作为历史记录的数据,但是只能进行插入和查询操作
CSV CSV 存储引擎是基于 CSV 格式文件存储数据(应用于跨平台的数据
MRG_MyISAM(MERGE) 将多个表联合成一个表使用,在超大规模数据存储时很有用

InnoDB存储引擎

InnoDB和MyISAM存储引擎区别

特性 InnoDB MyISAM
存储文件 .frm:表结构 .ibd:数据和索引 .frm:表结构 .myd:数据文件 .myi:索引文件
表锁,行锁 表锁
事物 支持 不支持
外键 支持 不支持
count 扫表 独立计数器
使用场景 常规系统CRUD 读多写少

InnoDB架构图

MySQL架构解析 - 图6

  1. 上图详细显示了InnoDB存储引擎的体系架构,从图中可见,InnoDB存储引擎由**内存池(Buffer Pool)**,**后台线程**和**磁盘文件(Redo Log)**三大部分组成。接下来我们就来简单了解一下内存相关的概念和原理。

InnoDB磁盘文件

InnoDB的主要的磁盘文件主要分为三大块:一是系统表空间,二是用户表空间,三是redo log日志文件

二进制文件(binlog)等文件是MySQL Server层维护的文件,所以未列入InnoDB的磁盘文件中。

重做日志文件

MySQL架构解析 - 图7

默认情况下,在InnoDB存储引擎的数据目录下会有两个名为ib_logfile0ib_logfile1的文件,这就是InnoDB的重做日志文件(redo log file),它记录了对于InnoDB存储引擎的事务日志。

重做日志文件的作用是什么?

  • 当InnoDB的数据存储文件发生错误时,重做日志文件就能派上用场。InnoDB存储引擎可以使用重做日志文件将数据恢复为正确状态,以此来保证数据的正确性和完整性。
  • 为了得到更高的可靠性,用户可以设置多个镜像日志组,将不同的文件组放在不同的磁盘上,以此来提高重做日志的高可用性。

重做日志文件组是如何写入数据的?

  1. 每个InnoDB存储引擎至少有1个重做日志文件组(group),每个文件组下至少有2个重做日志文件,如默认的**ib_logfile0**和**ib_logfile1**。

在日志组中每个重做日志文件的大小一致,并以循环写入的方式运行。InnoDB存储引擎先写入重做日志文件1,当文件被写满时。

会切换到重做日志文件2,再当重做日志文件2也被写满时,再切换到重做日志文件1。

如何设置重做日志文件大小?

  1. 用户可以使用innodb_log_file_size来设置重做日志文件的大小,这对InnoDB存储引擎的性能有着非常大的影响。

如果重做日志文件设置的太大,数据丢失时,恢复时可能需要很长的时间;另一方面,如果设置的太小,重做日志文件太小会导致依据checkpoint的检查需要频繁刷新脏页到磁盘中,导致性能的抖动。

系统表空间和用户表空间

MySQL架构解析 - 图8

系统表空间存储哪些数据?

系统表空间是一个共享的表空间,因为它是被多个表共享的。InnoDB系统表空间包含InnoDB数据字典(元数据以及相关对象)、double write buffer、changebuffer、undo logs的存储区域。

系统表空间也默认包含任何用户在系统表空间创建的表数据和索引数据。

系统表空间配置解析

系统表空间是由一个或者多个数据文件组成。默认情况下,一个初始大小为10MB,名为ibdata1的系统数据文件在MySQL的data目录下被创建。用户可以使用 innodb_data_file_path 对数据文件的大小和数量进行配置。

innodb_data_file_path 的格式如下:

  1. innodb_data_file_path=datafile1[,datafile2]...
  2. #示例(这里将/db/ibdata1和/dr2/db/ibdata2两个文件组成系统表空间):
  3. innodb_data_file_path=/db/ibdata1:1000M;/dr2/db/ibdata2:1000M:autoextend
  4. #如果这两个文件位于不同的磁盘上,磁盘的负载可能被平均,因此可以提高数据库的整体性能。
  5. #两个文件的文件名之后都跟了属性,表示文件ibdata1的大小为1000MB,文件ibdata2的大小为1000MB,而且用完空间之后可以自动增长(autoextend)。

如何使用用户表空间?

如果设置了参数innodb_file_per_table,则用户可以将每个基于InnoDB存储引擎的表产生一个独立的用户表空间。用户表空间的命名规则为:表名.ibd

通过这种方式,用户不用将所有数据都存放于默认的系统表空间中。

用户表空间存储哪些数据?

  1. 用户表空间只存储该表的数据、索引信息,其余信息还是存放在默认的系统表空间中。

InnoDB逻辑存储结构

  1. InnoDB存储引擎逻辑存储结构可分为五级:表空间、段、区、页、行。

MySQL架构解析 - 图9

表空间

  1. InnoDB存储引擎的逻辑存储结构看,所有数据都被逻辑地存放在一个空间中,称之为表空间(tablespace)。
  2. 从功能上来看,InnoDB存储引擎的表空间分为系统表空间,独占表空间,通用表空间,临时表空间,Undo表空间。
  3. 如果开启了独立表空间innodb_file_per_table=1,每张表的数据都会存储到一个独立的表空间,即一个单独的.ibd文件。
  4. InnoDB 存储引擎有一个共享表空间,叫做系统表空间,对一个磁盘上的文件名为ibdata1。如果设置了参数innodb_file_per_table=0,关闭了独占表空间,则所有基于InnoDB存储引擎的表数据都会记录到系统表空间。

  1. 表空间是由各个段组成的,常见的段有数据段、索引段、回滚段等。
  2. 如果开启了独立表空间innodb_file_per_table=1,每张表的数据都会存储到一个独立的表空间,即一个单独的.ibd文件。一个用户表空间里面由很多个段组成,创建一个索引时会创建两个段:数据段和索引段。
  3. 数据段存储着索引树中叶子节点的数据。
  4. 索引段存储着索引树中非叶子节点的数据。
  5. 一个段的空间大小是随着表的大小自动扩展的:表有多大,段就有多大。
  6. 一个段会包含多个区,至少会有一个区,段扩展的最小单位是区。

  1. 一个区由64个连续的页组成,一个区的大小=1M=64个页(16K)。为了保证区中页的连续性,区扩展时InnoDB 存储引擎会一次性从磁盘申请4 ~ 5个区。

  1. InnoDB 每个页默认大小时是 16KB,页是 InnoDB管理磁盘的最小单位,也InnoDB中磁盘和内存交互的最小单位。
  1. show global variables like 'innodb_page_size';
  1. 索引树上一个节点就是一个页,MySQL规定一个页上最少存储2个数据项。如果向一个页插入数据时,这个页已将满了,就会从区中分配一个新页。如果向索引树叶子节点中间的一个页中插入数据,如果这个页是满的,就会发生页分裂。
  2. 操作系统管理磁盘的最小单位也是页,是操作系统读写磁盘最小单位,Linux中页一般是4K
  3. 所以InnoDB从磁盘中读取一个数据页时,操作系统会分4次从磁盘文件中读取数据到内存。写入也是一样的,需要分4次从内存写入到磁盘中。

MySQL架构解析 - 图10

  1. InnoDB的数据是以行为单位存储的,1个页中包含多个行。

InnoDB内存结构

  1. ![](https://i.loli.net/2021/07/26/j4XFZQwMNJrAl3q.png#id=c5RuC&originHeight=429&originWidth=813&originalType=binary&ratio=1&status=done&style=none)

Buffer Pool缓冲池

  1. **概述**

InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。但是由于CPU速度和磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用缓冲池记录来提高数据库的的整体性能。

  1. 所以,缓冲池的大小直接影响着数据库的整体性能,可以通过配置参数 **innodb_buffer_pool_size**来设置。
  2. 具体来看,缓冲池中缓存的数据页类型有:

具体来看,缓冲池中缓存的数据页类型有:索引页、数据页、undo页、插入缓冲(insertbuffer)、自适应哈希索引(adaptive hash index)、InnoDB存储的锁信息(lock info)和数据字典信息(data dictionary)。

  1. 在架构图上可以看到,InnoDB存储引擎的内存区域除了有缓冲池之外,还有重做日志缓冲和额外内存池。InnoDB存储引擎首先将重做日志信息先放到这个缓冲区中,然后按照一定频率将其刷新到重做日志文件中。重做日志缓冲一般不需要设置的很大,该值可由配置参数 innodb_log_buffer_size 控制。

数据页和索引页

  1. InnoDB存储引擎工作时,需要以**Page页**为最小单位去将磁盘中的数据加载到内存中,与数据库相关的所有内容都存储在Page结构里。
  2. Page分为几种类型,数据页和索引页就是其中最为重要的两种类型。

Change Buffer(更新缓冲/插入缓冲)

  1. 我们都知道,在InnoDB引擎上进行插入操作时,一般需要按照主键顺序进行插入,这样才能获得较高的插入性能。当一张表中存在**次要索引**时,在插入时,数据页的存放还是按照主键进行顺序存放,但是对于次要索引叶节点的插入不再是顺序的了,这时就需要离散的访问次要索引页,由于随机读取的存在导致插入操作性能下降。InnoDB为此设计了**Change Buffer**来进行插入优化。对于次要索引的插入或者更新操作,不是每一次都直接插入到索引页中,而是先判断插入的非主键索引是否在缓冲池中,若在,则直接插入;若不在,则先放入到一个Change Buffer中。看似数据库这个非主键的索引已经插到叶节点,而实际没有,这时存放在另外一个位置。然后再以一定的频率和情况进行Change Buffer和非聚簇索引页子节点的合并操作。这时通常能够将多个插入合并到一个操作中,这样就大大提高了对于非聚簇索引的插入性能。

自适应哈希索引

InnoDB会根据访问的频率和模式,为热点页建立哈希索引,来提高查询效率。注意:人工是无法干预这个自适应哈希索引的。

  1. 哈希(hash)是一种非常快的查找方法,在一般情况下这种查找的时间复杂度为O(1),即一般仅需要一次查找就能定位数据。而B+树的查找次数,取决于B+树的高度,在生产环境中,B+树的高度一般为34层,故需要34次的查询。
  2. InnoDB存储引擎会监控对表上各索引页的查询。如果观察到建立哈希索引可以带来速度提升,则建立哈希索引,称之为自适应哈希索引(Adaptive Hash Index,AHI) AHI是通过缓冲池的B+树页构造而来,因此建立的速度很快,而且不需要对整张表构建哈希索引。 InnoDB存储引擎会自动根据访问的频率和模式来自动地为某些热点页建立哈希索引。

自适应hash创建的条件:

  • 索引被查询的次数足够多。如果查询次数用N1来表示的话那么N1应该大于17(N1>17),此过程无时间限制。
  • 基于查询的sql语句创建一个hash信息(hash info)其中包含了三部分内容:
    匹配索引的列数
    下一列匹配的字节数
    是否从左匹配
  1. #例如有如下SQL语句:
  2. select * from table1 where a1=1 and a2=2;
  3. #则生成如下哈希描述:
  4. hash info2, 0, true
  1. 如果这个生成的hash info被使用的次数足够多 `(N2 > 100)`
  • 生成的hash info能够命中某个数据页,且命中的该页上的记录数要大于该页上总记录数的1/16(N3 > 页记录数的1/16)

满足以上三点要求那么就会将hash info生成一个key,value就是指向数据页上记录的指针。 这样就建立起了mysql的自适应哈希索引

锁信息

  1. 多地方使用锁,从而允许对多种不同资源提供并发访问。数据库系统使用锁是为了支持对共享资源进行InnoDB存储引擎会在行级别上对表数据进行上锁。不过InnoDB也会在数据库内部其他很并发访问,提供数据的完整性和一致性。

数据字典信息

  1. InnoDB有自己的表缓存,可以称为表定义缓存或者数据字典(Data Dictionary)。当InnoDB打开一张表,就增加一个对应的对象到数据字典。
  2. 数据字典是对数据库中的数据、库对象、表对象等的元信息的集合。在MySQL中,数据字典信息内容就包括表结构、数据库名或表名、字段的数据类型、视图、索引、表字段信息、存储过程、触发器等内容。MySQL INFORMATION_SCHEMA库提供了对数据局元数据、统计信息、以及有关MySQL server的访问信息(例如:数据库名或表名,字段的数据类型和访问权限等)。该库中保存的信息也可以称为MySQL的数据字典。

额外内存池(Addtional memory pool)

  1. 额外内存池是InnoDB存储引擎用来存放数据字典信息以及一些内部数据结构的内存空间,控制参数为:
  1. innodb_additional_mem_pool_size
  1. 这个参数我们平时调整的可能不是太多,很多人都使用了默认值,可能很多人都不是太熟悉这个参数的作用。所以当我们一个MySQL Instance中的数据库对象非常多的时候,是需要适当调整该参数的大小以确保所有数据都能存放在内存中提高访问效率的。这个参数大小是否足够还是比较容易知道的,因为当过小的时候,MySQL 会记录 Warning 信息到数据库的 error log 中,这时候你就知道该调整这个参数大小了。

注:此参数在 MySQL 5.7.4 中移除。

Redo log Buffer重做日志缓冲

MySQL架构解析 - 图11

如上图所示,InnoDB在缓冲池中变更数据时,会首先将相关变更写入重做日志缓冲中,然后再按时或者当事务提交时写入磁盘,这符合Force-log-at-commit原则;

当重做日志写入磁盘后,缓冲池中的变更数据才会依据checkpoint机制择时写入到磁盘中,这符合WAL原则。

  1. checkpoint择时机制中,就有重做日志文件写满的判断,所以,如前文所述,如果重做日志文件太小,经常被写满,就会频繁导致checkpoint将更改的数据写入磁盘,导致性能抖动。
  2. 操作系统的文件系统是带有缓存的,当InnoDB向磁盘写入数据时,有可能只是写入到了文件系统的缓存中,没有真正的“落袋为安”。
  3. InnoDBinnodb_flush_log_at_trx_commit属性可以控制每次事务提交时InnoDB的行为。
  • 0:事务提交时,不会对重做日志进行写入操作,而是等待主线程按时写入每秒写入一次;
  • 1:事务提交时,会将重做日志写入文件系统缓存,并且调用文件系统的fsync,将文件系统缓冲中的数据真正写入磁盘存储,确保不会出现数据丢失;
  • 2:事物提交时,也会将日志文件写入文件系统缓存,但是不会调用fsync,而是让文件系统自己去判断何时将缓存写入磁盘。

Innodb_flush_log_at_commit是InnoDB性能调优的一个基础参数,涉及InnoDB的写入效率和数据安全。当参数值为0时,写入效率最高,但是数据安全最低;参数值为1时,写入效率最低,但是数据安全最高;参数值为2时,二者都是中等水平。一般建议将该属性值设置为1,以获得较高的数据安全性,而且也只有设置为1,才能保证事务的持久性。

刷盘机制:

MySQL架构解析 - 图12

内存数据落盘:

  1. **整体流程分析**

InnoDB内存缓冲池中的数据page要完成持久化的话,是通过两个流程来完成的,一个是脏页落盘;一个是预写redo log日志。

当缓冲池中的页的版本比磁盘要新时,数据库需要将新版本的页从缓冲池刷新到磁盘。但是如果每次一个页发送变化,就进行刷新,那么性能开发是非常大的,于是InnoDB采用了Write Ahead Log(WAL)策略和 Force Log at Commit 机制实现事务级别下数据的持久性。

WAL要求数据的变更写入到磁盘前,首先必须将内存中的日志写入到磁盘。

Force-log-at-commit要求当一个事务提交时,所有产生的日志都必须刷新到磁盘上,如果日志刷新成功后,缓冲池中的数据刷新到磁盘前数据库发生了宕机,那么重启时,数据库可以从日志中恢复数据。

为了确保每次日志都写入到重做日志文件,在每次将重做日志缓冲写入重做日志后,必须调用一次fsync操作,将缓冲文件从文件系统缓存中真正写入磁盘。可以通过 innodb_flush_log_at_trx_commit 来控制重做日志刷新到磁盘的策略。

脏页落盘

  1. 在数据库中进行**读取操作**,将从磁盘中读到的页放在缓冲池中,下次再读相同的页时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。
  2. 对于数据库中页的**修改操作**,则首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。页从缓冲池刷新回磁盘的操作并不是在每次页发生更新时触发,而是通过一种称为**CheckPoint**的机制刷新回磁盘。

重做日志落盘

  1. redo log落盘机制我们在上面已经提及。Log Buffer写入磁盘的时机,由参数 innodb_flush_log_at_trx_commit 控制,默认是 1,表示事务提交后立即落盘。

CheckPoint检查点机制

  1. **简介**

思考一下这个场景:如果重做日志可以无限地增大,同时缓冲池也足够大,那么是不需要将缓冲池中页的新版本刷新回磁盘。因为当发生宕机时,完全可以通过重做日志来恢复整个数据库系统中的数据到宕机发生的时刻。

但是这需要两个前提条件:

  • 缓冲池可以缓存数据库中所有的数据。
  • 重做日志可以无限增大。

因此Checkpoint(检查点)技术就诞生了,目的是解决以下几个问题:

  1. 缩短数据库的恢复时间;
    当数据库发生宕机时,数据库不需要重做所有的日志,因为Checkpoint之前的页都已经刷新回磁盘。数据库只需对Checkpoint后的重做日志进行恢复,这样就大大缩短了恢复的时间。
  2. 缓冲池不够用时,将脏页刷新到磁盘;
    当缓冲池不够用时,根据LRU算法会溢出最近最少使用的页,若此页为脏页,那么需要强制执行Checkpoint,将脏页也就是页的新版本刷回磁盘。

Checkpoint发生的时间、条件及脏页的选择等都非常复杂。而Checkpoint所做的事情无外乎是将缓冲池中的脏页刷回到磁盘,不同之处在于每次刷新多少页到磁盘,每次从哪里取脏页,以及什么时间触发Checkpoint。

Checkpoint分类

  1. InnoDB存储引擎内部,有两种Checkpoint,分别为:Sharp CheckpointFuzzy Checkpoint
  1. Sharp Checkpoint:在数据库关闭的时候会将buffer pool中的脏页全部刷到磁盘中。
  2. Fuzzy Checkpoint:数据库正常运行时,在不同的时机,将部分脏页写入磁盘。仅刷新部分脏页到磁盘,也是为了避免一次刷新全部的脏页造成的性能问题。
    Master Thread Checkpoint
    在Master Thread中,会以每秒或者每10秒一次的频率,将部分脏页从内存中刷新到磁盘,这个过程是异步的。正常的用户线程对数据的操作不会被阻塞。
    FLUSH_LRU_LIST Checkpoint
    MySQL对缓存的管理是通过buffer pool中的LRU列表实现的,LRU 空闲列表中要保留一定数量的空闲页面,来保证buffer pool中有足够的空闲页面来相应外界对数据库的请求。
    Async/Sync Flush Checkpoint
    redo log空间 < 25%时使用异步刷盘。(不阻塞读写)
    redo log空间 < 10%时使用同步刷盘。(不阻塞读,但阻塞写)
    Dirty Page too much
    Dirty Page too much Checkpoint是在Master Thread 线程中每秒一次的频率实现的。

Double Write双写

  1. 如果说Change BufferInnoDB存储引擎带来了性能上的提升,那么Double Write带给InnoDB存储引擎的是数据页的可靠性。

MySQL架构解析 - 图13

如上图所示,Double Write由两部分组成,一部分是内存中的double write buffer,大小为2MB,另一部分是物理磁盘上共享表空间连续的128个页,大小也为2MB。

在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是通过memcpy函数将脏页先复制到内存中的double write buffer区域,之后通过double write buffer再分两次,每次1MB顺序地写入共享表空间的物理磁盘上,然后马上调用fsync函数,同步磁盘,避免操作系统缓冲写带来的问题。在完成doublewrite页的写入后,再讲double wirite buffer中的页写入各个表空间文件中。

如果操作系统在将页写入磁盘的过程中发生了崩溃,在恢复过程中,InnoDB存储引擎可以从共享表空间中的double write中找到该页的一个副本,将其复制到表空间文件中,再应用重做日志。

MySQL架构解析 - 图14