| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 大数据 -> 详解Redo log与Undo log -> 正文阅读 |
|
[大数据]详解Redo log与Undo log |
一、前言关系型数据的四大特性包括了原子性、一致性、隔离性、持久性(ACID)。 总的来说,InnoDB存储引擎的原子性是通过 而原子性、持久性、隔离性都只是手段,其目的是为了实现一致性。MySQL满足的是其自身内部数据的一致性,而对于具体业务的一致性,还需要应用程序本身遵守一致性规约。 MySQL事务实现的机制是WAL(Write-ahend logging,预写式日志),这是比较主流的方案。 在MySQL服务异常奔溃后,使用WAL,可以在系统重启之后,通过比较日志和系统状态来决定继续之前的操作或者是撤销之前的操作。
除了 Commit Logging只有在日志记录全部都安全写入磁盘之后,数据库在日志中看到代表事务成功的“提交记录”(Commit Record)之后,才会根据日志上的信息对真正的数据进行修改,修改完成后,再在日志中加入一条“结束记录”(End Record)表示事务已完全持久化。与WAL的区别是:WAL允许在事务提交之前,提前写入变动数据,而Commit Loggin不行;同时WAL中有undo log,Commmit Logging中却没有。 注:阿里的OceanBase使用的Commint Logging来实现事务。 Shadow Paging的实现是数据的变动并不直接修改原来的数据,而是对需要修改的数据生成一个副本,保留原数据,修改副本数据。因此在整个事务过程中,需要修改的数据会同时存在两份,即修改前的数据和修改后的数据,当事务成功提交,所有数据的修改都成功持久化之后,最后一步是去修改数据引用的指针,将引用从原数据修改为副本数据,最后修改指针的这个操作被认为是原子操作。 二、Redo log在前面 如果每次修改一条数据,就把整个内存页数据刷新到磁盘是非常浪费的,并且由于一个事务可能包含了多个执行语句,而执行语句对应的数据可能分散在不同的数据页,这样写磁盘就是多次随机IO操作,性能是非常低下的。 所以InnoDB引擎就引入了Redo Log来提高性能, 但每一条数据的修改,都会记录一条 变相来说,redo log实现了内存页数据刷新到磁盘从随机IO变成了顺序IO,当然 与在事务提交时将所有修改过的内存中的页面刷新到磁盘中相比,只将该事务执行过程中产生的redo日志刷新到磁盘的好处如下:
2.1 redo 日志格式
大部分类型的 在前面介绍行记录的文章中提到,如果没有为某个表显式的定义主键,并且表中也没有定义Unique键,那么InnoDB会自动的为表添加一个称之为 为这个row_id隐藏列赋值的方式如下:
这个写入实际上是在
上边提到的 offset代表在页面中的偏移量。 其余MLOG_1BYTE、MLOG_2BYTE、MLOG_4BYTE类型的redo日志结构和MLOG_8BYTE的类似,只不过具体数据中包含对应个字节的数据罢了。MLOG_WRITE_STRING类型的redo日志表示写入一串数据,但是因为不能确定写入的具体数据占用多少字节,所以需要在日志结构中还会多一个len字段。 但通常执行一条SQL语句,除了要记录索引树的变化外,还有什么File Header、Page Header、Page Directory等等部分,所以每往叶子节点代表的数据页里插入一条记录时,还有其他很多地方会跟着更新,比如说: 更新Page Directory中的槽信息、Page Header中的各种页面统计信息,比如槽数量可能会更改,还未使用的空间最小地址可能会更改,本页面中的记录数量可能会更改,各种信息都可能会被修改。 同时数据页里的记录是按照索引列从小到大的顺序组成一个单向链表的,每插入一条记录,还需要更新上一条记录的记录头信息中的next_record属性来维护这个单向链表。 如果使用上边介绍的简单的物理redo日志来记录这些修改时,可以有两种解决方案:
正因为上述两种使用物理redo日志的方式来记录某个页面中做了哪些修改比较浪费,InnoDB中就有非常多的redo日志类型来做记录。 2.2 redo 日志写入过程2.2.1 redo log block和log buffer前面提到了,redo log本身也有自己对应的缓冲区,实际上在服务器启动时就向操作系统申请了一大片称之为 这片内存空间被划分成若干个连续的 向 2.2.2 redo log刷盘时机
2.2.3 redo log 文件组MySQL的数据目录(使用
磁盘上的redo日志文件可以不只一个,而是以一个日志文件组的形式出现的。这些文件以ib_logfile[数字](数字可以是0、1、2…)的形式进行命名。在将redo日志写入日志文件组时,是从ib_logfile0开始写,如果ib_logfile0写满了,就接着ib_logfile1写,同理,ib_logfile1写满了就去写ib_logfile2,依此类推。如果最后一个文件写满,那就重新转到ib_logfile0继续写。 注:Redo log文件是循环写入的,在覆盖写之前,总是要保证对应的脏页已经刷到了磁盘。在非常大的负载下,为避免错误的覆盖,InnoDB 会强制的flush脏页 2.2.4 redo log文件格式
将 redo日志文件组中的每个文件大小都一样,格式也一样,都是由两部分组成:前2048个字节,也就是前4个block是用来存储一些管理信息的。 从第2048字节往后是用来存储 2.3 Log Sequence NumberInnoDB为记录已经写入的redo日志量,设计了一个称之为 规定初始的lsn值为8704(也就是一条redo日志也没写入时,LSN的值为8704)。 注:LSN记录的写入都log buffer中的日志序列号,并不是写入到redo log文件中日志序列号 2.3.1 flushed_to_disk_lsnredo log首先是就到 而InnoDB中有一个 上面说的 系统第一次启动时,该变量的值和初始的lsn值是相同的,都是8704。随着系统的运行,redo日志被不断写入 当有新的redo日志写入到 注:应用程序向磁盘写入文件时其实是先写到操作系统的缓冲区中去,如果某个写入操作要等到操作系统确认已经写到磁盘时才返回,那需要调用一下操作系统提供的fsync函数。其实只有当系统执行了fsync函数后, 2.3.2 查看系统中的各种LSN值可以使用下面的命令查看当前InnoDB存储引擎中的各种LSN值的情况:
查询信息如下:
Log sequence number:代表系统中的lsn值,也就是当前系统已经写入的redo日志量,包括写入log buffer中的日志。 2.3.3 innodb_flush_log_at_trx_commit为了保证事务的持久性,用户线程在事务提交时需要将该事务执行过程中产生的所有redo日志都刷新到磁盘上。 这会很明显的降低数据库性能。如果对事务的持久性要求不是那么强烈的话,可以选择修改系统变量 0:当该系统变量值为0时,表示在事务提交时不立即向磁盘中同步redo日志,这个任务是交给后台线程做的。 1:当该系统变量值为1时,表示在事务提交时需要将redo日志同步到磁盘,可以保证事务的持久性。1也是innodb_flush_log_at_trx_commit的默认值。 2:当该系统变量值为2时,表示在事务提交时需要将redo日志写到操作系统的缓冲区中,但并不需要保证将日志真正的刷新到磁盘。 这种情况下如果数据库挂了,操作系统没挂的话,事务的持久性还是可以保证的,但是操作系统也挂了的话,那就不能保证持久性了。 三、Undo log事务原子性需要保证事务中的操作要么全部完成,要么什么也不做。但通常会遇到下面的情况:
上面这两种情况就导致事务执行到一半就结束了,但可能已经修改了很多数据,为了事务的原子性,需要把修改的数据给还原回来,这个过程就是回滚。 InnoDB引擎中的回滚通过 而对于增删查改,不同的操作产生的 3.1 事务ID3.1.1 分配时机事务可以是只读事务,也可以是读写事务。 可以通过 对于只读事务来说,它不能对普通的表进行增删改的操作,但是可以对创建的临时表执行增删改操作,且只有在第一次执行增删改操作时,这个事务才会给分配一个事务id,否则的话是不分配事务id的。 对于读写事务来说,只有在它第一次对某个表(包括用户创建的临时表)执行增删改操作时才会为这个事务分配一个事务id,否则的话也是不分配事务id的。 注:虽然开启了一个读写事务,但是在这个事务中全是查询语句,并没有执行增、删、改的语句,那也就意味着这个事务并不会被分配一个事务id。 3.1.2 分配策略事务id本质上就是一个数字,它的分配策略和我们前边提到的对隐藏列row_id(当用户没有为表创建主键和UNIQUE键时InnoDB自动创建的列)的分配策略大抵相同:
这样就可以保证整个系统中分配的事务id值是一个递增的数字。先被分配id的事务得到的是较小的事务id,后被分配id的事务得到的是较大的事务id。 3.1.3 隐藏列trx_id在前面《InnoDB存储结构》的文章中介绍过了,一条记录除了保存真实数据外,还会有额外信息和隐藏列,而隐藏列中有 其中的 3.2 undo log格式一个事务在执行过程中可能新增、删除、更新若干条记录,也就是说需要记录很多条对应的undo日志,这些undo日志会被从0开始编号,也就是说根据生成的顺序分别被称为第0号undo日志、第1号undo日志、…、第n号undo日志等,这个编号也被称之为undo NO。 表空间其实是由许许多多的页面构成的,页面有不同的类型,其中有一种称之为
3.2.1 INSERT对应的undo log对于插入操作的回滚日志,InnoDB设计了一个类型为 当向某个表中插入一条记录时,实际上需要向聚簇索引和所有的二级索引都插入一条记录。 但对于 3.2.2 roll_pointerroll_pointer本质上就是一个指向记录对应的undo日志的一个指针。 比方说我们向表里插入了2条记录,每条记录都有与其对应的一条undo日志。记录被存储到了类型为 3.2.3 DELETE对应的undo log在介绍行记录结构和索引页结构的时候,介绍过每个行记录都有一个
如图所示(只把记录的 使用DELETE语句把正常记录链表中的最后一条记录给删除掉,其实这个删除的过程需要经历两个阶段。 第一阶段: 将记录的 可以看到,正常记录链表中的最后一条记录的 第二阶段: 所谓真正的删除就是把该记录从正常记录链表中移除,并且加入到垃圾链表中,然后还要调整一些页面的其他信息,比如页面中的用户记录数量PAGE_N_RECS、上次插入记录的位置PAGE_LAST_INSERT、垃圾链表头节点的指针PAGE_FREE、页面中可重用的字节数量PAGE_GARBAGE、还有页目录的一些信息等等。这个阶段称之为purge。 把阶段二执行完了,这条记录就算是真正的被删除掉了。这条已删除记录占用的存储空间也可以被重新利用了。 从上边的描述中也可以看出来,在删除语句所在的事务提交之前,只会经历阶段一,也就是 3.2.4 UPDATE对应的undo log在执行UPDATE语句时,InnoDB对更新主键和不更新主键这两种情况有截然不同的处理方案。 不更新主键在不更新主键的情况下,又可以细分为被更新的列占用的存储空间不发生变化和发生变化的情况。
针对UPDATE不更新主键的情况(包括上边所说的就地更新和先删除旧记录再插入新记录),InnoDB设计了一种类型为 更新主键针对UPDATE语句中更新了记录主键值的这种情况,InnoDB在聚簇索引中分了两步处理:
针对UPDATE语句更新记录主键值的这种情况,在对该记录进行 3.3 事务流程3.3.1 事务执行MySQL在事务执行的过程中,会记录相应SQL语句的UndoLog 和 Redo Log,然后在内存中更新数据并形成数据脏页。 接下来Redo Log会根据一定规则触发刷盘操作,Undo Log 和数据脏页则通过刷盘机制将数据持久化至磁盘文件。 事务提交时,会将当前事务相关的所有Redo Log刷盘,只有当前事务相关的所有Redo Log 刷盘成功,事务才算提交成功。 3.3.2 事务恢复如果MySQL由于某种原因崩溃或者宕机,就需要数据的恢复或者回滚操作。 如果事务在执行至上面的第8步(事务未成功提交),即事务提交之前,MySQL 崩溃或者宕机,此时会先使用Redo Log恢复数据,然后使用Undo Log回滚数据。 如果在执行第8步之后MySQL崩溃或者宕机,此时会使用Redo Log恢复数据,大体流程如下图所示。 MySQL崩溃恢复后,首先会获取日志检查点信息,随后根据日志检查点信息使用Redo Log进行恢复。MySQL崩溃或者宕机时事务未提交,则接下来使用Undo Log回滚数据。如果在MySQL崩溃或者宕机时事务已经提交,则用Redo Log恢复数据即可 3.3.3 恢复机制MySQL可以根据redo日志中的各种LSN值,来确定恢复的起点和终点。 然后将redo日志中的数据,以哈希表的形式,将一个页面下数据放到哈希表的一个槽中。 之后就可以遍历哈希表,因为对同一个页面进行修改的redo日志都放在了一个槽里,所以可以一次性将一个页面修复好(避免了很多读取页面的随机IO)。并且通过各种机制,避免无谓的页面修复,比如已经刷新的页面,进而提升崩溃恢复的速度。 3.3.4 崩溃后的恢复为什么不用binlog?
3.4 redo log和undo log关系数据库崩溃重启后,需要先从redo log中把未落盘的脏页数据恢复回来,重新写入磁盘,保证用户的数据不丢失。 当然,在崩溃恢复中还需要把未提交的事务进行回滚操作。由于回滚操作需要undo log日志支持,undo log日志的完整性和可靠性需要redo log日志来保证,所以数据库崩溃需要先做redo log数据恢复,然后做undo log回滚。 redo log是物理日志,记录的是数据库页的物理修改操作。所以undo log(可以看成数据库的数据)的写入也会伴随着redo log的产生,这是因为undo log也需要持久化的保护。 事务进行过程中,每次sql语句执行,都会记录undo log和redo log,然后更新数据形成脏页。 事务执行 3.5 redo log和binlog一致性当我们开启了MySQL的BinLog日志,很明显需要保证BinLog和事务日志的一致性,为了保证二者的一致性,使用了两阶段事务2PC(所谓的两个阶段是指:第一阶段:准备阶段和第二阶段:提交阶段)。步骤如下:
|
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 1:18:58- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |