磁盘 I/O 并行度
为了改善存储在多个磁盘驱动器上的大型 SQL Server 数据库的性能,一个有效的方法是创建磁盘 I/O 并行机制,该机制同时对多个磁盘驱动器进行读写操作。RAID 通过硬件和软件实现磁盘 I/O 并行度。下一个主题讨论使用分区来组织 SQL Server 数据以进一步增加磁盘 I/O 并行度。
使用分区来提高性能 | |
对于存储在多个磁盘驱动器上的 SQL Server 数据库,可通过对数据进行分区以增加磁盘 I/O 并行度来改善性能。
可使用多种方法来进行分区。分区的创建和管理方法包括配置存储子系统(磁盘、RAID 分区)和在 SQL Server 中应用各种数据配置机制(例如,文件、文件组、表和视图)。虽然本节重点介绍一些与性能相关的分区功能,但是第 18 章“在 SQL Server 2000 数据仓库中使用分区”也特别介绍了分区主题。
创建磁盘 I/O 并行度的最简单方法是,使用硬件分区并创建一个为所有的 SQL Server 数据库文件(事务日志文件除外,它们总是应当存储在从物理上分开且仅专用于日志文件的磁盘驱动器上)提供服务的驱动器池。驱动器池可以是一个 RAID 阵列,它在 Windows 中呈现为一个物理驱动器。可以使用多个 RAID 阵列和 SQL Server 文件/文件组来设置较大的池。可以将一个 SQL Server 文件与每个 RAID 阵列相关联,并将这些文件组合成一个 SQL Server 文件组。然后,可基于该文件组构建一个数据库,以便将数据均匀地分布到所有的驱动器和 RAID 控制器上。驱动器池方法依赖 RAID 在所有的物理驱动器之间划分数据,这样有助于确保在数据库服务器操作过程中对该数据进行并行访问。
该驱动器池方法简化了 SQL Server I/O 的性能优化,因为数据库管理员知道只有一个物理位置可供创建数据库对象。可监视单个驱动器池的磁盘队列情况,必要时可向该池中添加更多的硬盘驱动器以防出现磁盘排队现象。一般情况下,无法确定数据库哪些部分的利用率最高,此时使用该方法有助于优化性能。最好不要只是因为 SQL Server 可能要用 5% 的时间来对另一磁盘分区进行 I/O 而将总体可用 I/O 能力的一部分隔离到该磁盘分区上。“单个驱动器池”方法有助于使所有可用的 I/O 能力对于 SQL Server 操作“始终”可用。它还允许 I/O 操作分布到最大数量的可用磁盘上。
SQL Server 日志文件始终 都应该从物理上分散到不同的硬盘驱动器,与所有其他 SQL Server 数据库文件分开。对于管理多个繁忙数据库的极其繁忙的 SQL Server 来说,每个数据库的事务日志文件应当在物理上互相分离,以减少争用现象。
由于事务日志记录主要是顺序写入 I/O,所以将日志文件分开往往会显著提高 I/O 性能。包含日志文件的磁盘驱动器可以非常高效地执行这些顺序写入操作,但前提是这些操作不被其他 I/O 请求中断。有时,将需要在 SQL Server 操作(例如,复制、回滚和延迟更新)过程中读取事务日志。有些实现通过将新数据几乎实时地装载到数据仓库中,将复制用作其数据转换实用工具的前端。参与复制的 SQL Server 的管理员需要确保所有用于事务日志文件的磁盘都有足够的 I/O 处理能力,以便处理除正常日志事务写入之外需要发生的读取操作。
物理上分割的文件和文件组需要额外的管理工作。事实证明,为了隔离和改善对非常活动的表或索引的访问而进行分割时,这些额外的工作是值得的。下面列出了一些益处:
- 对于特定对象的 I/O 需求,可以进行更准确的评估,而如果所有数据库对象都放在一个大驱动器池中,进行这种评估就不那么容易了。
- 使用文件和文件组对数据和索引进行分区,可以增强管理员创建粒度更细的备份和恢复策略的能力。
- 文件和文件组可用于维护数据在磁盘上的顺序放置,从而减少或消除非顺序的 I/O 活动。如果数据装载到数据仓库的可用时间窗口要求并行执行处理以满足最终期限,则该功能就变得尤其重要。
- 在数据库开发和基准检验阶段,可能适于对文件和文件组进行物理分割,这样可收集数据库 I/O 信息并将其应用于生产数据库服务器环境的容量计划。
有关对象分区的注意事项
可以在不同的硬盘驱动器、RAID 控制器和 PCI 通道(或者三者的组合)之间分隔以下方面的 SQL Server 活动:
- 事务日志
- tempdb
- 数据库
- 表
- 非聚集索引
注意 在 SQL Server 2000 中,Microsoft 增强了分布式分区视图,使用这种视图可以创建联合数据库(通常称作扩展),这种数据库会将资源负荷和 I/O 活动分布到多个服务器上。联合数据库适于某些高端联机分析处理 (OLTP) 应用程序,但是建议不要使用该方法来解决数据仓库的需求。
使用硬件 RAID 控制器、RAID 热插拔驱动器和联机 RAID 扩展功能可以轻松实现对 SQL Server I/O 活动的物理分割。最灵活的方法是排列 RAID 控制器,让单独的 RAID 通道与上述不同活动方面相关联。同样,应当将每个 RAID 通道连接到一个单独的 RAID 热插拔机柜,以便充分利用联机 RAID 扩展功能(如果可通过 RAID 控制器使用该功能)。随后,Windows 逻辑驱动器盘符将会与每个 RAID 阵列相关联,并且 SQL Server 文件会基于已知的 I/O 使用模式在不同的 RAID 阵列之间被分隔开。
使用这种配置,有可能将与每个活动相关联的磁盘队列重新与一个不同的 RAID 通道及其驱动器机柜相关联。如果某个 RAID 控制器及其驱动器阵列机柜均支持联机 RAID 扩展功能,而且机柜中有热插拔硬盘驱动器的插槽,则只需向 RAID 阵列中添加更多的驱动器,直到系统监视器报告该 RAID 阵列的磁盘队列已经达到可接受的程度(对于 SQL Server 文件最好少于两个),即可解决该 RAID 阵列的磁盘队列问题。这可以在 SQL Server 联机时完成。
分离事务日志
维护事务日志文件的存储设备应该在物理上与数据文件所在的设备分开。根据您的数据库恢复模型设置不同,大多数更新活动既产生数据设备活动又产生日志活动。如果将这两个活动设置为共享同一个设备,则要执行的操作将争用同一个有限资源。大多数安装都受益于将这些竞争 I/O 活动分开。
分离 tempdb
SQL Server 会在每个服务器实例上创建一个名为 tempdb 的数据库,以供服务器用作各种不同活动的共享工作区,这些活动包括:临时表、排序、处理子查询、生成聚合以支持 GROUP BY 或 ORDER BY 子句、使用 DISTINCT 的查询(必须创建临时工作表才能删除重复行)、游标,以及哈希联接。通过将 tempdb 分割到其自己的 RAID 通道上,我们使 tempdb I/O 操作能够与它们的相关事务的 I/O 操作并行发生。由于 tempdb 实际上是一个草稿区域,而且更新频繁,所以 RAID 5 对于 tempdb 并不是好的选择,而 RAID 1 或 0+1 提供的性能更好。虽然 Raid 0 不提供容错功能,但可以考虑将它用于 tempdb,因为每次重新启动数据库服务器时都会重新生成 tempdb。RAID 0 使用最少的物理驱动器为 tempdb 带来了最佳的 RAID 性能,但在生产环境中将 RAID 0 用于 tempdb 时主要的顾虑是:如果有物理驱动器(包括用于 tempdb 的驱动器)出现故障,就可能影响到 SQL Server 的可用性。如果将 tempdb 放在具备容错能力的 RAID 配置上,就可以避免这一点。
要移动 tempdb 数据库,请使用 ALTER DATABASE 命令更改与 tempdb 相关联的 SQL Server 逻辑文件名的物理文件位置。例如,要将 tempdb 以及与之相关联的日志移到新文件位置 E:\mssql7 和 C:\temp,请使用以下命令:
alter database tempdb modify file (name='tempdev',filename=
'e:\mssql7\tempnew_location.mDF')
alter database tempdb modify file (name='templog',filename=
'c:\temp\tempnew_loglocation.mDF')
与用户数据库相比,master 数据库 msdb 和model 数据库在生产过程中很少使用,因此,在考虑优化 I/O 性能时,通常不必考虑它们。master 数据库通常只用于添加新登录、数据库、设备和其他系统对象。
数据库分区
可以使用文件和/或文件组对数据库进行分区。文件组只是为管理目的而将多个单独的文件组合在一起的命名集合。一个文件不能是多个文件组的成员。表、索引、text、ntext 和 image 数据都可以与一个特定的文件组相关联。这就是说,它们所有的页都是从该文件组中的文件中分配而来的。下面介绍三种类型的文件组。
主文件组
该文件组包含主数据文件以及未放到另一个文件组中的所有其他文件。系统表的所有页都是从主文件组分配的。
用户定义的文件组
该文件组是使用 CREATE DATABASE 或 ALTER DATABASEfilegroup 语句中的 FILEGROUP 关键字或者在 SQL Server 企业管理器中的属性对话框上指定的任何文件组。
默认文件组
默认文件组包含在创建时未指定文件组的所有表和索引的页。在每个数据库中,每次只能有一个文件组是默认文件组。如果未指定默认文件组,则主文件组就是默认文件组。
文件和文件组对于控制数据和索引的位置以及消除设备争用现象很有用。有相当一部分安装还将文件和文件组用作一种比数据库粒度更细的机制,以便对它们的数据库备份/恢复策略进行更多的控制。
水平分区(表)
水平分区将一个表分割成多个表,每个表都包含相同的列数,但是行数会减少。怎样对表进行水平分区要根据分析数据的方式而定。根据一般经验,在对表进行分区时,应当使查询引用的表尽可能少。否则,用于在查询时按逻辑合并表的 UNION 查询就会过多,从而会影响性能。
例如,假定企业要求规定:我们要将十年来不断滚动的事务数据存储到我们数据仓库的中央事实表中。我们公司十年来的事务数据意味着数据会超过十亿行。数量达到十亿的任何内容管理起来都会很困难。现在,请考虑每年我们都必须除去第十年的数据,然后装载最新一年的数据。
管理员通常采用的方法是:创建十个独立但结构相同的表,每个表中存放一年的数据。然后,管理员在这十个表的基础上定义一个联合视图,以便让最终用户看到所有数据都放在一个表中。实际上并非如此。针对该视图执行的任何查询都被优化成只搜索指定的年份(和相应的表)。不过,管理员确实获得了管理能力。现在,管理员能够以粒度方式单独管理每年的数据。每年的数据都可以单独装载、索引或维护。添加新年份就是这样简单:除去该视图,除去包含第十年数据的表,装载和索引新年份的数据,然后重新定义新视图以包括新年份的数据。
当您在多个表或多个服务器之间对数据进行分区时,只访问部分数据的查询运行得更快,因为要扫描的数据比较少。如果这些表位于不同的服务器上,或者在一台具有多个处理器的计算机上,还可以并行扫描查询所涉及的每个表,从而改善查询性能。另外,维护任务(例如,重建索引或备份表)的执行速度会更快。
通过使用分区视图,数据仍显示为一个表,而且在查询数据时可以不必手动引用相应的基础表。如果满足下列任一条件,分区视图就可以进行更新。有关分区视图及其限制的详细信息,请参阅“SQL Server 联机丛书”。
- 在该视图上用可支持 INSERT、UPDATE 和 DELETE 语句的逻辑定义了 INSTEAD OF 触发器。
- 该视图以及 INSERT、UPDATE 和 DELETE 语句遵循为可更新的分区视图定义的规则。
分离非聚集索引
索引驻留在 B 型树结构中,通过使用 ALTER DATABASE 命令来设置一个不同的文件组,这些索引可以与它们的相关数据库表分开(聚集索引除外)。在下面的示例中,第一个 ALTER DATABASE 创建一个文件组。第二个 ALTER DATABASE 向新创建的文件组中添加一个文件。
alter database testdb add filegroup testgroup1
alter database testdb add file (name = 'testfile',
filename = 'e:\mssql7\test1.ndf') to filegroup testgroup1
在创建了文件组及其关联的文件之后,可以在创建索引时指定该文件组,从而使用该文件组来存储索引。
create table test1(col1 char(8))
create index index1 on test1(col1) on testgroup1
SP_HELPFILE 会将有关给定数据库中文件和文件组的信息反馈回来。SP_HELP <表名> 的输出结果中有一节,该节提供有关表的索引及其文件组关系的信息。
sp_helpfile
sp_help test1
并行数据检索
SQL Server 在具有多个处理器的计算机上运行时可以并行扫描数据。如果一个表在包含多个文件的文件组中,则可以对该表执行多个并行扫描。只要按顺序访问某个表,就会创建一个独立线程来并行读取每个文件。例如,如果完全扫描在包含四个文件的文件组上创建的表,将会使用四个独立线程来并行读取数据。因此,为每个文件组创建多个文件会有助于提高性能,因为这样会使用独立的线程来并行扫描每个文件。同样,当某个查询联接着不同文件组上的表时,可以并行读取每个表,从而改进查询性能。
另外,表中的任何 text、ntext 或 image 列都可以在除基表所在文件组以外的文件组上创建。
最终,文件过多会导致并行线程过多,进而导致磁盘 I/O 子系统中出现瓶颈,这时就会达到饱和点。通过使用系统监视器来监视 PhysicalDisk 对象和磁盘队列长度计数器,可以确定这些瓶颈。如果磁盘队列长度计数器大于 3,请考虑减少文件数量。
为了通过使用多个文件并行访问数据来提高吞吐量,将尽可能多的数据分布到尽可能多的物理驱动器上是很有益处的。要将数据均匀地分布到所有磁盘上,请首先设置基于硬件的磁盘条带化,然后根据需要使用文件组将数据分布到多个硬件条带集上。
并行查询建议
SQL Server 可自动以并行方式执行查询。这样就会对在多处理器计算机上执行查询进行优化。工作会细分为多个线程(受线程和内存的可用性影响),而不是一个查询用一个操作系统线程执行,这样,完成复杂查询时就会速度更快,效率更高。
SQL Server 中的优化器会为查询生成计划并确定将在何时并行执行查询。确定时会依据以下条件:
- 计算机是否有多个处理器?
- 是否有足够的内存来并行执行查询?
- 服务器上的 CPU 负荷是多少?
- 正在运行哪种类型的查询?
如果允许 SQL Server 以并行方式运行并行操作(例如 DBCC 和创建索引),对服务器资源的压力就会变重,而且在执行繁重的并行操作任务时,您可能会看到警告信息。如果服务器错误日志中经常出现有关资源不足的警告消息,请考虑使用系统监视器来调查哪些资源(例如,内存、CPU 使用率和 I/O 使用率)可用。
当服务器上有活动用户时,请不要并行运行大量查询。请尝试在没有负载的时间段中执行维护作业(例如,DBCC 和创建索引)。这些作业可以并行执行。监视磁盘 I/O 性能。观察系统监视器(在 Windows NT 4.0 中为性能监视器)中的磁盘队列长度,确定是升级硬盘还是将数据库重新分布到不同的磁盘上。如果 CPU 的使用率非常高,请升级或添加更多的处理器。
下列服务器配置选项可能会影响查询的并行执行:
- 并行度的成本阈值
- 最大并行度
- 最大工作线程
- 查询调控器成本限制
优化数据负荷
在加速数据装载活动时,一定要记住多种提示和方法。根据您执行的是初始数据装载还是增量数据装载,这些方法可能会有所不同。一般来说,增量装载更复杂且限制性更强。您选择的方法可能还基于您无法控制的因素。处理窗口要求、所选存储配置、服务器硬件的限制等都会影响可供您使用的选项。
在执行初始数据装载和增量数据装载时,有一些共同的要点需要记住。下面将详细讨论以下主题:
- 选择适当的数据库恢复模型
- 使用 bcp、BULK INSERT 或大容量复制 API
- 控制锁定行为
- 并行装载数据
- 杂项,其中包括:
- 绕过引用完整性检查(约束和触发器)
- 装载预先排序的数据
- 删除索引带来的影响
选择适当的数据库恢复模型
我们已在“影响性能的配置选项”一节中讨论了数据库恢复模型。一定要记住所选恢复模型对执行数据装载所需的时间可能会有很大的影响。这些恢复模型主要控制将写出到事务日志中的数据量。因为对事务日志执行写入操作基本上会使工作负荷加倍,所以这非常重要。
日志记录和最小日志记录大容量复制操作
在使用完全恢复模型时,由某个大容量数据装载机制(将在下面讨论)执行的所有插入行操作都记录到事务日志中。对于大型数据装载,这可能会导致快速填充事务日志。为了帮助防止事务日志的空间不足,可执行最小日志记录大容量复制操作。是以日志记录还是以无日志记录形式执行大容量复制不作为大容量复制操作的一部分来指定;它取决于大容量复制中涉及到的数据库和表的状态。如果符合以下所有的条件,将进行无日志记录的大容量复制:
- 恢复模型是“简单”或“大容量日志记录的”,或者数据库选项 select into/bulkcopy 设置为真。
- 目标表未在进行复制。
- 目标表没有索引,或者如果目标表有索引,在开始大容量复制时它也是空的。
- TABLOCK 提示是在将 eOption 设置为 BCPHINTS 的情况下使用 bcp_control 指定的。
任何不满足上述条件的向 SQL Server 实例中进行的大容量复制将完全记录下来。
在执行初始数据装载时,应当总是在“大容量日志记录的”或“简单”恢复模型下运行。对于增量数据装载,只要数据丢失的可能性很低,就考虑使用“大容量日志记录的”模型。因为许多数据仓库基本上都是只读的或者事务活动的数量很少,所以将数据库恢复模型设置为“大容量日志记录的”不会产生任何问题。
使用 bcp、BULK INSERT 或大容量复制 API
SQL Server 内部存在两个机制,用来解决大容量移动数据的需求。第一个机制是 bcp 实用工具。第二个机制是 BULK INSERT 语句。bcp 是一个命令提示符实用工具,它既将数据复制到 SQL Server 中又从其中复制数据。在 SQL Server 2000 中,bcp 实用工具是用 ODBC 大容量复制应用程序编程接口 (API) 重新编写的。bcp 实用工具的早期版本是使用 DB-Library 大容量复制 API 编写的。
BULK INSERT 是 SQL Server 附带的 Transact-SQL 语句,该语句可从数据库环境内执行。与 bcp 不同的是,BULK INSERT 只能将数据拉入 SQL Server 中。它不能将数据推出。使用 BULK INSERT 的一个好处在于,它可以使用 Transact-SQL 语句将数据复制到 SQL Server 的实例中,而不必退出解释器转到命令提示符中。
第三个选项是大容量复制 API,程序员通常对该选项很感兴趣。有了这些 API,程序员就能够使用 ODBC、OLE DB、SQL-DMO 或者甚至基于 DB 库的应用程序将数据移入或移出 SQL Server。
所有这些选项都使您能够对批处理大小进行控制。除非您使用的是小容量数据,否则最好习惯于指定批处理大小以进行恢复。如果未指定批处理大小,则 SQL Server 将所有要装载的行作为一批提交。例如,您尝试将 1,000,000 行新数据装载到某个表中。服务器在处理完第 999,999 行后突然断电。当服务器恢复时,将需要从数据库中回滚处理完的 999,999 行,然后再尝试重新装载数据。您可以通过将批处理大小指定为 10,000 来大大节省自己的恢复时间,这是由于您已经将 1 到 990,000 行提交到数据库中,因此将只需回滚 9,999 行(而不是 999,999 行)。同样,如果未指定批处理大小,则将必须从第 1 行重新启动装载处理才能重新装载数据。如果将批处理大小指定为 10,000 行,则只需从第 990,001 行重新启动装载处理,这样就高效地绕过了已经提交的 990,000 行。
控制锁定行为
bcp 实用工具和 BULK INSERT 语句接受 TABLOCK 提示,该提示允许用户指定要使用的锁定行为。TABLOCK 指定在大容量复制操作过程中将采用大容量更新表级锁。使用 TABLOCK 可以减少表上对锁的争用,从而改进大容量复制操作的性能。当针对单个表处理并行装载时,该设置有非常重要的含义(将在下一节讨论)。
例如,要将 Authors.txt 数据文件中的数据大容量复制到 pubs 数据库中的 authors2 表中,请指定表级锁,并从以下命令行提示符执行:
bcp pubs..authors2 in authors.txt -c -t, -Sservername -Usa -Ppassword -h "TABLOCK"
或者,您可以从查询工具(如 SQL 查询分析器),使用 BULK INSERT 语句来大容量复制数据,如下例所示:
BULK INSERT pubs..authors2 FROM 'c:\authors.txt'
WITH (
DATAFILETYPE = 'char',
FIELDTERMINATOR = ',',
TABLOCK
)
如果未指定 TABLOCK,除非对于表将 table lock on bulk load 选项设置为 on,否则默认锁定会使用行级锁。将 table lock on bulk load 选项与 sp_tableoption 命令一起使用,也可以设置大容量装载操作过程中表的锁定行为。
Table lock on bulk load | 表的锁定行为 |
---|---|
Off | 使用行级锁 |
On | 使用表级锁 |
注意 如果指定了 TABLOCK 提示,则在大容量装载过程中,它将替代使用 sp_tableoption 声明的设置。
并行装载数据
并行装载 — 非分区表
使用 SQL Server 中的任一大容量数据装载机制,都可以将数据并行装载到一个非分区表中。这是通过同时运行多个数据装载来完成的。在开始装载之前,需要将要并行装载的数据拆分成多个独立文件(大容量插入 API 的数据源)。然后,可同时启动所有的独立装载操作,以便并行装载数据。
例如,假设您需要为在全球四个地区运作的服务公司装载合并的数据库,每个地区每个月都报告寄给客户的帐单上的报告时间(小时)。对于大型服务组织,这可能表示需要合并大量事务数据。如果这四个报告地区都分别提供独立文件,则可以使用上面介绍的方法将这四个文件同时装载到一个表中。
注意 并行处理的并行线程(装载)的数量不应超过 SQL Server 的可用处理器数。
下面的插图说明了对非分区表进行的并行装载。
如果您的浏览器不支持嵌入式框架,请单击此处在单独的页中查看。
并行装载:水平分区(表)
本节重点介绍如何使用水平分区表来提高数据装载的速度。在上一节中,我们讨论了将数据从多个文件装载到一个(非分区)表中。如果对表进行水平分区,则可以减少设备争用现象,从而有机会改善数据的连续性并加速装载过程。虽然上图显示的是数据装载到了表的不同部分中,但这样的表述可能不准确。如果上述装载中的所有三个线程是同时处理的,为该表提取的扩展盘区最后就可能会是混合状态。在数据混合后,可能导致在检索数据时无法实现最佳性能。这是由于数据不是按物理上连续的顺序存储的,从而可能导致系统使用不连续的 I/O 访问它。
在该表基础上生成聚集索引将解决上述问题,因为数据会按连续顺序被读入、按键顺序排序,并被回写。但是,读取、排序、删除旧数据以及将新排序的数据回写可能是一项非常耗时的任务(请参阅下面的装载预先排序的数据)。为避免出现这种混合的情形,请考虑使用文件组在可以存储大表的位置保留多块连续空间。许多安装还使用文件组将索引数据与表数据分开。
为便于阐述,假定有一个数据仓库分配在一个大型物理分区上。任何对该数据库并行执行的装载操作都有可能导致以非连续(混合)状态存储受影响的数据/索引页。将执行哪种操作?任何对数据进行修改的操作都将导致数据变得不连续。为了满足处理窗口的要求,用户可能会尝试并行执行初始数据装载、增量数据装载、索引创建、索引维护、插入、更新、删除等活动。
下面的插图显示的是跨多个文件组对表分区。
如果您的浏览器不支持嵌入式框架,请单击此处在单独的页中查看。
装载预先排序的数据
SQL Server 的早期版本提供了一个选项,您可以在创建索引时用它来指定 SORTED_DATA 选项。SQL Server 2000 取消了这个选项。在早期版本中,将该选项指定为 CREATE INDEX 语句的一部分是:它能够让您在索引创建过程中避免排序的步骤。默认情况下,在 SQL Server 中创建聚集索引时,表中数据会在处理过程中排序。要在 SQL Server 2000 中获得同样的效果,请考虑在大容量装载数据之前创建聚集索引。SQL Server 2000 中的大容量操作使用了增强的索引维护策略,这样,对于已具有聚集索引的表,可以改进数据导入性能,而且在导入之后不需要对数据重新排序。
FILLFACTOR 和 PAD_INDEX 对数据装载的影响
FILLFACTOR 和 PAD_INDEX 将在题为“索引和索引维护”的一节中更完整地介绍。对于 FILLFACTOR 和 PAD_INDEX,都需要记住关键的一点:创建索引时,如果将它们保留为默认值设置,可能会导致 SQL Server 为存储数据而执行比必需数量多的写入和读取 I/O 操作。如果数据仓库中没有发生多少写入活动但发生了大量读取活动,则更是如此。要让 SQL Server 在一页数据页或索引页中写入更多的数据,您可以在创建索引时指定特定的 FILLFACTOR。最好在提供覆盖 FILLFACTOR 值时指定 PAD_INDEX。
初始数据装载的一般准则
在装载数据时
- 删除索引(唯一的例外可能是在装载预先排序的数据时,请参阅上文)
- 使用 BULK INSERT、bcp 或大容量复制 API
- 使用分区数据文件并行装载到分区表中
- 对于每个可用的 CPU 运行一个装载流
- 设置“大容量日志记录的”或“简单”恢复模型
- 使用 TABLOCK 选项
在装载数据之后
- 创建索引
- 切换到相应的恢复模型
- 执行备份
增量数据装载的一般准则
- 用索引将数据装载到适当位置。
- 应当根据性能和并发要求来确定锁定粒度 (sp_indexoption)。
- 除非特别需要保留时点恢复(例如,联机用户在大容量装载过程中修改数据库),否则请将恢复模型从“完全”更改为“大容量日志记录的”。读取操作不应当影响大容量装载。
索引和索引维护
前面已经讨论了服务器硬件设备的 I/O 特征。现在,我们将讨论 SQL Server 数据和索引结构在物理上是如何放置在磁盘驱动器上的。如果要在设计完成之后改善性能,则索引位置有可能是影响数据仓库的一个最大因素。
SQL Server 中的索引类型
虽然 SQL Server 2000 引入了几种新索引类型,但它们全部都基于两个核心窗体。这两个核心窗体的格式是聚集索引或非聚集索引。在 SQL Server 中,数据库设计人员可以使用以下两种主要类型的索引:
- 聚集索引。
- 非聚集索引。
这两个主要类型的其他变体包括:
- 唯一索引。
- 计算列的索引。
- 索引视图。
- 全文索引。
以下各节将详细介绍上面提到的每种索引(全文索引除外)。全文索引是一种特殊情况,它与其他数据库索引不同,本章不对它进行介绍。索引视图是 SQL Server 2000 中新引入的一种索引,它应该会引起数据仓库用户的特别关注。SQL Server 2000 中引入的另一项新功能是按升序或降序创建索引。
索引的工作原理
数据库中的索引类似于图书中的索引。在一本书中,您使用索引可以迅速找到信息,而不必读完全书。在一个数据库中,数据库程序使用索引可以找到表中的数据,而不必扫描整个表。书中的索引是一个字词以及各字词所在页码的列表。数据库中的索引是表中的值以及各值存储位置(在表中所在的行)的列表。
索引可以针对表中的一列或一组列创建,并以 B 树的形式实现。索引包含一个条目以及一个或多个对应于表中每一行的列(搜索键)。B 树根据搜索键的排序次序按升序或者降序(视创建索引时所选选项而定)存储,利用该搜索键的任何前导子集,可以高效地搜索到 B 树。例如,利用以下组合,可以高效地搜索 A、B、C 列的索引:A;A 和 B;A、B 和 C。
当您创建数据库并优化其性能时,应当为查询中使用的列创建用来查找数据的索引。在 SQL Server 附带的 pubs 示例数据库中,在 employee 表的 emp_id 列有一个索引。当用户执行的语句根据指定的 emp_id 值在 employee 表中查找数据时,SQL Server 查询处理器识别 emp_id 列的索引并使用该索引来查找数据。下面的插图说明了该索引如何存储每个 emp_id 值并指向表中具有相应值的数据所在的行。
如果您的浏览器不支持嵌入式框架,请单击此处在单独的页中查看。
但是,带索引的表需要在数据库中占用更多的存储空间。同样,用来插入、更新或删除数据的命令的运行时间以及维护索引所需的处理时间会更长。您在设计和创建索引时,一定要注意:性能益处比存储空间和处理资源所导致的额外成本更加重要。
索引交集
SQL Server 查询处理器中有一项独特的功能:执行索引交集。这是一种特殊形式的索引覆盖,我们将在以后详述,但是现在因以下两个原因而需要提及索引交集。第一,它是一种可能会影响您的索引设计策略的技术。
第二,该技术可能会减少您需要的索引数,从而可以大大节省大型数据库占用的磁盘空间。
索引交集允许查询处理器使用多个索引来解决查询。大多数数据库查询处理器在尝试解决查询时都只使用一个索引。SQL Server 可以组合给定表或视图中的多个索引,基于这些索引生成哈希表并利用哈希表来减少给定查询的 I/O 操作。就本质而言,从索引交集中生成的哈希表变成了覆盖索引,而且,它提供的 I/O 性能与覆盖索引提供的相同。在数据库用户环境中,很难预先确定将针对该数据库运行的所有查询,而索引交集为这种环境提供了更大的灵活性。在这种情况下,较好的策略是针对所有经常会被查询的列定义单列的非群集索引,并让索引交集处理来需要覆盖索引的情形。
下面的示例使用了索引交集:
Create index Indexname1 on Table1(col2)
Create index Indexname2 on Table1(col3)
Select col3 from table1 where col2 = 'value'
在执行上面的查询时,可以通过组合这些索引来快速高效地解决该查询。
SQL Server 中的索引结构
SQL Server 中所有的索引在物理上都是基于存储在 8 KB 索引页上的 B 树索引结构构建的。每个索引页都有一个页头,页头后面是索引行。每个索引行都包含一个键值和一个指向行级索引页或实际数据行的指针。索引中的每一页又被称作一个索引节点。B 树的顶层节点被称作根节点。索引中的底层节点被称作叶节点。根和叶之间的任何索引层都统称为中级层或节点。每层索引中的页都在双向链接列表中链接在一起。
SQL Server 数据页和索引页的大小均为 8 KB。SQL Server 数据页包含所有与表中某行关联的数据(文本和图像数据可能除外)。就文本和图像数据而言,在默认情况下,包含与该文本或图像列关联的行的 SQL Server 数据页将包含一个指针,该指针指向一个或多个包含该文本或图像数据的 8 KB 页的二进制树(或 B 树)结构。SQL Server 2000 中的一个新功能是能够将小型文本和图像值存储在行中,这意味着小型文本或图像列将存储在数据页上。因为可以避免提取相应的图像或文本数据所必需的额外 I/O,所以该功能可以减少 I/O 操作。有关如何将表设置为在行中存储文本或图像的信息,请参阅“SQL Server 联机从书”。
聚集索引
聚集索引对于从表中检索一定范围的数据值非常有用。非聚集索引最适于检索特定行,而聚集索引最适于检索一定范围的行。但是,由于每个表只允许使用一个聚集索引,因此按照这个简单的逻辑来确定要创建哪种类型的索引并不总能成功。对于该问题有一个简单的物理原因。对于聚集索引 B 树结构的上部(非叶层),如果像对它们的非聚集索引部分那样组织,则聚集索引的底层由表的实际 8 KB 数据页组成。但这种情况有一个例外,那就是在视图的基础上创建聚集索引时。因为将在下面介绍索引视图,所以我们将讨论针对实际表创建的聚集索引。在针对表创建聚集索引时,会按与索引搜索键相同的顺序读取与该表关联的数据、对这些数据进行排序,并会在物理上将它们存回数据库。因为该表的数据只能按照一种顺序保存到存储器中,不会导致重复,所以符合一个聚集的限制。
下图描述了聚集索引的存储器。
如果您的浏览器不支持嵌入式框架,请单击此处在单独的页中查看。
聚集索引和性能
聚集索引有一些会影响性能的固有特征。
在使用聚集索引根据搜索键来检索 SQL Server 数据时,不需要指针跳转(会导致硬盘上的位置可能不按顺序更改)来检索关联的数据页。这是由于聚集索引的叶层实际上就是关联的数据页。
如前所述,叶层(当然也包括表或索引视图的数据)在物理上会按照与搜索键相同的顺序进行排序和存储。因为聚集索引的叶层包含表的实际 8 KB 数据页,所以整个表的行数据会按照由聚集索引确定的顺序以物理方式排列在磁盘驱动器上。这就会在根据聚集索引的值从该表中提取大量行(至少大于 64 KB)时带来潜在的 I/O 性能优势,因为使用的是顺序磁盘 I/O(除非该表上发生了页拆分,这种情况将在题为“FILLFACTOR 和 PAD_INDEX”的一节中讨论)。正因为如此,所以在检索大量行时,一定要根据将用于执行范围扫描的列来对表选取聚集索引。
表中与聚集索引相关联的行必须按照与索引搜索键相同的顺序排序和存储,这一点具有以下意义:
- 在您创建聚集索引时,表会被复制,表中的数据会被排序,然后,原来的表会被删除。所以,数据库中必须有足够的空闲空间来存放数据的副本。
- 在默认情况下,会在创建索引时对表中的数据进行排序。但是,如果数据已按正确顺序排过序,则会自动跳过排序操作。这样就可以显著加快索引创建过程。
- 将数据装载到表中时的顺序应尽可能与您计划用于生成聚集索引的搜索键的顺序相同。对于大表(例如那些通常会成为数据仓库特征的表),该方法将大大加速索引创建过程,从而缩短您处理初始数据装载所需的时间。只要表中的行仍保持未创建聚集索引时所排的顺序,就可以在除去和重建聚集索引时可以使用该方法。任何行排序有误,操作都会被取消,会出现相应的错误信息,而且不会创建索引。
- 同样,针对排过序的数据生成聚集索引时所需要的 I/O 也少得多,这是因为不必复制数据、对数据进行排序、将数据存回数据库,然后删除旧表数据,而是会将数据留在原来分配给它的扩展盘区中。索引扩展盘区只是添加到数据库中来存储顶层节点和中间节点。
注意 针对大表生成索引的首选方法是:先生成聚集索引,然后生成非聚集索引。这样,就不会因为数据移动而需要重新生成非聚集索引。在除去所有索引时,首先会除去非聚集索引,最后除去聚集索引。这样,就不需要重新生成索引。