事务有4种特性:原子性、一致性、隔离性和持久性
。那么事务的四种特性到底是基于什么机制实现呢?
锁机制
实现。redo 日志
和undo 日志
来保证。
undo log(回滚日志)
:是 Innodb 存储引擎层
生成的日志,实现了事务中的原子性,主要用于事务回滚和 MVCC。(回滚行记录到某个特定版本,用来保证事务的原子性、一致性。)redo log(重做日志)
:是 Innodb 存储引擎层
生成的日志,实现了事务中的持久性,主要用于掉电等故障恢复;(提供再写入操作,恢复提交事务修改的页操作,用来保证事务 的持久性。)binlog (归档日志)
:是 Server 层
生成的日志,主要用于数据备份和主从复制;MySQL是先把磁盘上的数据加载到内存中,在内存中对数据进行修改,再写回到磁盘上。如果此时突然宕机,内存中的数据就会丢失。 怎么解决这个问题? 简单啊,事务提交前直接把数据写入磁盘就行啊。 这么做有什么问题?
于是,决定采用redo log解决上面的问题。当做数据修改的时候,不仅在内存中操作,还会在redo log中记录这次操作。当事务提交的时候,会将redo log日志进行刷盘(redo log一部分在内存中,一部分在磁盘上)。当数据库宕机重启的时候,会将redo log中的内容恢复到数据库中,再根据undo log和binlog内容决定回滚数据还是提交数据。
redo日志占用的空间非常小
,只是记录哪一页修改了啥,修改什么内容是不知道的。体积小,刷盘快,降低刷盘频率。(具体数据是回滚还是提交,再根据undo log和redo log内容决定是回滚数据还是提交数据)redo日志是顺序写入磁盘的
,它是一直往末尾进行追加,属于顺序IO。事务执行过程中,redo log不断记录
第1步:先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝
第2步:生成一条重做日志并写入redo log buffer,记录的是数据被修改后的值(此时并不会直接将redo log buffer的内容进行刷盘,而是放在了操作系统的缓冲区中,由操作系统进行系统调用fsync()
函数决定什么时候刷盘)
第3步:当事务commit时,将redo log buffer中的内容刷新到 redo log file,对 redo log file采用追加写的方式
第4步:定期将内存中修改的数据刷新到磁盘中
Write-Ahead Log(预先日志持久化):在持久化一个数据页之前,先将内存中相应的日志页持久化(MySQL 的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上。)。
被修改 Undo 页面,需要记录对应 redo log 吗?
刷盘是指从redo log buffer到redo log file中,只要这个过程不出问题,就不会有事
redo log buffer
刷到 文件系统缓存(page cache)
中去,然后由操作系统判断什么时候刷盘。InnoDB给出 innodb_flush_log_at_trx_commit
参数,该参数控制 commit提交事务时,如何将 redo log buffer 中的日志刷新到 redo log file 中。它支持三种策略:
设置为0
:表示每次事务提交时不进行刷盘操作。(系统默认master thread每隔1s进行一次重做日设置为1
:表示每次事务提交时都将进行同步,刷盘操作( 默认值
)设置为2
:表示每次事务提交时都只把 redo log buffer 内容写入 page cache,不进行同步。由os自「重做日志文件组」由有 2 个 redo log 文件组成
ib_logfile0 和 ib_logfile1
。流程
:
更新数据
的 前置操作
其实是要先写入一个 undo log
。事务需要保证原子性
,也就是事务中的操作要么全部完成,要么什么也不做。但有时候事务执行到一半会出现一些情况,比如:
服务器本身的错误 , 操作系统错误
,甚至是突然 断电 导致的错误。ROLLBACK
语句结束当前事务的执行。以上情况出现,我们需要把数据改回原先的样子,这个过程称之为 回滚
,这样就可以造成一个假象:这个事务看起来什么都没做,所以符合 原子性
要求。
一条记录的每一次更新操作产生的 undo log 格式都有一个 roll_pointer 指针和一个 trx_id 事务id:
trx_id
可以知道该记录是被哪个事务修改的;roll_pointer
指针可以将这些 undo log 串成一个链表,这个链表就被称为版本链;作用1:回滚数据,保证事务的原子性
逻辑日志
,因此只是将数据库从逻辑上恢复到原来的样子,但是物理层面做了一些改变是不会恢复的(例如创建了一个页,里面插入一个数据,然后回滚是删除该条数据,但是整个页是不会删除的)作用2:MVCC
当前事务可以通过undo读取之前的版本信息
,以此实现非锁定读取row_id
:行ID,唯一标识一条记录(如果没有定义主键,也没有定义唯一索引,那么innodb会自动为表添加一个row_id的隐藏列作为主键)transaction_id
:事务ID(每个事务都会分配一个事务ID,当对某条记录发生变更时,就会将这个事务的事务ID写入trx_id中)roll_pointer
::回滚指针(本质上是指向undo log的指针)针对于insert undo log
针对于update undo log
总结
:
undo log是逻辑日志
,对事务回滚时,只是将数据库逻辑地恢复到原来的样子。redo log是物理日志
,记录的是数据页的物理变化,undo log不是redo log的逆过程。
- undo log 和数据页的刷盘策略是一样的,都需要通过 redo log 保证持久化。
- buffer pool 中有 undo 页,对 undo 页的修改也都会记录到 redo log。redo log 会每秒刷盘,提交事务时也会刷盘,数据页和 undo 页都是靠这个机制保证持久化的。
binlog即binary log,二进制日志文件,也叫作变更日志(update log)。它记录了数据库所有执行的
DDL
和 DML
等数据库更新事件的语句,但是不包含没有修改任何数据的语句(如数据查询语句select、show等)。
binlog主要应用场景:
数据恢复
:如果MySQL数据库意外停止,可以通过二进制日志文件来查看用户执行了哪些操作,对数据库服务器文件做了哪些修改,然后根据二进制日志文件中的记录来恢复数据库服务器。数据复制
:由于日志的延续性和时效性,master把它的二进制传递给slaves来达到master-salve数据一致的目的。事务执行过程中,先把日志写到 binlog cache ,事务提交的时候,再把binlog cache写到binlog文件中。
因为一个事务的binlog不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为binlog cache。
write
,指的就是指把日志写入到 binlog 文件,但是并没有把数据持久化到磁盘,因为数据还缓存在文件系统的 page cache 里,write 的写入速度还是比较快的,因为不涉及磁盘 I/O。
fsync
,才是将数据持久化到磁盘的操作,这里就会涉及磁盘 I/O,所以频繁的 fsync 会导致磁盘的 I/O 升高。
write和fsync的时机,可以由参数 sync_binlog 控制,默认是 0 。为0的时候,表示每次提交事务都只write,由系统自行判断什么时候执行fsync。虽然性能得到提升,但是机器宕机,page cache里面的binglog 会丢失。如下图
1.适用对象不同:
redo log 它是 物理日志
,记录内容是“在某个数据页上做了什么修改”,属于 InnoDB 存储引擎层
产生的。(真实记录在物理磁盘页的)binlog 是 逻辑日志
,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”,属于MySQL Server 层
,所有存储引擎都可以使用;2.文件格式不同:
3.用途不同:
redo log在事务执行的过程中不断写入,而binlog只有在提交事务时才写入,所以redolog与binlog的写入时机不一样
4.写入方式不同
追加写
,写满一个文件,就创建一个新的文件继续写,不会覆盖以前的日志,保存的是全量的日志。循环写
,日志空间大小是固定,全部写满就从头开始,保存未被刷入磁盘的脏页日志。 写入时机
不一样。就会导致由于binlog没写完就异常,这时候binlog里面没有对应的修改记录。
两阶段提交
方案。两阶段提交
后,写入binlog时发生异常也不会有影响流程
prepare 阶段
:将 XID(内部 XA 事务的 ID) 写入到 redo log,同时将 redo log 对应的事务状态设置为 prepare,然后将 redo log 持久化到磁盘(innodb_flush_log_at_trx_commit = 1 的作用);
commit 阶段
:把 XID 写入到 binlog,然后将 binlog 持久化到磁盘(sync_binlog = 1 的作用),接着调用引擎的提交事务接口,将 redo log 状态设置为 commit,此时该状态并不需要持久化到磁盘,只需要 write 到文件系统的 page cache 中就够了,因为只要 binlog 写磁盘成功,就算 redo log 的状态还是 prepare 也没有关系,一样会被认为事务已经执行成功;
不管是时刻 A(redo log 已经写入磁盘, binlog 还没写入磁盘),还是时刻 B (redo log 和 binlog 都已经写入磁盘,还没写入 commit 标识)崩溃,此时的 redo log 都处于 prepare 状态。
如果 binlog 中没有当前内部 XA 事务的 XID,说明 redolog 完成刷盘,但是 binlog 还没有刷盘,则回滚事务。
对应时刻 A 崩溃恢复的情况。如果 binlog 中有当前内部 XA 事务的 XID,说明 redolog 和 binlog 都已经完成了刷盘,则提交事务
。对应时刻 B 崩溃恢复的情况。注意:
事务没提交的时候,redo log 也是可能被持久化到磁盘的。
磁盘 I/O 次数高:对于“双1”配置,每个事务提交都会进行两次 fsync(刷盘),一次是 redo log 刷盘,另一次是 binlog 刷盘。
锁竞争激烈:两阶段提交虽然能够保证「单事务」两个日志的内容一致,但在「多事务」的情况下,却不能保证两者的提交顺序一致,因此,在两阶段提交的流程基础上,还需要加一个锁来保证提交的原子性,从而保证多事务的情况下,两个日志的提交顺序一致。