SQL 问题与解答分区、一致性检查以及其他更多内容

Paul S. Randal

问:我无意中将某个产品数据库连接到了 SQL Server® 2005 服务器,现在我打算把它连接到运行 SQL Server 2000 的正确服务器上。我尝试过分离数据库然后将其连接到 SQL Server 2000 服务器,我也尝试过通过备份和还原来执行同样的操作,但这两种方法都不起作用。为什么它无法在 SQL Server 2000 服务器上运行呢?这是我唯一的一份数据库副本。

答:首先要强调的是进行备份的重要性。DBA 通常认为备份是为了能够从损坏中或其他一些破坏性灾难中进行恢复。可您却遇上了一个不太明显的灾难 — 在升级过程中出现了问题,尽管在这种情况下它属于意外升级。但是,问题的重点在于您应当始终备份最新的完整数据库,以防数据库出现问题。

升级(无论是有意的还是无意的)是一种单向操作,因而很难逆转其效果。在两个版本的 SQL Server 之间升级时,会针对数据库执行一系列的升级步骤。每个步骤通常都包括对数据库所做的一些物理更改,并且每个步骤都会增加数据库的版本号。

例如,在将数据库从 SQL Server 2000 升级到 SQL Server 2005 时执行的其中一个主要更改就是改变数据库系统目录(通常称为系统表或数据库元数据)的结构,其中存储着有关表、索引、列、分配以及与数据库的关系和物理结构相关的其他详细信息的各种元数据。

每次执行升级步骤时,数据库版本号都会增加。例如,SQL Server 7.0 数据库的版本号为 515,SQL Server 2000 数据库的版本号为 539,而 SQL Server 2005 数据库的版本号为 611(如果启用了 vardecimal 功能,则为 612)。这将使 SQL Server 能够了解对数据库执行的最后一个升级步骤。

SQL Server 的版本无法读取已升级到更高版本的 SQL Server 数据库(例如,SQL Server 2000 无法读取已升级到 SQL Server 2005 的数据库)。这是因为较低版本没有解释升级后的结构和数据库布局所需的代码。而这也正是您升级到 SQL Server 2005 后的数据库所面临的问题:无法重新连接到 SQL Server 2000。

在缺少完整数据库备份的情况下,您的唯一选择是从已升级的数据库中导出所有数据,然后将其手动传回新的 SQL Server 2000 数据库。只要尚未使用 SQL Server 2005 中的任何新功能,您即可编写数据库结构的脚本、在 SQL Server 2000 上创建数据库,然后导出/导入数据。

要在 SQL Server 2005 中编写数据库脚本,可使用 SQL Server Management Studio 中的“对象资源管理器”(右键单击数据库并选择“任务”,然后选择“生成脚本”选项)。此向导带有自解释功能,可以生成一个脚本来创建所有对象、索引、约束、触发器等。

问:我们最近重新设计了架构以便在主表上使用表分区 — 因为我们听说这有助于提高性能。数据库存储在单个的 120GB 驱动器上,并且表包含在单个文件组中。没有需要实现的滑动窗口,只是每周会添加新分区,并且所有数据都需要在线访问。大部分查询处理的都是近一周内的数据,也有一些处理去年的数据。这似乎在单个文件组中更容易实现。我的说法是否正确,有什么需要补充吗?

答:尽管使用单个文件组似乎会比较容易,但这样使用分区可能不会给您带来任何好处。分区的主要用途是在发生灾难时提供更加有效的数据库维护手段并提高数据可用性,另外还可以创建一个可以使性能得到改善的架构。

分区的典型示例是一个具有 12 个分区的销售表,每个分区都代表去年一个月的销售数据。在月末时,系统将切换出最旧的分区(进行归档或删除),然后切换进一个新分区。这就是您所说的滑动窗口方案。当月的分区设为读取/写入,而较旧的月份设为只读。每个分区都存储在单独的文件组中。此架构可实现分区的所有好处,但它并不是适用所有情形的最优架构。

我的妻子 Kimberly 针对上述架构想出了一个主意,可以更有效地编制索引。假如将此销售表拆分成两个表,分别存储单独的读取/写入分区和其余的 11 个只读分区,并且针对两个表使用一个分区视图。

这样,读取/写入表只拥有少量索引,而只读表拥有更多索引以支持报告查询。这会使针对读取/写入数据的数据操作语言 (DML) 操作变得更加有效,因为它们不必维护过多的非群集索引。

同样,针对读取/写入数据的查询也不必处理只读数据。在 SQL Server 2005 中,查询计划中的分区消除功能并不完善,尤其是使用复杂的谓词时;但这一点在 SQL Server 2008 中已大为改善。如需更多信息,请参阅 Kimberly 的博客帖子,网址为:sqlskills.com/blogs/kimberly/default,month,2007-10.aspx#a71c70243-3d57-4940-9af7-a802b73f2f93

为了说明我的意思,我将介绍分区针对多个文件组启用的一些功能。

部分数据库可用性 此功能是指在灾难恢复过程中,只要主要文件组在线,数据库就会在线并可供访问。如果只有一个文件组,则在还原过程中,整个数据库都会脱机。如果将数据分布到多个文件组,则在还原过程中,只有受损的文件组是脱机的,应用程序可能仍然能够继续运行。

逐步还原 此方案类似于部分数据库可用性。当只有一个文件组时,还原单位是单个页面或整个数据库。如果有多个文件组,则可以只还原单个文件组 — 从而实现部分数据库可用性。

分区数据库维护 在使用以上介绍的任何分区方案时,可针对每个分区来执行索引碎片删除操作(即使所有分区都在一个文件组中)。但如果只有一个文件组,则无法针对每个文件组执行一致性检查,而此操作可以显著减少在进行数据库一致性检查 (DBCC) 时需要处理的数据量(以及使用的 CPU 和 I/O 资源数量)。

简而言之,尽管可以在同一文件组中拥有多个分区,但在分区和文件组之间保持 1-1 映射有许多好处。

问:我们最近被一台高端数据库服务器吓坏了 — 由于内存故障导致数据库被损坏。当应用程序中开始出现随机数据时我们才发现这一情况。我们运行了 DBCC CHECKDB 并找到了所有类型的损坏。不幸的是,这一情况也出现在备份文件中,因此我们不得不手动清除坏掉的数据。

长话短说 — 我们更换了坏掉的硬件并开启了“页面校验和”功能。我们想运行定期的一致性检查,但我们没有大型维护窗口,并且我们 2.4TB 的数据库在检查时需要很长的时间。我们该怎么办呢?

答:如何针对 VLDB(超大数据库)执行一致性检查和其他维护操作的问题正变得越来越常见。在发现 DBCC CHECKDB 的运行时间超过了其维护窗口的允许值后,许多 DBA 都放弃了检查(在某些情况下,数据库处于全天候使用状态,基本没有有效的时间来长时间停止执行 DBCC CHECKDB 时所需的 CPU 和 I/O 开销)。

除了放弃和不运行任何一致性检查(我非常反对这样做)外,有以下四种方法可供您选用。我将亲自指导客户如何使用这四种方法。

使用 DBCC CHECKDB 的 WITH PHYSICAL_ONLY 选项 常规 DBCC CHECKDB 运行大量的逻辑一致性检查,从而占用大量 CPU(并且基本上是一个受 CPU 限制的操作)。使用 WITH PHYSICAL_ONLY 选项,可将检查限制为运行非常快速的 DBCC CHECKALLOC 分配位图一致性检查,然后读取和审核数据库中每个已分配的页面,从而强制所有“页面校验和”显示在待测试页面上。这将把 DBCC CHECKDB 变成一个受 I/O 限制的操作,而且运行时间非常短(实际上,它有时甚至比完整的 DBCC CHECKDB 还要快很多,因此可极大地节省时间)。

拆分一致性检查工作量 这需要将数据库中的表分成大小相同的组(最简单的方法是按页面数量拆分),然后每天晚上使用 DBCC CHECKTABLE 命令针对每个组中的所有表执行一致性检查。例如,假设有七个组,我们进行每天一组的检查,即每周一次 DBCC CHECKALLOC 和 DBCC CHECKCATALOG,这将等效于 DBCC CHECKDB(虽然完成周期延长到了一周时间)。

通过多个文件组来使用表分区 VLDB 中的最大表可被拆分成多个文件组。可能采取的一致性检查架构可以是针对存储读取/写入分区的文件组每天运行一次 DBCC CHECKFILEGROUP,此外针对存储只读分区的文件组每周运行一次 DBCC CHECKFILEGROUP。原因是只读数据已全部备份,在进行日常处理时不会用到。因此,无需经常对其执行一致性检查,因为即使此类数据发生损坏也不会造成太过严重的后果。

将一致性检查卸载到其他服务器 此选项将把常规的完整数据库备份还原到另一服务器并在该服务器上运行 DBCC CHECKDB。很显然,这种方法完全避免了在生产服务器上导致一致性检查工作量。但是其缺点就是如果发现有损坏,必须在生产服务器上运行一致性检查 — 不过发生此类事件的概率极低。

正如您所看到的,DBA 可使用多种方法来针对 VLDB 执行一致性检查,从而减少运行完整的 DBCC CHECKDB 所需的额外资源负载。至此,我已按我先前所说的为客户讲解了如何使用这四种方法。我想这些方法对您会非常有帮助。

提示:使用触发器实现服务器端逻辑

在某些情况下,您需要使用触发器来实现服务器端逻辑。但此时存在一些您必须要了解的缺陷问题。下面是一些应该牢记的重要事项。

  • 触发器是由语句触发的,而不是按行触发。也就是说,必须在触发器逻辑中添加额外的逻辑以处理多个行或没有行受到语句影响的情形(触发器是按语句触发的,即使没有行受到影响)。受影响的数据存储在数据操作语言 (DML) 语句的虚拟表中。可将这些表连接起来以便处理其中的数据。
  • 触发器在事务内部同步运行。每次想要调用外部应用程序或访问外部资源时(如果无法确定它是否会在预计的响应时间或合理的时间内返回)都要记住这一点。例如,如果对某个表触发了 Update 语句并且在该操作中有一个触发器被触发,则只有在该触发器的所有逻辑都完成后,此事务(即 Update 语句)才会完成。如果外部应用程序或进程返回一个错误代码,SQL Server 可能会取消事务并回滚该事务(具体取决于实施的错误处理和错误代码)。因此,如果需要在触发器内部执行一些外部操作而它对事务而言又不太重要(或者不必在同一范围内运行),则应将它扩展到另一个进程中并通过异步方式提取数据。SQL Server 2005 引入了 SQL Server Service Broker,它可以异步执行此类操作。
  • 由触发器内部语句导致的错误非常不容易发现。如果事务中包括多个表,则在出现错误时务必要记得检查触发器是否存在错误并执行正确的错误处理操作。如果更改数据库中的架构,务必同时跟踪触发器逻辑 — 否则,一个小小的触发器也可能会对整体性能和稳定性产生极大的影响。架构更改涉及的大部分影响都可以使用 Visual Studio® for Database Professionals 进行检查,它可以在编辑项目时执行自动化架构检查,并可以通过执行静态代码分析来检查数据类型的不一致问题。

—Jens K. Suessmeyer,Microsoft 的数据库顾问

Paul S. RandalSQLskills.com 的总裁,也是 SQL Server 的 MVP。从 1999 年到 2007 年,他一直在 Microsoft 的 SQL Server 存储引擎团队工作。Paul 曾编写过 DBCC CHECKDB/repair for SQL Server 2005,并在 SQL Server 2008 的开发过程中负责核心存储引擎部分的工作。Paul 是灾难恢复、高可用性和数据库维护方面的专家,经常会出席一些会议。他的博客地址是 SQLskills.com/blogs/paul

© 2008 Microsoft Corporation 和 CMP Media, LLC。保留所有权利;未经允许不得复制本文的部分或全部内容。