InnoDB体系结构
Buffer与磁盘
1.page是InnoDB磁盘I/O的最小单位,数据存放于page中,对应内存为一个个buffer
2.buffer有三种状态
①free buffer:从未被使用过的buffer
②clean buffer:内存中buffer里面的数据和磁盘page的数据一致
③dirty buffer:内存中新写入的,还没有刷新到磁盘,跟磁盘中的数据不一致
3.对应三种不同的Buffer状态,InnoDB是通过三条双向链表来进行管理的
①free list:每次page调用到内存中,都会先判断free buffer的使用情况,如果不够用,就会从flush list链表和lru list中进行释放
②lru list:数据和磁盘一致,最近最少使用的buffer
③flush buffer:dirty buffer串成的链.一般是把最近最少被弄脏的数据串起来
各大刷新线程以及作用
master thread
后台线程中的主线程,优先级最高,内部有四个循环
main loop{
if( key == background){
//后台循环线程
background loop
}
if( key == flush ){
//刷新线程
flush loop
}
if (key == suspend ){
//暂停线程
suspend loop
}
主循环内又分为两种操作
每1s的操作
//(1)redo log日志缓冲刷新到磁盘,即使这个事务还没提交 备注:binlog的是指写一次 redo log会在过程就开始写
//(2)刷新脏页到磁盘
//(3)执行合并插入缓冲的操作
//(4)产生checkpoint
//(5)清除无用的table cache
//(6)如果当前没有用户活动,就可能会切换到background loop
每10s的操作 基本和1s的相同
//(1)redo log日志缓冲刷新到磁盘,即使这个事务还没提交
//(2)刷新脏页到磁盘
//(3)执行合并插入缓冲的操作
//(4)产生checkpoint
//(5)删除无用的undo页
然后是四大I/O线程
1.read thread 负责数据库的读,默认4个
2.write thread 负责数据库的写,默认4个
3.redo log thread 负责把日志缓冲区的内容刷新到redo log文件中
4.change buffer thread 负责把change buffer中的内容刷新到磁盘
脏页刷新线程
page cleaner thread
无用undo页删除线程
purge thread
checkpoint推进线程
当redo log发生切换时,推进check point
write pos是当前记录的位置,一边写一边后移,写到3号末尾就回到0号开头.
checkpoint是当前要擦除的位置,也是往后移并且循环的,擦除前需要把记录更新到数据文件.
锁监控线程
lock monitor thread检测死锁
数据库报错的监控线程
erro monitor thread
内存的刷新机制
首先,mysql是先写日志,再写数据文件
其次,redo log,记录的是数据修改后的值
结合之前的图
为了保证性能,一般都是写到缓存后就立即返回了,然后异步的将缓存中的数据刷新到磁盘
控制redo log buffer中的数据刷新到磁盘的参数(备注,buffer和cache都可以理解为缓存,只是buffer主要针对的是I/O方面,cache主要针对cpu方面)
innodb_flush_log_at_trx_commit
有三个值0,1,2
0:每隔1s就会将redo log buffer中的数据写入到redo log 文件,同时进行刷盘操作.但有一个问题是,每次事务提交并不会触发redo log thread 将日志缓冲区中的数据写入到redo log 文件中
1:每次事务提交,都会触发redo log thread将日志缓冲区中的数据写入文件,并刷新到磁盘
2:每次事务提交,都会把redo log buffer中的数据写入到redo log文件,但不会同时刷新到磁盘
一般都设为1,和控制Binlog的参数sync_binlog都设置为1
binlog文件
Mysql的二进制文件
binlog和redo log的三点区别
1.redo log是InnoDB引擎特有的;binlog是Mysqlde Server层实现的,所有引擎都可以使用
2.redo log是物理日志,记录的是在"某个数据页上做了什么修改"(同时可以引申出后面要说的double write);binlog是逻辑日志,记录的是语句的原始逻辑,比如"给id=2这一行的c字段加1"
3.redo log 是循环写的,空间固定会用完(所以会有checkpoint和刷新到磁盘);binlog是追加写的,当文件写到一定大小后会切换到下一个,并不会覆盖之前的日志.
他们的使用场景
1.binlog可以作为数据恢复使用,主从复制搭建
2.redo log作为异常宕机或者介质故障后的数据恢复使用
那么他们是怎么配合的?
假设我们执行
updatetableasetc=c+1whereid=2;
这里主要用到了两阶段提交
两阶段提交分为prepare和commit阶段
准备阶段(transaction prepare):事务SQL先写入redo log buffer,然后做一个准备标记,再将log buffer中的数据刷新到redo log
提交阶段(commit):将事务产生的binlog写入文件,刷入磁盘
再在redo log 中做一个事务提交的标记,并把binlog写成功的标记也一并写入到redo log文件。
那他们是怎么保证一致性的呢?
情况一:准备阶段,redo log刷新到了磁盘,但是binlog写盘前发生了mysql实例crash.
这时我们redo log是没有binlog提交的日志的,所以我们可以通过回滚来保证数据库一致性
情况二:binlog写盘成功,这时mysql实例crash
进行数据恢复时,只需要使用redo log重做一次就好了。
redo log 和binlog的写入时机
binlog的写入时机
事务执行过程中,先把日志写入到binlog cache,事务提交时,再把binlog cache写入到Binlog文件中
系统会给每一个线程分配一个binlog cache,参数binlog_cache_size可以用于控制单个线程内binlog_cache所占内存的大小,如果超过这个数,就要暂存到磁盘(磁盘swap)
一个事务的binlog是不能被拆开的,因此不论事务有多大,也要确保一次性写入.
一般分为write和异步刷新,而write和fsync的时机,是由参数sync_binlog控制的
1.sync_binlog=0时,每次提交事务都只write,不fsync
2.sync_binlog=1时,每次提交事务都会执行fsync
3.sync_binlog=N(n>1),每次提交事务都write,但累计N个事务才fsync
一般都设置为1
redo log的写入时机
可以参考以上的两阶段提交
此外,也是先写入到redo log buffer,再write到磁盘,再进行磁盘持久化(fsync)
这里和binlog不同的是,redo log是事务执行过程中就会直接写入到redo log buffer中,然后会被我们画的主线程里的几个线程持久化到磁盘,也就是事务可能没提交,但我们的redo log已经持久化到磁盘了。
除了每1s的轮询操作会写入外(这种场景已经刷盘了),还有另外两种场景
场景一.
redo log buffer占用的空间即将达到innodb_log_buffer_size一半的时候,后台回主动写盘
,注意这个时候并没有刷盘,只是留在了文件系统的page cache
场景二
并行的事务提交的时候,顺带将这个事务的redo log buffer持久化到磁盘
此外为看保证数据页不会被多次执行重复的redo log,还有一个组提交的概念
使用日志逻辑序列号(log sequence number,LSN)来对应redo log的一个个写入点,每次写入长度为length的redo log,LSN的值就会加上length
假设三个并发事务(trx1,trx2,trx3)在prepare阶段,都写完redo log,持久化到磁盘的过程,对应的LSN分别是50,120和160
假设trx1是第一个到的,那么他会被选为这组的组长leader
等trx1要开始写盘的时候,组内有三个事务,这个时候LSN也变成了160
那么trx1去写盘的时候,带的LSN就是160,等trx1写完返回时,LSN小于等于160的redo log就已经被持久化到磁盘了
那么trx2和trx3就可以直接返回了。
所以,一次组提交了,组员越多,节省的磁盘Iops效果越好.
进一步推导,在并发更新的场景下,第一个事务写完redo log buffer以后,接下来的fsync越晚调用,组员就会越多,就节省i/o时间(感觉和redis的pipeline有点类似,将多个命令执行一次i/o操作,有点异曲同工之妙)
所以Mysql做的优化就是拖时间
它把redo log做fsync的时间拖到了binlog write之后
流程图如下
这样一来,binlog也可以组提交了
所以这里又有个参数可以控制I/O性能瓶颈了
1.binlog_group_commit_sync_delay表示延迟多少微秒后才会调用fsync
2.binlog_group_commit_sync_no_delay_count,表示累积多少次以后才会调用fsync
最后再来看一下
InnoDB的三大特性
插入缓冲(change buffer)
两次写(double write)
自适应哈希索引(adaptive hash index)
(1)插入缓冲
和磁盘打交道,最主要的性能问题就是I/O.
而插入缓冲的作用就是把普通索引上的DML操作从随机I/O变成顺序I/O
原理
先判断插入的普通索引页是否在缓冲池中,如果在就可以直接插入,不在的话就需要先放到change_buffer中,然后进行change_buffer和普通索引的合并操作,可以将多个操作合并为一个操作中。
(2)两次写(double write)
它主要用来保证数据写入的安全性
我们前面提到,redo log是物理日志,如果页都损坏了,是没办法进行恢复的,所以我们需要一个数据页的备份.
可以通过innodb_doublewrite为0来关闭双写缓冲.
(3)自适应哈希索引
主要用于监控我们的查询是否可以通过建立哈希索引得到优化,可以的话,它会自动帮助我们完成这件事.
可以通过innodb_adaptive_hash_index来进行控制,默认是开启的