MySQL架构 & InnoDB存储引擎

1. 什么是Mysql?

  • 我们在开发的时候,我们都需要对业务数据进行存储,这个时候,你们就会用到MySQL、Oracal等数据库。
  • MySQL它是一个关系型数据库,这种关系型数据库就有Oracal、 MySQL,以及最近很火的PgSQL等。

那什么是关系型数据库呢?

  • 就是它是基于我们的SQL语句去执行操作的
  • 其实就是关系表数据库由表结构来存储数据与数据之间的关系,同时用SQL(Structured query language)结构化查询语句来进行数据操作。
  • 关系型数据库,对应会有一个非关系型数据库,像我们用的比较多的 Redis 这种Key - Value结构数据存储、hbase这种列存储格式、MongoDB这种文档存储等等。

那么关系型数据库相比非关系型数据的区别:

  1. 关系型数据库都是用表来进行维护,所以格式一致,可以统一用SQL语言来进行操作
  2. 关系型数据库都是表结构,所以灵活度不够,操作复杂的海量数据性能比较差,所以我们才会有表结构、索引以及索引优化。
  1. 虽然性能可能会比较慢,但是能做复杂的关联查询操作。 比如一对一, 一对多,多对多等等。
    官网介绍: https://www.oracle.com/mysql/what-is-mysql/

MySQL 的优势:

  1. **易用性:**开发者可以在几分钟内安装好MySQL,数据库易于管理。
  2. 可靠性: MySQL 是最成熟、使用最广泛的数据库之一。超过 25 年,它已经在各种场景中进行了测试,其中包括许多世界上最大的公司。由于MySQL 的可靠性,组织依赖 MySQL 来运行关键业务应用程序。
  3. 可扩展性: MySQL 可扩展以满足最常访问的应用程序的需求。MySQL 的本机复制架构使 Facebook 等组织能够扩展应用程序以支持数十亿用户。
  4. 高性能: MySQL HeatWave比其他数据库服务更快且成本更低,多项标准行业基准测试证明了这一点,包括 TPC-H、TPC-DS 和 CH-benCHmark。
  5. 高可用性: MySQL 为高可用性和灾难恢复提供了一套完整的本机、完全集成的复制技术。对于关键业务应用程序,并满足服务级别协议承诺,客户可以实现 零数据丢失以及秒级的故障转移恢复。
  6. **安全性:**数据安全需要保护和遵守行业和政府法规,包括欧盟通用数据保护条例、支付卡行业数据安全标准、健康保险可移植性和责任法案以及国防信息系统局的安全技术实施指南。MySQL 企业版提供高级安全功能,包括身份验证/授权、透明数据加密、审计、数据屏蔽和数据库防火墙。
  7. 灵活性: MySQL 文档存储为用户开发传统 SQL 和 NoSQL 无模式数据库应用程序提供了最大的灵活性。开发人员可以在同一个数据库和应用程序中混合和匹配关系数据和 JSON 文档。
Mysql服务安装
Mysql连接或者断开服务器
Mysql里面的基本库表信息
库基本操作

查询所有的库:

创建库:

查询当前选择的库:

表基本操作

系统库表

  • 我们发现除了我们自己创建的库以外,还有很多系统的库、以及表来保证MySQL的系统运行。

官网:MySQL :: MySQL 8.0 参考手册 :: 5.3 mysql 系统架构

三个系统库:

  1. **mysql库:**这一个系统库是来保证我的数据库服务正常运行的一个系统表全部在这个库里面。
  2. **information_schema库:**包括InnoDB里面的数据、日志、事务、表;还有PROCESSLIST表:它记录了当前正在运行的数据库有多少个连接和查询进程的信息;ENGINES表:存储引擎插件表。
  3. performance_schema库 - 性能库:这个库里面所有的数据是当前只存在内存里面的,这个库下面所有的事件、锁(比如data_locks表) - 我会去锁哪些数据。这些数据它只针对我当前的服务,如果重启了,所有的数据会丢失,所以这个它只存在当前内存。
mysql系统库下几个重要的表:
  1. 数据字典表(Data Dictionary Tables)
  2. 授权表(Grant System Tables)
  3. **对象信息表(Object Information System Tables):**plugin 插件注册表 等待
  4. 日志系统表(Log System Tables):
  • general_log:一般查询日志表。
  • slow_log:慢查询日志表。
日志配置:
sql 复制代码
show variables like 'general_log'; //一般查询日志,默认关闭
SELECT @@long_query_time;
show global variables like 'long_query_time';
show global variables like 'min_examined_row_limit'; //至少需要检索这么多行
show global variables like 'slow_query_log'; //是否开启慢日志查询 默认关闭
SET GLOBAL slow_query_log=1;
set global long_query_time=0.1; //超过100毫秒
log_output=table |file |none //设置是放在文件中,还是在mysql.slow_log表中

Sql语句的执行流程

  • 客户端发送一条语句给到服务器,然后服务器它能给你一个它的数据。

一. 连接器 - 跟MySQL服务器建立连接

  • 只有建立连接以后,我才能够发送SQL语句给到MySQL服务器,MySQL服务器它才能够去进行接收,这样才能进行网络IO。
  • 连接的过程需要先经过TCP三次握手,因为MySQL是基于TCP协议进行传输的,如果MySQL服务并没有启动,则连接会报错;如果MySQL服务正常运行,完成TCP连接的建立后,此时连接器就要开始验证你的用户名和密码,如果用户名或密码不对,就会收到一个"Access denied for user"的错误,然后客户端程序结束执行。
  • 如果用户名和密码都对了,会读取该用户的权限,然后后面的权限逻辑判断都基于此时读取到的权限;
连接管理
  • 首先,我们得有连接,那么Mysql里面就有一个连接层来管理连接,我们看下跟连接有关的变量/参数
  • 变量:随着我的服务的运行,它会变更的,这些变量会随着我的客户端连接的变多而变多。

MySQL的四个线程状态变量:MySQL :: MySQL 8.0 参考手册 :: 5.1.6 服务器状态变量参考

  1. **Thread_cached:是MySQL的一个状态变量,表示MySQL线程缓存中当前缓存的线程数,**是为了我的服务,不用每一次客户端建立连接的时候都去创建一个线程,以此减少线程的创建和销毁的开销,提高数据库性能,所以它有一个缓存的线程数。当一个客户端连接到MySQL数据库服务器时,服务器会为该连接创建一个线程来处理客户端的请求。线程缓存的作用就是在该连接请求结束后,将这些线程缓存在内存中,以便下次有新的连接请求时能够复用这些线程,而不需要重新创建。thread_cache_size是MySQL的一个静态配置参数用来配置线程缓存的大小,默认是-1,需要手动调整(在MySQL配置文件当中配置)并重启MySQL服务才能生效,最大是16384。
  2. Thread_connected:当前打开的线程数,就是我现在有多少个线程是打开的。
  3. **Thread_created:总共创建的线程数,即创建的线程总数。**创建的线程总数越多,我们的thread_cache_size 可以对应的更大,来提升线程的缓存命中率。
  4. Threads_running:正在运行的线程数
sql 复制代码
-- 查看MySQL的四个Thread线程状态变量
show status like 'Thread%';
sql 复制代码
-- 查询thread_cache_size系统变量
select @@thread_cache_size;
查看MySQL服务被多少个客户端连接 / 查看当前正在运行的线程:
sql 复制代码
-- 显示当前正在运行的线程
show full PROCESSLIST;
该查询将返回一个结果集,包含所有当前连接的信息。每一行代表一个连接,每个连接的信息包括
  • id:线程 ID

  • State:连接状态

  • User:操作的用户名

  • Host:主机 / IP

  • db:操作的数据库

  • command:当前连接执行的命令:Sleep-休眠、Query-查询

  • Time:这个状态持续的时间,单位是s

  • info - 信息:查询会有查询的信息,但是长度有限制,可能不全

删除阻塞线程

使用PROCESSLIST表可以查看当前正在运行的连接-线程,假如当前有客户端连接已经阻塞了,那么此时你可以去把它KILL掉:

demo

1. 会话一:

sql 复制代码
SELECT * FROM product_new --表中有500W数据,查询很慢

2. 会话二:

sql 复制代码
SHOW PROCESSLIST; -- 查看当前线程

执行结果:

3. 关闭查询query:

sql 复制代码
​​​​​​​KILL QUERY 9328; -- 终止查询 

查看会话一的结果:[Err] - Query execution was interrupted 请求被中断

4. 杀死 / 关闭连接线程 - Connection Thread

sql 复制代码
KILL 2280; -- kill 连接线程

查看会话一的结果:

  • [Err] - Lost connection to MySQL server during query - 丢失连接Connection
其他连接相关配置参数:
sql 复制代码
show status like 'Max_used_connections%';
  • Max_used_connections 自服务启动以来最大的连接数
  • Max_used_connections_time 达到这个峰值的时间
几个全局系统变量:
sql 复制代码
-- 查询最大连接数 默认151
SELECT @@max_connections;
select @@GLOBAL.max_connections;

-- 手动设置最大连接数
SET @@GLOBAL.max_connections = 1000;

-- 查询服务器超时等待时间  默认28800s - 8h
SELECT @@wait_timeout;
select @@GLOBAL.wait_timeout;

-- 手动设置服务器最大等待时间
SET @@GLOBAL.wait_timeout = 1000;
  • max_connections:最大的连接数,即我的服务最多能开启的连接数超过该值不允许建立连接,默认151,最小1,最大 100000。**如果开启太大,同时会有很多的客户端来进行连接操作,MySQL性能可能会跟不上;如果开启太小,可能在高并发场景下导致并发量上不来。**超过默认值,系统就会拒绝接下来的连接请求,并报错提示"Too many connections"。
  • wait_timeout:空闲连接最大的等待时间 / 服务器超时等待时间 / 非交互连接等待的时间(单位s),默认28800s,也就是8小时 ,**用于指定一个连接在空闲状态下的最长等待时间。如果一个连接在8小时内没有进行任何操作,那么MySQL服务器会自动关闭该连接,以释放资源。**wait_timeout => 服务器什么时候会自动关闭?

跟服务器建立完连接之后,此时就代表客户端能跟服务端去进行通信了,也就是客户端能向服务端去发送SQL语句的请求了。

++MySQL Server中的第一层 - 网络连接层++

-- MySQL 8.0版本以后就已经移除掉了缓存模块:查询缓存 --

  • 连接器的工作完成后,客户端就可以向 MySQL 服务器发送 SQL 语句了,MySQL 服务器收到 SQL 语句后,就会解析出 SQL 语句的第一个字段,看看是什么类型的语句。

  • 如果 SQL 是查询语句(select 语句),MySQL 就会先去查询缓存( Query Cache )里查找缓存数据,看看之前有没有执行过这一条命令,这个查询缓存是以 key-value 形式保存在内存中的,key 为 SQL 查询语句,value 为 SQL 语句查询的结果。

  • 如果查询的语句命中查询缓存,那么就会直接返回 value 给客户端。如果查询的语句没有命中查询缓存,那么就要往下继续执行,等执行完后,查询的结果就会被存入查询缓存中。

  • 这么看,查询缓存还挺有用,但是其实查询缓存挺鸡肋的。

  • 对于更新比较频繁的表,查询缓存的命中率是很低的因为只要一个表有更新操作,那么这个表的查询缓存就会被清空。如果刚缓存了一个查询结果很大的数据,还没被使用的时候,刚好这个表有更新操作,查询缓冲就被清空了,相当于缓存了个寂寞。

  • 所以,MySQL 8.0 版本直接将查询缓存删掉了,也就是说 MySQL 8.0 开始,执行一条 SQL 查询语句,不会再走到查询缓存这个阶段了。

二. 解析器 - 解析SQL

  • 当建立连接后,客户端向服务端去发送SQL语句请求时需要解析SQL语句,会把一条SQL语句解析生成语法树;因为MySQL不是人,不会一眼看到SQL语句就知道要做什么事情。
  • 所以它会借用解析器去把SQL语句解析出来看是否符合我们的SQL语法,最终生成一个语法树(理解为一个数据结构)。
解析器分为词法解析跟语法解析!
词法解析(器)
  • 将SQL语句打碎,转化成一个一个关键单词 =>然后交给语法解析(器)去构建语法树,判断语法是否正确
语法解析(器)
  • 语法解析已经知道每个SQL语句的单词了,那么在语法解析的时候,会去判断 / 检查语法是否正确,比如,where是不是写出where1,from写成from1;
  • 表名、字段名是否存在、用户是否有操作权限等等。
  • 如果发现SQL语法错误,则MySQL直接抛出相应的错误信息,并拒绝执行该SQL语句 => You have an error in your SQL syntax;
  • 否则会去解析构建出SQL语法树,以便后面模块获取SQL语句类型、表名、字段名、where条件等等。
总结:
  • 先将输入的SQL语句解析为语法树,然后对语法树进行语法检查,这样可以确保在执行之前先判断SQL语句是否符合MySQL的语法规则,避免执行无效或错误的语句。
  • 注意:表名不存在或者字段名不存在,并不是在解析器里做的,解析器只负责检查语法和构建语法树,但是不会去查表名或字段名是否存在。

三. 预处理器 / 预编译器(可做可不做)

预处理 / 预编译的两个作用:
  1. 提升性能
  2. 防止SQL注入,更安全

MyBatis中的SQL注入是MyBatis去做的参数化,而这里的SQL注入是我们的MySQL服务器自己能支持的,预处理器它是我们的MySQL服务能支撑的。

什么是预处理?
  • 以我们的工作场景为例,一个查询接口,SQL语句都是一样的,但是每次查询的参数都不一样,所以我们想只需要变更参数部分就行。
  • 那么,我们就是在拼接SQL语句的时候,将用户的输入跟语句拼接成一个SQL语句给到MySQL执行。
  • 但是会发生一个SQL注入问题!
什么是SQL注入?
  • 因为参数是客户端传过来的,所以可以传任何值,那么就有可能传入任何值就有了SQL注入问题。
sql 复制代码
-- 要执行的SQL语句
select * from emp where password = '';

-- SQL注入演示-客户端传入查询的参数为: ' or '1' = '1
select * from emp where password = '' or '1' = '1';

SQL注入是因为客户端拼接用户传入的参数,然后拼接好语句给到MySQL,这样会导致会安全问题。

那么能不能把这个参数化的事情交给MySQL自己做呢?
  • 当然可以,这个就是预处理
  • 如果你需要参数化,你只要告诉MySQL,传一个预处理语句就行,MySQL会将参数与语句编译分开。
预处理操作解决SQL注入

官网地址: MySQL :: MySQL 8.0 Reference Manual :: 13.5 Prepared Statements

预处理语句的工作流程 / 预处理语句为什么能够防止SQL注入?
  1. 首先在应用程序中,应该去创建一个预处理语句,将SQL查询与占位符(通常使用问号?来表示)组合起来,形成一个带有参数占位符的SQL查询语句。
  2. 接着,应用程序将这个预处理语句发送到MySQL服务器进行编译。服务器会对SQL语句进行语法解析和执行计划生成,但不会执行实际的查询。
  3. 在执行查询之前,应用程序通过绑定参数的方式将实际的参数值与占位符关联起来,这样可以防止SQL注入攻击,因为用户输入的参数不会直接嵌入到查询语句中,而是作为参数传递给服务器,并允许在多次执行中重复使用预处理语句(因为预处理语句只需编译一次,再次执行时就无需重新编译,以此提高性能)
  4. 一旦参数绑定完成,应用程序可以多次执行相同的预处理语句,通过更改参数值来获取不同的结果。
预处理Demo,防止SQL注入:
sql 复制代码
-- 创建预处理语句
-- PREPARE 预处理名字 from 'SQL语句';
PREPARE select_user from 'select * from emp where password = ?'

-- 绑定参数(设置参数值)
SET @passsword = '123456';

-- 执行预处理语句
-- EXECUTE 预处理名字 USING @绑定参数名;
EXECUTE select_user USING @passsword;

-- 清除预处理语句
-- DEALLOCATE PREPARE 预处理名字;
DEALLOCATE PREPARE select_user;
预处理主要做主要做以下2个事情:
  1. 将语句编译、优化跟参数分开处理当执行SQL语句相同,参数不同的场景,提升性能。
  2. 因为是参数化去执行的,而不是拼接参数,从而解决了SQL注入问题。
比如我们经常被问的Mybatis里面#跟$符号的区别:
  • #符号执行SQL时,会将#{...}替换成?,生成预编译SQL / 预处理SQL,然后进行预处理,能防止SQL注入,并且必须传入参数;
  • 符号会拼接SQL,直接将{...}参数拼接在SQL语句中,存在SQL注入问题

四. 优化器 - 决定怎么做 => 生成执行计划,确定执行方案

  • 根据上面的流程,我们知道要去执行什么语句,但是具体怎么执行会有很多的方式,比如走哪个索引,要不要回表,要不要去在内存里面排序,你的语句是不是可以优化等等。

做哪些优化,有哪些优化器,通过全局变量 optimizer_switch 来决定, 控制优化器策略的一种方法是设置 optimizer_switch 系统变量。
具体参数如下:

sql 复制代码
-- 获取MySQL数据库中的全局优化器开关配置信息
SELECT @@GLOBAL.optimizer_switch;
  • 优化器说明:https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html,里面包含了每个优化选项说明.
  • 优化方式官网地址:https://dev.mysql.com/doc/refman/8.0/en/optimization.html;这个里面就有很多优化器的实践,比如优化sql语句等等。
  • ++优化器它是基于服务,觉得自己最快的一些方式去执行这个语句,优化后会生成一个最优的执行计划,并将该执行计划传递给存储引擎层,所以这个语句到底怎么走,优化器来决定。++
  • ++它是基于内存与CPU或性能的消耗得到一个算法或者说得到哪一个执行计划它是最快的或查询成本最小的。++
  • 当前,优化器里面有一些东西它是可以自己设置的,比如说要不要索引下推,比如说要不要回表,比如说要不要去用联合索引,要不要用hash_join,要不要用跳跃扫描skip_scan,要不要用mrr,mrr是它底层的一个算法,能够更快的跟我的磁盘去进行交互等等。
  • 优化器负责将 SQL 查询语句的执行方案确定下来!
  • 要想知道优化器选择了哪个索引,我们可以在查询语句最前面加个 explain 命令,这样就会输出这条 SQL 语句的执行计划然后执行计划中的 key 就表示执行过程中使用了哪个索引,比如下图的 key 为 PRIMARY 就是使用了主键索引。
  • 如果查询语句的执行计划里的 key 为 null 说明没有使用索引,那就会全表扫描(type = ALL),这种查询扫描的方式是效率最低档次的。
++MySQL Server中的第二层 - 核心服务层,核心服务层不牵扯到数据的存储与查询。++

五. 执行器 - 去操作数据的 - 真正跟存储引擎进行交互

  • ++执行器根据执行计划,去调用数据存储的地方,也就来到了我们MySQL Server中的第三层 - 存储引擎层!++
  • 刚刚讲了 MySQL的很多插件但是还没有真正的去跟数据交互 ,也就是没有去查询数据。
  • ++数据是放在我们的MySQL Server中的存储引擎层++
  • ++存储引擎层是真正的跟数据进行交互的,是真正的来保存以及怎么去查询数据的,所以存储引擎层决定了我这个数据以什么样子的方式来保存,比如说你是保存到磁盘,还是保存到内存,还是磁盘跟内存都有。++
  • 执行器去根据表设置的存储引擎,调用不同存储引擎的API接口获取数据。
  • ++至于这个数据是怎么存的,这个数据有哪些优化(比如内存去缓存)等等,就是每个存储引擎自己去做的事情,也就来到了我们MySQL Server中的第三层中的存储引擎层,并且存储引擎是跟MySQL解耦的,存储引擎跟MySQL的开发者都不是同一批人。**++
  • ++存储引擎它是我们MySQL的一个插件++ ,你如果有能力,MySQL都支持你自己写存储引擎。
  • ++基于不同的一些场景,比如说有一些场景我要去保证性能,有一些场景我要去保证一致性,所以它会有不同的存储方案,这里就牵扯到我们不同的存储引擎。++
  • ++MySQL支持不同的存储引擎,这些存储引擎决定了我们数据的存储方式,以及数据的可靠性、一致性、持久性、原子性。也就是我们经常讲的ACID。++
那么官网提供了哪些存储引擎?

可以通过语句查询当前服务器支持哪些存储引擎:

sql 复制代码
SHOW ENGINES; -- 查询当前服务器支持的存储引擎
InnoDB
  • MySQL 8.0默认的存储引擎InnoDB是一个事务安全(兼容ACID) 的MySQL存储引擎具有提交、回滚和崩溃恢复功能,以保护用户数据。
  • InnoDB支持行级别的锁(没有升级到更粗粒度的锁)和Oracle风格一致的非锁读取提高了多用户并发性和性能
  • InnoDB将用户数据存储在聚集索引中,以减少常见的基于主键的查询的I/O。
  • 为了维护数据的完整性,InnoDB还支持外键引用完整性约束。
MyISAM
  • 这些表占用空间很小。
  • 表级锁限制了读/写工作负载的性能,因此它经常用于Web和数据仓库配置中的只读或以读为主的工作负载中,所以它的性能要比InnoDB要高。
Memory
  • Memory它只是把数据存储在内存,它不会做持久化。

不同的存储引擎会有自己不同的存储实现方式 / 存储方案不管是什么存储引擎,它一定要做的事情是把这个数据保存起来, 是用内存还是磁盘,还是都用,或者磁盘的文件格式等等都会不一样。

  • 既然要保存,那么就一定要有个数据的目录这个目录,也就是我们的一个变量,这个变量就是代表你保存到哪里。
接下来我们看下数据到底存储在哪里,以什么样的方式存储?

六. 存储层 - 数据存储地址

SQL语句查询:

sql 复制代码
-- 查询数据库的数据目录
select @@datadir;
show variables like '%datadir%';

++该目录就是我们的数据库的数据目录,我们的数据保存在该目录下。++
++不同的存储引擎,存储的文件以及格式都不一样,我们今天来重点分析下InnoDB的内存以及磁盘结构。++

·································································································································

InnoDB存储引擎

InnoDB 的主要优势

InnoDB的架构 - 数据磁盘结构

  • ++首先,我们的 InnoDB 数据是肯定会落到我们的磁盘!++ ++文件后缀我们知道肯定是ibd。++
  • ibd文件是我们默认的InnoDB的存储文件。
那么存在什么空间?我们就有一个表空间的概念。
  • 简单一点***,++表空间就是存储表和索引数据的位置,就是用来管理表与索引数据的。++***

表空间 - TableSpaces

官网地址: MySQL :: MySQL 8.0 Reference Manual :: 15.6.3 Tablespaces

  • 表空间又分为很多的类型,比如系统表空间、通用表空间、独立表空间 等等。
  • InnoDB的存储结构:InnoDB肯定会保存到磁盘,并且默认每个表都会有一个独立的ibd文件,用专有名词就叫做表空间,表空间就是我这个数据到底怎么去保存到我的磁盘的,这一个叫做表空间。表空间来决定了我这个数据是怎么存的,存在哪一个文件。

独立表空间 - File-Per-TableSpaces:

独立表空间优点:
  • 真正做到了解耦,易于管理,同时更大的利用磁盘空间。当然还有很多其它的优势。
怎么决定我的数据与索引文件放在哪个表空间?
  • 最关键的innodb_file_per_table参数,代表是存储到系统表空间,还是独立表空间1 代表开启了独立表空间,数据文件会保存在file_per_table独立表空间下, ++然后以表名.ibd保存。++
sql 复制代码
-- 查看是否启用了每个表的独立表空间 1或者ON代表开启,0或OFF表示关闭
select @@innodb_file_per_table; -- 1
show variables like '%innodb_file_per_table%'; -- ON

-- 也可以设置为0或OFF进行关闭独立表空间,进而变成了系统表空间
set global innodb_file_per_table = 0;
set global innodb_file_per_table = OFF;

系统表空间 - System TableSpace:

  • 我想把所有的表都放在一个文件里面 =>系统表空间:++所有的表的数据都在一个文件或者说在多个指定的文件里面。++
  • ++系统表空间可以有1个或者多个数据文件++ , 默认情况下,在data目录中创建一个名为ibdata1的系统表空间数据文件,所有的Innodb表的文件会保存在ibdata1目录下。
  • 系统表空间数据文件的大小和数量由innodb_data_file_path启动选项定义!
  • 官网:MySQL :: MySQL 8.0 Reference Manual :: 15.14 InnoDB Startup Options and System Variables
sql 复制代码
-- 查询系统表空间配置 / 查询系统表空间数据文件的文件名和文件大小
select @@innodb_data_file_path;
SHOW VARIABLES LIKE '%innodb_data_file_path%';
  • ibdata1: 文件名
  • 12M: 默认文件大小
  • autoextend 自动扩容,扩容大小由innodb_autoextend_increment决定。
sql 复制代码
-- 查看扩容的大小,默认扩容增量为64M
SHOW VARIABLES LIKE '%innodb_autoextend_increment%';
  • ++如果扩容到64M还不够,我们可以采用多个数据文件来提升系统表空间++ ,可以设置innodb_data_file_path,但是autoextend 只对最后一个文件生效,并且,不能对已经生成的文件的大小进行更改。
  • ++为了减少系统表空间的大小,我们也可以用通用表空间,这样我可以自行设置哪些表放在哪些表空间。++

通用表空间 - General TableSpaces:

  • ++与系统表空间类似,通用表空间是能够为多个表存储数据的共享表空间,但是这个空间可以自己进行维护管理。++
  • ++使用场景:假如说我想把订单表的放在一起,把商品表的放在一起,这时就用到了通用表空间。++
  • ++通用表空间说白了就是你可以自己指定哪个表到哪个文件!但前提是你要先创建文件!++
  • 官方网址: MySQL :: MySQL 8.0 参考手册 :: 15.6.3.3 通用表空间
创建通用表空间:
sql 复制代码
-- 创建通用表空间
CREATE TABLESPACE huihui_tablespace ADD DATAFILE '/var/lib/mysql/huihui.ibd' ENGINE = InnoDB;

创建表到通用表空间:

sql 复制代码
CREATE TABLE gp_student
(
    studentId   INT PRIMARY
        KEY,
    studentName VARCHAR(500)
) TABLESPACE huihui_tablespace
  Engine = InnoDB;

添加数据后,我们发现数据存储在huihui.ibd通用表空间中!
删除通用表空间:

sql 复制代码
-- 删除通用表空间,但是必须先保证通用表下没有表
DROP TABLESPACE huihui_tablespace;
通用表空间的限制:
  • 同时通用表空间只能创建在已知目录,已知目录是由datadir、innodb_data_home_dir和innodb_directories变量定义的目录。

临时表空间

  • 官网:MySQL :: MySQL 8.0 参考手册 :: 15.6.3.5 临时表空间

  • 在我们的SQL当 中,可能会用到临时表的逻辑,就是 在会话中创建一个表以供当前会话使用,会话关闭后,表失效。

    • Demo
      会话一:
    sql 复制代码
    CREATE TEMPORARY TABLE temp_table (
    id INT,
    name VARCHAR(50)
    ); -- 创建临时表
    SELECT * FROM temp_table; -- 能查询临时表

    会话二:

    sql 复制代码
    SELECT * FROM temp_table; -- 会报表不存在错误

    临时表空间在innodb_temp_tablespaces_dir进行配置:

    sql 复制代码
    -- 查询临时表空间的目录路径
    SELECT @@innodb_temp_tablespaces_dir;

表空间中数据存储

官网地址:MySQL :: MySQL 8.0 参考手册 :: 15.11.2 文件空间管理

  • 在MySQL当中,所有的数据存储都是基于一行表中数据存储最底层或者说最原子的单位是 row 行,但是不是通过row来直接保存文件的。
​​​​​​​page - 页
  • page页可以理解为是一个数据结构,page页里面保存的是行数据!
  • page页是保存到文件里面的。
  • 每个表空间都是由相同大小的page页来组成的,默认page页的大小为16KB,一个page页能保存的row行数据也是有大小的,一个表的数据可能会有很多很多的page页
可以根据innodb_page_size来设定页的大小,可设置的值有:64KB,32KB,16KB(默认),8KB和4KB:
sql 复制代码
-- 查询页大小,默认是16KB - 16384字节
select @@innodb_page_size;
  • 然后page页又不是直接放在表空间的,而是会用其它的一些数据结构来封装目的是为了提高数据磁盘的扩展性,管理空间分配与性能以及利于磁盘的分配,又有segement、extent的概念。
  • 就比如人 假如一个一个管理很麻烦,但是给你们圈起来,比如在某个班,就很好管理了。
segement - 段​​​​​​​
  • 段是表空间的分区一个表空间中,会有多个段组成常见的段有数据段、索引段、8.0之前有回滚段。
  • segement - 段 用来区分 page - 页 它是什么数据。
extent - 区
  • 区来管理页一个 extent - 区 会有很多的page页,一个 entent - 区默认占用1M的空间
  • 当页的大小在16K,则一个区的大小是1M,如果32K是2M,64K则为4M,后面磁盘释放分配都是以区为单位。
  • 一个 exnent - 区 默认有连续的 64 个页组成!(1M = 1024K,1024K / 16K = 64)
​​​​​​​​​​​​​​行 - Rows

内存缓存结构(BufferPool - 缓冲池)

  • 预读 - 提前加载数据到内存
  • 不管你是怎么存储的,都需要去跟磁盘进行交互(Memory引擎除外),因为你要去保证数据的持久性与可靠性,这样性能很慢 => 磁盘IO导致!因为每次操作查询都去跟磁盘进行交互!
  • ++此时InnoDB为了提升读写性能,采用了内存缓存机制,InnoDB引擎引入了一个中间层/缓存层 - BufferPoll++ ,++BufferPoll它是一个内存区间,是在内存上的一块儿连续空间,它的主要用途就是用来在内存中缓存相应的数据页 - Page,每个数据页 - Page的大小是16KB,它的目的是为了防止每次操作数据时需要实时的跟磁盘进行交互。++
  • ++页是InnoDB做数据存储的单元,无论是在磁盘,还是Buffer Pool中,都是按照Page - 页读取的,这也是一种"预读"的思想。++
  • 官网:MySQL :: MySQL 8.0 参考手册 :: 15.5.1 缓冲池
  • ++Buffer Pool缓冲池是主内存中的一个区域,在 InnoDB 访问时缓存表和索引的数据页。++
  • ++Buffer Pool缓冲池允许直接从内存访问频繁使用的数据,从而加快处理速度。++ ++​​​​​​​++
  • 在专用服务器上,高达80%的物理内存通常分配给缓冲池。
  • ++因为Buffer Pool是基于内存的,所以空间不可能无限大,它的默认大小是128M,当然这个大小也不是完全固定的,我们可以通过修改MySQL配置文件中的innodb_buffer_pool_size参数来调整Buffer Pool的大小。++
sql 复制代码
-- 获取InnoDB缓冲池BufferPool的大小,默认大小是128M
select @@innodb_buffer_pool_size; -- 134217728字节(/1024/1024=128M)

-- 设置BufferPool的大小
set global innodb_buffer_pool_size = xxx;
那么内存跟磁盘到底怎么交互呢?

内存与磁盘数据交互机制 - page页加载机制

  • 我们知道磁盘里面存的数据首先是表空间,表空间里面的单元是page页 ,页里面的数据是行。
  • InnoDB在内存跟磁盘交互的方式中选择了page页 ,所以, page页是内存跟磁盘交互的最小单位
为什么要用page页,有几个点考虑:
  1. 如果用行交互,那么假如我查询200条数据,那么200条数据都不在我们内存的话,需要跟磁盘交互200次性能低下!
  2. 也不会用extent - 区来交互,因为一个extent - 区包含64个page - 页。可能我只需要查一条数据,但是会加载64个页到内存,导致内存浪费。

​​​​​​​​​​​​​​​​​​​​​所以,基于内存的利用率与性能考虑,InnoDB选择了page - 页!

有了Buffer Pool之后取 / 查询数据的流程
  • 举例:select where id = 5 的数据,原来是每次查询都需要去磁盘进行IO交互
  • 此时有了BufferPool之后,InnoDB会先去BufferPool中查看是否存在该数据,如果存在,那么直接返回,数据就可以直接从内存中获取,避免了频繁的磁盘读取,从而提高查询性能;
  • 如果不存在,需要跟磁盘进行IO交互,去磁盘中进行读取,去查,并且将查询返回的结果所在的这个Page页给到 / 保存到BufferPool中,并返回给客户端,后续如果再次读取就可以从Buffer Pool中就近读取了,所以BufferPool相当于是一个缓存。

​​​​​​​

注意:

  • ​​​​​​​ BufferPool跟磁盘交互的最小单位它不是一条数据,它是整个Page页,这也就意味着返回的这个Page页里面可能会有很多很多的数据,假如后面再去select查id = 4的数据,可能在内存中已经存在了,那么直接ffafan返回即可。

Page页是内存跟磁盘交互的最小单位。

预读机制

  • 这就是我们每次读取数据的时候,将对应的page页加载到内存;
​​​​​​​那么有没有一种我就算没读,就能够提前把一些数据加载到内存呢?
​​​​​​​​​​​​​​ 线性预读
  • 线性预读是一种技术,它根据缓冲池中顺序访问的页来执行预加载.。
  • 通过调整配置参数 innodb_read_ahead_threshold - 触发异步读请求所需的连续 / 顺序页(面)访问次数 来控制InnoDB何时执行预读操作。
  • 如果一个extent区间连续读取的 Page - 页 的数目大于等于innodb_read_ahead_threshold,InnoDB就会对接下来的整个区间发起一个异步预读操作, 会预加载接下来整个区里面所有的页。
  • innodb_read_ahead_threshold可以设置为0 ~ 64之间的任意值,因为一个extent区为64个page页,默认值是56。
  • 配置参数 innodb_read_ahead_threshold 控制着InnoDB检测连续页 / 顺序页访问模式的敏感度,值越高,访问模式检越严格。
  • 例如:你把这个值设置为48,那么InnoDB只会在当前区段的48页被顺序访问时才会触发线性预读请求。如果该值为8,即使连续访问区间中只有8页,InnoDB也会触发异步预读
  • 可以在MySQL配置文件中设置此参数的值,或者使用SET GLOBAL语句动态更改它,该语句需要足够的权限来设置全局系统变量。
sql 复制代码
-- 获取触发异步读请求所需的连续页访问次数
select @@innodb_read_ahead_threshold; -- 56
随机预读
  • 随机预读是一种技术,它根据缓冲池中已经存在的页来预测何时可能需要这些页,而不管这些页的读取顺序如何。
  • ++如果在缓冲池中发现来自同一区段的13个连续页,InnoDB异步发出一个请求来预取 / 预加载 / 预读 区段的剩余页。++
  • **要启用此功能,请将配置变量innodb_random_read_ahead设置为ON。**​​​​​​​
sql 复制代码
-- 查看是否启用了随机预读技术,1或ON代表开启,0或OFF表示关闭
SELECT @@innodb_random_read_ahead; -- 0,说明默认是关闭的,可通过SET GLOBAL语句来开启
sql 复制代码
-- 获取当前MySQL数据库实例中InnoDB存储引擎的详细状态信息和SQL查询语句
show engine innodb status;
++既然有缓存了,那么就一定会有一个问题:内存跟磁盘的一致性问题!你改数据肯定会有个先后顺序,你是改磁盘呢还是改内存呢++

数据同步机制

  • 我们刚才讲了,我们的数据都会以page页的方式同步到我们的内存在我们的系统中,就有2份数据,一份在内存,一份在磁盘,那么这2个地方是怎么去做数据同步的。
  • 那么我操作数据,是不是也可以不用跟磁盘同步呢,直接去操作内存,然后通过异步刷新的方式同步到磁盘是不是更快。
  • InnoDB就是这么干的。
  • 比如修改操作:每次更改数据,会先去判断内中存是否存在,如果数据所在的Page页存在内存,我直接更改内存中Page页的数据,然后通过异步刷新的方式同步到磁盘。
​​​​​​​脏页概念引出:
  • 改了内存的数据,如果还没有同步到磁盘,那么这个数据所在的Page页叫做脏页。
  • 脏页说白了就是我的数据跟磁盘不一致的页。
  • 官网定义:脏页是指已经修改,但尚未写入磁盘上的数据文件的页。
​​​​​​​干净页 & 空页
  • 在内存里面没有被改动的页叫做干净页。
  • 如果分配的Page页都还没有数据,则叫做空页。

我们看下我们InnoDB的数据结构 / 架构:

我们发现我们的Page页会有不同的颜色,这个就是代表不同的page,并且每个不同的类型会有不同的双向链表链接。
  • 为了提高大容量读操作的效率,缓冲池被划分为可以容纳多行数据的页。
  • 为提高缓存管理的效率,缓冲池实现为页的链表。
  • 很少使用的数据使用最近最少使用(least recently used, LRU)算法的变体从缓存中老化。**
  • 了解如何利用缓冲池将频繁访问的数据保存在内存中是MySQL调优的一个重要方面。**

**缓冲池 LRU-**最近最少使用 算法

  • 缓冲池使用LRU算法的一种变体作为一个链表来管理。
  • 当需要空间向缓冲池添加新页时,会将最近最少使用的页清除,并将新页添加到列表的中间。
  • 这种中点插入策略将列表视为两个子列表:
  1. 在头部,是最近访问过的新("年轻")页的子列表
  2. 在尾部,是最近访问次数较少的旧页的子链表

缓冲池列表

  • 该算法将经常使用的页保存在新的子链表中;旧的子列表包含不常用的页,这些页面是被驱逐的候选者。
  • 默认情况下,该算法的操作如下:
  • 缓冲池的3/8用于旧的子列表;列表的中点是新子列表的尾部与旧子列表的头部的边界。
  • 当InnoDB读入一个页到缓冲池时,它首先将它插入到中间位置(旧子列表的头部)。
  • 访问旧子列表中的页面会使其"年轻",将其移动到新子列表的头部。
  • 当数据库运行时,缓冲池中未被访问的页通过向列表的尾部移动来"老化"。
  • 新旧子列表中的页都会随着其它页的更新而老化。
  • 旧子链表中的页也会老化,因为页是在中间位置插入的。
  • 最终,剩余未使用的页到达旧子列表的尾部并被移除。

Flush链表

  • 但是现在要解决一个问题,我刷脏的时候需要去找到我们的脏页,脏页现在我们知道,在LRU链表有,但是LRU也有干净的页,并且页会比较多,我们去找那些脏页会比较慢。
  • 所以,在InnoDB的Buffer Pool中,将脏页信息单独放在一个链表,刷脏的时候,只需要从这个链表找就可以了,那么这个链表就叫做Flush链表。

异步刷脏 - 从缓冲池中刷出脏页(缓冲池的冲洗)

这个异步到底怎么去刷的呢?这些脏页怎么同步到磁盘的?
  • 它是开启异步线程去做的。
是不是每次更改一条数据,我就把这个数据所在的Page页同步到磁盘呢?
  • 假如每次操作都需要先跟磁盘同步,我们发现有以下几个问题:
  1. ++由于内存跟磁盘交互的最小单位是Page页,那么你改动一行数据,整个页都需要跟磁盘进行交互同步。++
  2. ++这个更改的数据你是不知道在哪个磁盘位置的,它是属于一个随机IO。++ ​​​​​​​
  • 所以,如果每次都先保证同步到磁盘,那么操作数据会非常非常非常慢。

因此,采用的是异步刷盘机制!

首先,刷脏是由异步线程去做的。
​​​​​​​提问,什么是刷脏?
  • 刷脏就是把脏页的数据同步到磁盘!
  • 在MySQL 8.0中,缓冲池的冲洗(刷脏)是由 页面清理线程 - page cleaner线程 执行的,页面清理线程的数量由innodb_page_cleaners变量控制,其默认值为4。
  • 但是,页面清理线程的数量不能超过缓冲池实例的数量,如果页面清理线程的数量超过缓冲池实例的数量,innodb_page_cleanes会自动设置为innodb_buffer_pool_instances相同的值。
  • ​​​​​​当脏页的百分比达到由innodb_max_dirty_pages_pct_lwm变量定义的低水位值时,将启动缓冲池刷新(刷脏),默认的低水位时缓冲池页面的10%。
  • 即​​​​​​当脏页的百分比达到默认的低水位值,即缓冲池页面的10%时,InnoDB存储引擎会积极的刷新缓冲池中的脏页!即page cleaner线程扫描寻找脏页并将其刷新到磁盘!这是一个后台操作,由页面清理线程每秒执行一次。
  • innodb_max_dirty_pages_pct_lwm阈值的目的是控制缓冲池中脏页的百分比,并防止脏页的数量达到由innodb_max_dirty_pages_pct变量定义的阈值,该变量指的是内存中可以存在脏页的最大百分比,该变量的默认值为90。
sql 复制代码
-- 查询 页面清理线程 - page cleaner线程 的数量,默认值为4,但是不能超过Buffer-Pool的实例数
select @@innodb_page_cleaners; -- 1
-- 查询缓冲池实例的数量
select @@innodb_buffer_pool_instances; -- 1

-- 以下两个变量控制内存中的脏页量
-- 查询默认的低水位值,默认为10
-- 当脏页的百分比达到缓冲池页面的默认低水位值时,InnoDB存储引擎将开始刷新脏页的行为
select @@innodb_max_dirty_pages_pct_lwm; -- 10
-- 查询内存中可以存在脏页的最大百分比,默认值为90
select @@innodb_max_dirty_pages_pct; -- 90

1. 自适应刷脏/冲洗

  • 在DML活动较多的情况下,如果刷出不够积极,则刷出可能会落后,如果刷出过于积极,则磁盘写操作可能会耗尽I/O容量。
  • InnoDB采用自适应刷出/刷新算法,通过跟踪缓冲池中脏页的数量和重做日志记录的生成速度来动态调整刷新速率,其目的是通过确保刷新活动与当前工作负载保持同步来平滑整体性能。根据这些信息,它决定每秒从缓存池中刷写多少脏页。
  • 动态调整刷新速率有助于避免吞吐量突然下降
  • 自适应:根据脏页产生的频率来决定刷脏的频率。比如去判断脏页占比整个Buffer-Pool的大小,如果超过该大小,就会去刷脏。

2. RedoLog自适应

  • ++就是我发现RedoLog保存不了的时候,你要去刷盘。即RedoLog快满的时候,要去刷脏。++

3. **空闲时间刷新 -**空闲的时候会去刷脏

sql 复制代码
-- 表示在空闲时刷新脏页的百分比,默认是100,表示将全部脏也刷新
-- 刷脏是要跟磁盘进行IO交互的,如果频繁刷脏,肯定是会有问题的
select @@innodb_idle_flush_pct; -- 100
  • 刷脏是要跟磁盘进行IO的,如果很频繁的去刷脏,那肯定会有问题。

4. 服务正常关闭

  • 肯定会保证我的脏页能够保存到磁盘,因为我要去防止它的数据丢失。

Doublewrite Buffer - 双写缓存区 - InnoDB的三大特性之一

  • 双写缓冲区是InnoDB的三大特性之一,还有两个是Buffer Pool,简称BP、自适应Hash索引。
  • doublewrite缓冲区是一个存储区域,DoubleWrite双写就是当page页刷新到磁盘的时候,把这个page页的数据写到不同的地方去,当出现问题时,有备份的来达到持久性跟数据的一致性。
  • 我们知道了,我们的page页会异步刷新到磁盘,但是page页的大小是16k,而操作系统 的是4K,一个页是需要多次同步到磁盘的,如果在页面写入过程中出现有操作系统、磁盘(存储子系统)或者意外mysqlId进程退出,所以Mysql提供了一个doubleWrite机制, InnoDB可以在崩溃恢复期间从doublewrite缓冲区找到页面的一个很好的副本。
  • 尽管数据写两次,doublewrite buffer并不需要两倍的I/O开销或两倍的I/O操作,数据以一个大的顺序块的形式写入doublewrite缓冲区,只需要对操作系统进行一次fsync()调用。
  • 在MySQL 8.0.20之前,doublewrite buffer的存储区域位于InnoDB system表空间中。在MySQL 8.0.20中,doublewrite缓冲区的存储区域位于doublewrite文件中。
doublewrite缓冲区的配置提供了下列变量:
1. innodb_doublewrite
  • innodb_doublewrite变量控制doublewrite buffer是否启用,大多数情况下默认启用,要禁用doublewrite缓冲区,将innodb_doublewrite设置为OFF。

  • 如果用户更关心性能而不是数据完整性,比如在执行基准测试时,可以考虑禁用doublewrite缓冲区。

sql 复制代码
-- 查询DoubleWriter-Buffer是否启用,默认开启,会加强一致性,但是会影响一定的性能
select @@innodb_doublewrite; -- ON
2. innodb_doublewrite_dir
  • innodb_doublewrite_dir变量( 在MySQL 8.0.20中引入)定义了InnoDB创建doublewrite文件的目录
  • 如果不指定目录,则doublewrite文件创建在innodb_data_home_dir目录下,如果未指定,默认为data目录。
​​​​​​​​​​​​​​双写磁盘的位置配置
sql 复制代码
SELECT @@innodb_doublewrite_dir;
SELECT @@innodb_data_home_dir;

如果以上2个都没配置,则默认用的是数据目录:

sql 复制代码
SELECT @@datadir;
3. innodb_doublewrite_files
  • innodb_doublewrite_files变量定义了doublewrite文件的数量。
  • 默认情况下,为每个缓冲池实例创建两个doublewrite文件:一个flush list doublewrite file和一个LRU list doublewrite file。
  • 至少有两个doublewrite文件。
  • doublewrite文件的最大数量是缓冲池实例数量的两倍。(缓冲池实例的数量由innodb_buffer_pool_instances变量控制。)
sql 复制代码
-- 查询doubleWrite文件的数量 - 写几个备份,默认是2,至少两个备份
select @@innodb_doublewrite_files; -- 2

DoubleWrite双写过程

  1. 当脏页被写入到缓存池(也称为内存池)时,它们将被写入到 Doublewrite缓冲区中,而不是直接写入到磁盘上的表空间中。
  2. Doublewrite缓冲区是一个位于内存中的缓冲区,用于存储即将写入磁盘的脏页的数据在写入到Doublewrite缓冲区后,数据将被写入到两个不同的磁盘区域,这样即使其中一个磁盘出现问题,数据也可以从另一个磁盘恢复。在写入到磁盘之前,数据会被缓存到磁盘的Write Cache中,确保数据能够快速写入磁盘。
  3. 在数据被写入磁盘之前,Doublewrite缓冲区中的每个脏页的LSN(日志序列号)都会被更新,以确保数据的一致性 。这是因为LSN是一种用于恢复数据的唯一标识符,它可以确保在数据库出现故障时,数据可以恢复到一个一致的状态。
  4. 一旦数据被成功写入到磁盘上的两个不同区域并且LSN已经被更新,数据就被标记为干净页并从缓存池中移除 。此时,这个脏页的数据已经被持久化到磁盘中并且数据库可以确保数据的一致性和可靠性。

所以,Doublewrite机制会占用一部分内存和磁盘的空间,同时也会导致一定的性能损失,但这是为了保证数据的安全性和可靠性而进行的权衡。

场景:

  • 既然你现在内存里面有些数据跟磁盘不一致,因此我们的数据是异步刷新到磁盘的,假如:++内存里面的脏数据没有同步到磁盘的时候,断电了,此时内存里面的数据会发生丢失,此时我们要去解决, 那么InnoDB怎么去保证数据的一致性与持久性呢?++

解决办法:RedoLog - 重做日志 - 内存跟磁盘的数据一致性保证

  • InnoDB里面的一个很重要的概念叫做RedoLog(重做日志)!
  • RedoLog又称作重做日志,当发生异常情况,内存没有同步到磁盘的数据丢失了,此时会基于RedoLog重做日志去恢复。
  • RedoLog只需要去保证内存跟磁盘不一致的数据就可以了,不需要保存所有的数据,说白了就是去保证脏页的数据不会被丢失。
  • 因为InnoDB的数据操作是只会实时去操作我们的bufferpool的page页的,然后通过其他的一些异步方式将bufferpool中的数据同步到磁盘,所以, 数据丢失是很容易产生的。那么就需要我们的RedoLog,它属于InnoDB存储引擎层面实现
  • ++​​​​​​​RedoLog是当数据提交成功的时候,必须要保证成功的,如果RedoLog没有写成功,你这个事务它提交不了。++

RedoLog格式

  • 当操作数据时,会记录一条RedoLog日志,这个日志只是一条记录,记录的是在什么表空间、什么页对数据做了什么样的更改。
  • 我们看下Redolog记录的大概结构:
  • type:操作类型 - 插入、修改还是删除
  • spaceId:表空间ID(磁盘)
  • page number:所在的页
  • data:修改的前后数据,修改前是什么,修改后是什么,做了哪些更改
  • 当然还有一些其他的结构,但是也只是为了内存考虑,怎么更好的节省空间,所以我们可以进行忽略。
  • 这种记录在某个偏移量发生了什么变更的这种日志格式,我们也叫作++物理日志(在哪个地方,在哪里做了哪些更改,这就叫物理日志)++。

RedoLog File - 重做日志文件存储

  • 既然RedoLog要去做恢复,那RedoLog也得去保存起来,不保存起来怎么做恢复。
  • InnoDB通过RedoLog解决数据丢失的问题,所以++RedoLog肯定是一种基于磁盘的数据结构,肯定会写入磁盘,每次SQL语句提交的时候,都会去写入RedoLog。++
  • ++RedoLog用于在故障恢复时纠正不完整事务写入的数据。++
  • ++首先,要保证数据不丢失,那么我们数据肯定要落到磁盘,所以,我们会​​​​​​​​​​​​​​有RedoLog的磁盘文件。++
  • ++RedoLog通过RedoLog File表示在磁盘上。++
  • 官网: MySQL :: MySQL 8.0 参考手册 :: 15.6.5 重做日志
  • MySQL :: MySQL 8.0 Reference Manual :: 8.5.4 Optimizing InnoDB Redo Logging
innodb_redo_log_capacity - 重做日志容量(RedoLog容量)
  • ++innodb_redo_log_capacity系统变量控制RedoLog File(重做日志文件)占用的磁盘空间大小,你可以在启动时或运行时使用set GLOBAL语句配置RedoLog的文件大小。:++ ++Eg:​​​​​​​SET GLOBAL innodb_redo_log_capacity = 8589934592;++
  • 指定完RedoLog File大小后,它被拆分成不同的文件保存到我们的磁盘。
  • ++重做日志文件(RedoLog File)的大小由innodb_redo_log_capacity设置决定。++
  • InnoDB尝试维护32个相同大小的重做日志文件,每个文件大小为1/32 * innodb_redo_log_capacity(即 size / 32)。
  • ++RedoLog File它会保存到32个不同的文件。++
  • RedoLog File文件路径:/ #innodb_redo

​​​​​​​​​​​​​

sql 复制代码
-- 查询RedoLog File的文件大小
select @@innodb_redo_log_capacity; -- 104857600
  • 我们去磁盘发现,InnoDB维护了32个大小为 104857600 / 32 = 3276800 的RedoLog File文件
  • ++我们发现有的文件是_tmp,代表这些文件是备用重做日志文件,还没有写入RedoLog。++
  • ++​​​​​​​RedoLog File它会保存到32个不同的文件。++

RedoLog写入方式

  • 我们知道 ++RedoLog的目的,是去保证我的数据页在内存,但是还没有同步++
    ++到磁盘的时候,宕机导致的数据丢失,++ ++所以在SQL语句提交之前,肯定会保存改动的Redolog到Redolog File文件,当改动数据还在内存,没有同步到磁盘就宕机的时候,会通过RedoLog File文件里面找到改动点,进行同步到磁盘。++
  • ++但是RedoLogFile的大小是固定的,写入的日志数量是有限制的,并且它的目的只是去保证数据不丢失,数据落盘了,这些日志就没有用了,所以RedoLog采用的是循环覆盖写的方式。++
  • 当RedoLog空间的大小满了的时候,会对以前数据进行覆盖
假如真的RedoLog File - innodb_redo_log_capacity都没有落盘怎么办?假如你覆盖的数据真的还没有落盘怎么办?
  • ++为了保证被覆盖的数据不会丢失 => RedoLog自适应:在Redolog快满的时候,也会去进行刷脏,所以我能保证我覆盖过的数据一定是已经刷过脏的,一定是刷新到磁盘的;​​​​​​​++
  • ++刷脏后 /++ ++当数据同步到磁盘后++ ++++,++++++RedoLog就会进行删除,++ ++对应的Redolog日志就无效了,++++可以被覆盖,所以RedoLog的写是循环写的方式。++
  • 简单一句话: ++当32个文件被写满后,又会从第一个文件开始进行循环写入。++

思考题:为啥不直接实时数据同步到磁盘?

  • 为了去保证数据不丢失,RedoLog每次还是需要跟磁盘同步,还是会有磁盘IO。
既然每一次都需要跟磁盘进行IO,为什么不直接去让page页跟磁盘IO呢,让page页去同步刷新呢?都是同步到磁盘,数据页的数据是同步到磁盘、RedoLog也是同步到磁盘,那么为什么不直接将page页同步到磁盘,而要一个RedoLog呢?
  • 其实是 为了性能考虑,因为数据page页同步到磁盘有几个特点:
  1. 因为日志是顺序IO,日志它是一直往上面加,它不需要去修改原来的数据;但是原来你改动的数据是随机的(你知道它改哪个页吗,不知道;你知道它改哪一个磁盘位置吗,也不知道。), 不是顺序的,随机 IO的性能比较慢,而RedoLog是一直往上加,是顺序 IO ,速度比数据 page 同步要快。
  2. page页大,因为page页默认是16K ,它是B uffer-Pool 跟磁盘交互的最小单位,Buffer-Pool 跟磁盘交互的最小单位是 page页,所以,只要page页里面改动一条数据,如果同步跟磁盘交互,那么整个page页都要进行跟磁盘同步,导致不必要的同步;但是RedoLog只是一条,只会同步某些记录。

Log - Buffer:日志缓冲区

  • RedoLog是为了保证数据一致性跟持久性的同时,性能得到保证,虽然RedoLog是顺序IO,并且占用空间比较小,但是毕竟是跟磁盘进IO还是很慢,所以先不要直接跟磁盘同步,因此作者又在内存中申请了一个LogBuffer区间,这个内存区间用来缓存我们的RedoLog,所以RedoLog不马上写到磁盘,而是先写到LogBuffer,先同步到内存里面,然后再从Log Buffer同步到磁盘。
  • 为了性能的话,也不用实时去跟磁盘同步了;如果为了一致性,还是需要跟磁盘同步。
  • 所以为了不同的场景,需要你自己选择。
  • 所以不管怎样,先把RedoLog写到内存,至于你要不要实时跟磁盘同步,你来决定!

既然已经有一个Log buffer空间了,那么它什么时候同步到磁盘呢?

内存跟磁盘的数据交互 - 刷盘时机

1. LogBuffer空间不足时
  • LogBuffer是有大小限制的,可以通过innodb_log_buffer_size配置选项来进行设置日志缓冲区的大小,当内存不足时,会将LogBuffer数据刷新到磁盘!
sql 复制代码
-- 查询LogBuffer日志缓冲区的大小,默认大小是16M
select @@innodb_log_buffer_size; -- 16777216
2. 事务提交时
  • 为了保证持久性,每次事务提交时,都会根据配置的策略都会把LogBuffer的数据刷新到磁盘
3. 后台异步线程刷新
  • 后台有一个专用的日志写入线程 ,用于将重做日志记录RedoLog从日志缓冲区LogBuffer写入系统缓冲区SystemBuffer,并将系统缓冲区刷新到重做日志文件RedoLog File,大约每秒都会刷新一Log Buffer中的RedoLog日志到磁盘。
  • MySQL 8.0.22开始,你可以使用innodb_log_writer_threads变量来启用或禁用日志写线程。专用日志写入线程可以提高高并发系统的性能,但对于低并发系统,禁用专用日志写入线程可以提供更好的性能。
sql 复制代码
-- 查询是否启用日志写入线程,默认为1代表开启
select @@innodb_log_writer_threads; -- 1
4. 正常关闭服务器
5. checkPoint 检查点检查我哪些数据没有同步到磁盘、哪些没有同步的时候

Log Buffer跟磁盘的同步 & RedoLog同步机制

sql 复制代码
-- 查询RedoLog的同步方案(日志的刷写操作),该变量决定了RedoLog跟磁盘的刷新机制,默认设置为1
select @@innodb_flush_log_at_trx_commit; -- 1
  • 默认设置为1是完全符合ACID要求的,每次事务提交时,日志都会被写入并刷新到磁盘(刷新到操作系统并且刷新到磁盘),数据安全性是最高的,性能也是最慢的,能够保证持久性。
  • 设置为0时,日志每秒从内存写入到操作系统,并且刷新 - fsync() 一次磁盘。没有刷新日志的事务可能会在MySQL服务崩溃时丢失。
  • ***如果设置为2,则在每次事务提交后写入日志,并每秒刷新 - fsync() 一次磁盘,最终刷新交给操作系统操作,只要操作系统不挂,也能保持持久性,但是如果操作系统挂了,***没有刷新日志的事务可能会在崩溃时丢失。性能是最高的,数据一致性是最低的。
  • 对于设置0和2,不能100%保证每秒刷新一次。

​​​​​​​

**禁用RedoLog /**禁用重做日志

  • RedoLog虽然尽可能的去保证了我们数据的一致性,但是,如果为了性能考虑,而不需要保证一致性也可以对RedoLog进行关闭。
  • 从MySQL 8.0.21开始,你可以使用ALTER INSTANCE disable INNODB REDO_LOG语句来禁用重做日志。
  • 此功能旨在将数据加载到新的MySQL实例中。
  • 禁用重做日志通过避免重做日志写入和doublewrite缓冲来加速数据加载。
警告:
  • 此功能仅用于将数据加载到新的MySQL实例中。不要在生产系统上禁用重做日志。

  • 禁用重做日志记录时意外的服务器中断可能会导致数据丢失和实例损坏。

  • 当重做日志被禁用时,在一个意外的服务器停止后试图重新启动服务器,会被拒绝,错误信息如下:

  • [ERROR] [MY-013598] [InnoDB] Server was killed when Innodb Redo logging was disabled. Data files could be corrupt. You can try to restart the database with innodb_force_recovery=6

  • [错误][MY-013598] [InnoDB]++服务器在InnoDB重做时被杀死++

    日志被禁用。数据文件可能损坏。你可以试试

    使用innodb_force_recovery=6重启数据库

sql 复制代码
-- 查看RedoLog是否开启,默认为ON代表开启
show global status like 'Innodb_redo_log_enabled'; -- ON

-- 禁用RedoLog,注意:在生成环境中禁止禁用RedoLog重做日志
ALTER INSTANCE disable INNODB REDO_LOG

补充:

  • 有些变量描述涉及"启用"或"禁用"变量。
  • 可以使用SET语句将这些变量设置为ON或1来启用它们,也可以将它们设置为OFF或0来禁用它们。
  • 布尔变量在启动时可以设置为ON、TRUE、OFF和FALSE(不区分大小写),也可以设置为1和0。

小结:

  • MySQL整体架构分为MySQL的Server层以及存储引擎层。
  • Server层主要做管理客户端的连接,然后去拿到SQL语句进行解析,解析以后可能会有预处理,预处理以后知道了我要做什么以后,接下来要决定怎么去做,所以有一个优化器,优化器以后会有一个执行器,执行器是真正的跟存储引擎进行交互。
  • InnoDB是MySQL默认的存储引擎,也只支持事务的,支持ACID的存储引擎,保存ACID中的C跟D,即一致性和持久性。
  • RedoLog在事务提交的时候它能确保RedoLog是成功的。
  • 刷脏的时候由于是异步的,所以它不能保证数据的持久性跟一致性,因为它可能会丢失,在内存中没有同步到磁盘可能会丢失,所以在事务提交之前会记录一个RedoLog。RedoLog它只要保证内存里面的数据没有同步到磁盘的数据能够回滚就可以了。

MySQL真的能保证数据不丢失吗?

  • 保证不了的,如果 innodb_flush_log_at_trx_commit 系统变量设置的是0或2,那就保证不了;如果设置的是默认值1,那就可以保证数据不丢失。
  1. 请说一下InnoDB存储引擎的内存以及磁盘结构
  2. 请你说一下你对RedoLog的理解(存储同步等方式)
相关推荐
乘风御浪云帆之上2 小时前
数据库操作【JDBC & HIbernate & Mybatis】
数据库·mybatis·jdbc·hibernate
dazhong20123 小时前
PLSQL 客户端连接 Oracle 数据库配置
数据库·oracle
苹果醋35 小时前
React源码02 - 基础知识 React API 一览
java·运维·spring boot·mysql·nginx
了一li5 小时前
Qt中的QProcess与Boost.Interprocess:实现多进程编程
服务器·数据库·qt
盛派网络小助手6 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
码农君莫笑6 小时前
信管通低代码信息管理系统应用平台
linux·数据库·windows·低代码·c#·.net·visual studio
别致的影分身6 小时前
使用C语言连接MySQL
数据库·mysql
过过过呀Glik6 小时前
在 Ubuntu 上安装 MySQL 的详细指南
mysql·ubuntu
京东零售技术8 小时前
“慢”增长时代的企业数据体系建设:超越数据中台
数据库
sdaxue.com8 小时前
帝国CMS:如何去掉帝国CMS登录界面的认证码登录
数据库·github·网站·帝国cms·认证码