mysql(一)--mysql架构和执行流程

1. 一条查询 SQL 语句是如何执行的?

 

咱们的程序或者工具要操做数据库,第一步要作什么事情?
跟数据库创建链接。
 
1.1. 通讯协议
首先,MySQL 必需要运行一个服务,监听默认的 3306 端口。
在咱们开发系统跟第三方对接的时候,必需要弄清楚的有两件事。
第一个就是通讯协议,好比咱们是用 HTTP 仍是 WebService 仍是 TCP?
第二个是消息格式,好比咱们用 XML 格式,仍是 JSON 格式,仍是定长格式?报文
头长度多少,包含什么内容,每一个字段的详细含义。
好比咱们以前跟银联对接,银联的银行卡联网规范,约定了一种比较复杂的通信协
议叫作:四进四出单工异步长链接(为了保证稳定性和性能)。

 

 

1.1.1.通讯协议
MySQL 是支持多种通讯协议的,可使用同步/异步的方式,支持长链接/短链接。
这里咱们拆分来看。第一个是通讯类型。
通讯类型:同步或者异步
 
同步通讯的特色:
一、同步通讯依赖于被调用方,受限于被调用方的性能。也就是说,应用操做数据库,
线程会阻塞,等待数据库的返回。
二、通常只能作到一对一,很难作到一对多的通讯
 
异步跟同步相反:
一、异步能够避免应用阻塞等待,可是不能节省 SQL 执行的时间。
二、若是异步存在并发,每个 SQL 的执行都要单独创建一个链接,避免数据混乱。
可是这样会给服务端带来巨大的压力(一个链接就会建立一个线程,线程间切换会占用
大量 CPU 资源)。另外异步通讯还带来了编码的复杂度,因此通常不建议使用。若是要
异步,必须使用链接池,排队从链接池获取链接而不是建立新链接。
通常来讲咱们链接数据库都是同步链接
 
链接方式:长链接或者短链接
MySQL 既支持短链接,也支持长链接。短链接就是操做完毕之后,立刻 close 掉。
长链接能够保持打开,减小服务端建立和释放链接的消耗,后面的程序访问的时候还可
以使用这个链接。通常咱们会在链接池中使用长链接。
保持长链接会消耗内存。长时间不活动的链接,MySQL 服务器会断开
show global variables like 'wait_timeout'; -- 非交互式超时时间,如 JDBC 程序 
show global variables like 'interactive_timeout'; -- 交互式超时时间,如数据库工具
默认都是 28800 秒,8 小时。
 
咱们怎么查看 MySQL 当前有多少个链接?
能够用 show status 命令:
show global status like 'Thread%';
Threads_cached:缓存中的线程链接数。
Threads_connected:当前打开的链接数。
Threads_created:为处理链接建立的线程数。
Threads_running:非睡眠状态的链接数,一般指并发链接数。
每产生一个链接或者一个会话,在服务端就会建立一个线程来处理。反过来,若是要
杀死会话,就是 Kill 线程
 
有了链接数,怎么知道当前链接的状态?
也可使用 SHOW PROCESSLIST; (root 用户)查看 SQL 的执行状态。
https://dev.mysql.com/doc/refman/5.7/en/show-processlist.html

 

 一些常见的状态: https://dev.mysql.com/doc/refman/5.7/en/thread-commands.htmlhtml

 

 

MySQL 服务容许的最大链接数是多少呢?
在 5.7 版本中默认是 151 个,最大能够设置成 16384(2^14)
show variables like 'max_connections';

 

show 的参数说明:
一、级别:会话 session 级别(默认);全局 global 级别
二、动态修改:set,重启后失效;永久生效,修改配置文件/etc/my.cnf
set global max_connections = 1000;
通讯协议
MySQL 支持哪些通讯协议呢?
第一种是 Unix Socket。
好比咱们在 Linux 服务器上,若是没有指定-h 参数,它就用 socket 方式登陆(省略
了-S /var/lib/mysql/mysql.sock)。 
它不用经过网络协议,也能够链接到 MySQL 的服务器,它须要用到服务器上的一个
物理文件(/var/lib/mysql/mysql.sock)。
select @@socket;
若是指定-h 参数,就会用第二种方式,TCP/IP 协议
mysql -h192.168.8.211 -uroot -p123456
咱们的编程语言的链接模块都是用 TCP 协议链接到 MySQL 服务器的,好比
mysql-connector-java-x.x.xx.jar。

 

另外还有命名管道(Named Pipes)和内存共享(Share Memory)的方式,这两种
通讯方式只能在 Windows 上面使用,通常用得比较少。
 
1.1.2.通讯方式
第二个是通讯方式

 

 

单工:
在两台计算机通讯的时候,数据的传输是单向的。生活中的类比:遥控器。
半双工:
在两台计算机之间,数据传输是双向的,你能够给我发送,我也能够给你发送,
可是在这个通信链接里面,同一时间只能有一台服务器在发送数据,也就是你要给我发
的话,也必须等我发给你完了以后才能给我发。生活中的类比:对讲机。
全双工:
数据的传输是双向的,而且能够同时传输。生活中的类比:打电话。
MySQL 使用了半双工的通讯方式?
要么是客户端向服务端发送数据,要么是服务端向客户端发送数据,这两个动做不能
同时发生。因此客户端发送 SQL 语句给服务端的时候,(在一次链接里面)数据是不能
分红小块发送的,无论你的 SQL 语句有多大,都是一次性发送。
好比咱们用 MyBatis 动态 SQL 生成了一个批量插入的语句,插入 10 万条数据,
values
后面跟了一长串的内容,或者 where 条件 in 里面的值太多,会出现问题。
这个时候咱们必需要调整 MySQL 服务器配置 max_allowed_packet 参数的值(默认
是 4M),把它调大,不然就会报错

 

另外一方面,对于服务端来讲,也是一次性发送全部的数据,不能由于你已经取到了想
要的数据就中断操做,这个时候会对网络和内存产生大量消耗。
因此,咱们必定要在程序里面避免不带 limit 的这种操做,好比一次把全部知足条件
的数据所有查出来,必定要先 count 一下。若是数据量的话,能够分批查询。
 
执行一条查询语句,客户端跟服务端创建链接以后呢?下一步要作什么?

 

1.2. 查询缓存
MySQL 内部自带了一个缓存模块。
缓存的做用咱们应该很清楚了,把数据以 KV 的形式放到内存里面,能够加快数据的
读取速度,也能够减小服务器处理的时间。可是 MySQL 的缓存咱们好像比较陌生,历来
没有去配置过,也不知道它何时生效? 
好比 user_innodb 有 500 万行数据,没有索引。咱们在没有索引的字段上执行一样
的查询,你们以为第二次会快吗?
select * from user_innodb where name='lei1';
缓存没有生效,为何?MySQL 的缓存默认是关闭的。
show variables like 'query_cache%';
默认关闭的意思就是不推荐使用,为何 MySQL 不推荐使用它自带的缓存呢?
主要是由于 MySQL 自带的缓存的应用场景有限,第一个是它要求 SQL 语句必须一
模同样,中间多一个空格,字母大小写不一样都被认为是不一样的的 SQL。
第二个是表里面任何一条数据发生变化的时候,这张表全部缓存都会失效,因此对
于有大量数据更新的应用,也不适合。
因此缓存这一块,咱们仍是交给 ORM 框架(好比 MyBatis 默认开启了一级缓存),
或者独立的缓存服务,好比 Redis 来处理更合适。
在 MySQL 8.0 中,查询缓存已经被移除了
 
1.3. 语法解析和预处理(Parser & Preprocessor)
咱们没有使用缓存的话,就会跳过缓存的模块,下一步咱们要作什么呢?
OK,这里我会有一个疑问,为何个人一条 SQL 语句可以被识别呢?假如我随便执行一
个字符串 penyuyan,服务器报了一个 1064 的错
[Err] 1064 - You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version for the right syntax to use near 'penyuyan' at line 1
它是怎么知道我输入的内容是错误的?
这个就是 MySQL 的 Parser 解析器和 Preprocessor 预处理模块。
这一步主要作的事情是对语句基于 SQL 语法进行词法和语法分析和语义的解析
 
1.3.1.词法解析
词法分析就是把一个完整的 SQL 语句打碎成一个个的单词。
好比一个简单的 SQL 语句: 
select name from user where id = 1;
它会打碎成 8 个符号,每一个符号是什么类型,从哪里开始到哪里结束
 
1.3.2.语法解析
第二步就是语法分析,语法分析会对 SQL 作一些语法检查,好比单引号有没有闭合,
而后根据 MySQL 定义的语法规则,根据 SQL 语句生成一个数据结构。这个数据结构我
们把它叫作解析树(select_lex)。

 

 

任何数据库的中间件,好比 Mycat,Sharding-JDBC(用到了 Druid Parser),都
必需要有词法和语法分析功能,在市面上也有不少的开源的词法解析的工具(好比 LEX,Yacc)。
 
1.3.3.预处理器
问题:若是我写了一个词法和语法都正确的 SQL,可是表名或者字段不存在,会在
哪里报错?是在数据库的执行层仍是解析器?好比:
select * from penyuyan;
解析器能够分析语法,可是它怎么知道数据库里面有什么表,表里面有什么字段呢?
实际上仍是在解析的时候报错,解析 SQL 的环节里面有个预处理器。
它会检查生成的解析树,解决解析器没法解析的语义。好比,它会检查表和列名是
否存在,检查名字和别名,保证没有歧义。
预处理以后获得一个新的解析树
 
1.4. 查询优化(Query Optimizer)与查询执行计划
1.4.1.什么是优化器?
获得解析树以后,是否是执行 SQL 语句了呢?
这里咱们有一个问题,一条 SQL 语句是否是只有一种执行方式?或者说数据库最终
执行的 SQL 是否是就是咱们发送的 SQL?
这个答案是否认的。一条 SQL 语句是能够有不少种执行方式的,最终返回相同的结
果,他们是等价的。可是若是有这么多种执行方式,这些执行方式怎么获得的?最终选
择哪种去执行?根据什么判断标准去选择?
这个就是 MySQL 的查询优化器的模块(Optimizer)。
查询优化器的目的就是根据解析树生成不一样的执行计划(Execution Plan),而后选
择一种最优的执行计划,MySQL 里面使用的是基于开销(cost)的优化器,那种执行计划开销最小,就用哪一种。
可使用这个命令查看查询的开销:
show status like 'Last_query_cost';
https://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html#statvar_Last_query_cost
 
1.4.2.优化器能够作什么?
MySQL 的优化器能处理哪些优化类型呢?
举两个简单的例子:
一、当咱们对多张表进行关联查询的时候,以哪一个表的数据做为基准表。
二、有多个索引可使用的时候,选择哪一个索引。
实际上,对于每一种数据库来讲,优化器的模块都是必不可少的,他们经过复杂的算法实现尽量优化查询效率的目标。
若是对于优化器的细节感兴趣,能够看看《数据库查询优化器的艺术-原理解析与SQL性能优化》。 
可是优化器也不是万能的,并非再垃圾的 SQL 语句都能自动优化,也不是每次都
能选择到最优的执行计划,你们在编写 SQL 语句的时候仍是要注意。
若是咱们想知道优化器是怎么工做的,它生成了几种执行计划,每种执行计划的 cost是多少,应该怎么作? 
 
1.4.3.优化器是怎么获得执行计划的?
https://dev.mysql.com/doc/internals/en/optimizer-tracing.html
首先咱们要启用优化器的追踪(默认是关闭的):
SHOW VARIABLES LIKE 'optimizer_trace'; 
set optimizer_trace='enabled=on';
注意开启这开关是会消耗性能的,由于它要把优化分析的结果写到表里面,因此不
要轻易开启,或者查看完以后关闭它(改为 off)。
注意:参数分为 session 和 global 级别。
接着咱们执行一个 SQL 语句,优化器会生成执行计划:
select t.tcid from teacher t,teacher_contact tc where t.tcid = tc.tcid;
这个时候优化器分析的过程已经记录到系统表里面了,咱们能够查询:
select * from information_schema.optimizer_trace\G
它是一个 JSON 类型的数据,主要分红三部分,准备阶段、优化阶段和执行阶段。

 

 

expanded_query 是优化后的 SQL 语句。
considered_execution_plans 里面列出了全部的执行计划。
分析完记得关掉它: 
set optimizer_trace="enabled=off";
SHOW VARIABLES LIKE 'optimizer_trace';
1.4.4.优化器获得的结果
优化完以后,获得一个什么东西呢?
优化器最终会把解析树变成一个查询执行计划,查询执行计划是一个数据结构。
固然,这个执行计划是否是必定是最优的执行计划呢?不必定,由于 MySQL 也有可
能覆盖不到全部的执行计划。
咱们怎么查看 MySQL 的执行计划呢?好比多张表关联查询,先查询哪张表?在执行
查询的时候可能用到哪些索引,实际上用到了什么索引?
MySQL 提供了一个执行计划的工具。咱们在 SQL 语句前面加上 EXPLAIN,就能够
看到执行计划的信息
EXPLAIN select name from user where id=1;
*注意 Explain 的结果也不必定最终执行的方式。
 
1.5. 存储引擎
获得执行计划之后,SQL 语句是否是终于能够执行了?
问题又来了:
一、从逻辑的角度来讲,咱们的数据是放在哪里的,或者说放在一个什么结构里面?
二、执行计划在哪里执行?是谁去执行?
 
1.5.1.存储引擎基本介绍
咱们先回答第一个问题:在关系型数据库里面,数据是放在什么结构里面的?
(放在表 Table 里面的)
咱们能够把这个表理解成 Excel 电子表格的形式。因此咱们的表在存储数据的同时,
还要组织数据的存储结构,这个存储结构就是由咱们的存储引擎决定的,因此咱们也能够把存储引擎叫作表类型。
在 MySQL 里面,支持多种存储引擎,他们是能够替换的,因此叫作插件式的存储引擎。为何要搞这么多存储引擎呢?一种还不够用吗?
这个问题先留着。 
1.5.2.
查看存储引擎
好比咱们数据库里面已经存在的表,咱们怎么查看它们的存储引擎呢? 
show table status from `lei1`;

或者经过 DDL 建表语句来查看。
在 MySQL 里面,咱们建立的每一张表均可以指定它的存储引擎,而不是一个数据库
只能使用一个存储引擎。存储引擎的使用是以表为单位的。并且,建立表以后还能够修改存储引擎。
咱们说一张表使用的存储引擎决定咱们存储数据的结构,那在服务器上它们是怎么
存储的呢?咱们先要找到数据库存放数据的路径: 
show variables like 'datadir';
默认状况下,每一个数据库有一个本身文件夹,以 gupao 数据库为例。
任何一个存储引擎都有一个 frm 文件,这个是表结构定义文件。
不一样的存储引擎存放数据的方式不同,产生的文件也不同,innodb 是 1 个,
memory 没有,myisam 是两个。
这些存储引擎的差异在哪呢?
 
1.5.3.存储引擎比较
常见存储引擎
MyISAM 和 InnoDB 是咱们用得最多的两个存储引擎,在 MySQL 5.5 版本以前,
默认的存储引擎是 MyISAM,它是 MySQL 自带的。咱们建立表的时候不指定存储引擎,
它就会使用 MyISAM 做为存储引擎。
MyISAM 的前身是 ISAM(Indexed Sequential Access Method:利用索引,顺序存取数据的方法)。
5.5 版本以后默认的存储引擎改为了 InnoDB,它是第三方公司为 MySQL 开发的。
为何要改呢?最主要的缘由仍是 InnoDB 支持事务,支持行级别的锁,对于业务一致
性要求高的场景来讲更适合。 
这个里面又有 Oracle 和 MySQL 公司的一段恩怨情仇。
InnoDB 原本是 InnobaseOy 公司开发的,它和 MySQL AB 公司合做开源了 InnoDB
的代码。可是没想到 MySQL 的竞争对手 Oracle 把 InnobaseOy 收购了。
后来 08 年 Sun 公司(开发 Java 语言的 Sun)收购了 MySQL AB,09 年 Sun 公司
又被 Oracle 收购了,因此 MySQL,InnoDB 又是一家了。有人以为 MySQL 愈来愈像
Oracle,其实也是这个缘由

 

 

那么除了这两个咱们最熟悉的存储引擎,数据库还支持其余哪些经常使用的存储引擎呢?
数据库支持的存储引擎
咱们能够用这个命令查看数据库对存储引擎的支持状况:
show engines ;
其中有存储引擎的描述和对事务、XA 协议和 Savepoints 的支持。XA 协议用来实现分布式事务(分为本地资源管理器,事务管理器)。
Savepoints 用来实现子事务(嵌套事务)。建立了一个 Savepoints 以后,事务就能够回滚到这个点,不会影响到建立 Savepoints 以前的操做。 
这些数据库支持的存储引擎,分别有什么特性呢?
https://dev.mysql.com/doc/refman/5.7/en/storage-engines.html 
 
MyISAM(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)速度。
存储了表的行数(count 速度更快)。
(怎么快速向数据库插入 100 万条数据?咱们有一种先用 MyISAM 插入数据,而后
修改存储引擎为 InnoDB 的操做。)
适合:只读之类的数据分析的项目
 
InnoDB(2 个文件)
https://dev.mysql.com/doc/refman/5.7/en/innodb-storage-engine.html
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 还支持外键引用完整性约束。
特色:
支持事务,支持外键,所以数据的完整性、一致性更高。
支持行级别的锁和表级别的锁。
支持读写并发,写不阻塞读(MVCC)。
特殊的索引存放方式,能够减小 IO,提高查询效率。
适合:常常更新的表,存在并发读写或者有事务处理的业务系统
 
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 NDBCLUSTER provides fast key-value lookups for
huge distributed data sets.
将全部数据存储在 RAM 中,以便在须要快速查找非关键数据的环境中快速访问。这
个引擎之前被称为堆引擎。其使用案例正在减小;InnoDB 及其缓冲池内存区域提供了一
种通用、持久的方法来将大部分或全部数据保存在内存中,而 ndbcluster 为大型分布式
数据集提供了快速的键值查找。
特色:
把数据放在内存里面,读写的速度很快,可是数据库重启或者崩溃,数据会所有消
失。只适合作临时表。
将表中的数据存储到内存中。
 
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 applications 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 里面常见的一些存储引擎,咱们看到了,不一样的存储引擎提供的特性都
不同,它们有不一样的存储机制、索引方式、锁定水平等功能。
咱们在不一样的业务场景中对数据操做的要求不一样,就能够选择不一样的存储引擎来满
足咱们的需求,这个就是 MySQL 支持这么多存储引擎的缘由。
 
1.5.4.如何选择存储引擎?
若是对数据一致性要求比较高,须要事务支持,能够选择 InnoDB。
若是数据查询多更新少,对查询性能要求比较高,能够选择 MyISAM。
若是须要一个用于查询的临时表,能够选择 Memory。
若是全部的存储引擎都不能知足你的需求,而且技术能力足够,能够根据官网内部
手册用 C 语言开发一个存储引擎
https://dev.mysql.com/doc/internals/en/custom-engine.html
 
1.6. 执行引擎(Query Execution Engine),返回结果
OK,存储引擎分析完了,它是咱们存储数据的形式,继续第二个问题,是谁使用执
行计划去操做存储引擎呢?
这就是咱们的执行引擎,它利用存储引擎提供的相应的 API 来完成操做。
为何咱们修改了表的存储引擎,操做方式不须要作任何改变?由于不一样功能的存
储引擎实现的 API 是相同的。
最后把数据返回给客户端,即便没有结果也要返回。 
 
2. MySQL 体系结构总结
基于上面分析的流程,咱们一块儿来梳理一下 MySQL 的内部模块。
2.1. 模块详解 

 

 

一、 Connector:用来支持各类语言和 SQL 的交互,好比 PHP,Python,Java 的
JDBC;
二、 Management Serveices & Utilities:系统管理和控制工具,包括备份恢复MySQL 复制、集群等等;
三、 Connection Pool:链接池,管理须要缓冲的资源,包括用户密码权限线程等等;
四、 SQL Interface:用来接收用户的 SQL 命令,返回用户须要的查询结果
五、 Parser:用来解析 SQL 语句;
六、 Optimizer:查询优化器;
七、 Cache and Buffer:查询缓存,除了行记录的缓存以外,还有表缓存,Key 缓存,权限缓存等等;
八、 Pluggable Storage Engines:插件式存储引擎,它提供 API 给服务层使用,跟具体的文件打交道
2.2. 架构分层
整体上,咱们能够把 MySQL 分红三层,跟客户端对接的链接层,真正执行操做的服
务层,和跟硬件打交道的存储引擎层(参考 MyBatis:接口、核心、基础)。
2.1.1.链接层
咱们的客户端要链接到 MySQL 服务器 3306 端口,必需要跟服务端创建链接,那么
管理全部的链接,验证客户端的身份和权限,这些功能就在链接层完成。
2.1.2.服务层
链接层会把 SQL 语句交给服务层,这里面又包含一系列的流程:
好比查询缓存的判断、根据 SQL 调用相应的接口,对咱们的 SQL 语句进行词法和语
法的解析(好比关键字怎么识别,别名怎么识别,语法有没有错误等等)。
而后就是优化器,MySQL 底层会根据必定的规则对咱们的 SQL 语句进行优化,最
后再交给执行器去执行。
2.1.3.存储引擎
存储引擎就是咱们的数据真正存放的地方,在 MySQL 里面支持不一样的存储引擎。
再往下就是内存或者磁盘。
3. 一条更新 SQL 是如何执行的?
讲完了查询流程,咱们是否是再讲讲更新流程、插入流程和删除流程?
在数据库里面,咱们说的 update 操做其实包括了更新、插入和删除。若是你们有看
过 MyBatis 的源码,应该知道 Executor 里面也只有 doQuery()和 doUpdate()的方法,
没有 doDelete()和 doInsert()。
更新流程和查询流程有什么不一样呢?
基本流程也是一致的,也就是说,它也要通过解析器、优化器的处理,最后交给执行器。
区别就在于拿到符合条件的数据以后的操做。 
3.1. 缓冲池 Buffer Pool
首先,InnnoDB 的数据都是放在磁盘上的,InnoDB 操做数据有一个最小的逻辑单
位,叫作页(索引页和数据页)。咱们对于数据的操做,不是每次都直接操做磁盘,因
为磁盘的速度太慢了。InnoDB 使用了一种缓冲池的技术,也就是把磁盘读到的页放到一
块内存区域里面。这个内存区域就叫 Buffer Pool

 

 

下一次读取相同的页,先判断是否是在缓冲池里面,若是是,就直接读取,不用再
次访问磁盘。
修改数据的时候,先修改缓冲池里面的页。内存的数据页和磁盘数据不一致的时候,
咱们把它叫作脏页。InnoDB 里面有专门的后台线程把 Buffer Pool 的数据写入到磁盘,
每隔一段时间就一次性地把多个修改写入磁盘,这个动做就叫作刷脏。
Buffer Pool 是 InnoDB 里面很是重要的一个结构,它的内部又分红几块区域。这里
咱们趁机到官网来认识一下 InnoDB 的内存结构和磁盘结构。 
3.3.1.内存结构
Buffer Pool 主要分为 3 个部分: Buffer Pool、Change Buffer、Adaptive Hash
Index,另外还有一个(redo)log buffer。
一、Buffer Pool
Buffer Pool 缓存的是页面信息,包括数据页、索引页。
查看服务器状态,里面有不少跟 Buffer Pool 相关的信息:
SHOW STATUS LIKE '%innodb_buffer_pool%';
这些状态均可以在官网查到详细的含义,用搜索功能。
https://dev.mysql.com/doc/refman/5.7/en/server-status-variables.html 
Buffer Pool 默认大小是 128M(134217728 字节),能够调整。
查看参数(系统变量):
SHOW VARIABLES like '%innodb_buffer_pool%';
这些参数均可以在官网查到详细的含义,用搜索功能。
https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html

 

 

内存的缓冲池写满了怎么办?(Redis 设置的内存满了怎么办?)InnoDB 用 LRU
算法来管理缓冲池(链表实现,不是传统的 LRU,分红了 young 和 old),通过淘汰的
数据就是热点数据
内存缓冲区对于提高读写性能有很大的做用。思考一个问题:
当须要更新一个数据页时,若是数据页在 Buffer Pool 中存在,那么就直接更新好了。
不然的话就须要从磁盘加载到内存,再对内存的数据页进行操做。也就是说,若是
没有命中缓冲池,至少要产生一次磁盘 IO,有没有优化的方式呢? 
二、Change Buffer 写缓冲
若是这个数据页不是惟一索引,不存在数据重复的状况,也就不须要从磁盘加载索
引页判断数据是否是重复(惟一性检查)。这种状况下能够先把修改记录在内存的缓冲
池中,从而提高更新语句(Insert、Delete、Update)的执行速度。
这一块区域就是 Change Buffer。5.5 以前叫 Insert Buffer 插入缓冲,如今也能支
持 delete 和 update。
最后把 Change Buffer 记录到数据页的操做叫作 merge。何时发生 merge?
有几种状况:在访问这个数据页的时候,或者经过后台线程、或者数据库 shut down、
redo log 写满时触发。
若是数据库大部分索引都是非惟一索引,而且业务是写多读少,不会在写数据后立
刻读取,就可使用 Change Buffer(写缓冲)。写多读少的业务,调大这个值:
SHOW VARIABLES LIKE 'innodb_change_buffer_max_size';
表明 Change Buffer 占 Buffer Pool 的比例,默认 25%。
三、Adaptive Hash Index
索引应该是放在磁盘的,为何要专门把一种哈希的索引放到内存?下次课再说。
四、(redo)Log Buffer
思考一个问题:若是 Buffer Pool 里面的脏页尚未刷入磁盘时,数据库宕机或者重
启,这些数据丢失。若是写操做写到一半,甚至可能会破坏数据文件致使数据库不可用。
为了不这个问题,InnoDB 把全部对页面的修改操做专门写入一个日志文件,而且
在数据库启动时从这个文件进行恢复操做(实现 crash-safe)——用它来实现事务的持
久性。

 

 

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

问题:
一样是写磁盘,为何不直接写到 db file 里面去?为何先写日志再写磁盘?
咱们先来了解一下随机 I/O 和顺序 I/O 的概念。
磁盘的最小组成单元是扇区,一般是 512 个字节。
操做系统和内存打交道,最小的单位是页 Page。
操做系统和磁盘打交道,读写磁盘,最小的单位是块 Block
若是咱们所须要的数据是随机分散在不一样页的不一样扇区中,那么找到相应的数据需
要等到磁臂旋转到指定的页,而后盘片寻找到对应的扇区,才能找到咱们所须要的一块
数据,一次进行此过程直到找完全部数据,这个就是随机 IO,读取数据速度较慢。
假设咱们已经找到了第一块数据,而且其余所需的数据就在这一块数据后边,那么
就不须要从新寻址,能够依次拿到咱们所需的数据,这个就叫顺序 IO。
刷盘是随机 I/O,而记录日志是顺序 I/O,顺序 I/O 效率更高。所以先把修改写入日
志,能够延迟刷盘时机,进而提高系统吞吐。
固然 redo log 也不是每一次都直接写入磁盘,在 Buffer Pool 里面有一块内存区域
(Log Buffer)专门用来保存即将要写入日志文件的数据,默认 16M,它同样能够节省
磁盘 IO。 

 

 

SHOW VARIABLES LIKE 'innodb_log_buffer_size';
须要注意:redo log 的内容主要是用于崩溃恢复。磁盘的数据文件,数据来自 buffer
pool。redo log 写入磁盘,不是写入数据文件。
那么,Log Buffer 何时写入 log file?
在咱们写入数据到磁盘的时候,操做系统自己是有缓存的。flush 就是把操做系统缓
冲区写入到磁盘。 
log buffer 写入磁盘的时机,由一个参数控制,默认是 1。
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_flush_log_at_trx_commit

 

 

这是内存结构的第 4 块内容,redo log,它又分红内存和磁盘两部分。redo log 有
什么特色?
一、redo log 是 InnoDB 存储引擎实现的,并非全部存储引擎都有。
二、不是记录数据页更新以后的状态,而是记录这个页作了什么改动,属于物理日志。
三、redo log 的大小是固定的,前面的内容会被覆盖。

 

 

check point 是当前要覆盖的位置。若是 write pos 跟 check point 重叠,说明 redo
log 已经写满,这时候须要同步 redo log 到磁盘中。
这是 MySQL 的内存结构,总结一下,分为:
Buffer pool、change buffer、Adaptive Hash Index、 log buffer。
磁盘结构里面主要是各类各样的表空间,叫作 Table space。 
 
3.3.2.磁盘结构
表空间能够看作是 InnoDB 存储引擎逻辑结构的最高层,全部的数据都存放在表空
间中。InnoDB 的表空间分为 5 大类。
系统表空间 system tablespace
在默认状况下 InnoDB 存储引擎有一个共享表空间(对应文件/var/lib/mysql/
ibdata1),也叫系统表空间。
InnoDB 系统表空间包含 InnoDB 数据字典和双写缓冲区,Change Buffer 和 Undo 
Logs),若是没有指定 file-per-table,也包含用户建立的表和索引数据。
一、undo 在后面介绍,由于有独立的表空间。
二、数据字典:由内部系统表组成,存储表和索引的元数据(定义信息)。
三、双写缓冲(InnoDB 的一大特性):
InnoDB 的页和操做系统的页大小不一致,InnoDB 页大小通常为 16K,操做系统页
大小为 4K,InnoDB 的页写入到磁盘时,一个页须要分 4 次写
若是存储引擎正在写入页的数据到磁盘时发生了宕机,可能出现页只写了一部分的
状况,好比只写了 4K,就宕机了,这种状况叫作部分写失效(partial page write),可
能会致使数据丢失
show variables like 'innodb_doublewrite';
咱们不是有 redo log 吗?可是有个问题,若是这个页自己已经损坏了,用它来作崩
溃恢复是没有意义的。因此在对于应用 redo log 以前,须要一个页的副本。若是出现了
写入失效,就用页的副原本还原这个页,而后再应用 redo log。这个页的副本就是 double
write,InnoDB 的双写技术。经过它实现了数据页的可靠性。
跟 redo log 同样,double write 由两部分组成,一部分是内存的 double write,
一个部分是磁盘上的 double write。由于 double write 是顺序写入的,不会带来很大的
开销。
在默认状况下,全部的表共享一个系统表空间,这个文件会愈来愈大,并且它的空
间不会收缩。 
独占表空间 file-per-table tablespaces
咱们可让每张表独占一个表空间。这个开关经过 innodb_file_per_table 设置,默
认开启
SHOW VARIABLES LIKE 'innodb_file_per_table';
开启后,则每张表会开辟一个表空间,这个文件就是数据目录下的 ibd 文件(例如
/var/lib/mysql/gupao/user_innodb.ibd),存放表的索引和数据。
可是其余类的数据,如回滚(undo)信息,插入缓冲索引页、系统事务信息,二次
写缓冲(Double write buffer)等仍是存放在原来的共享表空间内。
通用表空间 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;
临时表空间 temporary tablespaces
存储临时表的数据,包括用户建立的临时表,和磁盘的内部临时表。对应数据目录
下的 ibtmp1 文件。当数据服务器正常关闭时,该表空间被删除,下次从新产生。
Redo log
磁盘结构里面的 redo log,在前面已经介绍过了。
undo log tablespace
https://dev.mysql.com/doc/refman/5.7/en/innodb-undo-tablespaces.html
https://dev.mysql.com/doc/refman/5.7/en/innodb-undo-logs.html
undo log(撤销日志或回滚日志)记录了事务发生以前的数据状态(不包括 select)。
若是修改数据时出现异常,能够用 undo log 来实现回滚操做(保持原子性)。
在执行 undo 的时候,仅仅是将数据从逻辑上恢复至事务以前的状态,而不是从物
理页面上操做实现的,属于逻辑格式的日志。
redo Log 和 undo Log 与事务密切相关,统称为事务日志。
undo Log 的数据默认在系统表空间 ibdata1 文件中,由于共享表空间不会自动收
缩,也能够单首创建一个 undo 表空间。 
show global variables like '%undo%';
有了这些日志以后,咱们来总结一下一个更新操做的流程,这是一个简化的过程。
name 原值是 lei1。
update user set name = 'penyuyan' where id=1;
一、事务开始,从内存或磁盘取到这条数据,返回给 Server 的执行器;
二、执行器修改这一行数据的值为 penyuyan;
三、记录 name=qingshan 到 undo log;
四、记录 name=penyuyan 到 redo log;
五、调用存储引擎接口,在内存(Buffer Pool)中修改 name=penyuyan;
六、事务提交。 
 
内存和磁盘之间,工做着不少后台线程。
3.3.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,它能够被全部的存储引擎使用。
3.3. Binlog
https://dev.mysql.com/doc/refman/5.7/en/binary-log.html
binlog 以事件的形式记录了全部的 DDL 和 DML 语句(由于它记录的是操做而不是
数据值,属于逻辑日志),能够用来作主从复制和数据恢复。
跟 redo log 不同,它的文件内容是能够追加的,没有固定大小限制。
在开启了 binlog 功能的状况下,咱们能够把 binlog 导出成 SQL 语句,把全部的操
做重放一遍,来实现数据的恢复。
binlog 的另外一个功能就是用来实现主从复制,它的原理就是从服务器读取主服务器
的 binlog,而后执行一遍。
配置方式和主从复制的实现原理在 Mycat 第二节课中有讲述。
有了这两个日志以后,咱们来看一下一条更新语句是怎么执行的: 

 

 

例如一条语句:update teacher set name='盆鱼宴' where id=1;
一、先查询到这条数据,若是有缓存,也会用到缓存。
二、把 name 改为盆鱼宴,而后调用引擎的 API 接口,写入这一行数据到内存,同时
记录 redo log。这时 redo log 进入 prepare 状态,而后告诉执行器,执行完成了,能够随时提交。
三、执行器收到通知后记录 binlog,而后调用存储引擎接口,设置 redo log为 commit状态。
四、更新完成。 
这张图片的重点(不必背下来):
一、先记录到内存,再写日志文件
二、记录 redo log 分为两个阶段。
三、存储引擎和 Server 记录不一样的日志。
三、先记录 redo,再记录 binlog。