可扩展存储引擎体系结构

 

适用于: Exchange Server 2007 SP3, Exchange Server 2007 SP2, Exchange Server 2007 SP1, Exchange Server 2007

上一次修改主题: 2008-11-19

中心传输服务器和边缘传输服务器上的 Exchange 邮箱数据库和队列利用可扩展存储引擎 (ESE) 数据库。ESE 是多用户索引顺序访问方法 (ISAM) 表管理器,具有完整的数据操作语言 (DML) 和数据定义语言 (DDL) 功能。通过 ESE,应用程序可以存储记录并创建索引,以便通过不同的方式访问这些记录。

ESE 有两个版本:

  • ESENTl   用于 Active Directory 以及许多 Microsoft Windows 组件的数据库引擎。与其他版本的 ESE(使用 5 MB 日志文件和 4 KB 页大小)不同,Active Directory 实现的 ESENT 使用 10 MB 日志文件和 8 KB 页大小。

  • ESE98   Exchange 2000 Server、Exchange Server 2003 和 Exchange Server 2007 中的数据库引擎。

  • ESE 以前称为 Joint Engine Technology (JET) Blue。JET Blue 与 Microsoft 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+ 树被划分成 8 KB 页。这样,ESE 可以使用最少的磁盘 I/O 获取所需的数据。对于最大总大小约 16 TB 的数据库,ESE 数据库最多可以包含 2^31(2 的 31 次幂)个 8 KB 页。实际上,唯一限制数据库大小的因素是及时地对数据库进行备份、还原和执行其他维护操作(例如,脱机碎片整理、数据库修复等)的能力。

数据库页

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

为了尽可能提高性能,页面将在内存缓冲区中缓存尽可能长的时间。这样可以减少访问磁盘的次数。每个页以 40 字节的页头开始,页头包含下列值:

  • pgnoThis 此值指示该页的页号。

  • DbtimeDirtied 此值指示上次修改该页时的 Dbtime。

  • pgnoPrev 此值指示叶上左侧相邻页的页号。

  • pgnoNext 此值指示叶上右侧相邻页的页号。

  • ObjidFDP 此值指示数据库中某个特殊页的对象 ID,该特殊页称为数据页的父级 (FDP),FDP 用于指示此页所属的 B+ 树。修复期间将使用 FDP 页。

  • cbFree 此值指示页上可用的字节数。

  • cbUncommittedFree   此值指示页上可用的未提交字节数(空闲但可通过回滚回收的字节)。

  • ibMicFree 此值指示位于页的顶部的下一个可用字节的页偏移量。

ECC 校验和

通过纠错码 (ECC) 校验和,可以纠正(.edb 文件中)数据库页中的单个位错误。

ECC 校验和由两个 32 位的校验和组成。第一个是 XOR 校验和,在计算时,使用页号作为种子。第二个 32 位校验和是 ECC 校验和,可以纠正页上的单个位错误。

数据库一致性和 -1018 错误

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

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

  • 构建有错误校验和的页。

  • 正确构建页,但告诉操作系统写入页的位置不正确。

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

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

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

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

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

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

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

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

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

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

数据库树平衡

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

平衡树

平衡的树

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

这些表及其关联的 B+ 树的定义存储在另一个名为系统编录的 B+ 树中。丢失系统编录是严重的问题。因此,ESE 在每个数据库中保留了此 B+ 树的两个相同副本。

拆分

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

合并

当页接近已空时,会与相邻页合并,并更新父页中的指针,如果需要,将合并页。最终,如果一直到根块的每个指针页都被合并,则树会变矮。若要到达叶(数据),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 个标记列。