那么,执行一条update语句期间发生了什么?
执行步骤和查询语句是类似的
但是update还会涉及到几个重要日志文件:redo log、bin log、undo log。
update包含两步操作,先查询到对应的行记录,再根据条件进行更新操作。如果没有redo log的话,MySQL每次的update操作都要更新磁盘文件,整个过程的I/O成本和查询成本都很高。
InnoDB
引擎就会先更新内存(同时标记为脏页),然后将本次对这个页的修改以 redo log的形式记录下来,这个时候更新就算完成了。后续,InnoDB
引擎会在适当的时候,由后台线程将缓存在Buffer Pool的脏页刷新到磁盘里,这就是**WAL(Write-Ahead Logging)
**技术。
✨**
WAL
**技术指的是,MySQL的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上。
redo log是物理日志,记录了某个数据页做了什么修改,比如对XXX表空间中的YYY数据页ZZZ偏移量的地方做了AAA更新。
每当执行一个事务就会产生一条或者多条这样的物理日志。
在事务提交时,只要先将redo log持久化到磁盘即可,可以不需要等到将缓存在Buffer Pool里的脏页数据持久化到磁盘。
当系统崩溃时,虽然脏页数据没有持久化,但是redo log已经持久化,在MySQL重启后,可以根据redo log的内容,将所有数据恢复到最新的状态。
写入redo log的方式使用了追加操作,所以操作顺序是顺序写,写入数据是随机写,需要先找到写入位置,然后才写到磁盘。
✨磁盘的顺序写比随机写高效的多,因此 redo log写入磁盘的开销更小。
WAL
技术的另外一个优点:MySQL的写操作从磁盘的随机写变成了顺序写。
不是的
在执行一个事务的过程中,产生的 redo log也不是直接写入磁盘的,因为这样会产生大量的I/O操作,而且磁盘的运行速度远慢于内存。
redo log也有自己的缓存——redo log buffer,每当产生一条redo log时,会先写入到redo log buffer,后续再持久化到磁盘
redo log buffer默认大小16MB,可以通过 **innodb_log_Buffer_size
**参数动态的调整大小,增大它的大小可以让MySQL处理大事务的时候不必写入磁盘,进而提升写IO性能。
redo log buffer中的redo log还是在内存中,什么时候刷新到磁盘?
InnoDB
的后台线程每隔1s,将redo log buffer持久化到磁盘。默认情况下,**InnoDB
**存储引擎有1个重做日志文件组,由2个redo log文件组成,这两个redo日志的文件名是 **ib_logfile0
**和 ib_logfile1
在重做日志组中,每个 redo log File 的大小是固定且一致的,假设每个 redo log File 设置的上限是 1 GB,那么总共就可以记录 2GB 的操作。
重做日志文件组是以循环写的方式工作的,从头开始写,写到末尾就又回到开头,相当于一个环形。
如果随着系统运行,Buffer Pool 的脏页刷新到了磁盘中,那么 redo log 对应的记录也就没用了,这时候我们擦除这些旧记录,以腾出空间记录新的更新操作。
redo log 是循环写的方式,相当于一个环形,InnoDB
用 write pos
表示 redo log 当前记录写到的位置,用 **checkpoint **表示当前要擦除的位置,如下图:
如果**write pos
追上了checkpoint
,就意味着redo log文件满了,这时MySQL不能再执行新的更新操作,也就是MySQL会被阻塞 **,此时会停下将Buffer Pool中的脏页刷新到磁盘中,然后标记redo log哪些记录可以被擦除,接着对旧的redo log记录进行擦除,等擦除完了旧记录腾出了空间,checkpoint就会往后移动。
✨因此针对并发量大的系统,适当设置redo log的文件大小非常重要。
binlog
是什么上面提到的redo log是执行引擎层的log文件,binlog
是Server层面的log文件,也是就是所有执行引擎都有binlog
。
InnoDB
有一份log文件,MySQL还需要一份log文件呢这个跟MySQL的时间线有关系
最开始MySQL里并没有InnoDB
引擎,MySQL自带的是**MyISAM
,但是MyISAM
没有crash-safe的能力,binlog
**日志只能用于归档。
而 InnoDB
是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog
是没有 crash-safe 能力的,所以 InnoDB
** 使用 redo log 来实现 crash-safe 能力。**
redo log
和binlog
有什么区别?binlog
是MySQL的server层实现的日志,所有的存储引擎都可以使用InnoDB
**存储引擎实现的日志binlog
是追加写,写满一个文件,就创建一个新的文件继续写,不会覆盖以前的日志,保存的是全量的日志。binlog
用于备份恢复、主从复制binlog
是逻辑日志,记录的是语句的原始逻辑。不能,只能使用**binlog
,因为binlog
中保存的是全量**的日志,而redo log不是。
MySQL的主从复制依赖于binlog
,binlog
记录数据库中的所有变化并以二进制的形式保存在磁盘上,复制的过程就是将**binlog
**中的数据从主库传输到从库上。
✨这个过程一般是异步的
MySQL集群的主从复制过程可以整理成3个阶段
binlog
:主库写入binlog
**日志,提交事务并更新本地存储数据。binlog
:把binlog
复制到所有从库上,每个从库把binlog
写到暂存日志中 **。binlog
**:从库回放binlog
,并更新存储引擎中的数据。具体详细的过程如下
binlog
,再提交事务,更新存储引擎中的数据,事务提交完成后,返回给客户端操作成功的响应。binlog
日志,再把binlog
信息写入relay log的中继日志中,然后返回给主库复制成功的响应。binlog
的线程,去读relay log中继日志,然后回访binlog
更新存储引擎中的数据,最终实现主从的数据一致性。在完成主从复制后,你就可以在写数据时只写主库,在读数据时只读从库,这样即使写请求会锁表或者锁记录,也不会影响读请求的执行。
Redis的主从复制过程是如何进行的呢?
答案是否定的。
因为从库数量增加,从库连接上来的I/O线程也会更多,主库需要创建同样多的log dump线程来处理复制的请求,对主库资源消耗比较高,同时还受限于主库的网络带宽。
binlog
什么时候刷盘?事务执行过程中,先把日志写到**binlog cache
(Server层的cache) **,事务提交的时候,再把binlog cache
写到binlog
文件中。
✨一个事务的
binlog
是不能被拆开的
无论这个事务有多大,也要保证一次性写入,因为一个线程同时只能由一个事务在执行,所以每当执行一个begin/start transaction的时候,就会默认提交上一个事务,这样**如果一个事务的binlog
**被拆开的时候,在备库执行的时候就会被当作多个事务分段执行,这样破坏了原子性,是有问题的。
binlog cache
会写入到binlog
文件在事务提交的时候
✨虽然每个线程有自己
binlog cache
,但是最终都写到同一个**binlog
**文件:
Innodb
存储引擎设计了一个缓存池,来提高数据库的读写性能。
更新一条记录的时候,先缓存起来,这样下次有查询语句命中了这条记录,可以直接读取缓存中的记录,就不需要从磁盘中获取数据了。
Buffer Pool 除了缓存「索引页」和「数据页」,还包括了 Undo 页,插入缓存、自适应哈希索引、锁信息等等。
MySQL 刚启动的时候,你会观察到使用的虚拟内存空间很大,而使用到的物理内存空间却很小,这是因为只有这些虚拟内存被访问后,操作系统才会触发缺页中断,申请物理内存,接着将虚拟地址和物理地址建立映射关系。
在执行一条 增删改 语句的时候,虽然没有输入begin开始事务和commit提交事务,但是MySQL会隐式开启事务来执行增删改语句的。
执行一条语句是否自动提交事务,是由**autocommit
**参数决定的,默认是开启的。
✨undo log(回滚日志),保证了事务ACID特性中的原子性
undo log是一种用于撤销回退的日志。在事务没提交之前,MySQL会先记录更新前的数据到undo log日志文件里面,当事务回滚时,可以利用undo log来进行回滚。
每当 InnoDB
引擎对一条记录进行操作(修改、删除、新增)时,要把回滚时需要的信息都记录到 undo log 里
不同的操作,需要记录的内容也是不同的,所以不同类型的操作(修改、删除、新增)产生的 undo log 的格式也是不同的。
一条记录的每一次更新操作产生的undo log格式都有一个**roll_pointer
指针和一个trx_id
**事务id。
trx_id
**可以知道该记录是被哪个事务修改的roll_pointer
指针可以将这些undo log串成一个链表,这个链表就被称为版本链 **。undo log还有一个作用,通过**ReadView + undo log
**实现多版本并发控制
对于读已提交和可重复度隔离级别的事务来说,它们的快照读(普通的select语句)是通过**ReadView + undo log
**来实现的,区别在于创建Read View 的时机不同。
这两个隔离级别的实现是通过事务的Read View里的字段和undo log记录中的两个隐藏列(trx_id
和roll_pointer
)的对比,如果不满足可见行,就会顺着undo log版本链里找到满足其可见性的记录,从而控制并发事务访问同一个记录时的行为,就叫MVCC
。
MVCC
**的关键因素之一。事务提交后,redo log
和binlog
都要持久化到磁盘,但这两个是独立的逻辑,可能出现版成功的状态,这样就造成两份日志之间的逻辑不一致。
✨将redo log的写入拆成了两个步骤:prepare和commit,中间再穿插写入
binlog
。
虽然保证了两个日志文件的数据一致性,但性能很差。