网站开发的布局划分,免费做h5的手机软件,wordpress vip视频解析,怎么查询网站是谁做的简介 之前我已经写了一个关于SQL Server日志的简单系列文章。本篇文章会进一步挖掘日志背后的一些概念#xff0c;原理以及作用。如果您没有看过我之前的文章#xff0c;请参阅#xff1a; 浅谈SQL Server中的事务日志(一)----事务日志的物理和逻辑构架 浅谈SQL Server中的事…简介 之前我已经写了一个关于SQL Server日志的简单系列文章。本篇文章会进一步挖掘日志背后的一些概念原理以及作用。如果您没有看过我之前的文章请参阅 浅谈SQL Server中的事务日志(一)----事务日志的物理和逻辑构架 浅谈SQL Server中的事务日志(二)----事务日志在修改数据时的角色 浅谈SQL Server中的事务日志(三)----在简单恢复模式下日志的角色 浅谈SQL Server中的事务日志(四)----在完整恢复模式下日志的角色 浅谈SQL Server中的事务日志(五)----日志在高可用和灾难恢复中的作用 数据库的可靠性 在关系数据库系统中我们需要数据库可靠所谓的可靠就是当遇见如下两种情况之一时保证数据库的一致性: 在系统崩溃/故障等情况下保证数据库的一致性 数据不能在多个DML语句同时修改数据的情况下导致不一致或数据损坏 实际上上述第二种情况就是并发性所需要解决的问题传统关系数据库中我们用锁来解决这个问题而对于内存数据库或带有乐观并发控制的数据库系统通过多版本并发控制MVCC来解决这个问题。因为本篇文章的主旨是讨论日志而不是并发因此对于上述第二种情况不会详细解释。 我们上面还多次提到了一致性Consistence在开始了解日志如何维持一致性之前我们首先要明白什么是一致性。一致性在数据库系统中所指的内容比较广一致性不仅仅需要数据库中的数据满足各种约束比如说唯一约束主键约束等还需要满足数据库设计者心中的隐式约束简单的业务约束比如说性别这列只允许男或女这类隐式约束通常使用触发器或约束来实现或是在数据库所服务的应用程序中进行约束。 下面我们把一致性的范围缩减到事务一致性事务一致性的概念学术上的解释为 如果事务执行期间没有出现系统错误或其他事务错误并且数据库在事务开始期间是数据一致的那么在该事务结束时我们认为数据库仍然保证了一致性。 因此引申出来事务必须满足原子性也就是事务不允许部分执行。事务的部分执行等同于将数据库置于不一致的境地之下。此外多事务并发执行也可能导致数据库不一致除非数据库系统对并发进行控制。 关于上面的显式约束由数据库系统来实现比如说违反了一致性约束的语句会导致数据库系统报错并拒绝执行。但一些隐式的事务约束比如说写语句的开发人员对系统设计者所设计的规则并不了解导致了违反业务规则的数据修改这种情况在数据库端很难探查。但是这种问题通常可以规则到权限控制的领域我们认为授予某个用户修改特定数据的权限就认为这个用户应该了解数据库中隐式和显式的规则。 除去这些业务上的数据不一致之外我们需要在系统崩溃等情况下保证数据的一致性而可能导致这类数据不一致的情况包括但不限于下面这些情况 存储系统损坏比如说磁盘上字节级别的损坏这类问题通常可以通过磁盘上的奇偶校验发现另外还有一些大一些的问题比如说整个存储系统崩溃。这类问题的修复手段取决于前期工作比如说备份策略高可用性架构SAN Replication等技术。 机房整体损坏这类问题比较极端只有异地机房容灾可以解决。 系统故障修改数据的进程都需要事务作为上下文和其他概念一样事务也是有状态的。而事务状态通常存储在易丢失的主存中因此当出现系统故障、进程崩溃等系统失败时可能导致事务状态的丢失此时我们就无法得知事务中的哪部分已经执行而哪部分还未执行重新运行事务并不会解决这类问题因为有可能导致事务中某部分的重复执行。因此解决这类问题的方式就是将事务的状态以及对数据库修改的详细步骤与内存中的数据分开存放并存储于磁盘等稳定的介质中当系统故障等情况下我们可以通过这些记录来将系统恢复到一致性的状态之下我们对这类存储称之为日志。 SQLServer中的日志 SQL Server中靠日志来维护一致性当然日志的作用非常多但一致性是日志的基本功能其他功能可以看作是额外的功能。通常我们创建数据库的时候会附带一个扩展名为ldf的日志文件。日志文件其实本质上就是日志记录的集合。在SQL Server中我们可以通过DBCC LOGINFO来看这个日志的信息如图1所示。 图1.DBCC LOGINFO 该命令可以从VLF的角度从一个比较高的层级看日志。其中值得注意的列是VLF大小状态2表示使用0表示从未使用过偏移量。对于这些信息对我们规划VLF数量的时候很有帮助因为VLF过多可能引起严重的性能问题尤其是在复制等Scale-Out或HA环境下。 然后事务对数据库中每次修改都会分解成多个多个原子层级的条目被记录到持久存储中这些条目就是所谓的日志记录Log Record我们可以通过fn_dblog来查看这些条目。如图2所示。 图2.Fn_dblog 每个日志记录都会被背赋予一个唯一的顺序编号这个编号大小为10字节由三部分组成分别为 VLF顺序号4字节 Log Block顺序号4字节 Log Block内的顺序编号2字节 因此由于VLF是不断递增的同一个VLF被复用会导致编号改变因此LSN序号也是不断递增的。因此通过上面的LSN结构不难发现如果比VLF更小的粒度并不是直接对应LOG RECORD而是LOG Block。Log Block是日志写入持久化存储的最小单位Log Block的大小从512字节到60K不等这取决于事务的大小那些在内存还未被写入持久化存储的Log Block也就是所谓的In-Flight日志。以下两个因素决定Log Block的大小 事务提交或回滚 Log Block满60K会强制Flush到持久化存储以保证WAL 因此当一个事务很大时比如说大面积update每60K就会成为一个Log Block写入持久化存储。而对于很多小事务提交或回滚就会称为一个Block写入持久化存储因此根据事务的大小LOG Block的大小也会不同。值得疑惑的是因为磁盘上分配单元的大小是2的N次方因此最接近LOG BLOCK的大小应该是64K而SQL Server为什么不把Log Block设定为64K呢。这样可以更优化IO。 VLF和Log Block和Log Record的关系如图3所示。 图3.三者之间的关系 从比较高的层级了解了日志之后我们再仔细了解日志中应该存储的关键信息每条Log Record中都包含下面一部分关键信息 LSN Log Record的Context Log Record所属的事务ID所有的用户事务都会存在事务ID Log Record所占的字节 同一个事务中上一条Log Record的LSN用于Undo 为Undo所保留的日志空间 当然这些仅仅是日志的一小部分内容。通过Log Record所记录的内容就能够精确的记录对数据库所做的修改。 日志用于Undo 在了解为了Undo日志所起的作用之前我们首先可以了解一下为什么需要事务存在回滚 因为事务可能失败或者死锁等原因如果希望事务不违反原子性而造成数据库不一致的话则需要通过回滚将已经部分执行的事务回滚掉。 根据业务需求如果在某些关联业务失败等情况下回滚数据。 因此Log Record会为这些列保存一些字节来执行数据库回滚最简单的例子莫过于执行插入后Rollback事务则日志会产生一条所谓的Compensation Log Record来反操作前面已经插入的事务如图4所示。 图4.Compensation Log示例 图4执行的是一个简单的Insert语句然后回滚。我们看到SQL Server生成了一个Compensation Log Record来执行反向操作也就是Delete操作。值得注意的是为了防止这些回滚操作SQL Server会保留一些空间用于执行回滚我们看到LOP_INSERT_ROWS保留的74字节空间被下面的Compensation Log Record所消耗。Compensation Log record还有一个指向之前LSN的列用于回滚直至找到LOP_BEGIN_XACT的事务开始标记。另外Compenstion Log Record只能够用于Redo而不能用于Undo。 那假设我们某一个事务中删除了多条数据怎么办?比如说某一个事务中一个Delete语句删除了10行则需要在Log Record对应10个LOP_DELETE_ROWS引申一下由此我们可以看出某一个语句可能导致N个Log Record这么多Log Record在复制镜像时都需要在另一端Redo因此需要额外的开销如果我们此时RollBack了该事务则Redo的顺序是什么呢如图5所示。 图5.回滚事务 图5中删除3条数据后进行回滚首先从删除3开始生成对应的反向Compensation Log Record并指向删除2再对应删除2生成反向Compensation Log Record并指向删除1以此类推最终回滚事务指回开始事务。 日志用于Redo 与Undo不同在计算机存储体系中辅助存储通常是带有磁头的磁盘。这类存储系统的IOPS非常低因此如果对于事务对数据库执行的修改操作我们积累到一定量再写入磁盘无疑会提高IO的利用率。但是在数据在主存还没有持久化的辅助存储的期间如果遭遇系统故障则这部分数据的丢失则可能导致数据库的不一致状态。 因此使用日志使得该问题得到解决。与日志Undo方面的不同之处在于Undo用于解决事务未完成和事务回滚的情况而Redo则是为了保证已经提交的事务所做的修改持久化到辅助存储。 Redo则引申出了WAL即事务日志会在COMMIT或COMMIT之前写入持久化存储中然后事务对数据本身的修改才能生效。因此就能够保证在系统故障时可以通过读取日志来Redo日志的持久化操作。因此对于最终用户可以显示事务已经提交而暂时不用将所修改的数据写入持久化存储。由于数据在日志未写入持久化存储之前无法持久化则需要更大的主存作为BUFFER空间。 因为日志既要用于Undo又要用于Redo因此为了能够成功生成Compensation Log Record需要日志既记录被修改前的数据又记录被修改后的数据比如我们在图6中做一个简单的更新。 图6.记录更新之前和之后的数据 值得注意的是如果修改的值是聚集索引键则由于修改该数据会导致存储的物理位置改变所以SQL Server并不会像这样做即席更新而是删除数据再插入数据从而导致成本的增加因此尽量不要修改聚集索引键。 Undo/Redo Recovery 当SQL Server非正常原因关闭时也就是在没有走CheckPoint会在下面提到时关闭了数据库此时数据库中数据本身可能存在不一致的问题。因此在数据库再次启动的时候会去扫描日志找出那些未提交却写入持久化存储的数据或已提交却未写入持久化存储的数据来进行Undo和Redo来保证事务的一致性。Undo/Redo Recovery遵循以下规则 按照由早到晚的顺序Redo该已提交却未写入持久化存储的数据 按照由晚到早的顺序Undo未提交却写入持久化存储的数据 图7中我们进行一个简单测试在启动过程中首先禁用了CheckPoint以防止自动CheckPoint然后我们修改数据不提交并持久化到磁盘。另一个线程修改数据并提交但未持久化到磁盘。为了简单起见我把两个线程写到一个窗口中。 图7.需要Undo和Redo的两个事务 此时我们强制杀死SQL Server进程导致数据本身不一致此时在SQL Server的重启过程中会自动的Redo和Undo上面的日志如图所示。 图8.实现Redo和Undo 那么什么是CheckPoint? 图给出的简单例子足以说明Recovery机制。但例子过于简单假如一个非常繁忙的数据库可能存在大量日志一个日志如果全部需要在Recovery过程中被扫描的话那么Recovery过程所导致的宕机时间将会成为噩梦。因此我们引入一个叫CheckPoint的机制就像其名称那样CheckPoint就是一个存档点意味着我们可以从该点继续开始。 在Undo/Redo机制的数据库系统中CheckPoint的机制如下 .将CheckPoint标记写入日志标记中包含当前数据库中活动的事务信息并将Log Block写入持久化存储 2.将Buffer Pool中所有的脏页写入磁盘所有的脏页包含了未提交事务所修改的数据 3.将结束CKPT标记写入日志并将Log Block写入持久化存储 我们在日志中可以看到的CheckPoint标记如图9所示。 图9.CheckPoint标记 其中这些Log Record会包含CheckPoint的开始时间结束时间以及MinLSN用于复制的LSN等。由图中我们还可以看到一个LOP_XACT_CKPT操作的Log Record该操作符的上下文如果为NULL的话则意味着当前 包含未提交事务 该Log Record记录包含未提交事务的个数 包含未提交的事务所涉及的LSN 由CheckPoint的机制可以看出由于内存中的数据往往比持久化存储中的数据更新而CheckPoint保证了这部分数据能够被持久化到磁盘因此CheckPoint之前的数据一定不会再需要被Redo。而对于未提交的事物所修改的数据写入持久化存储则可以通过Undo来回滚事务未提交的事物会导致CheckPoint无法截断日志因此这部分日志可以在Recovery的时候被读取到即使这部分日志在CheckPoint之前。 此时我们就可以100%的保证CheckPoint之前的日志就可以被安全删除简单恢复模式或归档了完整恢复模式在Recovery时仅仅需要从CheckPoint开始扫描日志从而减少宕机时间。 小结 本篇文章深入挖掘了数据库中日志为保护数据一致性的的作用、实现原理。日志在这些功能之外也是为了用于实现高可用性因此了解这些原理可以更好的帮助我们在搭建高可用性拓扑以及设计备份计划时避免一些误区。