可扩展存储引擎体系结构

 

上一次修改主题: 2006-01-16

本部分前面提到过,Exchange 存储处于可扩展存储引擎 (ESE) 数据库的顶部。ESE 是多用户索引顺序访问方法 (ISAM) 表管理器,它具有完整的数据操作语言 (DML) 和数据定义语言 (DDL) 功能。ESE 允许应用程序存储记录并创建索引,以便以不同方式访问这些记录。

目前,有三个正在使用的 ESE 版本:

  • ESE97 Exchange Server 5.5 中的数据库引擎。
  • ESENT 用于 Active Directory 和许多其他 Microsoft Windows 组件的数据库引擎。与 ESE 的其他版本(它们使用 5 MB 日志文件和 4 KB 页大小)不同,ESENT 的 Active Directory 实现使用 10 MB 日志文件和 8 KB 页大小。
  • ESE98 Exchange 2000 Server 和 Exchange Server 2003 中的数据库引擎。
note注意:
ESE 在过去称为 Joint Engine Technology (JET) Blue。JET Blue 与 Access 中的 JET 版本(称为 JET Red)不同。

事务

ESE 是复杂的、基于事务的数据库引擎。事务是一系列被视为原子(不可划分)单位的操作。对于事务中的操作,要么完成并永久保存所有操作,要么不执行任何操作。例如,将邮件从“收件箱”移动到“已删除邮件”文件夹时需要执行相应的操作。邮件从一个文件夹中删除,然后被添加到另一个文件夹中,并更新文件夹属性。如果发生故障,您并不希望让邮件出现两个副本,或者根本没有副本,或者出现与实际文件夹内容不一致的文件夹属性值(例如,项目数)。

为了防止发生类似这样的问题,ESE 将操作捆绑到事务中。ESE 确保除非事务被提交到数据库,否则不会永久应用任何操作。事务被提交到数据库文件时,将永久应用所有操作。

万一发生崩溃,ESE 还会在启动时自动处理恢复,并回滚任何未提交的事务。如果在提交事务之前 ESE 出现故障,则整个事务都会回滚,就好像事务从未发生过。如果在提交事务之后 ESE 崩溃,则整个事务被永久性保存,并且客户端可以看见更改。

ACID 事务

有这种可靠性的事务通常称为 ACID 事务。可以按以下属性识别 ACID 事务:

  • 原子 该术语指示事务状态更改要么是全部更改,要么是没有更改。原子状态更改包括数据库更改,以及转换器的消息和操作。
  • 一致 该术语指示事务是正确的状态转换。按组执行的操作不会违反与状态关联的任何一个完整性约束。这要求事务是正确的程序。
  • 独立 该术语指示即使事务并发执行,但对每个事务 (T) 来说,也好象其他事务要么在 T 之前、要么在 T 之后执行,但不会既在前又在后。
  • 持久 该术语指示一旦事务成功完成(提交),那么即使在发生故障后它的状态更改依然会保存下来。

版本存储

版本存储使 ESE 能够跟踪和管理当前事务。因此,ESE 可以传递 ACID 测试的独立和一致部分。版本存储在内存中维护着一个对数据库所做的修改的列表。

在以下情况下将使用版本存储:

  • 回滚 如果事务必须回滚,那么它会检查版本存储以获得它执行过的操作的列表。通过对所有操作执行反向操作,就可以回滚事务。
  • 写入冲突检测 如果两个不同的会话试图修改同一个记录,则版本存储会注意到这一点,并拒绝进行第二次修改。
  • 可重复读取 当会话开始事务时,它总是会遇到相同的数据库视图,即使其他会话修改了它正在查看的记录。会话读取记录时,它会咨询版本存储,以确定会话应当查看记录的哪个版本。可重复读取提供了隔离级别,在该隔离级别中,在客户端开始事务之后,它会查看事务开始时数据库所处的状态,而不管其他客户端或会话进行了什么修改。可重复读取是使用版本存储来实现的。使用保存在内存中的数据库修改列表,它可以确定任何特定会话应当查看哪个记录视图。
  • 延迟的映像前日志记录 这是一种复杂的优化手段,它让 ESE 所记录的数据比其他可比较的数据库引擎更少。

快照隔离

事务开始之后,ESE 保证会话查看的是单个、一致的数据库映像,该映像与会话的事务开始时存在的数据库相同,外加它自己的更改。因为其他会话也可以修改数据并提交它们的事务,因此这些更改对于在提交它们之前已启动的任何事务均不可见。只有当用户正在查看最新版本时,该用户才能修改记录。否则,更新失败,并产生 JET_errWriteConflict。比最新事务更旧的版本会被自动放弃。

ESE 的一项功能是称为“快照隔离”的事务隔离级别。快照隔离级别允许用户使用数据库的过渡性一致视图来访问最后一个提交的行。快照隔离是一种并发控制算法,“A Critique of ANSI SQL Isolation Levels”第一次对它进行了描述。快照隔离是由 ESE 通过使用可重复读取实现的。

ESE 数据库结构

RTF 数据库文件中的所有数据都存储在 B 树中。B 树中的“B”代表平衡。“树”是指类似于文件系统中的文件夹结构的一种排列,其中,根是项目(数据库页)的父项目,而项目反过来又是更深的项目的父项目。B 树被设计成能够支持对磁盘上的数据进行快速访问。因为从磁盘读取和写入磁盘比在内存中执行这些操作慢得多,因此,B 树被划分成 4 KB 页,从而使 ESE 能够使用最少的磁盘 I/O 数获得它需要的数据。ESE 数据库可以包含最多 2 的 32 次方页,即 16 TB。事实上,唯一限制数据库大小的因素是以适时方式对数据库进行备份、还原和执行其他维护操作(例如,脱机碎片整理、数据库修复等)的能力。

数据库页

ESE 中的页大小是由使用它的应用程序定义的。例如,ESE97 (Exchange Server 5.5) 和 ESE98(Exchange 2000 Server 和 Exchange Server 2003)使用 4 KB 页,而 ESENT (Active Directory) 则使用 8 KB 页。每个这样的 4 KB 或 6 KB 页都包含指向其他页或存储在 B 树中的实际数据的指针。指针和数据页一起混合保存在文件中。

为了在任何可能的情况下都能提高性能,页面被尽可能长时间地缓存在内存缓冲区中,因而减少了访问磁盘的需要。每个页以 40 字节的页头开始,页头包括以下值:

  • pgnoThis 该值表示页的页号。
  • DbtimeDirtied 该值表示页最后一次修改时的 Dbtime。
  • pgnoPrev 该值表示叶上左侧相邻页的页号。
  • pgnoNext 该值表示叶上右侧相邻页的页号。
  • ObjidFDP 该值表示数据库中特殊页的对象 ID,特殊页称为数据页的父级 (FDP),FDP 用于指示该页属于哪个 B 树。修复期间将使用 FDP 页。
  • cbFree 该值表示页上可用的字节数。
  • cbUncommittedFree 该值表示页上可用的未提交字节数(空闲但可被回滚回收的字节)。
  • ibMicFree 该值表示位于页的顶部的下一个可用字节的页偏移量。

ECC 校验和

Exchange Server 2003 Service Pack 1 (SP1) 引入了称为纠错代码 (ECC) 校验和的新功能。ECC 校验和是新的校验和格式,它支持纠正(.edb 文件, .stm 文件和事务日志文件中)数据库页中的单个位错误。这种新的校验和格式使用 64 位,而更早的校验和格式使用 32 位。新代码可以用于更早格式的数据库,但当前格式的数据库无法与更早版本的 ESE 一起使用。更新数据库引擎之后,写入数据库的所有页都会有新的校验和格式。被读取而不修改的页不会升级它们的校验和格式。

note注意:
部署较新的 ESE.dll 之后,您执行的任何脱机碎片整理都会导致数据库中的所有页升级它们的校验和格式。这会极大增加数据库碎片整理的时间,因此建议不要这样做。此外,建议不要使用 /k 开关运行 esetuil,这也会处理数据库中所有页的校验和。

使用旧格式校验和的数据库页以四个字节的校验和开头,随后是四个字节的页号,页号用来验证所请求的页实际上是从磁盘读取的。

新的校验和格式删除了四个字节的页号,改为以八个字节的校验和开始。页号被用作计算校验和时的输入参数。因此,如果从磁盘读取了错误页,将会有校验和不匹配。

当前校验和格式实际上由两个 32 位校验和组成。第一个是 XOR 校验和,它的计算方式与旧格式校验和完全相同。在计算该校验和时,页号被用作种子。第二个 32 位校验和是 ECC 校验和,它允许纠正页上的单个位错误。

数据库一致性和 -1018 错误

  • 读取页时,ESE 将检查页上的标志,以确定页是否有当前的校验和格式。然后计算合适的校验和(当前格式或旧格式)。如果有与当前格式校验和不匹配的校验和,ESE 会尝试纠正错误。如果无法自动纠正错误,Exchange 将报告 -1018 错误。

如果 Exchange 存储执行了以下某一个操作,则可能是 Exchange 存储造成了自生的 -1018 错误:

  • 构建有错误校验和的页。
  • 正确构建页,但告诉操作系统在错误位置写入页。

如果系统管理员遇到 -1018 错误,或者对服务器运行诊断硬件测试并且这些测试没有报告问题,则管理员可能会认为问题一定出现在 Exchange Server 上,因为硬件通过了最初的分析。

在很多情况下,Microsoft 或硬件供应商通过进一步调查发现实际上是在硬件、固件或设备驱动程序中的微小问题导致了数据库文件损坏。

一般的诊断测试可能因为几个原因而无法检测出所有瞬时故障。诊断程序可能无法检测出固件或驱动程序软件中的问题。诊断测试可能无法充分地模仿长时间的运行或复杂的负载。另外,在增加诊断监视或调试日志记录以后,对系统的更改可能达到了让问题不再出现的程度。

生成校验和并将页写入数据库文件的 Exchange Server 机制所具有的简单性和稳定性说明 -1018 错误可能是由除 Exchange Server 以外的某些事情导致的。校验和与错误页检测机制是简单并且可靠的,自从第一个 Exchange 发布以来基本上保持不变,只是为了适应数据库版本之间的数据库页格式更改而有非常小的更改。

校验和是为将要写入磁盘的页而生成的,生成发生在所有其他数据(包括页号本身)写入页之后。Exchange Server 向页添加校验和之后,Exchange Server 将通过使用标准的、已发布的 Windows Server API 指示 Microsoft Windows Server 操作系统将页写入磁盘。

页的校验和可能会正确生成,但页可能被写入硬盘上的错误位置。这可以由临时的内存错误(例如“位翻转”)导致。例如,假设 Exchange 构造一个新版本的第 70 页。页本身没有遇到错误,但磁盘控制器或操作系统所使用的页号的副本则是随机改变的。如果 70(二进制 1000110)已经被不稳定的内存单元更改为 6(二进制 000110),则可以发生该问题。页的校验和仍然是正确的,但页在数据库中的位置现在是错误的。当 Exchange Server 检测到逻辑页号与页的物理位置不匹配时,它将报告页发生 -1018 错误。

如果 Exchange Server 在页本身上写入错误的页号,则可能发生另一种页编号错误(由 Exchange Server 导致)。但这会导致 -1018 错误以外的其他错误。如果 Exchange Server 在第 70 页上写入 71,然后对该页正确计算校验和,那么,该页将写入位置 71 并同时通过页号和校验和的测试。

通常,在 Exchange Server 数据库中报告的单个 -1018 错误不会导致数据库停止,也不会造成除出现 -1018 错误本身以外的其他症状。页可能位于很少访问的文件夹(例如,“已发送邮件”或“已删除邮件”文件夹)中,或者在很少打开的附件中,或者甚至为空。

即使单个 -1018 错误不太可能导致大范围的数据损失,但仍然要关注 -1018 错误,因为 -1018 错误是存储系统至少有一次未能可靠地存储或检索数据的证据。尽管 -1018 错误可能是永远不会再次发生的短暂问题,但该错误更有可能是将会逐渐变得更坏的其他问题的早期警告。即使第一个 -1018 错误发生在数据库中的空页上,您也无法知道下一次可能损坏哪个页。如果关键性全局表被损坏,则数据库可能无法启动,并且数据库修复可能部分或完全不成功。

记录 -1018 错误之后,在找到并消除根本原因之前,必须考虑到即将到来的故障或数据库发生进一步随机损坏的可能性,并为此制定相应的应对计划。

数据库树平衡

ESE 的一个主要功能是随时保持数据库树平衡。平衡树的过程是在所有页被拆分或合并时执行的。如下图所示,树的根级别的节点个数与树的叶级别的节点个数总是相同的。因此,树是平衡的。

3b77fce3-607c-48db-bcf9-e190d7c4e197

note注意:
尽管在 ESE 数据库内部的树通常称为 B 树,但它们实际上是 B+ 树。B+ 树包括 B 树的所有特征,但此外 B+ 树中的每个数据页都拥有指向在叶上该数据页的上一个和下一个相邻页的页指针。尽管在插入或拆分以及合并操作期间为了使这些指针保持最新而会有相应开销,但指针使更快地按顺序遍历 B+ 树结构中的数据成为可能。

从 ESE 角度看,数据库表是 B 树的集合。尽管可以有很多个用来提供不同数据视图的辅助性索引 B 树,但每个表均由一个包含数据的 B 树组成。如果表中的列或字段变得太宽而无法被存储在 B 树中,那么它会被划分成单独的 B 树,这称为长值树。

这些表及其关联的 B 树的定义存储在另一个叫做系统编录的 B 树中。丢失系统编录是严重的问题。因此,ESE 在每个数据库中都保存了该 B 树的两个相同副本,一个从第 4 页开始,另一个从第 24 页开始。

拆分

当页接近已满时,大约一半数据会被放在辅助页中,并且额外的键会放在辅助页的父页中。除非父页也已充满,否则就会执行该过程。如果父页也已充满,则拆分父页,并将指针添加到该页的父页。最终,一直到根块的每个指针页可能都需要拆分。如果需要拆分根块,则会在树中再插入一个页级别。形象地说,树长高了。

合并

当页接近已空时,它会与相邻页合并,并更新父页中的指针,如果需要,将合并页。最终,如果一直到根块的每个指针页都被合并,则树会变矮。若要到达叶(数据),ESE 会从根节点开始追溯页指针,直到到达希望访问的叶节点。

扇出

ESE B 树的树结构有非常高的扇出能力。高扇出能力意味着 ESE 能够通过不超过四次磁盘读取(三个指针页以及数据页本身)即可到达 50 GB 表中的任何数据片。ESE 的每 4 KB 页存储了 200 页指针,这使 ESE 能够使用具有最低父/子级别数的树(也称为浅树)。ESE 还存储了当前树的键,它使 ESE 能够沿当前树快速展开搜索。上图是具有三个父/子级别的树;具有四个父/子级别的树可以存储很多 GB 的数据。

索引

传统的 B 树只用一个特定方式进行索引。它使用一个键,并且必须使用这个键来检索数据。例如,邮件表中的记录是按邮件的唯一标识符(称为邮件传输服务 (MTS) ID)进行索引。但是,用户可能想以更具用户友好性的格式作为顺序来查看邮件表中的数据。

索引(或更明确地说是辅助索引)用于检索数据。每个辅助索引都是将所选辅助键映射到主键的另一个 B 树。这使 B 树与它们所索引的数据相比显得更小。

若要了解如何使用辅助索引,请考虑当用户更改邮件在邮件文件夹中的显示方式时所发生的操作。如果在 Outlook 中更改文件夹视图以便按主题而不是接收时间来排序视图,则 Outlook 将使存储和 ESE 为您的邮件文件夹表构建新的辅助索引。

第一次更改大型文件夹的视图时,将感觉到延迟。如果仔细观察服务器,会看见磁盘活动出现小高峰。再次切换到该视图时,索引已经建立,响应速度会快很多。

Microsoft Exchange Information Store 服务的辅助索引 B 树存在八天。如果不使用它们,Microsoft Exchange Information Store 服务将通过后台操作删除它们。

长值

在数据 B 树中,ESE 中的列或记录无法跨越页。有些值(例如,PR_BODY,它是邮件的邮件正文)会突破页的 4 KB 边界。这些值称为长值 (LV)。表的长值 B 树被用来存储这些大型值。

如果将一片数据输入 ESE 表中,并且它太大而无法进入数据 B 树,那么,它会被划分成 4 KB 大小的页,并存储在该表的单独的长值 B 树中。数据 B 树中的记录包含指向长值的指针。该指针称为长值 ID (LID),并且意味着记录有指向 LID 256 的指针。

记录格式

一个 B 树集合代表一个表,而一个表又是行的集合。行是也称为记录。记录由很多列组成。记录的最大大小(因此也是出现在单个记录中的列数)受数据库页大小(减去页头的大小)控制。ESE 有 4 KB 页大小。因此,最大记录大小大约是 4050 字节(4096 字节减去页头的大小)。

列数据类型

每个列定义必须指定该列所存储的数据类型。下表描述了 ESE 所支持的数据类型。

可扩展存储引擎列数据类型

列数据类型 描述

Bit

NULL、0 或非 0

Unsigned Byte

1 个字节的无符号整数

Short

2 个字节的带符号整数

Unsigned Short

2 个字节的无符号整数

Long

4 个字节的带符号整数

Unsigned Long

4 个字节的无符号整数

LongLong

8 个字节的带符号整数

Currency

8 个字节的带符号整数

IEEE Single

4 个字节的浮点数

IEEE Double

8 个字节的浮点数

Date Time

8 个字节的日期-时间(整数日期、小数时间)

GUID

16 个字节的唯一标识符

Binary

二进制字符串,长度 <= 255

Text

ANSI 或 Unicode 字符串,长度 <= 255 字节

Long Binary

大型值二进制字符串,长度 < 2 Gb

Long Text

大型值 ANSI 或 Unicode 字符串,长度 < 2 Gb

列数据类型属于两个类别。第一个类别是固定列和可变列。第二个类别是标记列。每个列均被定义为 16 个字节的 FIELD 结构,外加列名称的大小。

在 ESE 数据库中创建表时,将通过一个 FIELD 结构的数组来定义表。该数组用于标识表中的单个列。在该数组中,通过称为列 ID 的索引值来代表每个列。这与普通数组类似,在普通数组中,可以用 ID 来引用数组成员,例如,array[0]、array[1] 等等。通过 ID 可以快速访问列,但用列名称执行搜索需要对整个 FIELD 结构的数组进行线性扫描。

固定列和可变列

固定列包含的数据长度是固定的。每个记录占据所定义的记录空间量,即使没有定义值。数据类型 ID 1 到 10 可以被定义为固定列。每个表可以定义最多 126 个固定列(列 ID 1 到 127)。

可变列可以包含最多 256 个字节的数据。一个偏移量数组被存储在设置了最高可变列的记录中。每个列需要两个字节。数据类型 ID 10 和 11 可以定义为可变列。每个表可以定义最多 127 个可变列(列 ID 128 到 256)。

标记列

ESE 将很少出现或在单个记录中多次重复出现的列定义为标记列。未定义的标记列会造成无空间开销。在同一个记录中,一个标记列可以重复出现。如果在辅助索引中表示一个标记列,则该列每次有差别的出现均会被索引引用。

标记列可以包含无限长度的和可变长度的数据。其列 ID 和长度与数据存储在一起。所有数据类型都可以定义为标记列。每个表可以定义最多 64,993 个标记列。