zoukankan      html  css  js  c++  java
  • [译]数据库是如何工作(六)数据管理器

    这步中,查询管理器正在执行查询并需要从表和索引中获取数据。它这会要求数据管理器给它数据,但这有两个问题:

    • 关系数据库使用一个事务模型。所以你有可能会有时拿不到数据因为正好那时有人在使用/修改数据。
    • 数据库检索是数据库最慢的操作 。所以数据库需要很聪明地在内存缓冲区中存取数据。

    在这部分,我们会看到关系数据库是如何解决这两个问题。我不会谈及数据管理器获取数据的方式,因为这不太重要(这篇文章已经够长了)

    缓存管理器

    就像我之前说的,数据库的主要瓶颈是磁盘 I/O。为了提高性能,现代数据库使用缓存管理器。

    查询管理器不会直接从系统中拿数据,而是去缓存管理器请求数据。缓存管理器有个叫缓冲池(buffer pool)的内存缓存。从内存中获取数据会大大加快数据库的速度 。但这很难给出一个具体的数量级,因为这取决于你需要的是哪种操作:

    • 顺序访问(如:全局扫描) vs 随机访问(如:通过行ID直接访问)
    • 读 vs 写

    数据库用的是什么磁盘

    • 7.2k/10k/15k 转的 HDD
    • 固态硬盘
    • RAID 1/5/...

    但我还是要说内存比磁盘快100到100k倍。 这又导致另一个问题的出现(数据库总是这样。。。),缓存管理器需要在查询执行器使用数之前,从内存中获取数据;所以查询管理器需要等待数据从慢磁盘中获取

    预读取数据

    这个问题叫预读取。查询管理器知道将会需要数据了,因为它知道查询的完整流程和磁盘上的数据的统计信息。构思如下:

    • 当查询执行器正在处理第一块(bunch)的数据
    • 它会要求缓存管理器要预加载第二块(bunch)的数据
    • 当开始处理第二块的数据时
    • 它会要求缓存管理器要预加载第三块(bunch)的数据,并告诉缓存管理器可以第三块的数据可以从缓存中清理掉了
    • ...

    缓存管理器会在缓冲区中存储所有数据。为了知道数据是否仍然需要,缓存管理器会为数据添加了一个缓存日期(叫闩latch) 有时查询执行器不知道需要什么数据,有时候数据库也不提供功能。相反他们会用推测预读(例如:如果查询执行器要数据 1,3,5,它可能在不久的将来会要数据 7,9,11) 又或者一个顺序的预读取(在这种情况下,缓存管理器在一次请求后,简单地加载下一个连续的数据)

    注意:缓存命中率不高并不总是意味着缓存不正常。有关更多信息,请阅读 Oracle文档

    但,缓冲是的内存是有限的。因此,它需要将一些数据移走并加载新的数据。加载和清理缓存需要一点磁盘和网络的I/O 成本。如果你有一个查询要经常执行,使用这查询的时候总是要加载数据清理数据,这也未免太没效率的。为了解决这个问题,现代数据库使用一种缓冲区替换策略

    缓存区替换策略

    LRU

    LRU是指(Least Recently Used)最近用得最少的。这个算法背后的构想是在缓存中保留最近使用,这些数据更有可能会再次使用 下面是个直观的例子

    为了便于理解,我会设计这些在缓冲区的数据没有被闩(latch)锁住(所以能被移除)。在这个简单的例子中,这个缓冲区可以存储3个元素

    1. 缓冲管理器用了数据1,然后把数据放到一个空的缓冲区
    2. 缓冲管理器用了数据4,然后把数据放半满载缓冲区
    3. 缓冲管理器用了数据3,然后把数据放到半满载缓冲
    4. 缓冲管理器用了数据9,缓冲区已满,* 于是将数据1移除,因为它是最早使用的数据* 。然后把数据9加入到缓冲区 5) 缓冲管理器用了数据4,而数据4之前已经在缓冲区存在了,所有数据3成了缓冲区最早使用的数据
    5. 缓冲管理器用了数据1,缓冲区已满,于是数据3被清除因为它是最早使用的数据,数据1 被添加到缓冲区中。 这算法能很好地工作,但也存在一些局限性。如果在一个大表中进行全局搜索呢?换句话说,如果表/索引的大小比缓冲区还大会发生什么事呢?使用这算法会把之前在缓冲中的值全部移走,但是全局扫描可能只会使用一次

    改善一下

    为了防止上述的情况,某些数据库会添加特定的规则。如果根据Oracle 文档所言

    对于很大的表,数据库会直接用路径读,这直接加载块... 以避免填满缓冲区缓存。对于中等大小的表,数据库可以使用直接读取或者是读缓存。如果它决定要读缓存,数据库会把这块放到 LRU列表的最后,来防止扫描有效地清除缓存区缓存

    也有很多其他的办法像是一个LRU的高级版本叫 LRU-K。像 SQL Server 就用了 LRU-k 而 K = 2 这算法背后的思想是要考虑更多的历史。使用简单的 LRU(K=1时的LRU-K),算法只用考虑上次使用数据的时间。而LRU-K:

    • 它会考虑最后K次的数据使用情况
    • 数据的使用次数会加入权重
    • 如果一堆新数据会被加载到缓存,则不会删除经常使用的旧数据(因为他们的权重中更高)
    • 但如果数据不再使用了,这算法也不会一直把数据保留到缓冲区
    • 所以随着时间的推移, 一直没用到的数据权重会一直递减

    而计算权重的成本是很大的,这就是 SQL Server 只用到 K = 2。这个值在可接受的成本范围内性能不错。 关于 LRU-K 的更深入的学习,你可以阅读最原始的研究论文(1993):《用于数据库磁盘缓冲与的LRU-K页替换算法》

    其他算法

    当然啦,还有很多其他的用于管理缓存的算法,像是:

    • 2Q:(和 LRU-K 类似的算法)、
    • CLOCK算法 (和 LRU-K 类似的算法)
    • MRU (most recently use,最近最常使用,和 LRU 一样都是使用逻辑但用不同的规则)
    • LRFU (最近且最常使用)

    有些数据库可能允许使用其他的算法而不是默认算法

    写入缓冲区


    我只讲过在去缓冲区要在使用前先加载。但在数据库中,有写缓冲区的操作,这用来存储数据,把数据串联起来刷新磁盘数据。而不是逐个逐个地写数据,产生很多的单次磁盘访问。

    请记住,buffer 存储的是页(page,数据的最小单元)而不是 row(逻辑上/人性化观察数据的)。一个页在缓冲池被修改但没有写入到磁盘是肮脏的。有很多算法能决定脏页写入磁盘的最佳时间,它和事务概念关系很密切,那是下一部分的内容。

    事务管理器

    最后,但也很重要,这部分会讲事务管理器。我们将看到进程是如何确保每个查询都在自己的事务中执行。在此之前,我们需要明白事务的

    I’m on acid(我酸了。。。)

    一个ACID事务是一个工作单元,它要保证4个属性:

    • 原子性(Atomicity) :即使持续10个小时,交易也是“全部或全部”。如果事务崩溃,则状态返回到事务之前( 事务被回滚 )。
    • 隔离性(Isolation) : 如果2个事务A和B同时运行事务A和B的结果必须相同,不管A是否在事务B之前/之后/期间完成
    • 持久性(Durability) : 一旦事务被提交(即成功结束),无论发生什么(崩溃或错误),数据都会保留在数据库中。
    • 一致性(Consistency) : 只有合法的数据(关于关系约束和功能约束)能写入数据库,一致性与原子性和隔离性有关

    在同一事务期间,您可以运行多个SQL查询来读取,创建,更新和删除数据。当两个事务使用相同的数据时,开始混乱了。典型的例子是从账户A到账户B的汇款。想象一下,您有2笔事务:

    • 事务1(T1)从账户A取出100美元给账户B
    • 事务2(T2)从账户A取出50美元给账户B

    如果我们回到ACID属性:

    • 原子性(Atomicity) :确保无论在T1期间发生什么(服务器崩溃,网络故障......),你最终都不能出现从A取走100元而B没有收到钱的情况(这种情况是不一致的状态)
    • 隔离性(Isolation): 确保如果T1和T2同时发生,最终A都会取出150元,而B都会得到150元,而不是其他结果。例如:A被取走150元,而B只得到50元,因为T2清掉了T1的部分行为(这也是状态不一致)
    • 持久性(Durability) : 如果数据库在 T1 提交后奔溃,持久性能确保了T1不会凭空消失
    • 一致性(Consistency) : 确保系统中不会(无故)创建或销毁任何资金。

    [如果你愿意,可以跳到下一部分,我要说的对于文章的其余部分并不重要]

    许多现代数据库不使用纯隔离作为默认行为,因为它带来了巨大的性能开销。 SQL规范定义了4个级别的隔离:

    • 可序列化(Serializable,SQLite默认模式):最高级别的隔离。两个同时发生的事务100%隔离,每个事务有自己的“世界”。
    • 可重复读取(MySQL中的默认行为):除了一种情况外,每个事务都有自己的“世界”。 如果事务提交成功并添加新数据,则这些数据将在另一个仍在运行的事务中可见。 但是,如果A修改数据提交成功,修改后的数据对正在运行的事务中不可见。 因此,事务之间的这种隔离中断只涉及新数据,而不是现有数据。

    例如,如果事务A执行 “TABLE_X中的SELECT count(1)”,然后由事务B在TABLE_X中添加并提交新数据,如果事务A再次执行count(1),则该值将不是相同。 这称为幽灵读取(phantom read)

    • 读取已提交(Oracle,PostgreSQL和SQL Server中的默认行为):这是可重复读+突破隔离性。如果事务A读了数据D,然后这数据被修改(或者删除),并被B提交了。如果A再次读,事务A再次读取数据D时就会看到被B修改的数据的改变(或者删除部分) 这称为不可重复读取。
    • 读取未提交:最低级别的隔离。它是一个读取已提交+一个新的隔离中断。 如果事务A读取数据D,然后该事务(未提交但仍在运行)修改了数据D,则如果A再次读取数据D,则它将看到修改的值。 如果事务B被回滚,那么第二次由A读取的数据D没有任何意义,因为它已经被事件B修改过,从未发生过(因为它被回滚)。 这称为脏读

    多数数据库添加了自己的自定义的隔离级别(比如 PostgreSQL、Oracle、SQL Server的使用快照隔离),而且并没有实现SQL规范里的所有级别(尤其是读取未提交级别)。

    默认的隔离级别可以由用户/开发者在建立连接时覆盖(只需要增加很简单的一行代码)。

    并发控制

    确保隔离性,一致性和原子性的真正问题是对相同数据(添加,更新和删除)的写操作:

    • 如果所有事务仅读取数据,则它们可以同时工作,而无需修改另一个事务的行为。
    • 如果(至少)其中一个事务是修改其他事务读取的数据,则数据库需要找到一种方法来隐藏其他事务中的此修改。此外,它还需要确保不会被另一个没有查看修改数据的事务擦除此修改。

    这种问题叫 并发控制

    解决问题的最简单的方式是每个事务逐一运行(按顺序)。但这根本就没有伸缩性的,一个多进程/多核心的服务器上只有一个核,这太没效率了

    解决这个问题的方法是,每次创建或取消事务:

    • 监控所有事务的的操作
    • 检查是否存在两个或以上的事务存在冲突,因为他们正在读/改相同的数据
    • 给冲突的事务重新编排来减少冲突部分的数量
    • 按一定的顺序执行冲突的部分(同时非冲突的部分并发执行)
    • 要考虑事务有可能被取消

    更正规地说,这是一个调度冲突的问题。更具体地讲,这是个非常难的且CPU开销大的优化的问题。企业级数据库无法负担等待数小时,为新的事务找寻最佳的调度。因此,他们用不太理想的方法,它会让更多的时间花费在处理事务冲突上。

    锁管理

    为了解决这个问题,大部分数据库使用 和/或 数据版本控制。由于这是个大话题,我关注点会在锁的部分,然后我会说一小点数据版本控制

    悲观锁

    这锁背后的思想是:

    • 如果事务需要数据
    • 它就锁住数据
    • 如果另一个事务也需要数据
    • 它要等到第一个事务释放数据

    这种叫排他锁(exclusive lock) 但对事务只是要读取数据,使用排他锁就很昂贵了。因为它强制让那些只想读一些数据的事务去等待。 这就是为什么会有另外一种锁,共享锁(share lock) 共享锁是这样的:

    • 如果事务只需要读数据A
    • 它会共享锁定数据,并读取数据
    • 如果第二个事务也要读数据A
    • 它会共享锁定数据,并读取数据A
    • 如果第三个事务要修改数据A
    • 它“排除锁定”数据,它必须等到其他2个事务释放其共享锁,才能对数据A使用其排他锁

    但是,如果数据在用排它锁,而事务只需要读数据,也不得不等到排他锁结束才能用共享锁锁住数据

    锁管理器是提供和释放锁的进程。在内部,它用哈希表(key是被锁的数据)存储了锁,并且知道每个数据

    • 那个事务锁住了数据
    • 那个事务在等待数据

    死锁

    但是使用锁可能导致2个事务永远等待数据的情况:

    在这图中:

    • 事务A有一个排他锁锁住了数据1,要等待数据2
    • 事务B有一个排他锁锁住了数据2,要等待数据1

    这叫做 死锁 。 在死锁中,锁管理器选择要取消(回滚)事务来删除死锁,这个决定也不太容易啊

    • 杀掉修改数据量最小的事务(这会产生最便宜的回滚)更好吗?
    • 杀死另一个最新的事务,因为旧的事务已经等待很长时间了,是不是更好?
    • 杀死能用更少时间结束的事务(避免可能的资源饥饿)?
    • 在回滚的情况下,此回滚会影响多少个事务?

    但在做出这个选择之前,需要检查是否存在死锁。 哈希表可以看成是一张图表(像前面的那张图)。如果图中有个循环就会出现死锁。由于检查循环(因为所有锁的图标是相当的大)是成本是很昂贵的,所以一个更简单的方法会被经常使用:使用 时间超时(timeout) 。 如果在给定超时范围内未能锁定,就说明事务进入了死锁状态。 锁管理器也可以在加锁之前检查该锁会不会变成死锁,但要完美做到这点成本也是很昂贵的。因此这些预检经常设置一些基本规则。

    两段锁

    确保纯粹的隔离的 最简单方式 是在事务开始的时候加锁,在事务结束的时候释放锁。这意味着事务在开始前不得不等待它的所有锁,然后为事务持有锁,当结束时释放锁。它可以工作的,但是在等待所有锁的时候回浪费很多的时间

    一个更快的方法是 两段锁协议(由DB2和SQL Server使用),其中事务分为两个阶段:

    • 成长阶段:事务可以获得锁,但不能释放锁。
    • 收缩阶段,事务可以释放锁(对已经处理过的数据并且不会再次处理),但无法获得新的锁。

    这两条简单规则背后的思想是:

    • 释放不再使用的锁,以减少等待这些锁的其他事务的等待时间
    • 防止事务在事务开始后被修改数据的情况,因此与事务获取的第一个数据不一致。

    这协议能很好地工作,除非是那个事务修改后的数据并释放锁后,事务被取消或者回滚了。你可能遇到一种情况是,一个事务读了另一个事务修改后的值,而这个事务要被回滚的。要避免此问题,必须在事务结束时释放所有独占锁。

    说多几句

    当然,真正的数据库会用更复杂的系统,涉及更多类型的锁(如意向锁 intention lock )和更多粒度(行级锁,页级锁,分区锁,表锁,表空间锁)但是这个道理都是一样的。 我只探讨纯粹基于锁的方法,数据版本控制是解决这个问题的另一个方法。 版本控制背后的思想是:

    • 每个事务都可以同时修改相同的数据
    • 每个事务都有自己的数据副本(或版本)
    • 如果2个事务修改相同的数据,则只接受一个修改,另一个将被拒绝,相关的事务将被回滚(并且可能重新运行)。

    它提高了性能,因为:

    • 读事务不会阻塞写事务
    • 写事务不会阻塞读
    • 没有『臃肿缓慢』的锁管理器带来的额外开销

    一切都比锁更好,除了两个事务写入相同的数据(因为总有一个被回滚)。只是,你的磁盘空间会被快速增大。

    数据版本控制和锁定是两种不同的简介:乐观锁定与悲观锁定。他们都有利有弊;它实际上取决于应用场景(更多读取与更多写入)。有关数据版本控制的演示文稿,我推荐这篇关于PostgreSQL如何实现多版本并发控制,是非常好的演示文稿

    某些数据库(如DB2(直到DB2 9.7)和SQL Server(快照隔离除外))仅使用锁。其他像PostgreSQL,MySQL和Oracle使用涉及锁和数据版本控制的混合方法。我不知道只使用数据版本控制的数据库(如果您知道基于纯数据版本的数据库,请随时告诉我)。

    [2015年8月20日更新]读者告诉我: Firebird和Interbase使用没有锁的版本控制。 版本控制对索引有一个有趣的影响:有时一个唯一索引包含重复项,索引可以有比表有行更多的条目,等等。

    如果你在不同的隔离级别上读过那部分,你会发现增加隔离级别时,会增加锁的数量,从而增加事务等待锁定所浪费的时间。这就是大多数数据库默认情况下不使用最高隔离级别(Serializable)的原因。

    与往常一样,您可以自己检查主数据库的文档(例如 MySQLPostgreSQLOracle)。

    日志管理

    我们已经看到,为了提高性能,数据库将数据存储在内存缓冲区中。但是如果服务器在提交事务时崩溃,那么在崩溃期间你将丢失在内存中的数据,这会破坏事务的持久性。

    你可以在磁盘上写入所有内容,但如果服务器崩溃,你最终会将数据可能只有部分写入磁盘,这会破坏事务的原子性。

    任何事务的修改都只有撤销和已完成两个状态 要解决这个问题,

    有两种方法:

    • 影子副本/页面(Shadow copies/pages):每个事务都创建自己的数据库副本(或只是数据库的部分数据)并在此副本上工作。如果出现错误,则删除副本。如果成功,数据库会立即使用文件系统技巧切换副本中的数据,然后删除“旧”数据。
    • 事务日志:事务日志是一个存储空间。在每次磁盘写入之前,数据库会在事务日志中写入信息,以便在事务崩溃/取消的情况下,数据库知道如何删除(或完成)未完成的事务。

    WAL Write-Ahead Logging protocol (预写日志记录协议)

    在涉及许多事务的大型数据库上使用时,影子副本/页面会产生巨大的磁盘开销。 这就是现代数据库使用事务日志的原因。事务日志必须存储在稳定的存储中。我不会深入研究存储技术,但必须使用(至少)RAID磁盘来防止磁盘故障。

    大多数数据库(至少Oracle,SQL Server,[DB2PostgreSQL,MySQL和 SQLite)使用Write-Ahead Logging协议(WAL)处理事务日志。

    WAL协议是一组3条规则:
    1)数据库的每次修改都会生成一条日志记录,并且 必须在将数据写入磁盘之前将日志记录写入事务日志。
    2)日志记录必须按顺序写入;日志记录A在日志记录B之前发生就必须在B之前写入
    3)提交事务时,必须在事务成功结束之前,在事务日志中写入提交顺序。

    这个工作由日志管理器完成。一种简单的方法是在缓存管理器和数据访问管理器(在磁盘上写入数据)之间,日志管理器在将事务日志写入磁盘之前将每个更新/删除/创建/提交/回滚写入事务日志。容易,对吗? 错误的答案!都讲了这么多了,你应该知道与数据库相关的所有内容都受到“数据库效应”的诅咒。认真地是,问题是找到一种在保持良好性能的同时编写日志的方法。如果事务日志上的写入速度太慢,则会降低所有内容的速度。

    ARIES

    1992年,IBM研究人员“发明了”一种名为ARIES的WAL增强版。ARIES或多或少地被大多数现代数据库使用。逻辑可能不一样,但ARIES背后的理念随处可见。我给发明加了引号是因为,是因为根据麻省理工学院的这门课程,IBM的研究人员“只不过是编写事务恢复的良好实践”。自从我5岁时ARIES论文发表以来,我并不关心来自辛酸研究者的这个古老八卦。事实上,在我们开始这个最后的技术部分之前,我只是把这些信息给你一个休息时间。 我已经阅读了关于ARIES的大量研究论文,我发现它非常有趣!在这部分中,我将仅向你概述ARIES,但如果你需要真正的知识,我强烈建议您阅读本文。 ARIES 表示的是恢复和利用语义隔离算法(Algorithms for Recovery and Isolation Exploiting Semantics)。 这项技术的目的是有两个的:

    1. 写日志时有良好的性能
    2. 有快速可靠的恢复 数据库必须回滚事务有多种原因:
    • 用户取消了它
    • 由于服务器或网络故障
    • 因为事务已经破坏了数据库的完整性(例如,您对一个列有一个唯一性约束,事务添加了个重复值)。
    • 由于死锁

    有时候(比如网络出现故障),数据库可以恢复事务。 怎么可能?要回答这个问题,我们需要了解日志记录中的存储的信息。

    日志

    事务期间的每个 *操作(添加/删除/修改)都会生成一个日志* 。该日志记录包括:

    • LSN: 唯一的日志序列号(Log Sequence Number)。LSN按时间顺序给出。操作A在操作B之前发生,日志A的LSN会比日志B的LSN低。
    • TransID:产生操作的事务ID。
    • PageID:修改数据的磁盘位置。磁盘上的最小数据量是一个页(Page),因此数据的位置就是包含在数据的页的位置。
    • PrevLSN:指向同一事务生成的上一条日志记录的链接。
    • UNDO:取消本次操作的方法。

    比如,如果操作是更新,UNDO将会回到元素更新前的值或状态(物理UNDO),或者回到原来状态的反向状态(逻辑UNDO)

    • REDO:重复本次操作的方法。同样有2种方法: 保存操作后的元素值/状态,或者保存操作本身以便重复。
    • …:(供你参考,一个 ARIES 日志还有 2 个字段:UndoNxtLSN 和 Type)。

    此外,磁盘上的每个页面(存储数据,而不是日志)具有修改数据的最后一个操作的日志记录(LSN)的id。

    给出LSN的方式更复杂,因为它与日志的存储方式有关。但这个背后的思想仍然是一样的。 ARIES仅使用逻辑UNDO,因为处理物理UNDO真是一团糟。

    注意:据我所知,只有PostgreSQL没有使用UNDO。它使用垃圾收集器守护程序来删除旧版本的数据。这与PostgreSQL中数据版本控制的实现有关。

    为了更好地说明这点,这里是查询“UPDATE FROM PERSON SET AGE = 18;”生成的日志记录的可视化和简化示例。假设此查询在事务18中执行。

    每个日志都有一个唯一的LSN。连接的日志属于同一事务。日志按时间顺序链接(链接列表的最后一个日志是最后一个操作的日志)。

    日志缓冲区

    为避免日志写入成为主要瓶颈,使用 日志缓冲区

    当查询执行程序要求修改时:

    1. 缓存管理器将修改存储其缓冲区中
    2. 日志管理器将关联的日志存储在其缓冲区中
    3. 到了这一步,查询执行器认为操作完成了(因此可以请求做另一次修改);
      4)然后(稍后)日志管理器将日志写入事务日志。何时写日志的决定是由算法完成的。
      5)然后(稍后)缓存管理器将修改写入磁盘。何时在磁盘上写入数据是由算法完成的。 当事务被提交,这意味着对于事务中的每个操作,步骤1,2,3,4,5也做完了。 在事务日志中写入很快,因为它只是“在事务日志中的某处添加日志”,而在磁盘上写入数据则更复杂,因为它要 “以能快速读取数据的方式写入数据”。

    STEAL 和 FORCE 策略

    出于性能原因,步骤5可能在提交之后完成 ,因为在崩溃的情况下,仍然可以使用REDO日志恢复事务。这称为NO-FORCE政策 。 数据库可以选择FORCE策略(即必须在提交之前完成步骤5)以降低恢复期间的工作负载。 另一个问题是选择是否在磁盘上逐步写入数据(STEAL策略),或者缓冲区管理器是否需要等到提交顺序一次写入所有内容(NO-STEAL)。STEAL和NO-STEAL之间的选择取决于您的需求:使用UNDO日志快速写入长时间恢复或快速恢复? 以下是这些影响恢复策略摘要:

    • STEAL / NO-FORCE需要UNDO和REDO:性能高,但日志和恢复过程(如ARIES)更复杂。这是大多数数据库的选择。

    注意:我在多篇研究论文和课程中读到了这个事实,但我没有(明确地)在官方文件中找到它。

    • STEAL/ FORCE 只需要 UNDO
    • NO-STEAL/NO-FORCE 只需要 REDO.
    • NO-STEAL/FORCE 什么也不需要: 性能最差,而且需要巨大的内存。

    关于恢复

    好的,我们有很好的日志,让我们使用它们! 假设新实习生让数据库崩溃了(规则1:永远是实习生的错误)。你重启数据库并开始恢复进程 ARIES在3个关卡中让崩溃恢复过来:

    1. 分析关卡:恢复进程读全部的事务日志去创建奔溃期间发生的事情的时间线。它会确定哪些事务要回滚(所有事务没有提交的都会回滚)和哪些在奔溃期间的数据需要写入到磁盘
    2. redo关卡:这关从分析期间确定一条日志记录开始,并使用 REDO 来将数据库更新到崩溃之前的状态。

    在 REDO 阶段,REDO日志按时间顺序处理(使用LSN)。 对于每个日志,恢复过程将读取包含要修改数据的磁盘每页上的 LSN 如果 LSN(磁盘的页)>= LSN(日志记录),则表明数据在奔溃之前已经写入磁盘了(值已经被日志之后、奔溃之前的某个操作覆盖)所以不用做什么 如果LSN(磁盘的页)< LSN(日志记录),那么磁盘上的页将被更新。 即使对于要回滚的事务,重做也会完成,因为它简化了恢复过程(但我确信现代数据库不会这样做)。

    1. undo关卡: 此过程将回滚崩溃时未完成的所有事务。回滚从每个事务的最后日志开始,并按照反时间顺序处理UNDO日志(使用日志记录的PrevLSN)。

    在恢复期间,事务日志必须留意中恢复过程的操作,以便写入磁盘上的数据与事务日志中写入的数据同步。解决方案可能是移除被 undone的事务日志记录,这是很困难的。相反,ARIES在事务日志中写入补偿日志,逻辑上删除被取消的事务日志记录。 当事务被手动取消,或者被锁管理器取消(为了消除死锁),或仅仅因为网络故障而取消,那么分析阶段就不需要了。实际上,有关 REDO 和 UNDO 的信息在 2 个内存表中:

    • 事务表(保存当前所有事务的状态)
    • 脏页表(保存哪些数据需要写入磁盘)

    当新的事务产生时,这两个表由缓存管理器和事务管理器更新。因为它们是在内存中,当数据库崩溃时它们也被破坏掉了。 分析阶段的任务就是在崩溃之后,用事务日志中的信息重建上述的两个表。为了加快分析阶段,ARIES提出了一个概念:检查点(check point),就是不时地把事务表和脏页表的内容,还有此时最后一条LSN写入磁盘。那么在分析阶段当中,只需要分析这个LSN之后的日志即可。

  • 相关阅读:
    1105 Spiral Matrix (25分)(蛇形填数)
    1104 Sum of Number Segments (20分)(long double)
    1026 Table Tennis (30分)(模拟)
    1091 Acute Stroke (30分)(bfs,连通块个数统计)
    1095 Cars on Campus (30分)(排序)
    1098 Insertion or Heap Sort (25分)(堆排序和插入排序)
    堆以及堆排序详解
    1089 Insert or Merge (25分)
    1088 Rational Arithmetic (20分)(模拟)
    1086 Tree Traversals Again (25分)(树的重构与遍历)
  • 原文地址:https://www.cnblogs.com/jojo-feed/p/10816359.html
Copyright © 2011-2022 走看看