MySQL1:MySQL发展史,MySQL流行分支及其对应存储引擎,MySQL中一条查询SQL语句是如何执行的?MySQL中一条更新SQL是如何执行的?

news2025/2/27 2:31:56

MySQL1:MySQL发展史,MySQL流行分支及其对应存储引擎,一条查询SQL语句是如何执行的?一条更新SQL是如何执行的?

  • MySQL发展史
  • MySQL流行分支及其对应存储引擎
    • MariaDB-Maria存储引擎
    • Percona Server-XtraDB存储引擎
    • 其它存储引擎
  • MySQL中一条查询SQL语句是如何执行的?
    • 1.连接
      • 怎么查看MySQL当前有多少个连接?
      • 思考:为什么连接数是查看线程?客户端的连接和服务端的线程有什么关系?
      • MySQL参数级别说明
    • 2.查询缓存
    • 3.解析器(Parser)
      • 词法解析
      • 语法解析
    • 4.预处理器(Preprocessor)
    • 5.查询优化器(Query Optimizer)
      • 什么是查询优化器?
      • 优化器可以做什么?
    • 6.执行计划
      • EXPLAIN査看MySQL的执行计划
      • EXPLAIN FORMAT=JSON输出详细JSON格式的的执行计划
      • 开启optimizer trace
    • 7.存储引擎
      • MySQL表数据存在磁盘什么位置
      • 存储引擎是怎么选择的?可以修改吗?
      • MySQL为什么支持这么多存储引擎呢?
      • MySQL常见存储引擎
        • MylSAM (3个文件)
        • InnoDB (2个文件)
        • Memory (1个文件)
        • CSV (3个文件)
        • Archive (2 个文件)
      • 如何选择存储引擎?
    • 8.执行引擎(Query Execution Engine),返回结果
    • MySQL体系结构总结
  • MySQL中一条更新SQL是如何执行的?
    • 1.Buffer Pool缓冲池
    • 2.Redo log
      • redo log作用
      • Redo log文件位置
      • redo log为什么是2个?
    • 3.Undo log
    • 4.更新过程
    • 5.InnoDB官网架构
      • InnoDB架构-内存结构
        • ①Buffer Pool
          • 内存的缓冲池写满了怎么办?
          • 预读机制
            • 线性预读和随机预读
          • Buffer Pool List(LRU)官网架构
            • Buffer Pool List冷热分离
          • 为什么有Buffer Pool,二次查询相同SQL还是很慢?
      • Buffer Pool总结
        • ②Change Buffer写缓冲
        • ③Adaptive Hash Index
        • ④Redo Log Buffer
          • Redo Log Buffe刷盘机制
        • 总结
      • InnoDB架构-磁盘结构
        • InnoDB Doublewrite Buffer双写缓冲区
    • 6.后台线程
    • 7.Binlog

MySQL发展史

MySQL的发展历史和版本分支:

时间里程碑
1996年MySQL 1.0发布。它的历史可以追溯到1979年,作者Monty用BASIC设计 的一个报表工具
1996年10月3.11.1发布,MySQL没有2.x版本
2000年ISAM升级成MylSAM引擎
MySQL开源
2003年MySQL 4.0发布,集成InnoDB存储引擎
2005年MySQL 5.0本发布,提供了视图、存储过程等功能
2008年MySQL AB公司被Sun公司收购,进入Sun MySQL时代
2009年Oracle收购Sun公司,进入Oracle MySQL时代
2010年MySQL 5.5发布,InnoDB成为默认的存储引擎
2016年MySQL发布8.0.0版本。为什么没有6、7? 5.6可以当成6.x, 5.7可以当 成 7.Xo

虽然MySQL8.0.0出了这么多年,目前使用最多的还是MySQL5.7的版本。

2000年MySQL开源之后(也有商业版本),在MySQL稳定版本的基础上也发展出来了很多的分支,就像Linux也有很多分支—样,例如:Ubuntu、RedHat、CentOSs Fedora、Debian。

MySQL流行分支及其对应存储引擎

MariaDB-Maria存储引擎

我们最熟悉的MySQL分支应该是MariaDB,因为CentOS 7里面自带了一个MariaDB。
MariaDB的由来?
Oracle收购MySQL之后’MySQL创始人之一Monty担
心MySQL数据库发展的未来(开发缓慢,封闭,可能会被闭源),就创建了一个分支MariaDB (2009年),默认使用全新的Maria存储引擎,它是原MylSAM存储引擎的
升级版本。

Percona Server-XtraDB存储引擎

Percona Server是MySQL重要的分支之一,它基于InnoDB存储引擎的基础上,提升了性能和易管理性,最后形成了增强版的XtraDB存储引擎,可以用来更好地发挥服务器硬件上的性能。

其它存储引擎

国内也有一些MySQL的分支或者自研的存储引擎,比如网易的InnoSQL,极数云舟的ArkDB。

我们操作数据库有各种各样的方式,比如Linux系统中的命令行,比如数据库工具,又或者是程序,例如Java语言的JDBC API或者ORM框架。
那么当工具或者程序连接到数据库之后,实际上发生了什么 事情?MySQL它的内部是怎么工作的?
这个就涉及到MySQL的整体架构和工作流程了。

MySQL中一条查询SQL语句是如何执行的?

在这里插入图片描述

1.连接

MySQL服务监听的端口默认是3306,客户端连接服务端的方式有很多:

  • 通信类型:同步/异步
    通信类型可以是同步的,也可以是异步的,服务端都支持,只不过具体采用哪种方式取决于客户端的代码编写,通常我们都采用同步的方式,因为异步的方式编程会更加复杂,可能会造成客户端数据的混淆
  • 连接方式:长连接/短连接
    连接方式可以是长连接,也可以是短连接,服务端都支持。短连接就是用完直接将这个连接close掉,长连接用完之后连接可以保持,可以给其它的客户端继续使用,那么为了节省资源,在大部分情况下,我们都会采用长连接这样的方式
  • 协议:TCP/Unix Socket
    • TCP:从协议上来讲,MySQL服务端支持客户端和它使用TCP ip的通信协议和它进行交互,我们Java代码,包括其它程序语言,都是使用TCP这种方式
    • Unix Socket:本机的客户端连本机的服务端的时候,也就是说,你的Linux本机安装了MySQL,访问的时候不指定host直接访问,那么用到的就是一种文件的方式,这个文件叫做Unix Socket
    • 文件共享、命名空间:你的Windows本机安装了MySQL,就可以在界面上看到最后是否启用这两个选项

怎么查看MySQL当前有多少个连接?

可以用show status命令,模糊查询Thread:

show global status like 'Thread%';

在这里插入图片描述
字段含义
Threads_cached:缓存中的线程连接数
Threads_connected:当前打开的连接数
Threads_created:为处理连接创建的线程数
Threads_running:非睡眠状态的连接数,通常指并发连接数

思考:为什么连接数是查看线程?客户端的连接和服务端的线程有什么关系?

MySQL服务端是一个单进程多线程的模型,也就是说:
客户端每产生一个连接或者一个会话(session),在服务端就会创建一个线程来处理。既然如此, 如果要杀死会话,就是Kill线程。

既然是分配线程的话,保持连接肯定会消耗服务端的资源,因此,必然有一个定时的回收长时间不用的线程的资源的操作,MySQL会把那些长时间不活动的(SLEEP)连接自动断开,有两个参数wait timeoutinteractive timeout,也
可以叫做系统的变量:

show global VARIABLES like 'wait_timeout'; ## 非交互式超时时间,如 JDBC 程序

show global VARIABLES like 'interactive_timeout'; ## 交互式超时时间,如数据库连接工具

在这里插入图片描述
默认都是28800秒,8小时。
既然连接消耗资源,MySQL服务允许的最大连接数(也就是并发数)默认是多少呢?

show variables like 'max_connections';

在这里插入图片描述
在5.7版本中默认是151个,最大可以设置成100000

MySQL参数级别说明

MySQL中的参数(变量)分为session和global级别,分别是在当前会话中生效和全局生效,但是并不是每个参数都有两个级别,比如max_connections就只有全局级别。

当没有带参数的时候,默认是session级别,包括查询和修改。
比如修改了一个参数以后,在本窗口査询已经生效,但是其他窗口不生效:

所以,如果只是临时修改,建议修改session级别。 如果需要在其他会话中生效,必须显式地加上global参数。

执行一条查询语句,客户端跟服务端建立连接之后呢?下一步要做什么?

2.查询缓存

MySQL内部自带了一个缓存模块。MySQL的缓存默认是关闭的。

默认关闭的意思就是不推荐使用,为什么MySQL不推荐使用它自带的缓存呢?
主要是因为MySQL自带的缓存的应用场景有限,很鸡肋

  • 第一个是它要求SQL语句必须一 模一样,中间多一个空格,字母大小写不同都被认为是不同的的SQL
  • 第二个是表里面任何一条数据发生变化的时候,这张表所有缓存都会失效,所以对于有大量数据更新的应用,也不适合

所以缓存这一块,我们还是交给ORM框架(比如MyBatis默认开启了一级缓存),或者独立的缓存服务,比如Redis来处理更合适。
MySQL 8.0中,查询缓存已经被移除了。

没有使用缓存的话,就会跳过缓存的模块,下一步要做什么呢?

3.解析器(Parser)

一条SQL语句是如何被识别的呢?假如随便执行一个字符串aaa,服务器会报了一个1064的错:
在这里插入图片描述
那么MySQL是如何知道输入的内容是错误的?
这个就是MySQL的Parser解析器和Preprocessor预处理模块。

解析器主要做的事情是:对语句基于SQL语法,进行词法解析语法解析

词法解析

词法解析就是把一个完整的SQL语句打碎成一个个的单词。
比如一个简单的SQL语句:

select name from stu where id = 1001;

它会打碎成8个符号,每个符号是什么类型,从哪里开始到哪里结束。

语法解析

第二步就是语法解析,语法解析会对SQL做一些语法检查,比如单引号有没有闭合,然后根据MySQL定义的语法规则,根据SQL语句生成一个数据结构,这个数据结构,我们把它叫做解析树(select lex)。
在这里插入图片描述
词法、语法分析是一个非常基础的功能,Java的编译器、百度搜索引擎如果要识别语句,必须也要有词法语法分析功能。

任何数据库的中间件,要解析SQL完成路由功能,也必须要有词法和语法分析功能,比如Mycat,Sharding-JDBC (用到了 Druid Parser)。在市面上也有很多的开源的词法解析的工具(比如LEX, Yacc)

解析器可以分析语法,但是它怎么知道数据库里面有什么表,表里面有什么字段呢?

4.预处理器(Preprocessor)

问题:如果我写了一个词法和语法都正确的SQL,但是表名或者字段不存在,会在哪里报错?是在数据库的执行层还是解析器?比如:

select * from tbl1;

实际上还是在解析的时候报错,解析SQL的环节里面有个预处理器。

它会检査生成的解析树,解决解析器无法解析的语义。比如,它会检査表和列名是否存在,检査名字和别名,保证没有歧义。
预处理之后得到一个新的解析树。

5.查询优化器(Query Optimizer)

将解析树变为执行计划。

什么是查询优化器?

得到解析树之后,是不是执行SQL语句了呢?
这里我们有一个问题,一条SQL语句是不是只有一种执行方式?或者说数据库最终 执行的SQL是不是就是我们发送的SQL?
这个答案是否定的。
一条SQL语句是可以有很多种执行方式的,最终返回相同的结果,他们是等价的。比如索引有好几个,那么最终选择哪一个索引;或者是多张表进行关联查询的时候,使用哪个表作为基准表,以及其它的语法方面的一些优化等。

有多种执行方式,这些执行方式怎么得到的?最终选择哪一种去执行?根据什么判断标准去选择?
这个就是MySQL的査询优化器的模块(Optimizer)。
査询优化器的目的就是根据解析树生成不同的执行计划(Execution Plan),然后选 择一种最优的执行计划,MySQL里面使用的是基于开销(cost)的优化器,哪种执行计划开销最小,就用哪种。
可以使用这个命令査看査询的开销:

show status like Last query cost';

优化器可以做什么?

MySQL的优化器能处理哪些优化类型呢?

6.执行计划

优化完之后,得到一个什么东西呢?
优化器最终会把解析树变成一个査询执行计划,查询执行计划是一个数据结构。

EXPLAIN査看MySQL的执行计划

如何査看MySQL的执行计划呢?比如多张表关联查询,先査询哪张表?在执行査询的时候可能用到哪些索引,实际上用到了什么索引?

MySQL提供了一个执行计划的工具。我们在SQL语句前面加上EXPLAIN,就可以
看到执行计划的信息。

EXPLAIN select name from stu where id=1001;

在这里插入图片描述
为什么执行EXPLAIN很快,而执行一条SQL语句执行很慢?
因为EXPLAIN并没有真正执行SQL语句,而是一个参考,有一些值是不精准的。

EXPLAIN FORMAT=JSON输出详细JSON格式的的执行计划

如果要得到详细的信息,还可以在EXPLAIN后面加上FORMAT=JSON,这样会输出详细的JSON格式

EXPLAIN FORMAT=JSON select name from stu where id=1001;
{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "1.20"
    },
    "table": {
      "table_name": "stu",
      "access_type": "ALL",
      "rows_examined_per_scan": 1,
      "rows_produced_per_join": 1,
      "filtered": "100.00",
      "cost_info": {
        "read_cost": "1.00",
        "eval_cost": "0.20",
        "prefix_cost": "1.20",
        "data_read_per_join": "16"
      },
      "used_columns": [
        "id",
        "name"
      ],
      "attached_condition": "(`test`.`stu`.`id` = 1001)"
    }
  }
}

开启optimizer trace

如果要得到详细的信息,还有一种方式,开启optimizer trace,默认是关闭的,开启之后,会消耗性能。
在这里插入图片描述

SHOW VARIABLES LIKE 'optimizer_trace';
SET optimizer_trace = 'ENABLED=ON';

select name from stu where id=1001; ## 执行SQL语句

select * from information_schema.optimizer_trace; ## 执行之后会记录在这张表内

在这里插入图片描述

7.存储引擎

得到执行计划以后,SQL语句是不是终于可以执行了?
问题又来了:
1、从逻辑的角度来说,我们的数据是放在哪里的,或者说放在一个什么结构里面?
2、执行计划在哪里执行?是谁去执行?查询总归是要知道数据在哪里存放着的,才能去查,因此先了解存储引擎,看数据都存在哪里。

MySQL表数据存在磁盘什么位置

这些位置可能因具体的MySQL版本和安装方式而有所不同,用户可以通过MySQL的配置文件(my.cnf)来设置MySQL数据库文件的位置。

MySQL数据库文件的位置是由datadir参数决定的,下面命令可以查看到数据文件存储的位置。

show global variables like 'datadir%';

我这里用的是Windows的
在这里插入图片描述
可以看到,在Windows系统中,数据文件是在C:\ProgramData\MySQL\MySQL Server 5.7\Data
在这里插入图片描述
在这里插入图片描述
在MySQL的数据目录下,每个数据库对应一个文件夹,对应数据库的表文件就存在对应命名的文件夹中,以test数据库为例,点击进入该文件夹,可以发现数据表文件
在这里插入图片描述
图中红框中列出三种常用的存储引擎的表存储方式。

存储引擎是怎么选择的?可以修改吗?

我们在建表的时候,可以发现MySQL提供了多种存储引擎选项。
在这里插入图片描述
一张表的存储引擎,也叫表类型,是在创建表的时候指定的,使用ENGINE关键字来指定。

CREATE TABLE `stu_innodb` (
	`id` INT(11) NULL DEFAULT NULL,
	`name` INT(11) NULL DEFAULT NULL,
	`sex` INT(11) NULL DEFAULT NULL,
	`age` INT(11) NULL DEFAULT NULL
)
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB

没有指定的时候,数据库就会使用默认的存储引擎,5.5.5之前,默认的存储引擎是MylSAM, 5.5.5之后,默认的存储引擎是InnoDB。

存储引擎是可以修改的,很多时候,我们在做测试,需要插入大量的测试数据,就可以先将表的存储引擎改为MylSAM ,插入完成之后,再改为InnoDB。

MySQL为什么支持这么多存储引擎呢?

  • 如果有一张表,需要很高的访问速度,而不需要考虑持久化的问题,丢了就丢了,是不是只需要要把数据放在内存就可以了?
  • 如果一张表,是用来做历史数据存档的,不需要修改,也不需要索引,那它是不是要支持数据的压缩?
  • 如果一张表用在读写并发很多的业务中,是不是要支持读写不干扰,而且要保证比较高的数据一致性呢?

说到这里大家应该明白了,MySQL之所以支持这么多的存储引擎,就是因为我们有不同的业务需求,对表有不同的访问要求、存储要求、管理要求,而一种存储引擎是不能提供所有的特性。

MySQL常见存储引擎

MylSAM (3个文件)

These tables have a small footprint. Table-level locking limits the performance in read/write workloads, so it is often used in read-only or read-mostly workloads in Web and data warehousing configurations.

应用范围比较小。表级锁定限制了读/写的性能,因此在Web和数据仓库配置中,
它通常用于只读或以读为主的工作。

特点:

  • 支持表级别的锁(插入和更新会锁表)。
  • 不支持事务。
  • 拥有较高的插入(insert)和查询(select)速度。比如:怎么快速向数据库插入100万条数据?我们可以先用MylSAM插入数据,然后修改存储引擎为InnoDB的操作。
  • 存储了表的行数(count速度更快)。

适合:只读之类的数据分析的项目。

数据文件:
每个MyISAM表由三个文件组成:.frm文件、.MYD文件和.MYI文件。

  • .frm文件:是表结构定义文件,它存储了表的元数据信息,包括表的字段、索引等。
  • .MYD文件:是数据文件,它存储了实际的表数据。
  • .MYI文件:是索引文件,它存储了表的索引信息,用于加快查询速度。
InnoDB (2个文件)

The default storage engine in MySQL 5.7. InnoDB is a transaction-safe (ACID compliant) storage engine for MySQL that has commit, rollback, and crash-recovery capabilities to protect user data. InnoDB row-level locking (without escalation to coarser granularity locks) and Oracle-style consistent nonlocking reads increase multi-user concurrency and performance. InnoDB stores user data in clustered indexes to reduce I/O for common queries based on primary keys. To maintain data integrity, InnoDB also supports FOREIGN KEY referential-integrity constraints.

mysql 5.7中的默认存储引擎。InnoDB是一个事务安全(与ACID兼容)的MySQL 存储引擎,它具有提交、回滚和崩溃恢复功能来保护用户数据。InnoDB行级锁(不升级
为更粗粒度的锁)和Oracle风格的一致非锁读提高了多用户并发性和性能。InnoDB将
用户数据存储在聚集索引中,以减少基于主键的常见查询的I/O。为了保持数据完整性, InnoDB还支持外键引用完整性约束。

InnoDB本来是InnobaseOy公司开发的,它和MySQL AB公司合作开源了InnoDB的代码。但是MySQL的竞争对手Oracle把InnobaseOy收购了。后来08年Sun公司(开发Java语言的Sun)收购了MySQL AB,09年Sun公司又被Oracle收购了,所以MySQL,InnoDB又是一家了。有人觉得MySQL越来越像Oracle,其实也是这个原因。

特点:

  • 支持事务,支持外键,因此数据的完整性、一致性更高。
  • 支持行级别的锁和表级别的锁。
  • 支持读写并发,写不阻塞读(MVCC)。
  • 特殊的索引存放方式,可以减少IO,提升査询效率。

适合:经常更新的表,存在并发读写或者有事务处理的业务系统。

数据文件:

  • .ibd文件:InnoDB将每个表,存储在以表名命名的文件中,这些文件的后缀名为.ibd。
Memory (1个文件)

Stores all data in RAM, for fast access in environments that require quick lookups of non-critical data. This engine was formerly known as the HEAP engine. Its use cases are decreasing; InnoDB with its buffer pool memory area provides a general-purpose and durable way to keep most or all data in memory, and DBCLUSTER provides fast key-value lookups for huge distributed data sets.
将所有数据存储在RAM中,以便在需要快速查找非关键数据的环境中快速访问。这
个引擎以前被称为堆引擎。其使用案例正在减少;InnoDB及其缓冲池内存区域提供了一
种通用、持久的方法来将大部分或所有数据保存在内存中,而ndbduster为大型分布式 数据集提供了快速的键值查找。

特点:把数据放在内存里面,读写的速度很快,但是数据库重启或者崩溃,数据会全部消失。

适合:适合做临时表。将表中的数据存储到内存中。

CSV (3个文件)

Its tables are really text files with comma-separated values. CSV tables let you import or dump data in CSV format, to exchange data with scripts and pplications that read and write that same format. Because CSV tables are not indexed, you typically keep the data in InnoDB tables during normal operation, and only use CSV tables during the import or export stage.

它的表实际上是带有逗号分隔值的文本文件。csv表允许以CSV格式导入或转储数据,
以便与读写相同格式的脚本和应用程序交换数据。因为CSV表没有索引,所以通常在正
常操作期间将数据保存在innodb表中,并且只在导入或导出阶段使用csv表。

特点:不允许空行,不支持索引。格式通用,可以直接编辑。

适合:在不同数据库之间导入导出。

Archive (2 个文件)

These compact, unindexed tables are intended for storing and
retrieving large amounts of seldom-referenced historical, archived, or
security audit information.

这些紧凑的未索引的表用于存储和检索大量很少引用的历史、存档或安全审计信息。

特点:不支持索引,不支持update、delete。

如何选择存储引擎?

不同的存储引擎有各自的特性,它们有不同的存储机制、索引方式、锁定水平等功能。我们在不同的业务场景中对数据操作的要求不同,就可以选择不同的存储引擎来满足我们的需求,这个就是MySQL支持这么多存储引擎的原因。

如果对数据一致性要求比较高,需要事务支持,可以选择InnoDB。
如果数据查询多更新少,对查询性能要求比较高,可以选择MyISAM。
如果需要一个用于查询的临时表,可以选择Memory。
如果所有的存储引擎都不能满足你的需求,并且技术能力足够,可以根据官网内部 手册用C语言开发一个存储引擎

这个开发规范,实现相应的接口,给执行器操作。
也就是说,为什么能支持这么多存储引擎,还能自定义存储引擎,表的存储引擎改 了对Server访问没有任何影响,就是因为大家都遵循了一定了规范,提供了相同的操作 接口。
每个存储引擎都有自己的服务。

show engine innodb status;

在这里插入图片描述
这些存储引擎用不同的方式管理数据文件,提供不同的特性,但是为上层提供相同 的接口。

为什么我们修改了表的存储引擎,操作方式不需要做任何改变?
因为不同功能的存储引擎实现的API是相同的。

MySQL中文帮助文档:https://www.mysqlzh.com/doc/213.html

8.执行引擎(Query Execution Engine),返回结果

存储引擎分析完了,它是我们存储数据的形式,那么是谁使用执行计划去操作存储引擎呢?
这个东西就是执行引擎,它利用存储引擎提供的相应的API来完成操作。最后把数据返回给客户端。

MySQL体系结构总结

在这里插入图片描述
基于上面分析的流程,总体上,我们可以把MySQL分成三层:

  • 跟客户端对接的连接层:客户端要连接到MySQL服务器3306端口,必须要跟服务端建立连接,那么管理所有的连接,验证客户端的身份和权限,这些功能就在连接层完成
  • 真正执行操作的服务层:连接层会把SQL语句交给服务层,这里面又包含一系列的流程:
    • 比如查询缓存的判断
    • 根据SQL调用相应的接口
    • 对我们的SQL语句进行词法和语法的解析(比如关键字怎么识别,别名怎么识别,语法有没有错误等等)
    • 然后是优化器,MySQL底层会根据一定的规则对我们的SQL语句进行优化,最后再交给执行器去执行
  • 和跟硬件打交道的存储引擎层:存储引擎就是我们的数据真正存放的地方,在MySQL里面支持不同的存储引擎。

再往下就是内存或者磁盘。

MySQL中一条更新SQL是如何执行的?

讲完了查询流程,我们是不是再讲讲更新流程、插入流程和删除流程?
在数据库里面,我们说的update操作其实包括了更新、插入和删除。如果大家有看 过MyBatis的源码,应该知道Executor里面也只有doQuery()和doUpdate()的方法, 没有doDelete()和dolnsert()。

更新流程和查询流程有什么不同呢?
基本流程也是一致的,也就是说,它也要经过解析器、优化器的处理,最后交给执 行器。区别就在于拿到符合条件的数据之后的操作。

1.Buffer Pool缓冲池

首先,对于InnoDB存储引擎来说,数据都是放在磁盘上的,存储引擎要操作数据, 必须先把磁盘里面的数据加载到内存里面才可以操作。

这里就有个问题,是不是我们需要的数据多大,我们就一次从磁盘加载多少数据到内存呢?比如我要读6个字节,磁盘就只返回6字节吗?
并不是,因为磁盘I/O的读写相对于内存的操作来说是很慢的。如果我们需要的数据分散在磁盘的不同的地方,那就意味着会产生很多次的I/O操作。所以,无论是操作系统也好,还是存储引擎也好,都有一个预读取的概念。也就是 说,当磁盘上的一块数据被读取的时候,很有可能它附近的位置也会马上被读取到,这个就叫做局部性原理。所以它会每次多读取一点,而不是用多少读多少。从磁盘读取数据到内存的最小的单位,叫做页,操作系统的页大小一般是4KB。

InnoDB也有这样的设置,在InnoDB里面,这个最小的单位默认是16KB大小。如果要修改这个值的大小,需要清空数据重新初始化服务。我们要操作的数据就在这样的页里面,数据所在的页叫数据页。

这里有一个问题,操作数据的时候,每次都要从磁盘读取到内存(再返回给Server),有没有什么办法可以提高效率?
还是缓存的思想。把读取过的数据页缓存起来。
InnoDB设计了一个内存的缓冲区,读取数据的时候,先判断是不是在这个内存区域里面,如果是,就直接读取,然后操作,不用再次从磁盘加载。如果不是,读取后就写到这个内存的缓冲区。这个内存区域有个专属的名字,叫缓冲池Buffer PooL。

修改数据的时候,也是先写入到Buffer PooL,而不是直接写到磁盘。内存的数据页和磁盘数据不一致的时候,我们把它叫做脏页

那脏页什么时候才同步到磁盘呢?
InnoDB里面有专门的后台线程把Buffer Pool的数据写入到磁盘,每隔一段时间就一次性地把多个修改写入磁盘,这个动作就叫做刷脏。

总结:Buffer Pool的作用是为了提高读写的效率。

2.Redo log

思考一个问题:因为刷脏不是实时的,如果Buffer Pool里面的脏页还没有刷入磁盘时,也就是说,写入磁盘是有一个延时的过程,如果这个时候,数据库宕机或者重启,这些数据就会丢失。那怎么办呢?
所以内存的数据必须要有一个持久化的措施。为了避免这个问题,InnoDB把所有对页面的修改操作专门写入一个日志文件。如果有未同步到磁盘的数据,数据库在启动的时候,会从这个日志文件进行恢复操作(实现crash-safe)。我们说的事务的ACID里面D(持久性),就是用它来实现的。这个日志文件就是磁盘的redo log(重做日志)。

那么有个新的疑问,现在要写Buffer Pool,为了保证Buffer Pool的可用性还要做持久化,写redo log,那么同样需要写磁盘,为什么不直接写到dbfile里面去?为什么先写日志再写磁盘?是否性能上有差异呢?
在这里插入图片描述
写日志文件和和写到数据文件有什么区别?
我们先说一下磁盘寻址的过程。
在这里插入图片描述
上图是磁盘的构造。
磁盘的盘片不停地旋转,磁头会在磁盘表面画出一个圆形轨迹,这个就叫磁道。从内到位半径不同有很多磁道。然后 又用半径线,把磁道分割成了扇区(两根射线之内的扇区组成扇面)。如果要读写数据,必须找到数据对应的扇区,这个过程就叫寻址

如果我们所需要的数据是随机分散在磁盘上不同页的不同扇区中,那么找到相应的数据需要等到磁臂旋转到指定的页,然后盘片寻找到对应的扇区,才能找到我们所需要的一块数据,依次进行此过程直到找完所有数据,这个就是随机IO,读取数据速度较慢

假设我们已经找到了第一块数据,并且其他所需的数据就在这一块数据后边,那么就不需要重新寻址,可以依次拿到我们所需的数据,这个就叫顺序IO

刷盘是随机I/O,而记录日志是顺序I/O(连续写的),顺序I/O效率更高,本质上是数据集中存储和分散存储的区别。

redo log作用

因此先把修改写入日志文件,在保证了内存数据的安全性的情况下,可以延迟刷盘时机,进而提升系统吞吐量。

  1. redo log是InnoDB存储弓|擎实现的,并不是所有存储引擎都有。支持崩溃恢复 是InnoDB的一个特性。
  2. redo log不是记录数据页更新之后的状态,而是记录的是"在某个数据页上做了 什么修改”。属于物理日志。
  3. redo log的大小是固定的,前面的内容会被覆盖,一旦写满,就会触发刷盘操作,完成buffer pool到磁盘的同步,以便腾出空间记录后面的修改,因此这个值可以稍微设置大点。

在innodb中,除了redo log之外,还有一个跟修改有关的日志,叫做undo log。redo log和undo log与事务密切相关,统称为事务日志。

Redo log文件位置

redo log文件的位置是由datadir参数决定的。一般情况下,redo log文件位于数据目录(datadir)下,默认由两个文件ib_logfile0ib_logfile1组成。每个48M。
下面命令可以查看到数据文件存储的位置。

show global variables like 'datadir%';

在这里插入图片描述

show variables like 'innodb_log%';

在这里插入图片描述
参数含义:
innodb_log_buffer_size:指定每个文件的大小,默认48M
innodb_log_file_size:指定每个文件的大小,默认48M
innodb_log_files_in_group:指定文件的数量,默认为2
innodb_log_group_home_dir:指定文件所在路径,相对或绝对。如果不指定,则为 datadir 路径
innodb_log_write_ahead_size:提前写入大小

redo log为什么是2个?

在这里插入图片描述
redo log的大小是固定的,所谓大小固定是指,它的结构是一个环状的,当新的内容写满了,会覆盖旧的内容,这个就是大小固定的意思。

它可以通过参数innodb_log_files_in_group来设置它的文件个数,可以通过innodb_log_buffer_size来指定每个redo log文件的大小,默认是两个文件,你设置成4就是4个文件。

所以在不断写的过程中,也需要不断地清理redo log的内容,如果一致不清理,那么当当前写的位置赶上了上一个清理的检查点的位置,那么就会触发buffer pool里面的内容刷盘。

3.Undo log

undo log(撤销日志或回滚日志)记录了事务发生之前的数据状态,分为insert undo log和update undo logo,如果修改数据时出现异常,可以用undo log来实现回滚操作(保持原子性)。

  • undo log:记录的是反向的操作,比如insert会记录delete,update会记录update原来的值,所以叫做回滚日志
  • redo log:记录在哪个物理页面做了什么操作不同,所以叫做逻辑格式的日志
show global variables like '%undo%'; 

在这里插入图片描述
参数含义:
innodb_max_undo_log_size:如果innodb_undo_log_truncate设置为1,超过这个大小的时候会触发触发 truncate回收(收缩)动作,如果page大小是16KB, truncate后空间 缩小到10Mo默认1073741824字节二1G。
innodb_undo_directory:undo文件的路径
innodb_undo_log_truncate:设置为1,即开启在线回收(收缩;undo log 0志文件
innodb_undo_logs:回滚段的数量,默认128,这个参数已经过时。
innodb_undo_tablespaces:设置undo独立表空间个数,范围为0-95,默认为0,0表示不开启独立undo表空间,且undo日志存储在ibdata1文件中。这个参数已经过时。

redo log和undo log与事务密切相关,统称为事务日志。

4.更新过程

有了这些日志之后,我们来总结一下一个更新操作的流程,比如:将id=1001的数据,修改name原值是'小李',现在改为'小王'

update stu set name = '小王' where id=l001;

以下是一个简化的过程:

  1. 事务开始,从内存(buffer pool)或磁盘(data file)取到包含这条数据的数据
    页,返回给Server的执行器;
  2. Server的执行器修改数据页的这一行数据的值为 ‘小王’;
  3. 记录name=‘小李’ 到undo log,保证原子性;
  4. 记录name=‘小王’ 到redo log,保证持久性;
  5. 调用innodb存储引擎接口写入数据,记录数据页到Buffer Pool(修改name= ‘小王’);
  6. 事务提交。

5.InnoDB官网架构

了解了内存的Buffer Pool和磁盘的两个日志,我们也从总体上看下InnoDB的架构是什么样的。
下图是MySQL5.7的InnoDB的架构图
MySQL官方文档如何查看,MySQL中文文档
在这里插入图片描述
Buffer Pool主要分为3个部分:
Buffer Pool、Change Buffer、Adaptive Hash Index,另外还有一个(redo)log buffer。

左边为In-Memory Structures(内存结构),右边为On-Disk Structures(磁盘结构)。

InnoDB架构-内存结构

①Buffer Pool

Buffer Pool缓存的是页面信息,包括数据页、索引页。
Buffer Pool在Linux系统下默认大小是128M(134217728字节),在Windows系统下是8M(8388608字节),可以调整。
査看系统变量:

SHOW VARIABLES like '%innodb_buffer_pool%;

在这里插入图片描述
查看服务器状态,里面有很多跟Buffer Pool相关的信息:

SHOW STATUS LIKE '%innodb_buffer_pool%;

在这里插入图片描述
这些参数都可以在官网查到详细的含义,用搜索功能。
https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html

内存的缓冲池写满了怎么办?

InnoDB用LRU算法来管理缓冲(链表实现,不是传统的LRU,分成了young和old),经过淘汰的数据就是热点数据。

传统LRU,可以用Map+链表实现。value存的是在链表中的地址。
InnoDB中确实使用了一个双向链表,LRU list,也叫Buffer Pool List,它里面放的不是data page,而是指向缓存页的指针。

如果写buffer pool的时候发现没有空闲页了,就要从buffer pool中淘汰数据页了,它要根据LRU链表的数据来操作。

预读机制

首先,InnoDB的数据页并不是都在访问的时候才缓存到buffer pool的。
InnoDB有一个预读机制(read ahead)。也就是说,设计者认为访问某个page的数据的时候,相邻的一些page可能会很快被访问到,所以先把这些page放到buffer pool中缓存起来。
https://dev.mysql.com/doc/refman/5.7/en/innodb-performance-read_ahead.html

线性预读和随机预读

这种预读的机制又分为两种类型:

  • Linear read-ahead——线性预读(异步的)
    • 为了便于管理,InnoDB中把64个相邻的page叫做一个extent(区)。如果顺序地访问了一个extent的56个page,这个时候InnoDB就会把下一个extent(区)缓存到buffer pool。顺序访问多少个page才缓存下一个extent,由innodb_read_ahead_threshold参数控制:
      show variables like '%innodb_read_ahead_threshold%';
      
      在这里插入图片描述
  • Random read-ahead——随机预读
    • 如果buffer pool已经缓存了同一个extent(区)的数据页的个数超过13时,就会把这个extent剩余的所有page全部缓存到buffer pool。随机预读的功能默认是不启用的,由innodb_random_read_ahead参数控制:
      show variables like '%innodb_random_read_ahead%';
      
      在这里插入图片描述

很明显,线性预读或者异步预读,都能够把可能即将用到的数据提前加载到buffer pool,肯定能提升I/O的性能,所以是一种非常有用的机制。

但是预读肯定也会带来一些副作用,就是导致占用的内存空间更多,剩余的空闲页更少。如果说buffer pool size不是很大,而预读的数据很多,很有可能那些真正的需要被缓存的热点数据被预读的数据挤出buffer pool,淘汰掉了,下次访问的时候又要先去磁盘。怎么让这些真正的热点数据不受到预读的数据的影响呢?
从Buffer Pool List的架构来入手,看看它是如何设计的。

Buffer Pool List(LRU)官网架构

Buffer Pool List官网架构图:https://dev.mysql.com/doc/refman/5.7/en/innodb-buffer-pool.html
在这里插入图片描述

Buffer Pool List冷热分离

从Buffer Pool List官网架构可以得知,InnoDB是把LRU list分成了两部分,通过中间的分割线叫做Midpoint insertion(中点插入),也就是对buffer pool做一个冷热分离:

  • new sublist:靠近head的叫做new sublist,用来放热数据(我们把它叫做热区)
  • old sublist:靠近tail的叫做old sublist,用来放冷数据(我们把它叫做冷区)

所有新数据加入到buffer pool的时候,一律先放到冷数据区的head,不管是预读的,还是普通的读操作。所以如果有一些预读的数据没有被用到,会在old sublist(冷区)直接被淘汰。

放到LRU List以后,如果再次被访问,就把它移动到热区的head。如果热区的数据长时间没有被访问,会被先移动到冷区的head部,最后慢慢在tail被淘汰。

在默认情况下,热区占了5/8的大小,冷区占了3/8,这个值由参数innodb_old_blocks_pct控制,它代表的是old区的大小,默认是37%也就是3/8。

show variables like '%innodb_old_blocks_pct%';

在这里插入图片描述
innodb_old_blocks_pct的值可以调整,在5%到95%之间,这个值越大,new区越小,这个LRU算法就接近传统LRU。如果这个值太小,old区没有被访问的速度淘汰会更快。

预读的问题,通过冷热分离解决了,还有没有其他的问题呢?
我们先把数据放到冷区,用来避免占用热数据的存储空间。但是如果刚加载到冷区的数据立即被访问了一次,按照原来的逻辑,这个时候我们会马上把它移动至热区。假设这一次加载然后被立即访问的冷区数据量非常大,比如我们查询了一张几 千万数据的大表,没有使用索引,做了一个全表扫描;或者dump全表备份数据,这种查询属于短时间内访问,后面再也不会用到了。

如果短时间之内被访问了一次,导致它们全部被移动到热区的head,它会导致很多热点数据被移动到冷区甚至被淘汰,造成了缓冲池的污染。这个问题我们又怎么解决呢?
对于加载到冷区然后被访问的数据,设置一个时间窗口,只有超过这个时间之后被访问,我们才认为它是有效的访问。

InnoDB 里面通过innodb_old_blocks_time这个参数来控制,默认是1秒钟。

show variables like '%innodb_old_blocks_time%';

在这里插入图片描述
也就是说1秒钟之内被访问的,不算数,待在冷区不动。只有1秒钟以后被访问的,才从冷区移动到热区的head。这样就可以从很大程度上避免全表扫描或者预读的数据污染真正的热数据。

似乎比较完美了,还有没有可以优化的空间呢?
InnoDB支持读写并发,写不阻塞读(MVCC)。那么为了避免并发的问题,对于LRU链表的操作是要加锁的。也就是说每一次链表的移动,都会带来资源的竞争和等待。从这个角度来说,如果要进一步提升InnoDB LRU的效率,就要尽量地减少LRU链表的移动。

比如,把热区一个非常靠近head的page移动到head,有没有这个必要呢?
所以InnoDB对于new区还有一个特殊的优化:
如果一个缓存页处于热数据区域,且在热数据区域的前1/4区域(注意是热数据区域的1/4,不是整个链表的1/4),那么当访问这个缓存页的时候,就不用把它移动到热数据区域的头部;如果缓存页处于热区的后3/4区域,那么当访问这个缓存页的时候,会把它移动到热区的头部。

为什么有Buffer Pool,二次查询相同SQL还是很慢?

InnoDB中有Buffer Pool,二次查询相同SQL会优先查询Buffer Pool中的数据,但是如果你的查询结果超过了Buffer Pool的大小,根据LRU淘汰算法,那么一下就会将原来Buffer Pool加载进来的数据,全部挤出去,尤其是在做全表扫描的时候。

show VARIABLES LIKE '%innodb_buffer_pool_size%';

在这里插入图片描述
可以看到,Windows环境下,默认buffer pool大小为8388608 bytes,也就是8M,old区3M,young区5M,在全表扫描的过程中,所有的page都会加入old区的头部。

从page中找到对应行的时候,所有的page都会移动到new区的头部,因为容量有限,前面的数据也全部被淘汰了。因此,可以将buffer pool调大,再进行测试。

通常来说,我们建议一个比较合理的、健康的比例,是给buffer pool设置你的机器内存的50%~60%左右 比如你有32GB的机器,那么给buffer设置个20GB的内存,剩下的留给OS和其他人来用,这样比较合理一些。 假设你的机器是128GB的内存,那么buffer pool可以设置个80GB左右,大概就是这样的一个规则。

Buffer Pool总结

内存缓冲区对于提升读写性能有很大的作用。当需要更新一个数据页时,如果数据页在Buffer Pool中存在,那么就直接更新好了。否则的话就需要从磁盘加载到内存,再对内存的数据页进行操作。也就是说,如果没有命中缓冲池,至少要产生一次磁盘IO,有没有优化的方式呢?

②Change Buffer写缓冲

Change Buffer是Buffer Pool的一部分,可以大大提升非唯一性索引的增删改效率。
如果这个数据页不是唯一索引,不存在数据重复的情况(或者说你的业务允许这些数据重复,因此没有使用unique),也就不需要从磁盘加载索引页判断数据是不是重复(唯一性检查)。这种情况下可以先把修改记录在内存的缓冲池中,从而提升更新语句(Insert、Delete、Update)的执行速度。

这一块区域就是Change Buffer,MySQL 5.5之前叫Insert Buffer(插入缓冲),现在也能支持 delete 和 update。最后把Change Buffer记录到数据页的操作叫做merge。

什么时候发生merge?分以下几种情况:

  • 在访问这个数据页的时候
  • 通过后台线程、或者数据库shut down
  • redo log写满时触发

可以通过参数innodb_change_buffer_max_size来查看Change Buffer占Buffer Pool的比例。

show variables like '%innodb_change_buffer_max_size%';

在这里插入图片描述
如果数据库大部分索引都是非唯一索引,并且业务是写多读少,不会在写数据后立 刻读取,就可以使用Change Buffer(写缓冲)。Change Buffer占Buffer Pool的比例默认25%;可以调大这个值,来扩大Change Buffer的大小,以支持写多读少的业务场景。

③Adaptive Hash Index

自适应的hash索引。
索引应该是放在磁盘的,为什么要专门把一种哈希的索引放到内存?后面单独再说。

④Redo Log Buffer

Redo log也不是每一次都直接写入磁盘,在Buffer Pool里面有一块内存区域(Log
Buffer)专门用来保存即)镀写入日志文件的数据,默认16M,其设计初衷也是一样为了较少磁盘IO。

SHOW VARIABLES LIKE '%innodb_log_buffer_size%';

在这里插入图片描述
需要注意:redo log的内容主要是用于崩溃恢复。磁盘的数据文件,数据来自buffer pool,redo log写入磁盘,不是写入数据文件。

Redo Log Buffe刷盘机制

在我们写入数据到磁盘的时候,操作系统本身是有缓存的。flush就是把操作系统缓冲区写入到磁盘,也叫刷盘。

log buffer写入磁盘的时机,由参数innodb_flush_log_at_trx_commit的状态控制刷盘时机,默认是1,实时写。

SHOW VARIABLES LIKE '%innodb_flush_log_at_trx_commit%';

在这里插入图片描述
log buffer写入磁盘的时机一共由三种状态,分别是0,1,2,如下图:
在这里插入图片描述

  • 0延退写。log buffer将每秒一次地写入log file中,并且log file的flush操作同时进行。该模式下,在事务提交的时候,不会主动触发写入磁盘的操作。
  • 1默认,实时写,实时刷。每次事务提交时MySQL都会把log buffer的数据写入log file,并且刷到磁盘 中去。
  • 2实时写,延迟刷。每次事务提交时MySQL都会把log buffer的数据写入log file。但是flush操作并不会同时进行。该模式下,MySQL会每秒执行一次flush操作。

刷盘越快,越安全,但是也会越消耗性能;反之,你更想提升性能,你认为你的MySQL足够稳定,崩溃或者重启的情况是很少的,就可以尽量将log buffer的内容晚一点写入磁盘中去,这样性能吞吐量上去了,但是可靠性降低了。

总结

以上是MySQL的InnoDB架构-内存结构,分为:
Buffer pool、change buffer、Adaptive Hash Index、log buffer。

InnoDB架构-磁盘结构

下面我们来看一下磁盘结构,磁盘结构里面主要是各种各样的表空间,叫做Table space。

表空间可以看做是InnoDB存储引擎逻辑结构的最高层,所有的数据都存放在表空 间中。InnoDB的表空间分为5大类:

  • System Tablespace:InnoDB存储引擎有一个共享表空间,在默认情况下,所有的表共享一个系统表空间,这个文件会越来越大,而且它的空间不会收缩。它是一个文件,就是ibdata1这个文件,文件的位置是由datadir参数决定的。文件里面会存很多的内容,包括如下:
    • InnoDB Data Dictionary:InnoDB数据字典。所谓数据字典就是表结构定义的那些相关元数据的信息。
    • Doublewrite Buffer:双写缓冲区,InnoDB的一大特性,独有的设计。
    • Change Buffer:写缓冲区。它同时也作为内存的一个区域划分,因此磁盘中也会有这样一个操作。
    • Undo logs:有了Undo Tablespace,为什么这里还要Undo logs?你既可以把undo log放在系统表空间存储,也可以把undo log独立出来,放在它自己的表空间中,只是在那里存的区别
    • 如果没有指定File-Per-Table Tablespace,也包含用户创建的表和索引数据
  • Undo Tablespace:能够提供回滚的操作来保证原子性。undo log的数据默认在系统表空间ibdata1文件中,因为共享表空间不会自动收
    缩,也可以单独创建一个undo表空间
  • Redo Log:log buffer的数据会通过到此,主要是用于崩溃恢复
  • File-Per-Table Tablespace:独占表空间
    • 我们可以让每张表独占一个表空间。这个开关通过innodb_file_per_table设置,默认开启。

      SHOW VARIABLES LIKE '%innodb_file_per_table%';
      

      在这里插入图片描述

      开启后,则每张表会开辟一个表空间,这个文件就是数据目录下的ibd文件,存放表的索引和数据。但是其他类的数据,如回滚(undo)信息,插入缓冲索引页、系统事务信息,二次写缓冲(Double write buffer)等还是存放在原来的共享表空间内。

  • General Tablespace:通用表空间,多个表共享。也是一种共享的表空间,跟ibdata1类似。
    • 可以创建一个通用的表空间,用来存储不同数据库的表,数据路径(路径要用'/'而不是'\''\'会语法错误)和文件可以自定义,没有指定存储目录,使用的是默认存储路径。语法:

      create tablespace mytblspace add datafile 'C:/ProgramData/MySQL/mytblspace/mytblspace.ibd' file_block_size= 16K engine=innodb;
      create tablespace mytblspace1 add datafile 'mytblspace1.ibd' file_block_size= 16K engine=innodb;
      

      查看已存在的表空间和对应的文件

      select TABLESPACE_NAME,FILE_NAME from information_schema.FILES;	
      

      在这里插入图片描述
      查看表空间文件
      在这里插入图片描述
      在这里插入图片描述
      在创建表的时候可以指定表空间,用ALTER修改表空间可以转移表空间。

      create table mytbl(id integer) tablespace mytblspace ;
      

      表导出为SQL脚本的时候可以看到会指定表空间
      在这里插入图片描述

      不同表空间的数据是可以移动的,删除表空间需要先删除里面的所有表:

      drop table mytbl;
      drop tablespace mytblspace ; 
      
  • Temporary Tablespace:临时表空间,存储系统临时的一些数据。比如用户创建的临时表,还有磁盘去做数据排序的时候,要占用磁盘的一块临时空间。它是一个文件,就是ibtmp1这个文件,文件的位置是由datadir参数决定的。
InnoDB Doublewrite Buffer双写缓冲区

Doublewrite Buffer它是页的一个备份,它保证了内存同步磁盘的可靠性。解决防止页写到一半,没写完,被破坏了,而没法恢复。
在这里插入图片描述

InnoDB的页和操作系统的页大小不一致,InnoDB页大小一般为16K,InnoDB存储引擎16K,操作系统页 大小为4K,InnoDB的页写入到磁盘时,一个页需要分4次写。

操作系统从内存读取数据时,以页为单位,如果存储引擎正在写入页的数据到磁盘时发生了宕机,可能出现页只写了一部分的情况,比如只写了 4K,就宕机了,这种情况叫做部分写失效(partial page write),可能会导致数据丢失。

show variables like Innodb doublewrite1;

我们不是有redo log吗?是否可以基于redo log将没有写完的16K的文件做一个恢复呢?
这里有个问题,当你把内存的16K的内容写到磁盘的16K的内容,因为只写了一个4K,已经导致磁盘页上的内容遭到破坏了,这个磁盘上的页已经不完整了,那么用它来做崩溃恢复是没有意义的。

所以在对于应用red log之前,需要一个页的副本。如果出现了 写入失效,就用页的副本来还原这个页,然后再应用redo log。这个页的副本就是double write, InnoDB的双写技术,通过它实现了数据页的可靠性。
跟redo log —样,double write由两部分组成,一部分是内存的double write, —个部分是磁盘上的double write。因为double write是顺序写入的,不会带来很大的开销。

6.后台线程

后台线程的主要作用是负责刷新内存池中的数据和把修改的数据页刷新到磁盘。后 台线程分为:master thread,IO thread,purge thread,page cleaner thread。- - - master thread:负责刷新缓存数据到磁盘并协调调度其它后台进程。

  • IO thread:分为 insert buffer、log、read、write 进程。分别用来处理 insert buffer. 重做日志、读写请求的io回调。
  • purge thread:用来回收 undo 页。
  • page cleaner thread:用来刷新脏页。

7.Binlog

除了InnoDB架构中的日志文件,MySQL的Server层也有一个日志文件,叫做 binlog,它可以被所有的存储引擎使用。

binlog以事件的形式记录了所有的DDL和DML语句(因为它记录的是操作而不是
数据值,属于逻辑日志),可以用来做主从复制和数据恢复。

跟redo log不一样,它的文件内容是可以追加的,没有固定大小限制。
在开启了 binlog功能的情况下,我们可以把binlog导出成SQL语句,把所有的操
作重放一遍,来实现数据的恢复。
binlog的另一个功能就是用来实现主从复制,它的原理就是从服务器读取主服务器 的binlog,然后执行一遍。

有了这两个日志之后,我们来看一下一条更新语句是怎么执行的(redo不能一次写
入了):

有了这些日志之后,我们来总结一下一个更新操作的流程,这里redo log是两阶段提交,比如:将id=1001的数据,修改name原值是'小李',现在改为'小王'

update stu set name = '小王' where id=1001;

在这里插入图片描述

  1. 先查询到这条数据,如果有缓存,也会用到缓存。
  2. 把name改成盆鱼宴,然后调用引擎的API接口,写入这一行数据到内存, 同时InnoDB记录redo logo这时redo log进入prepare状态,然后告诉执行器,执行完成了,可以随时提交。
  3. 执行器收到通知后记录binlog,然后调用存储引擎接口提交事务,InnoDB设置redo log为commit状态。
  4. 更新完成。

图中重点步骤:

  • 先记录到内存,再写日志文件。
  • 记录redo log分为两个阶段。
  • 存储引擎和Server记录不同的日志。
  • 先记录redo,再记录binloq。

为什么需要两阶段提交?
两阶段提交的作用,就是提供一个可以协调的机制,如果一次写完,那么是没有办法做到你成功他失败的,因此MySQL中设置了两阶段提交的方式,来保证redo log和binlog的内容是一致的。

举例:
如果我们执行的是把name改成'小王',如果写完redo log,在还没有写binlog的时候,MySQL重启了。因为redo log可以在重启的时候用于恢复数据,所以写入磁盘的是'小王',但是 binlog里面没有记录这个逻辑日志,所以这时候用binlog去恢复数据或者同步到从库, 就会出现数据不一致的情况。

所以在写两个日志的情况下,binlog就充当了一个事务的协调者。通知InnoDB来执行prepare或者commit或者rollback。
如果第⑥步Server层写入binlog失败,就不会提交。

简单地来说,这里有两个写日志的操作,类似于分布式事务,不用两阶段提交,就
不能保证都成功或者都失败。

在崩溃恢复时,判断事务是否需要提交:

  • binlog无记录,redo log无记录:在redo log写之前crash(崩溃),恢复操作:回滚事务
  • binlog无记录, redo log状态prepare:在binlog写完之前的crash(崩溃),恢复操作:回滚事务
  • binlog有记录,redo log状态prepare:在binlog写完提交事务之前的crash(崩溃),恢复操作:提交事务
  • binlog有记录,redo log状态commit:正常完成的事务,不需要恢复

如果binlog不开启,redo log也就不需要两阶段提交了,因为不需要保证和binlog的内容一致,它也不会影响到主从复制,和基于binlog的数据恢复。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1126510.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VS设置dll加载路径

属性配置-调试-环境: 当存在多个dll路径时,需按图示要求保证计算的值如图格式所示,计算的值:PATH%PATH%;路径;路径; 或者PATH路径;路径;%PATH% 由于qt路径为继承的值,所以将其“从父级或项目默认机床”√取消 上图…

RabbitMQ高级篇 笔记

这是一些高级的内容。 RabbitMQ还是运行在网络上的,倘若遇到了网络故障,mq自己挂了,出异常了,都会造成最终状态不一致的问题。这就是可靠性问题。 可靠性:一个消息发送出去之后,至少被消费1次。 要解决这3个…

vue实现响应式改变scss样式

需求:侧边导航栏点击收起,再次点击展开,但是我这个项目的位置是在左侧菜单栏所以需要自定义 效果图: 实现步骤: 1:定义一个变量(因为我这里会存储菜单栏的状态所以需要存储状态,一…

c语言的程序环境和预处理(一眼丁真)

前言: 正所谓,万物c为首。在我们较为深入的学完c语言之后,可以说是打开了编程的第一扇大门。代码我们会敲了,可是这些代码到底是咋运行起来的呢?这些源文件,头文件里的代码又是怎么“整合”在一起的呢&…

前端数据可视化之【series、series饼图配置】配置项

目录 🌟Echarts配置项🌟series🌟饼图 type:pie🌟写在最后 🌟Echarts配置项 ECharts开源来自百度商业前端数据可视化团队,基于html5 Canvas,是一个纯Javascript图表库,提供直观&…

Python装饰器(包装函数、拦截函数)

无参装饰器 decorate: f decorate(f1) f wrapper 有参装饰器 多层装饰器 装饰器带参数 装饰器带参数,3层 案例 import timeisLogin Falsedef login():username input("请输入用户名:")passward input("请输入密码:&q…

HNSW-分层可导航小世界 算法学习

一、knn的缺陷 1. K-NN方法的工作机制 K-Nearest Neighbors (K-NN) 是一种基于实例的分类方法。它通过逐一比较新样本与已有样本的相似度,挑选出与新样本最接近的k个已有样本,然后根据这些样本的类别,通过投票或加权的方式来决定新样本的类…

如何设计实时聊天系统的架构

1. 系统的要求和目标 1.1 功能要求 对话:系统应支持用户之间的一对一和群组对话。确认消息:系统应支持消息传递确认,如已发送、已送达、已读。共享:系统应支持媒体文件的共享,例如图像、视频和音频。聊天存储&#x…

第3章 指令级并行及其利用

3.1 指令级并行:概念和挑战 1985年之后几乎所有处理器都使用流水线来使指令能重叠执行。由于指令可以并行执行,所有指令之间的这种可能得重叠称为指令级并行ILP。 ILP大体有两种实现方法: 1. 依靠硬件来动态发现并实现并行&#xf…

【解决】设置pip安装依赖包路径默认路径在conda路径下,而不是C盘路径下

【解决】设置pip安装依赖包路径默认路径在conda路径下,而不是C盘路径下 问题描述 在win11下安装miniconda,在conda环境里使用pip安装,依赖包总是安装到C盘路径,如 C:\Users\Jimmy\AppData\Local\Programs\Python\Python311\Lib\…

基于.Net CEF 实现 Vue 等前端技术栈构建 Windows 窗体应用

零、参考资料 1、https://github.com/cefsharp/CefSharp/wiki/Quick-Start-For-MS-.Net-5.0-or-greater 2、https://github.com/cefsharp/CefSharp/wiki/Quick-Start 3、https://github.com/cefsharp/CefSharp/wiki/General-Usage#javascript-integration 一、安装 Nuget 包…

计算机网络文章荟萃

脑残式网络编程入门(二):我们在读写Socket时,究竟在读写什么?-网络编程/专项技术区 - 即时通讯开发者社区! 1.什么是 socket - 掘金2.socket 的实现原理 - 掘金本文讲述了 socket 在 linux 操作系统下的数据结构,以及阻塞 IO 利用…

RHCE---作业3

一.判断磁盘空间 1、判断当前磁盘剩余空间是否有20G,如果小于20G,则将报警邮件发送给管理员,每天检查一次磁盘剩余空间。 # 为了方便测式使用每分钟发送一封邮件,若想要每天定时检查则需要将前两个*改为0 0 [roottimeserver ~]…

Unity protobuf中repeated转C#文件List只读问题

Unity protobuf中repeated转C#文件List只读问题 介绍问题解决方案总结 介绍 工具这里我就不多介绍了,如果有用到ProtoGen工具的可以继续看一下我后面的方法。 问题 如下图所示,我这里随便用了一个.proto文件,看下我这里面的repeated标记的…

el-form动态增减数据的问题,编辑时候input输入不了

新增的时候可以展示&#xff0c;但是编辑在点新增就没有效果 原因&#xff1a;改变了数组内的值&#xff0c;但是页面没有重新渲染 <el-form-item label"信息:" required><div style"display: flex; align-items: flex-end"><div><e…

Spring MVC常用十大注解

Spring MVC常用十大注解 一&#xff0c;什么要使用注解 使用注解可以简化配置&#xff0c;提高代码的可读性和可维护性。通过注解可以实现依赖注入&#xff0c;减少手动管理对象的代码量。注解还支持面向切面编程&#xff0c;实现切面、切入点和通知等。此外&#xff0c;注解提…

maven仓库改国内源

今天准备复现漏洞环境&#xff0c;发现太慢&#xff0c;需要配置国内源 file -> settings 搜索maven 修改settings.xml&#xff0c;这里的需要修改两个文件 1.上图的settings.xml文件 2.idea的maven模块 settings.xml文件将原来的注释掉&#xff0c;然后把阿里的添加上&…

【机器学习合集】激活函数合集 ->(个人学习记录笔记)

文章目录 1. S激活函数(sigmoid&Tanh)2. ReLU激活函数3. ReLU激活函数的改进4. 近似ReLU激活函数5. Maxout激活函数6. 自动搜索的激活函数Swish 1. S激活函数(sigmoid&Tanh) Sigmoid函数在机器学习中经常用作激活函数&#xff0c;但它在某些情况下容易出现梯度消失问题…

QWidget快速美化-蓝色边框圆角按钮

将代码复制进QPushButton的样式表 效果: 代码: QPushButton{ color:#52DCFE;border:2px solid #52DCFE;border-radius:5px; }QPushButton::hover{background-color:#52DCFE;color:#ffffff; }QPushButton::pressed,QPushButton::checked{background-color:#52DCFE;color:#ffff…

学习vue3

一、入门 1.引入外部库 ①直接将所有的js都通过script标签引入到html文件中&#xff0c;所有的js资源在web页面中都能通用。 ②使用js引用js&#xff08;ES6&#xff09;&#xff0c;模块导入与导出 2.模块是只读引用 这段话是在解释 Vue.js 中的概念和用法。在 Vue.js 中&a…