MySQL必知必会:用十一张图讲清楚,当你CRUD时BufferPool中发生了什么!
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL必知必会:用十一张图讲清楚,当你CRUD时BufferPool中发生了什么!相关的知识,希望对你有一定的参考价值。
一、前言
下面让我们就一起看下,当你执行CURD时,InnoDB的Buffer Pool中都发生了什么!以及Buffer Pool的优化!
二、Let‘s go
你知道的,mysql对数据的增删改查都是内存中完成的,这块内存就是Buffer Pool。
你可以像下面这样查看下你的MySQL的Buffer的Buffer Pool的默认大小
上图中的0.125单位为GB,转换成MB就是 1024* 1/8 = 128MB
总结来说,就是MySQL启动后就会为我们初始化好这块Buffer Pool。如下图:
你可以看着上图,然后读下面这段话:
MySQL以数据页为单位,从磁盘中读取数据。数据页被读取到内存中,所谓的内存其实就是Buffer Pool。
Buffer Pool中维护的数据结构是缓存页,而且每个缓存页都有它对应的描述信息。
由于MySQL刚启动,还没有从磁盘中读取任何数据页到内存(Buffer Pool)中,那此时Buffer Pool中所有的缓存页其实都是空的。
除了缓存页之外,你还能看到Buffer Pool中存在三个双向链表。分别是FreeList、LRUList以及FlushList。这三个双向链表中维护着缓存页的描述信息。
三、好,假设你读取出来了1个数据页
当你通过select读取出一个数据页之后,是需要将这个数据页加载进Buffer Pool中的缓存页中的。
那问题来了,MySQL怎么知道该将你读取出来的数据页存放在那个缓存页中呢?相信你看了上图应该也能想到答案了。FreeList这个双向链表不是存放了空闲的缓存页的描述信息吗?那从FreeList中取出一个空间缓存页的描述信息不就好了?于是得到了下面这张图:
啰嗦一点:对这张图稍微做一下解读:
InnoDB会将你读取出来的数据页加载进Buffer Pool中的缓存页中,然后缓存页的描述信息也会被维护进LRU链表中。链表做了冷热数据分离优化,5/8的区域是热数据区域,3/8的区域算是冷数据区域。(本质上它们都是双向链表),而你新读取的数据页会被放在冷数据区的靠前的位置上。
如果你将该数据页读取出来加载进缓存页中后,间隔没到1s,就使用该缓存页。那么InnoDB是不会将这个描述信息移动到5/8的热数据区域的。
但是当超过1s后,你又去读这个数据页。那这个数据页的描述信息就会被放到热数据区域。如下图:
四、假设你一次性读取出来了好多数据页
白日梦在第 6 篇文章中跟大家分享过,MySQL是存在预读机制的,感兴趣可关注公众号阅读。
假设触发了MySQL的预读机制。一次性从磁盘中读取来N多个缓存页。会得到下面这张图:
因为发生了预读,所以你的一次磁盘IO读出了大量的数据页,但是这些数据页中很可能是有一些是你根本不需要的,仅仅是预读把它们级联查出来了。这时按老规矩,从FreeList中找到空闲的缓存页信息,然后将其从FreeList中移除。根据找到的空闲缓存页的描述信息,将从磁盘中读取出来的数据页加载进去。相应的该缓存页的描述信息也会被维护进LRU链表的冷数据区域。
这时你就会发现这种冷热数据分离的机制多么妙!即使发生了预读又怎么样?根本没有机会将热数据区的描述信息1挤下去。当内存不够用了需要将部分缓存页刷新到磁盘中时,那就从冷数据区域开始刷新好了,反正他们本来就不经常被使用。
同样的,当你超过1s后又访问了冷数据区的缓存页,比如访问了缓存页66和数据页67,该缓存页对应的描述信息是会被提升到热数据区,于是有了下面这张图:
那,如果你访问上图中的数据页67,它会移动到描述信息66所在节点的前面去吗?
其实MySQL的LRU链表做了优化,数据67是不会往前跑的。
五、假设你修改了某数据页
假设你执行了update xxx set xxx where id in (xxx,xxx,xxx,xxx);
而符合条件的数据行恰巧就在描述信息1、描述信息66、描述信息67所指向的缓存页中,那BufferPool中会发生什么呢?
如下图:
你会看到,被你修改了的缓存页的描述信息,被添加到了FlushList这个双向链表中。
想必看到这里你已经知道了,原来FlushList中的节点存放就是被修改了脏数据页的描述信息块。
随着MySQL被使用的时间越来越长,BufferPool的大小就越来越小。等它不够用的时候,就会将部分LRU中的数据页描述信息移除出去,这时如果发现被移除出来的数据页在FLushList中,就会触发fsync的操作,触发随机写磁盘。如果该数据页是干净的,那移除出去就好了。其他也不用干啥。
举个例子:假设需要将描述信息66、描述信息67指向的缓存页落盘。会得到下面这张脑图:
描述信息66、67指向的缓存页被刷新进磁盘。 同时从FlushList中将其移除,然后存入FreeList中。完成一个循环
当然,将脏数据页刷新进磁盘的时机除了上图中说的还有好多种情况,白日梦在上一篇文章中有分享。可关注公众号查看哦
下面再看一下关于Buffer Pool的设置和相关的优化。
六、配置Buffer Pool的大小
buffer pool越大,MySQL的性能就越强悍。你可以像下面这样配置Buffer Pool的大小。
mysql> SET GLOBAL innodb_buffer_pool_size=402653184;
七、配置多个Buffer Pool的实例
你可以为MySQL实例配置多个Buffer Pool,每个Buffer Pool各自负责管理一部分缓存页,并且有自己独立的LRU、Free、Flush链表。
当有多线程并发请求过来时,线程可以在不同的Buffer Pool中执行自己的操作,MySQL性能就会得到很大的提升
在my.d中进行配置
[server]
innodb_buffer_pool_size = xxx
innodb_buffer_pool_instances = 4
意思是将总容量为xxx的buffer pool划分成4个实例。每个实例都有 xxx/4 的容量。
参数innodb_buffer_pool_instances的最大值为64,并且想让该参数生效,innodb_buffer_pool_size容量至少是1G。
可以像下面这样查看你的MySQL的Buffer Pool实例状态。
八、揭秘BufferPool的真实结构
现实中Buffer Pool动辄就占用好几G的内存,相对于直接申请几G的内存完成扩容,MySQL有更优雅的实现方式。
为了实现动态调整Buffer Pool的大小。MySQL设计了chunk 机制。
可以看上图脑补一下Buffer Pool 以及 Chunk长什么样。
总的来说:就是将每一个 Buffer Pool Instance 更加细力度化。将Buffer Pool拆分成更小的独立单元。
每个Buffer Pool划分成多个chunnk,每个chunk中维护一部分缓存页、缓存页的描述信息。同属于一个Buffer Pool的chunk共享该Buffer Pool的lru、free、flush链表。
块大小由参数innodb_buffer_pool_chunk_size控制,默认值为 128M
该参数可以像下面这样修改:
shell> mysqld --innodb-buffer-pool-chunk-size=134217728
或者通过配置文件自定义
[mysqld]
innodb_buffer_pool_chunk_size=134217728
九、看一看Buffer Pool相关的参数
执行命令
> mysql show engine innodb status
十、如何规划你的Buffer Pool大小
推荐将Buffer Pool的总大小设置为服务器内存的 50%~60%左右
BufferPool总大小 = (chunkSize * bufferPoolInstanceNum)*2
十一、Buffer Pool的预热机制
这种机制实际上是想让重启后的MySQL快速适应大规模的流量请求。
InnoDB 在服务器关闭时为每个缓冲池保存一部分最近高频使用的页面,并在服务器启动时恢复这些页面。保存多大比例的缓存页由参数innodb_buffer_pool_dump_pct控制。
在启动时还原缓冲池,实际上会缩短预热的时间。
你可以通过下面的方式配置该参数
# 通过命令
SET GLOBAL innodb_buffer_pool_dump_pct=40;
# 通过文件
[mysqld]
innodb_buffer_pool_dump_pct=40
参数innodb_buffer_pool_dump_at_shutdown控制 MySQL关闭时保存缓冲池的状态,默认为on的状态。
启动参数--innodb-buffer-pool-load-at-startup 表示启动MySQL的时候恢复缓冲池中的状态,默认也是开启的。
《SQL必知必会》学习笔记二)
咱们接着上一篇的内容继续。这一篇主要回顾子查询,联合查询,复制表这三类内容。
上一部分基本上都是简单的Select查询,即从单个数据库表中检索数据的单条语句,但是实际应用中的业务逻辑往往会非常复杂,所以会用到一些比较复杂的查询,如子查询,联合查询。
1.子查询
当一个查询是另一个查询的条件时,称为子查询。但是说到子查询又不的不说它与嵌套查询两者的区别,下面一张图来说明
下面再用一条sql语句来说明他们的关系。
其中在查询中又分为嵌套子查询和相关子查询,他们之间的区别就是查询是否依赖与外部的查询,嵌套子查询的执行不依赖与外部的查询,而相关子查询的执行依赖于外部查询。
select * from Books where 价格 < (select AVG(价格) from Books) --查询所有价格高于平均价格的书信息
上边的一条sql语句便是嵌套子查询,来分析他的执行过程。
①先执行子查询,即查询出Books表中书的平均价格,然后将结果传递给父查询,作为父查询的条件。
②执行父查询,返回结果。
select * from Books as a where 价格 < (select AVG(价格) from Books as b where a.类编号=b.类编号)--查询表中大于该类图书价格平均值的图书信息
上边一条语句便是嵌套子查询,来分析他的执行过程。
①先从父查询中读取一个数据,即类编号,然后将类编号传递给子查询。
②执行子查询,将这个类编号的书的平均价格查询出来,并将其传递给父查询。
③父查询判断这条数据是否满足条件,不满足就排除,满足则保留。
④然后父查询获取下一条数据中的类编号,重复①到③步骤,直到外层所有数据被处理完。
2.表联合查询
SQL最强大的功能之一就是能在数据查询的执行中联结(join)表。联结是利用SQL的SELECT能执行的最重要的操作,在能够有效地使用联结前,先了解一下数据库的三个范式。
1NF(原子性):字段不可再分,否则就不是关系型数据库。
2NF(唯一性):有主键,非主键字段依赖于主键;或者说,一个表中只能说明一个事务。
3NF(无依赖):主键字段不能相互依赖,不能传递依赖。
三范式是解决了数据库的冗余问题,但是在很多业务逻辑下,必须同时查询两个或两个以上的表,这个时候就用到了表的联合(结)查询。
笛卡儿积( 笛卡儿积(cartesian product):由没有联结条件的表关系返回的结果为笛卡儿积。即将是第一个表中的行数乘以第二个表中的行数。
联合查询,就是先求出所查表的笛卡尔积之后,在对笛卡儿积进行筛选。
而连接类型又分为好几种,如下图所示。
- (Inner) Join: 如果表中有至少一个匹配,则返回行
- Left Join: 即使右表中没有匹配,也从左表返回所有的行
- Right Join: 即使左表中没有匹配,也从右表返回所有的行
- Full Join: 只要其中一个表中存在匹配,就返回行
- Cross Join: 就是返回两个关联表的笛卡儿积
内连接(Inner Join)是最常用的连接操作。内连接有两种不同的语法 ,一种是显示连接符号,另一种是隐式链接符号,他们的区别就是用不用Join关键字。
语法:select <要选字段> from <主要资料表> <join方式> <次要资料表> [on <join 规则>] --在内连接中,Join规则不相符的都会被排除:排他性 写法1:select * from A inner join B on A.name=B.name --显示连接符号 写法2:select * from A,B where A.name=B.name --隐式链接符号 --对照上图,内连接查询出a和b两表的公共集c
外连接并不要求连接的两表的每一条记录在对方表中都一条匹配的记录,要保留所有记录的表(即使这条记录没有匹配也要保留)称为保留表,保留表在join关键字左边的连接就称为左连接,在右边就称为右连接,当两表记录都要保存时,称为全外连接。左外连接查询时,如果右表中无匹配记录, 来自于右表的所有列的值设为 NULL,右外连接,全外连接也是这种情况。
语法:select <要选字段> from <left资料表> <left|right>[outer] join <right资料表> [on <join 规则>] --在外连接中:包容性 左外连接:select * from A left (outer)join B on A.name=B.name --对照上图即左外连接查询出表a的所有集合和b中和a公共集合 a1+c 右外连接:select * from A right(outer)join B on A.name=B.name --对照上图即右外连接查询出表a的所有集合和b中和a公共集合 b1+c 全外连接:select * from A full(outer)join B on A.name=B.name --对照上图即全外连接查询出表a的所有集合和b中所有集合 a1+c+b1
3.复制表
有时候需要将一个表的数据直接导入到一个新表中,这时候可以用select...into...from或者insert into...select,不过两者有区别。
select * into NewTable from Books --使用这条语句时,数据库中必须原先不存在表NewTable ,若数据库中原先有此表,则会报错 insert into NewTable select * from Books --这条语句中,数据库中必须原先存在表NewTable, 若不存在,则执行报错
还有一个问题需注意:在使用复制表语句时,只会复制表的数据和结构,但是表中的主键,外键,约束,索引这些信息不会复制过来。
以上是关于MySQL必知必会:用十一张图讲清楚,当你CRUD时BufferPool中发生了什么!的主要内容,如果未能解决你的问题,请参考以下文章