MySQL架构

367 阅读27分钟

MySQL与InnoDB架构思维导图

dev.mysql.com/doc/refman/… image.png

发展历史

时间里程碑
1996 年MySQL 1.0 发布。它的历史可以追溯到 1979 年,作者 Monty 用 BASIC 设计的一个报表工具。
1996 年 10 月3.11.1 发布。MySQL 没有 2.x 版本。
2000 年ISAM 升级成 MyISAM 引擎。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.x。

1. 总体架构

总体上,我们可以把 MySQL 分成三层,跟客户端对接的连接层,真正执行操作的服务层,和跟硬件打交道的存储引擎层。存储引擎往下就是内存或磁盘。 image.png

2. 连接层

image.png 首先,MySQL 必须要运行一个服务,监听默认的 3306 端口。 在我们开发系统跟第三方对接的时候,必须要弄清楚的有两件事。 第一个就是通信协议,比如我们是用 HTTP 还是 WebService 还是 TCP? 第二个是消息格式,比如我们用 XML 格式,还是 JSON 格式,还是定长格式?报文头长度多少,包含什么内容,每个字段的详细含义。 MySQL 是支持多种通信协议的,可以使用同步/异步的方式,支持长连接/短连接。

2.1.1 通信类型

2.1.1.1 同步

同步通信依赖于被调用方,受限于被调用方的性能。也就是说,应用操作数据库线程会阻塞,等待数据库的返回。异步只能做到一对一,很难做到一对多通信。

2.1.1.2 异步

异步可以避免应用阻塞等待,但是不能节省SQL执行的时间。 如果异步存在并发,每一个SQL的执行都要单独建立一个连接,避免数据混乱。但是这样会给服务端带来巨大的压力(一个连接就会创建一个线程,线程间切换会占用大量CPU资源)。另外异步通信还带来了编码的复杂度,所以一般不建议使用,如果要异步,必须使用连接池,排队从连接池获取连接而不是创建新连接。

一般连接数据库采用同步连接

2.1.2 连接方式

2.1.2.1 长连接

长连接,保持打开减少服务端创建和释放连接的消耗,后面程序访问的时候还可以使用这个连接。一般在连接池中使用长连接。

2.1.2.2 短连接

短连接,操作完毕以后,马上关闭连接。

show global variables like 'wait_timeout';-- 非交互式超时时间,如JDBC程序
show global variables like 'interactive_timeout';-- 交互式超时时间,如数据库工具

2.1.3 连接数

show status命令,模糊匹配Thread

show global status like 'Thread%';

image.png

字段含义
Threads_cached缓存中的线程连接数
Threads_connected当前打开的连接数
Threads_created为处理连接创建的线程
Threads_running非睡眠状态的连接数,通常指并发连接数

每产生一个连接或者一个会话,在服务端就会创建一个线程来处理。反过来,如果要杀死会话,就是 Kill 线程。

2.1.3.1 最大连接数

在 5.7 版本中默认是 151 个,最大可以设置成 16384(2^14)。

show variables like 'max_connections';

image.png

2.1.4 连接状态

dev.mysql.com/doc/refman/…

2.1.4.1 查看SQL执行状态

使用 SHOW PROCESSLIST;(root 用户)查看 SQL 的执行状态。

image.png

2.1.4.2 常见状态

dev.mysql.com/doc/refman/…

状态含义
Sleep线程正在等待客户端,以向它发送一个新语句
Query线程正在执行查询或往客户端发送数据
Locked该查询被其它查询锁定
Copying to tmptable on disk临时结果集合大于 tmp_table_size。线程把临时表从存储器内部格式改变为磁盘模式,以节约存储器
Sending data线程正在为 SELECT 语句处理行,同时正在向客户端发送数据
Sorting for group线程正在进行分类,以满足 GROUP BY 要求
Sorting for order线程正在进行分类,以满足 ORDER BY 要求

2.1.5 参数级别说明

MySQL中参数(变量)分为session和global级别 show 的参数说明:

  1. 级别:会话 session 级别(默认);全局 global 级别
  2. 动态修改:set,重启后失效;永久生效,修改配置文件/etc/my.cnf

2.1.6 通信协议

MySQL支持哪些通信协议呢?

2.1.6.1 Unix Socket

第一种是Unix Socket 在Linux服务器上没有指定-h参数,它就用socket方式登录(省略了-S /var/lib/mysql/mysql.sock ).

它不通过网络协议,也可以连接到MySQL的服务器,它需要用到服务器上的一个物理文件(var/lib/mysql/mysql.sock)。

image.png

2.1.6.2 TCP/IP 协议

如果指定-h 参数,就会用第二种方式,TCP/IP 协议。

image.png

我们编程语言的连接模块都是用TCP协议连接到MySQL服务器的,比如:mysql-connector-java-x.x.xx.jar。

image.png

2.1.6.3 其他

另外还有命名管道(Named Pipes)和内存共享(Share Memory)的方式,这两种通信方式只能在 Windows 上面使用,一般用得比较少。

2.1.7 通信方式

image.png

2.1.7.1 单工

在两台计算机通信的时候,数据的传输时单向的。生活中的类比:遥控器。

2.1.7.2 半双工:

在两台计算机之间,数据传输时双向的,你可以给我发送,我也可以给你发送,但是在这个通讯连接里面,同一时间只能有一台服务器在发送数据,也就是你要给我发的话,也必须等我发给你完了之后才能给我发。生活中的类比:对讲机。

2.1.7.3 全双工

数据的传输时双向的,并且可以同时传输。生活中的类比:打电话。

2.1.7.4 MySQL使用半双工通信方式

要么是客户端向服务端发送数据,要么是服务端向客户端发送数据,这两个动作不能同时发生。所以客户端发送SQL语句给服务端的时候,(在一次连接里面)数据不能分成小块发送,不过SQL语句有多大,都是一次性发送。

比如用mybatis动态SQL生成了一个批量插入的语句,插入10万条数据,values后面跟了一长串内容,或者where条件in里面的值太多,会出现问题。

这个时候我们必须要调整MySQL服务器配置max_allowed_packet参数的值(默认是4M),把它调大,否则就会报错

image.png

另一方面,对于服务端来说,也是一次性发送索引的数据,不能因为你已经取到了想要的数据就中断操作,这个时候会对网络和内存产生大量消耗。

索引,我们一定要在程序里面避免不带limit的这个操作,比如一次把所有满足条件的数据全部查出来,一定要先count一下。如果数据量大,可以分批查询。

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

3. 服务层

image.png

3.2.1 缓存

query_cache_type=off默认关闭,官方不推荐使用,sql语句大小写有变化,或表设计有变化,都会造成缓存失效

3.2.2 解析器

语法分析会对 SQL 做一些语法检查,比如单引号有没有闭合,然后根据 MySQL 定义的语法规则,根据 SQL 语句生成一个数据结构。这个数据结构我们把它叫做解析树(select_lex)。 image.png

3.2.3 预处理器

解析器可以分析语法,但是它怎么知道数据库里面有什么表,表里面有什么字段呢?实际上还是在解析的时候报错,解析 SQL 的环节里面有个预处理器。它会检查生成的解析树,解决解析器无法解析的语义。比如,它会检查表和列名是否存在,检查名字和别名,保证没有歧义。预处理之后得到一个新的解析树。

3.2.4 优化器(重要)

查询优化器的目的就是根据解析树生成不同的执行计划(Execution Plan),然后选择一种最优的执行计划,MySQL 里面使用的是基于开销(cost)的优化器,那种执行计划开销最小,就用哪种。

优化器的优化方式:
解析树
子查询优化
等价谓词优化
条件化简
外连接消除
嵌套连接消除
连接消除
语义优化
非SPI优化

3.2.5 执行计划(通过优化器得到)

3.2.5.1 生成执行计划

optimizer_trace查看SQL是如何优化的,开启会消耗一部分性能,生产环境不要开启。注意开启这开关是会消耗性能的,因为它要把优化分析的结果写到表里面,所以不要轻易开启,或者查看完之后关闭它(改成 off)。注意:参数分为 session 和 global 级别。

--要启用优化器的追踪(默认是关闭的)
show variables like 'optimizer_trace';
set optimizer_trace='enabled=on';
select t.tcid from teacher t,teacher_contact tc where t.tcid = tc.tcid;

这个时候优化器分析的过程已经记录到系统表里面了,我们可以查询: 接着我们执行一个 SQL 语句,优化器会生成执行计划:

select * from information_schema.optimizer_trace\G

它是一个 JSON 类型的数据,主要分成三部分,准备阶段、优化阶段和执行阶段。

image.png

expanded_query 是优化后的 SQL 语句。considered_execution_plans 里面列出了所有的执行计划。分析完记得关掉它:

set optimizer_trace="enabled=off";
SHOW VARIABLES LIKE 'optimizer_trace';

3.2.5.2 查看执行计划

explain select <字段名> from <表名>
explain format=json select <字段名> from <表名>

MySQL 提供了一个执行计划的工具。我们在 SQL 语句前面加上 EXPLAIN,就可以看到执行计划的信息。 explain select * from user_innodb; image.png

select_type:查询类型
table:查询的表名
partitions:
type:怎么访问表
possible_keys:可能使用的索引
key:实际使用的索引
key_len:索引的长度
ref:用什么字段过滤数据
rows:预估扫描行数
filtered:从存储引擎拿到数据后到服务端过滤百分比
extra:

4. InnoDB总体架构

为什么有这么多存储引擎?

  1. 很快访问速度,不需要持久化,不担心可不可靠会不会丢失。需要速度快
  2. 历史数据,不需要提供给用户,但又需要保留,不需要索引支持。需要压缩特性
  3. 读写并发,一致性

4.1 MySQL常见存储引擎

以下是 MySQL 里面常见的一些存储引擎,我们看到了,不同的存储引擎提供的特性都不一样,它们有不同的存储机制、索引方式、锁定水平等功能。我们在不同的业务场景中对数据操作的要求不同,就可以选择不同的存储引擎来满足我们的需求,这个就是 MySQL 支持这么多存储引擎的原因。 不同的存储引擎,使用不同的文件存储方式, 查看存储引擎状态:show engine innodb status;

  1. MyISAM
  2. InnoDB
  3. Memory
  4. CSV
  5. Archive

4.1.1 MyISAM(3个文件)

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

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

结论:只读之类的数据分析的项目。

4.1.2 InnoDB(2个文件)

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

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

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

4.1.3 Memory(1个文件)

将所有数据存储在 RAM 中,以便在需要快速查找非关键数据的环境中快速访问。这个引擎以前被称为堆引擎。其使用案例正在减少;InnoDB 及其缓冲池内存区域提供了一种通用、持久的方法来将大部分或所有数据保存在内存中,而 ndbcluster 为大型分布式数据集提供了快速的键值查找。

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

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

4.1.4 CSV(3个文件)

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

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

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

4.1.5 Archive(2个文件)

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

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

4.2 InnoDB 内存结构(In-Memory Structures)

https://dev.mysql.com/doc/refman/5.7/en/innodb-architecture.html image.png

Buffer Pool主要分为3个部分:Buffer Pool、Change Buffer、Adaptive Hash Index、(redo)log buffer。

4.2.1 Buffer Pool

InnoDB的数据都是放在磁盘上的,Innodb操作数据有一个最小的逻辑单位,叫做页(索引页和数据页)。我们对于数据的操作,不是每次都直接操作磁盘,因为磁盘速度太慢了,InnoDB使用了一种缓冲池技术,也就是把磁盘读到的页放到一块内存区域里面。这个内存区域就叫Buffer Pool。

image.png

4.2.1.1 buffer pool起作用了吗?

windows机器,默认buffer pool大小8388608bytes(8M),old区3M,young区5M。
在全部扫描的过程中,所以的page都会加入old区的头部。
从page中找到相应行的时候,所以的page都会移动到new区的头部。因为容量有限,前面数据页全部被淘汰。
可以把buffer pool调整足够大在测试一下。

4.2.1.2 查看Buffer Pool信息

Buffer Pool缓存的是页面信息,包括数据页、索引页。查看服务器状态,里面有跟多跟Buffer Pool相关的信息:

show status like '%innodb_buffer_pool%';

image.png 这些状态可以在官网查到详细的含义:dev.mysql.com/doc/refman/…

4.2.1.2 脏页

下一次读取相同的页,先判断是不是在缓冲词里面,如果是,就直接读取,不用再次访问磁盘。修改数据的时候先修改缓冲池里面的页。内存的数据页和磁盘数据不一致的时候,我们把它叫做脏页

4.2.1.3 刷脏

InnoDB里面有专门的后台线程把Buffer Pool的数据写入到磁盘,每隔一段时间就一次性把多个修改写入磁盘,这个动作叫做刷脏

4.2.1.4 buffer_pool 容量

show variables like '%innodb_buffer_pool%'; Linux默认大小128M,很小,在专用的数据库服务器上面,buffer pool的大小可以占到机器内存的80%。128M太小了,生产环境需要调大buffer pool,buffer_pool缓冲区越大对内存的读写性能就越大。

image.png

内存的缓冲池写满了怎么办?(Redis设置的内存满了怎么办?)InnoDB用LRU算法来管理缓冲池(链表实现,不是传统的LRU,分成了young和old),经过淘汰的数据就是热点数据。

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

4.2.2 Change Buffer写缓冲

提升非唯一索引的,增删改的效率。如果这个数据页不是唯一索引,不存在数据重复的情况,也就不需要从磁盘加载索引页判断数据是不是重复(唯一性检查)。这种情况下可以先把修改记录在内存的缓冲池中,从而提升更新语句(Insert、Delete、Update)的执行速度。这一块区域就是 Change Buffer。5.5 之前叫 Insert Buffer 插入缓冲,现在也能支持 delete 和 update。

4.2.2.1 merge

最后把Change Buffer记录到数据页的操作叫做merge。什么时候发生merge? 有几种情况:在访问这个数据页的时候,或者通过后台线程、或者数据库shutdown、redo log写满时触发。如果数据库大部分索引都是非唯一索引,并且业务是写多读少,不会在写数据后立刻读取,就可以使用Change Buffer(写缓冲)。写多读少的业务,调大这个值: SHOW VARIABLES LIKE 'innodb_change_buffer_max_size';

4.2.2.2大小如何调整

使用场景:写多读少,大部分索引都是非唯一索引,可以调大这个百分比的值,下图是25%,一般不调整。

show variables like 'innodb_change_buffer_max_size';

image.png

4.2.3 Adaptive Hash Index

哈希索引放到内存

4.2.4 buffer pool LRU算法

传统LRU算法,把List的下班存在Map的Value中新增、访问时移动到head从tail淘汰.

image.png

4.2.5 MySQL改进版LRU算法(冷热分离)

image.png

4.2.4 (redo)Log Buffer

思考一个问题:如果 Buffer Pool 里面的脏页还没有刷入磁盘时,数据库宕机或者重启,这些数据丢失。如果写操作写到一半,甚至可能会破坏数据文件导致数据库不可用。为了避免这个问题,InnoDB 把所有对页面的修改操作专门写入一个日志文件,并且在数据库启动时从这个文件进行恢复操作(实现 crash-safe)——用它来实现事务的持久性

image.png 这个文件就是磁盘的 redo log(叫做重做日志),对应于/var/lib/mysql/目录下的ib_logfile0 和 ib_logfile1,每个 48M。这 种 日 志 和 磁 盘 配 合 的 整 个 过 程 , 其 实 就 是 MySQL 里 的 WAL 技 术(Write-Ahead Logging),它的关键点就是先写日志,再写磁盘

show variables like 'innodb_log%';

image.png

刷盘是随机 I/O,而记录日志是顺序 I/O,顺序 I/O 效率更高。因此先把修改写入日志,可以延迟刷盘时机,进而提升系统吞吐

当然 redo log 也不是每一次都直接写入磁盘,在 Buffer Pool 里面有一块内存区域(Log Buffer)专门用来保存即将要写入日志文件的数据,默认 16M,它一样可以节省磁盘IO。 image.png

查看Log Buffer容量:

show variables like 'innodb_log_buffer_size';

image.png

redo log的内容主要用于崩溃恢复。磁盘的数据文件,数据来自buffer pool。redo log写入磁盘,不是写入数据文件。那么,Log Buffer什么时候写入log file? 答:在我们写入数据到磁盘的时候,操作系统本身是有缓存的。flush就是把操作系统缓冲区写入到磁盘。

Log Buffer写入磁盘的时机,由一个参数控制,默认是1.

show variables like 'innodb_flush_log_at_trx_commit';

image.png

image.png

image.png

4.2.5 Redo Log(InnoDB独有)

image.png

  1. InnoDB的buffer pool的设计为了延迟刷盘提升读写的效率,同步之前称之为脏页,同步之后称为干净页。
  2. redu log是InnoDB存储引擎独有的特性。
  3. redo log作用是为了保证内存数据的安全性。写redo log时,数据都是连续往后追加,是顺序IO,他比直接写磁盘文件要快得多。这就是为什么,我们通过内存提高效率之后,又加上日志文件,效率依然高的原因。

4.2.5.1 查看路径

Redo Log所在路径show variables like 'innodb_log%';,如果是.\是按照目录cd /var/lib/mysql/

image.png

4.2.5.2 redo log存储文件名:ib_logfile0和ib_logfile1两个文件

image.png

4.2.5.3 文件解析

  1. 默认两个文件
  2. 默认文件大小固定50331648(48M)

4.2.5.4 Redo Log特点

  1. redo log是InnoDB存储引擎实现,并不是所有存储引擎都有。支持崩溃恢复时InnoDB的一个特性。
  2. redo log不是记录数据页更新之后的状态,而是记录的“在某个数据页上做了什么修改”。数据物理日志。
  3. redo log大小是固定的,前面的内容会被覆盖,一旦写满,就好触发buffer pool同步到磁盘,以便腾出空间,记录后面的修改。

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

4.2.5.5 顺序IO和随机IO

默认磁盘加载数据大小默认16kb,为了提升数据文件的效率,读写先把数据存储在buffer pool里面(脏页)。刷脏,后台线程处理,会有延时。宕机导致数据丢失,redo log实现异常恢复。 寻找扇区(寻址):

  • 顺序IO:数据存储在同一扇区依次相连,只需要寻址一次,称之为顺序IO。
  • 随机IO:数据分散存储,每次都需要寻址,称之为随机IO。

image.png

Redo Log分成内存和磁盘两部分。redo log有什么特点?

  1. redo log是InnoDB存储引擎实现的,并不是所有存储引擎都有。
  2. 不是记录数据页更新之后的状态,而是记录这个也做了什么改动,属于物理日志
  3. redo log的大小固定,前面的内容会被覆盖。

image.png

check point 是当前要覆盖的位置。如果 write pos 跟 check point 重叠,说明 redo log 已经写满,这时候需要同步 redo log 到磁盘中。

4.1.1.6 总结

这是 MySQL 的内存结构分为:Buffer poolchange bufferAdaptive Hash Indexlog buffer

4.2.6 Undo Log

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

可以理解为undo log记录的是反向操作,比如insert会记录delete,update会记录update原来的值,跟redo log记录在哪个物理页做了什么操作不同,所以叫做逻辑的日志

show global variables like '%undo%';

image.png

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

redo log和undo log与实务密切相关,通常为实务日志

4.3 InnoDB 硬盘结构(On-Disk Structures)

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

4.3.1 系统表空间 system tablespace

在默认情况下 InnoDB 存储引擎有一个共享表空间(对应文件/var/lib/mysql/ibdata1),也叫系统表空间。InnoDB 系统表空间包含 InnoDB 数据字典双写缓冲区Change BufferUndo Logs),如果没有指定 file-per-table,也包含用户创建的表和索引数据。

  1. undo 在后面介绍,因为有独立的表空间。
  2. 数据字典:由内部系统表组成,存储表和索引的元数据(定义信息)。
  3. 双写缓冲InnoDB 的一大特性):InnoDB 的页和操作系统的页大小不一致,InnoDB 页大小一般为 16K,操作系统页大小为 4K,InnoDB 的页写入到磁盘时,一个页需要分 4 次写。

image.png

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

4.3.1.1 双写缓冲(double write)

show variables like 'innodb_doublewrite';

image.png

我们不是有 redo log 吗?但是有个问题,如果这个页本身已经损坏了,用它来做崩溃恢复是没有意义的。所以在对于应用 redo log 之前,需要一个页的副本。如果出现了写入失效,就用页的副本来还原这个页,然后再应用 redo log。这个页的副本就是 double write,InnoDB 的双写技术。通过它实现数据页的可靠性。跟 redo log 一样,double write 由两部分组成,一部分是内存的 double write,一个部分是磁盘上的 double write。因为 double write 是顺序写入的,不会带来很大的开销。

在默认情况下,所有的表共享一个系统表空间,这个文件会越来越大,而且它的空间不会收缩。

4.3.2 独占表空间 file-per-table tablespaces

我们可以让每张表独占一个表空间。这个开关通过 innodb_file_per_table 设置,默认开启。

show variables like 'innodb_file_per_table';

image.png

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

4.3.3 通用表空间 general tablespaces

通用表空间也是一种共享的表空间,跟 ibdata1 类似。可以创建一个通用的表空间,用来存储不同数据库的表,数据路径和文件可以自定义。语法:

create tablespace ts2673 add datafile '/var/lib/mysql/ts2673.ibd' file_block_size=16K engine=innodb;

在创建表的时候可以指定表空间,用 ALTER 修改表空间可以转移表空间。

create table t2673(id integer) tablespace ts2673;

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

drop table t2673;
drop tablespace ts2673;

4.3.4 临时表空间 temporary tablespaces

存储临时表的数据,包括用户创建的临时表,和磁盘的内部临时表。对应数据目录下的 ibtmp1 文件。当数据服务器正常关闭时,该表空间被删除,下次重新产生。

4.3.5 undo log tablespace

dev.mysql.com/doc/refman/…

dev.mysql.com/doc/refman/…

undo log撤销日志或回滚日志)记录了事务发生之前的数据状态(不包括 select)。如果修改数据时出现异常,可以用 undo log 来实现回滚操作(保持原子性)。在执行 undo 的时候,仅仅是将数据从逻辑上恢复至事务之前的状态,而不是从物理页面上操作实现的,属于逻辑格式的日志

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

undo Log 的数据默认在系统表空间 ibdata1 文件中,因为共享表空间不会自动收缩,也可以单独创建一个 undo 表空间。

show global variables like '%undo%';

image.png

有了这些日志之后,我们来总结一下一个更新操作的流程,这是一个简化的过程。name 原值是 qingshan。

update user set name = 'penyuyan' where id=1;
  1. 事务开始,从内存或磁盘取到这条数据,返回给 Server 的执行器;
  2. 执行器修改这一行数据的值为 penyuyan;
  3. 记录 name=qingshan 到 undo log;
  4. 记录 name=penyuyan 到 redo log;
  5. 调用存储引擎接口,在内存(Buffer Pool)中修改 name=penyuyan;
  6. 事务提交。

内存和磁盘之间,工作着很多后台线程。

4.1.3 后台线程

后台线程的主要作用是负责刷新内存池中的数据和把修改的数据页刷新到磁盘。后台线程分为: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 用来刷新脏页。

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

5. Binlog(服务层)

dev.mysql.com/doc/refman/…

binlog 以事件的形式记录了所有的 DDL 和 DML 语句(因为它记录的是操作而不是数据值,属于逻辑日志),可以用来做主从复制和数据恢复。跟 redo log 不一样,它的文件内容是可以追加的,没有固定大小限制。在开启了 binlog 功能的情况下,我们可以把 binlog 导出成 SQL 语句,把所有的操作重放一遍,来实现数据的恢复。binlog 的另一个功能就是用来实现主从复制,它的原理就是从服务器读取主服务器的 binlog,然后执行一遍。

5.1 bin log实现数据恢复

前提:定时的全量备份的数据(比如1:00)。如果没有备份,恢复数据是特别困难的。假设上午9:00删库:

  1. 恢复的备份的数据
  2. 解析1:00-9:00 的SQL语句全部执行一遍(剔除删库的语句)

5.2 更新语句如何执行?

例如一条语句:update teacher set name='盆鱼宴' where id=1;

  1. 先查询到这条数据,如果有缓存,也会用到缓存。

  2. 把 name 改成盆鱼宴,然后调用引擎的 API 接口,写入这一行数据到内存,同时记录 redo log。这时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,可以随时提交。

  3. 执行器收到通知后记录 binlog,然后调用存储引擎接口,设置 redo log为 commit状态。

  4. 更新完成。

    1)先记录到内存,再写日志文件。
     (2)记录 redo log 分为两个阶段。
     (3)存储引擎和 Server 记录不同的日志。
     (4)先记录 redo,再记录 binlog。
    

image.png

5.2 rodo log + bin log实现崩溃恢复

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

1. bin log无记录,redo log无记录:
   在redo log写之前crash,恢复操作:回滚事务
2. bin log无记录 ,redo log状态prepare:
    在bin log写完之前的crash,恢复操作:回滚事务
3. bin log有记录,redo log状态prepare:
    在bin log写完提交事务之前的crash,恢复操作:提交事务
4. bin log有记录,redo log状态commit:
    正常完成的事务,不需要恢复

5.3 如果binlog没开启

如果bin log没有开启,那redo log也不需要两阶段提交,不需要保证跟bin log内容一致,也不会影响到主从复制,不需要基于bin log的数据恢复。