在 Microsoft SQL Server 2000 数据仓库中使用分区
摘要:本文介绍如何使用分区来改善 SQL Server 2000 Enterprise Edition 中数据仓库的可管理性、查询性能和加载速度,并讨论关系型数据库和分析服务多维数据集中的矢量架构的水平分区。
目录
概述
在 SQL Server 2000 关系型数据仓库中使用分区
在 SQL Server 2000 分析服务中使用分区
总结
更多信息
附录:复制分区的 VBScript 代码示例
概述
本文讨论数据仓库中数据分区的作用。关系型数据仓库和分析服务多维数据集都支持数据分区。分区的逻辑概念在 Microsoft? SQL Server? 的两个引擎中是相同的:通过键(例如日期)对数据进行水平分区。在关系型数据库中,分区是通过创建单独的物理表(例如为每个月的数据创建一个表)并且定义一个成员表的联合视图来实现的。与此类似,SQL Server Enterprise Edition 中的分析服务支持显式的多维数据集分区。在关系型数据库和联机分析处理 (OLAP) 引擎中,物理存储的复杂性对于分析用户是不可见的。
数据仓库分区的优点:
- 大大缩短查询时间。
- 减少加载时间,改善数据库的可维护性。
- 解决从活动数据库中删除旧数据时出现的数据修剪问题。
该技术需要创建比非分区系统更复杂的数据分阶段应用程序。本文介绍设计、实现和维护水平分区数据仓库的最佳方法。
因为有效的分区计划可以极大地改善查询性能,所以我们极力建议您对大型分析服务系统进行分区。尽管对于某些特定的数据仓库维护问题,对关系型数据仓库进行分区是有效的解决方案,但通常不推荐您这样做。
在 SQL Server 2000 关系型数据仓库中使用分区
分区视图联接来自一组成员的水平分区数据,使数据看起来象来自同一张表。SQL Server 2000 区分本地分区视图和分布式分区视图。在本地分区视图中,所有相关表和视图驻留在 SQL Server 的同一实例上。在分布式分区视图中,相关表中至少有一张表驻留在其他某个(远程)服务器上。建议您不要将分布式分区视图用于数据仓库应用程序。
矢量数据仓库围绕事实(标量)和矢量构建,从物理上通常表示为星形架构和雪花形架构,极少有同时包含事实和矢量的完全非正交化的平面表。由于矢量架构是最常见的关系型数据仓库结构,本文集中讨论这类架构的分区。下面的建议也适用于其他通用数据仓库架构。
分区的优点
数据修剪
许多数据仓库管理员会定期将陈旧的数据归档。例如,一个单击流数据仓库可能只将详细数据联机保留三至四个月。其他常见的规则可能是联机保留 13 个月、37 个月或 10 年,当旧数据不在活动窗口中时就归档并从数据库中删除。这种滚动窗口结构是大数据仓库通常采取的做法。
在没有分区表的情况下,从数据库中删除旧数据的进程需要一个很大的 DELETE 语句,例如:
DELETE FROM fact_table WHERE date_key < 19990101
执行该语句开销会非常大,可能比同一张表的加载进程需要更多的时间。相反,对于分区表,管理员重新定义 UNION ALL 视图以排除最旧的表,然后将该表从数据库中删除(假设已确保备份该表),这个过程几乎可以在瞬间完成。
后面我们会讨论到,维护分区表的费用也很高。如果数据修剪是采用分区的唯一原因,设计者应考虑以数据分解的方式从未分区的表中删除旧数据。在低优先级进程上连续运行一个每次删除 1000 行(用“set rowcount 1000”命令)的脚本,直至删除所有希望删除的数据。该技术可在大系统上有效运用,比创建必要的分区管理系统更为直接。根据加载量和系统使用状况,该技术适合于某些系统,并应该考虑在系统上进行基准测试。
加载速度
加载数据最快的方法是将数据加载至空表或没有索引的表。通过加载至较小的分区表,渐变加载进程的效率将大大提高。
可维护性
一旦已建成支持分区的数据仓库分阶段应用程序,整个系统将变得容易维护。维护活动(包括加载数据、备份和还原表)可以并行地执行,这样可以极大地改善性能。渐变填充下行数据流多维数据集的进程可以被加速和简化。
查询速度
查询速度不应该作为对数据仓库关系型数据库进行分区的理由。对于分区和未分区的事实表,查询性能都差不多。在正确设计的分区数据库中,关系引擎仅在查询计划中包括解析查询所需的相关分区。例如,如果数据库按月分区,查询条件为 2000 年 1 月,则查询计划仅包括 2000 年 1 月的分区。结果查询将对分区表正确执行,与在分区键上带有簇索引的已索引合并表上执行的大体相同。
分区的缺点
复杂性
分区的主要缺点是需要管理员创建应用程序来管理分区。在尚未设计、测试和试运行应用程序来管理分区之前,将在关系型数据库中使用水平分区的数据仓库投入正式运行是不恰当的。本文的目的之一就是讨论与分区管理应用程序有关的问题和设计决策。
查询设计约束
要获得最佳的查询性能,所有的查询都应将条件直接放在事实表中的筛选键上。将约束放在第二张表(例如以日期为矢量的表)的查询将包括所有分区。
设计时要考虑的因素
矢量数据仓库围绕事实(标量)和矢量构建,从物理上通常表示为星形架构和雪花形架构,极少有同时包含事实和矢量的完全非正交化的平面表。典型情况下,矢量数据仓库的管理员仅对事实表进行分区;对矢量表进行分区几乎没有什么好处。在某些情况下,对包含多于一千万个成员的大型矢量表进行分区会有些好处。也可以对非矢量关系型数据仓库进行分区,本文中的一般观点仍然适用。
只有充分考虑系统体系结构和设计目标,才能制订有效的分区计划。即使使用相同的架构设计,仅用于填充服务分析多维数据集的关系型数据仓库可能采用一个不同于分析员直接查询的数据仓库的分区结构。带有滚动窗口的系统必须按时间分区,其他系统则不一定。
如果数据仓库包括分析服务多维数据集,Microsoft 建议关系型数据仓库和分析服务数据库中的分区应该为并行结构。维护应用程序被简化了:应用程序在关系型数据库中创建新表的同时创建一个新多维数据集分区。管理员仅需要掌握一种分区策略。不过,一个应用程序也可能有充分的理由对两个数据库以不同方式进行分区,唯一降低的将是数据库维护应用程序的复杂性。
分区设计概述
SQL Server 数据库中的分区表可以使用可更新或可查询(不可更新)的分区视图。在这两种情况下,表分区都是由每个分区都包含正确数据的 CHECK 约束来创建的。一个可更新的分区视图支持对视图进行 INSERT (或 UPDATE 或 DELETE)操作,并将操作推入至正确的基础表。这很有益处,但数据仓库应用程序通常需要进行批量加载,而这是无法通过视图执行的。下表总结了可更新和可查询分区视图的要求、优点和缺点。
要求 | 优点 | 缺点 |
可更新的分区视图 | ||
|
|
|
可查询的分区视图 | ||
|
|
|
Microsoft 建议的做法是定义主键,并将事实表设计为本地(单个服务器上)的分区联合视图。大多数情况下,该定义会产生可更新的分区视图,但数据仓库维护应用程序应设计为直接将大多数数据批量加载至成员表(而不是通过视图进行)。
语法示例
以下代码示例用来说明定义成员表和联合视图以及将数据插入视图的语法:
-- 创建 1999 年事实表 CREATE TABLE [dbo].[sales_fact_19990101] ( [date_key] [int] NOT NULL CHECK ([date_key] BETWEEN 19990101 AND 19991231), [product_key] [int] NOT NULL , [customer_key] [int] NOT NULL , [promotion_key] [int] NOT NULL , [store_key] [int] NOT NULL , [store_sales] [money] NULL , [store_cost] [money] NULL , [unit_sales] [float] NULL ) ALTER TABLE [sales_fact_19990101] ADD PRIMARY KEY ( [date_key], [product_key], [customer_key], [promotion_key], [store_key]) ; -- 创建 2000 年事实表 CREATE TABLE [dbo].[sales_fact_20000101] ( [date_key] [int] NOT NULL CHECK ([date_key] BETWEEN 20000101 AND 20001231), [product_key] [int] NOT NULL , [customer_key] [int] NOT NULL , [promotion_key] [int] NOT NULL , [store_key] [int] NOT NULL , [store_sales] [money] NULL , [store_cost] [money] NULL , [unit_sales] [float] NULL ) ALTER TABLE [sales_fact_20000101] ADD PRIMARY KEY ( [date_key], [product_key], [customer_key], [promotion_key], [store_key]) ; --创建 UNION ALL 视图。 CREATE VIEW [dbo].[sales_fact] AS SELECT * FROM [dbo].[sales_fact_19990101] UNION ALL SELECT * FROM [dbo].[sales_fact_20000101] --现在插入几行数据,例如: INSERT INTO [sales_fact] VALUES (19990125, 347, 8901, 0, 13, 5.3100, 1.8585, 3.0) INSERT INTO [sales_fact] VALUES (19990324, 576, 7203, 0, 13, 2.1000, 0.9450, 3.0) INSERT INTO [sales_fact] VALUES (19990604, 139, 7203, 0, 13, 5.3700, 2.2017, 3.0) INSERT INTO [sales_fact] VALUES (20000914, 396, 8814, 0, 13, 6.4800, 2.0736, 2.0) INSERT INTO [sales_fact] VALUES (20001113, 260, 8269, 0, 13, 5.5200, 2.4840, 3.0)
要验证分区是否正常工作,请使用查询分析器来显示查询计划,例如:
SELECT TOP 2 * FROM sales_fact WHERE date_key = 19990324
您应该看到查询计划中仅包括表 1999。将该查询计划与主键已删除的相同表生成的查询计划相比较,我们会发现:表 2000 仍然被排除。将这些计划与在已删除 date_key 约束的架构上生成的查询计划进行对比。这些约束被删除的情况下,表 1999 和表 2000 都被包括在查询中。
请注意,在通常情况下,在大型表上执行查询时,使用“TOP N”语法是好的做法,因为它可以迅速返回结果并使用最少的服务器资源。查看分区表的查询计划时,这一点尤为重要,因为由“SELECT *”语句生成的查询计划很难解析。对于偶尔进行观察的人而言,尽管在查询执行期间,查询中仅使用相关的表,但表面看起来好象查询计划包括了 UNION ALL 视图的所有组件表。
将条件直接应用于事实表
要获得最佳的查询性能,所有的查询都应将条件直接放在事实表中的筛选键上。将约束放在第二张表(例如日期矢量表)的查询将包括所有分区。对 UNION ALL 事实表的标准星号联合查询工作良好:
- 将条件放在任意未分区的矢量表的属性上,以标准方式创建星号查询 WHERE 子句。
- 包括分区矢量(日期)的属性。
- 在分区矢量架构上设计查询与在未分区的架构上设计完全一样,只不过将日期条件直接放在事实表中的日期键上时日期条件最为有效。
如果每一分区表的索引中的第一个列为带日期的簇索引,转到所有分区解析某一特定查询的开销相对较小。编写预定义的查询时应尽可能提高其效率,例如那些生成标准报表或渐变更新下行数据流数据库的查询。
分区键的选择
事实表可以在多个矢量上进行分区,但是大多数人可能仅按日期进行分区。如前面描述的,日期分区可以支持简单的“滚动窗口”管理, 较旧的分区甚至可以被保存在不同的位置,或者减少索引次数。同样,大多数对数据仓库的查询是按日期进行筛选的。
对于按日期分区的应用程序,决策变量为:
- 使多少数据保持联机状态?这项决策的主要依据是业务要求,同时要考虑保持大量数据联机的费效比。
- 如何设计日期键?数据仓库最好对矢量表和事实表使用代理键,这是得到广泛认可的。对于按日期分区的事实表,建议的做法为使用 yyyymmdd 形式的“智能”整数代理键。作为整数,与 8 字节的 datetime 相比较,该键仅使用 4 个字节。许多数据仓库使用 datetime 类型的自然日期键。
- 如何确定分区的大小?尽管上面的示例使用年分区,但大多数系统会划分得更细致,例如月、星期或天。尽管我们会注意到用户查询通常是按月或周进行的,但最重要的因素还是系统总体规模和可管理性。您可能还记得,任何一个 SQL 查询最多可以引用 256 张表。对于维护多于一个月的数据的数据仓库,按天来分区的 UNION ALL 视图会超过该界限。作为一个好的规则,如果事实表仅按日期分区,那么最好按星期分区。
- 如何定义分区的范围?BETWEEN 语法最直接、可读性最强、执行效率最高。以下述形式的按月分区为例:
date_key < 19990101 date_key BETWEEN 1990101 AND 19990131 date_key BETWEEN 19990201 AND 19990229 ... date_key BETWEEN 19991201 AND 19991231 date_key > 19991231
请注意其中的第一个和最后一个分区:即使您认为决不会有数据进入这些分区,这仍是一个定义分区的好方法,这样可以覆盖所有可能的日期值。同时,请注意尽管 1999 年不是闰年,但二月分区仍覆盖 2 月 29 日。该结构使设计创建分区和约束的应用程序时不需要判断是否为闰年。
- 随着时间流逝,要合并分区吗?为了使活动分区的数量最小,创建分区应用程序时,数据库管理员可以选择将日分区合并为星期分区或月分区。我们将在下面有关填充和维护分区的一节中详细讨论这种方法。
关于如何按日期分区的详细讨论同样适用于采用其他可能的分区键时的情形。
数据加载:如果新的数据具有与其他矢量对齐的明显倾向,或者例如,如果每个存储或附件是由不同的系统分发的,这些就是自然的分区键。
多维数据集数据查询:尽管并没有技术原因要求以相同的方式对关系型数据库和分析服务多维数据集进行分区,但这是通常采用的做法。如果做出这个假设,维护应用程序将得以简化。这样,即使关系型数据库的存在仅是用于填充分析服务多维数据集,在选择分区键时也应该考虑到一般的查询模式。
命名约定
命名水平分区事实表的成员表的规则应该从分区设计自然地引出。为获得最大的通用性,请在标题中使用完整的分区开始日期:即使分区每年进行一次,[sales_fact_yyyymmdd] 也比 [sales_fact_yyyy] 好。
如果数据库支持多个大小的分区,命名约定应该反映每个分区的时间范围。例如,月分区使用 sales_fact_20001101m,日分区使用 sales_fact_20001101d。
成员表的名称对通过视图访问数据的最终用户是隐藏的,因此成员表的名称应该面向执行维护的应用程序。
下行数据流多维数据集的分区
如果关系型数据库仅用于支持分析服务多维数据集,就不必定义 UNION ALL 视图。这种情况下,该应用程序就不会受 256 个表的限制,但是建议您不要以这种无法定义 UNION ALL 视图的方式来对关系型数据仓库进行分区。
管理分区事实表
在分区管理已能够自动进行并通过测试之前,分区的数据仓库不应该正式投入使用。分区管理系统是一种简单的应用程序,该系统的一般要求在下面讨论。
下面的讨论假设分区是按日期进行的。
元数据
稳定的分区管理系统应由元数据驱动。只要确保能够编程访问元数据,就可以把元数据存储在任何位置。大多数数据仓库系统使用在数据仓库 SQL Server 或 Microsoft SQL Server Meta Data Services 上定义的自定义元数据表。
不论元数据的存储机制是什么,元数据的内容必须包括每个分区的以下信息:
- 分区名称
- 创建分区的日期
- 分区中数据的日期范围
- 分区开始联机的日期(加入 UNION ALL 视图)
- 分区不再联机的日期(从视图中丢弃)
- 丢弃分区的日期
作为数据仓库整个管理系统的一部分的其他元数据表,应该跟踪何时以及有多少数据被加载到每个分区。
创建新分区
分区管理系统的首要任务是创建新分区。应该安排周期性运行的任务,来创建用作下一个分区的新表。
执行该任务有许多有效的方式。建议的方法为使用 SQL-DMO(分布式管理对象)来创建与现有分区具有相同结构和索引的新表,但新表具有新的表名、索引名、分区键约束定义、文件组等等:
- 获取模板表定义(通常为最新的分区);
- 修改表和索引的 Name 属性,检查约束 Text 属性和其他属性;
- 使用 ADD 方法对表进行实例化。
使用智能命名约定,用几行代码即可完成这项任务。
如本文后面将要讨论的那样,您的应用程序可以将分析服务分区用于数据仓库系统的多维数据集。如果这样,在 RDBMS 中创建分区的脚本和程序可以使用决策支持对象 (DSO) 继续创建相应的多维数据集分区。
填充分区
前面提到过,数据可以被加载入 UNION ALL 视图。理论上,这是表分区结构的一大功能,但在实践中不推荐将其用于数据仓库应用程序。不能将数据批量加载到 UNION ALL 视图;对于大到必须对表进行分区的数据仓库来说,加载进程将会太慢。
相反,数据仓库应用程序的设计必须使每一个周期都可以把数据快速加载到相应的目标表。如果数据分阶段应用程序在 SQL Server 数据转换服务 (DTS) 中实现,动态属性任务可以很容易地更改数据泵任务或批量插入任务的目标表的名称。
只要新分区没有加入 UNION ALL 视图,就不需要在系统停机时间加载数据。
数据仓库分阶段应用程序应该设计为可以处理不属于当前分区的新数据。如果数据仓库加载进程不是在一个夜晚完成,就可能发生这种特殊情况。其他系统要处理不断到来的旧数据。系统的设计必须考虑到这些例外情况的可能性、频率和数据量。
如果旧数据以足够低的量到达,最简单的设计就是使用可更新的 UNION ALL 视图来加载所有不属于当前分区的数据。
定义 UNION ALL 视图
一旦渐变加载成功完成,就必须重新修订 UNION ALL 视图。仍然建议使用 SQL-DMO 完成本任务:使用 ALTER 方法更改 VIEW 对象的 TEXT 属性。从上面所述的元数据表中导出视图定义中要包括的分区列表是最佳途径。
合并分区
表面上看来,将若干分区合并至单个较大分区似乎是多余的。不过,对于日加载量巨大同时加载窗口很小的数据仓库,通过下列措施可以显著改善加载性能:
- 用要加载的数据创建文本文件,按簇索引的顺序排序。
- 批量加载到空的日分区。
- 创建所有的非簇索引。
- 通过重新创建 UNION ALL 视图,使新分区保持联机。
- 通过自日分区插入、重新创建索引和重新生成 UNION ALL 视图,每周创建和填充新的周分区。然后就可以丢弃日分区。
- 数据变得陈旧后就移动至周甚至月分区,这样更多的分区可以联机保留在 UNION ALL 视图中。
在 SQL Server 2000 分析服务中使用分区
SQL Server Enterprise Edition 中的分析服务显式支持分区多维数据集,这种分区多维数据集与关系型数据库中的分区表相当。对于中等或大型的多维数据集,分区可以大大改善查询性能、加载性能,并使多维数据集维护更容易。分区可以按一个或多个矢量来设计,但多维数据集通常仅按日期矢量分区。分区多维数据集的渐变加载(包括创建新分区)应该由自定义应用程序执行。
注意:分区可以在本地存储或分布在多个物理服务器上。尽管跨多个服务器的分布式分区对非常大的系统可能有好处,但我们的测试表明,多维数据集尺寸达数万亿字节时,分布式分区解决方案才能提供最大的好处。本文仅考虑本地分区多维数据集。分区多维数据集的渐变加载(包括创建新分区)应该由自定义应用程序执行。
分区的优点
查询性能
对多维数据集进行分区将极大地改善查询性能。甚至中等大小的多维数据集(基于 100 GB 来自关系型数据库的数据)也会从分区中受益。多维数据集分区的优势在多用户加载的情况下更显著。
每个应用程序查询性能的改善随多维数据集结构、使用方式和分区设计的不同而变化。如果仅需要按月分区多维数据集中一个月的数据,那么查询就只访问一个分区。一般情况下,放弃单个分区中的大多维数据集,转而采用精心设计的本地分区策略,我们预计查询性能可以平均改善 100% 至 1000%。
修剪旧数据
对于关系型数据仓库,分析服务系统管理员可能选择仅在多维数据集中保留最新的数据。如果是单个分区,清除旧数据的唯一方法就是重新处理多维数据集。通过按日期矢量分区,管理员可以不关闭系统就丢弃旧分区。
维护
从管理的观点来看,分区是在不影响其他分区的情况下可以被独立添加和丢弃的数据单位。这有助于在系统中管理数据的生命周期。每个多维数据集分区单独存储在一组文件中。由于分区文件相对较小,备份和还原这些数据文件的操作更容易管理。这对大小在 2 GB 以下的分区文件尤为明显。这种情况下,存档和还原实用程序也会有效。如果多维数据集的一部分损坏,或发现这部分包含不正确或不一致的数据,那么可以仅重新处理该分区,这比处理整个多维数据集更为迅速。另外,为节省空间,也可能更改以及合并旧分区的存储模式和设计。
不同的分区可以使用不同的数据源。单个多维数据集可以组合多个关系型数据库的数据。例如,建立企业数据仓库时,可以使来自欧洲和北美的数据驻留在不同的服务器上。如果多维数据集按地理分区,在逻辑上多维数据集可以合并这些完全隔离的数据源。单个多维数据集定义的多个源服务器上的关系架构必须几乎完全相同,这样才能正常工作。
加载性能
可以并行加载多个分区,因此分区多维数据集的加载速度可以比未分区多维数据集更快。后面我们将讨论,要并行处理分区,您必须获取第三方工具或创建一个简单的自定义工具。在多处理器计算机上,性能改善很明显。并行处理工具应该可以将 CPU 利用率提高到 90%。通常情况下,每两个处理器同时处理一到两个分区可获得这样高的性能。例如,在一个所有的处理器都用来处理多维数据集的四处理器的计算机上,您可能希望同时处理两到四个分区。如果试图处理的分区的个数多于您拥有的处理器的个数,性能将显著降低。每两个处理器处理一个分区是比较保守的;理想的数字取决于来自源数据库的数据流的速度、聚合设计、存储和其他一些因素。
在某些情况下,重新创建分区比渐变处理分区效率更高。当然,如果整个多维数据集保留在单个分区中,这种情况发生的可能性很小。
分区的缺点
复杂性
分区的主要缺点是需要管理员创建应用程序来管理分区。在尚未设计、测试和试运行应用程序来管理分区之前,将分区多维数据集投入正式运行是不恰当的。本文的目的之一就是讨论与分区管理应用程序有关的问题和设计决策。
元数据操作
随着分区数量的递增,元数据操作(如浏览多维数据集定义)的性能将下降。对于管理员(而不是最终用户)而言,管理分区多维数据集是一个负担,而管理一个过度分区的多维数据集将是一个难题。
设计时要考虑的因素
分区概述
一个有效的查询计划要权衡多个因素:
- 分区的数量:分析服务对多维数据集中分区的数量没有实际限制,但管理一个有几千个分区的多维数据集将极具挑战性。另外,当分区达到一定数量时,从多个分区合并结果集的开销将超过分区选择产生的查询性能改善。由于它取决于多维数据集设计、查询模式和硬件,我们很难给出明确的规则来确定这个数量。但是,如果将每 GB 多维数据集存储或每一千万行事实数据划分为一个分区,那多半是安全的。换言之,在适当硬件上的 100 GB 多维数据集(或者 10 亿项事实)应该可以轻松的支持 100 个分区。如果分区设计要求比上述数量更多的分区,我们就应该测试一下其他分区计划的性能。
- 加载和维护:数据可能沿着某一矢量(例如时间)自然流入多维数据集。要支持分阶段应用程序来填充和渐变更新多维数据集,这些矢量可以是自然的分区片。例如,日期矢量通常是第一个分区矢量。其他应用程序可能接收按地理区域、客户群等分段的数据。因为不同的分区可以使用不同的数据源,多维数据集填充应用程序可以高效地从分布式数据仓库或其他源系统加载数据。
- 查询性能:设计有效的分区需要了解用户查询的常见模式。理想的分区矢量应对最详细的用户查询具有很好的选择性。例如,由于许多查询都集中在最近的时期内,按日期分区通常可以改查询性能。与此类似,也可能有许多用户按地理或组织进行查询。为了最大限度的改善查询性能,您可能希望查询涉及尽可能少的分区。
如果采用静态的,或者象日期这样变化易于预测的矢量,分区就较容易管理。例如,按“美国各州”进行的分区是相对静态,应用程序设计者可预期并不会收到来自第五十一州的大量警告。相反,由于可能会较频繁地添加新产品,因此按产品矢量划分的分区可能会随时间而更改。设计者仍然可以用动态矢量来分区,但应该认识到管理系统会更加复杂。如果一个矢量被标记为“变化中”,那么就不允许以该矢量进行分区。无论如何,创建“其他”分区来容纳未知矢量成员的数据是明智的。
分区片和筛选
与关系分区一样,必须由管理员来为分析服务分区定义每个分区要包含的数据。RDBMS 使用 CHECK CONSTRAINT 执行此功能;分析服务使用分区片执行此功能。在一个矢量中,分区片被设置为单个成员,例如 [Dates].[1999] 或 [Dates].[1999].[Q1]。在分析管理器向导中,分区片是在标题为“Select the data slice (optional)”的屏幕中设置的。在 DSO 中,可以使用分区矢量级别对象中的 SliceValue 属性访问和设置分区片。本文后面有语法示例。
每个分区的定义也包括流入该分区的源数据流的信息。分区元数据存储填充分区所需的信息。管理员可以使用分区向导设置数据源和事实表,也可以使用 DSO 编程设置。处理分区期间,SliceValue 属性的设置将自动成为对数据源的筛选。分区定义可以包括一个可选的附加筛选,即 SourceTableFilter 属性,该属性可用于细化填充该分区的查询。处理分区期间,对源数据发出的查询的 WHERE 子句将包括基于分区片定义的默认条件和由 SourceTableFilter 属性定义的任意附加筛选。
要使分区正常工作,分区片和筛选都必须按顺序正确定义。分区片的作用是改善查询性能。分析服务引擎通过分区片定义中的信息,使查询仅在包含相关数据的分区中进行。在没有已定义分区片的分区多维数据集上,查询将精确解析,但由于缺少分区片定义而必须检查所有的分区,性能并不会得到优化。
筛选和源元数据的作用是定义流入分区的数据。必须正确定义这些要素,否则整个多维数据集都会包含不正确的数据。处理分区时,分区服务限制存储在多维数据集中的数据,使之与分区片相匹配。但是,不会执行任何检查来确保数据也不被加载到其他分区。
例如,假设按年对多维数据集进行分区,您错误地将 1998 分区设置为 [Dates].[Year].[1997],但将筛选约束设置为 1998。处理时,分区将包含零行:这并非您所希望的结果。相反,如果您已有一个 1998 年的分区,又添加了一个 1998 年 12 月的新分区,这就很可能将 1998 年 12 月的数据加载两次,并且,服务分析不会提示您出现了这种情况。
使分区片和筛选对齐并不困难,但分区管理系统的设计者必须意识到这个问题。
高级分区片和筛选
大多数分区策略是将一个矢量级别定为分区,将该矢量的每个成员的数据放入各自的分区。例如,“按年分区”或“按州分区”。
抽取多维数据集某个部分的分区计划也很常见。例如,较新数据可能按日或星期分区,但较旧的数据按月或年分区。
根据使用方式和数据基数不同,可能会需要设计更为复杂的分区计划。例如,假设客户中的 80% 居住在加利福尼亚、10% 居住在俄勒冈,其余 10% 平均分布在这个国家的其他地区。另外,大多数分析集中在加利福尼亚的客户。这种情况下,管理员可能希望为加利福尼亚创建县级分区、为俄勒冈创建州级分区、为所有其他地区创建一个分区。
该分区片可能会类似于:
- California counties: [All USA].[CA].[Amador] ... [All USA].[CA].[Yolo]
- Oregon state: [All USA].[OR]
- Rest of the country: [All USA]
- 如前面所讨论的,必须正确定义源数据筛选,以确保正确填充这些分区。请注意,如果一个查询需要合并加利福尼亚和俄勒冈的数据,那么它可能也需要查看“Rest of the country”分区。尽管服务分析查看“Rest of the country”映射表以了解那里是否有相关数据的花销不大,但如果多维数据集统一按州分区,然后进一步分解 CA(加利福尼亚),查询性能会更好。维护不均匀分区的应用程序逻辑也更加复杂,通常不推荐这种分区方法。然而,如果适当考虑维护应用程序的设计,同时理解查询性能的取舍,这项技术可以用来解决某些特定的设计问题。
对齐分区
由于本文的前半部分讨论的是 RDBMS 中的分区,读者很自然地会问服务分区是否必须与关系分区对齐。这两种分区策略不需要完全相同,但如果分区比较相似,分区管理应用程序更容易设计、创建和理解。常见的策略是在两个系统中按日期进行相同的分区,此外可以选择性地按多维数据集中的第二个甚至第三个矢量定义分区片。
最简单的策略就是使用 UNION ALL 视图作为所有多维数据集分区的源事实表。如果多维数据集分区与关系分区对齐,每个多维数据集分区都可以绕过 UNION ALL 视图直接指向它的相关联的关系分区。在这种配置中,从关系型数据库中提取数据的多维数据集处理查询将运行得最快。这种性能改善办法的代价是维护应用程序需要确保源表与每个分区都正确地相关联。
如果关系型数据库仅用于填充分析服务多维数据集,并不为其他查询提供服务,系统管理员可以选择不创建和管理 UNION ALL 视图。可适当设计关系表的索引,优化把数据加载到多维数据集的单个查询。这种情况下,关系型数据库的作用更象分阶段区域,而不是完全的数据仓库。
存储模式和聚合计划
每个分区可以拥有自己的存储和聚合计划。不经常被访问的数据可以是轻度聚合的,或作为 ROLAP 或 HOLAP 而不是 MOLAP 存储。由于更改这些参数需要重新处理分区,所以按时间渐变加载的多维数据集不大可能沿其分区的时间矢量使用此功能。大多数情形下,处理时间和系统复杂性的开销似乎使得最小多维数据集带来的节约变得几乎没有必要。
相反,沿其他矢量划分的分区可能具有不同的聚合计划。基于使用情况的优化向导可为每个分区设计聚合方式。系统管理员应该使优化向导集中于最近的分区,并以最近的分区上的每个新分区集的聚合设计作为基础,使聚合设计尽可能新。
管理分区多维数据集
开发人员可以使用不同的工具创建关系分区的管理系统。SQL-DMO 受到极力推荐,不过使用存储过程、扩展存储过程、甚至解析包含表定义的文本文件的 Perl 脚本也已经生成了有效的系统。相反,多维数据集分区维护程序必须使用 DSO。
对于具有传统数据库背景的开发人员而言,使用对象模型对数据库对象进行实例化的想法似乎有些不好理解。开发人员可以使用熟悉的脚本编程语言,例如 Microsoft? Visual Basic? Scripting Edition (VBScript)、Microsoft? JScript?、Perl 或类似于 Visual Basic (VB) 或 C++ 的开发环境来开发使用 DMO 和 DSO 的模块。这些模块可以从操作系统或 SQL-Agent 中定时执行,或从 DTS 程序包中调用。即使开发人员以前从未使用过对象模型,也不能因为要求用 DSO 创建管理系统而放弃使用分区。本文后面将提供一个 VBScript 示例来说明如何使用脚本复制分区。
如果关系型数据仓库使用分区,多维数据集分区管理系统应该设计为关系型数据库分区管理系统的一部分。多维数据集分区管理系统必须具有以下功能:
- 创建必要的新分区,通常按与日期矢量相关的计划进行。
- 将数据加载到分区。
- 丢弃旧分区(可选)。
- 合并分区(可选)。
创建新分区
分区管理系统在关系型数据库中创建新日期分区的同时,它应该创建与该日期相对应的所有的必要的多维数据集分区。由于可能沿分区片之一添加新矢量成员,所以在创建新分区前,渐变更新多维数据集矢量是比较好的做法。
最简单的情况是多维数据集仅按日期分区。分区管理系统只是按适当的时间周期(日、星期、月等等)创建一个新分区。
除了按日期分区外,如果按另一矢量对多维数据集进行分区,分区管理系统将一次添加许多分区。例如,以一个按月和按美国各州分区的多维数据集为例。系统每个月都会创建 50 个新的州分区。这种情况下,通过复制上个月的分区、编辑必要的属性(例如分区片和源表名)以及更新多维数据集中的分区定义来创建这个月的分区是安全的。
然而,假设有一个按月和品牌分区的多维数据集。品牌比州或省容易变化得多;在多维数据集存在期间,向产品系列添加一个新品牌是很可能的。维护应用程序必须确保创建一个分区来容纳保留新品牌的数据。建议的做法为:
- 在创建新分区之前处理矢量。
- 复制现有的分区以确保存储模式和聚合计划的连续性。
- 在已处理的矢量中搜索新成员,为分区级别的所有新成员创建一个分区。系统必须指定默认的存储模式和聚合计划。
必须仔细设计分区管理系统以确保分区片和筛选的定义是对齐的,并在一段时间后仍保持精确。如果关系型数据库是分区的,并且这些分区象本文前面所述的那样定期合并,分区管理系统就应该更新多维数据集分区定义,与源数据保持同步。多维数据集分区不需要重新处理,但在将来有必要重新处理时应该更改其定义。
数据完整性
确保将数据处理进一个并且仅一个分区是多维数据集设计和分区管理系统的任务。分析服务不检查是否所有的行均来自在多维数据集中进行实例化的一个事实表,也不验证某一行是否仅加载到一个分区中。如果不经意地将一个事实行加载到两个分区,分析服务会把它们看作不同的事实。所有的聚合将重复计入该数据,查询将返回不正确的结果。
处理分区
处理分区与处理多维数据集基本相同。对于处理任务,自然的工作单位就是一个分区。分析管理器处理向导为处理多维数据集或分区提供了以下三种模式:
- “渐变更新”向现有多维数据集或分区中添加新数据,更新和添加被该新数据影响的聚合。
- “刷新数据”丢弃多维数据集或分区中的所有数据和聚合,并重新生成多维数据集或分区中的数据。
- “全部过程”完全重新创建多维数据集或分区的结构,然后刷新数据和聚合。
渐变处理需要管理员在源查询上定义筛选条件,以识别多维数据集的新数据集。通常该筛选基于日期(存储在事实表中的事件日期或处理日期)。
DTS 多维数据集处理任务提供了完全相同的功能。大多数系统使用 DTS 多维数据集处理任务来定时安排多维数据集处理。经过渐变处理的多维数据集使用动态属性任务来更改源筛选。尽管渐变更新比刷新数据需要的代码要多一些,DSO 中的自定义代码也提供了相同的功能。
设计分区管理系统时,请特别注意正在处理的渐变多维数据集或正在处理的分区要求过去已经处理过的分区。请勿在未被处理的多维数据集或分区上使用渐变处理。
仅按日期分区的多维数据集有直接加载管理的要求。典型情况下,每个加载循环有一个要更新的单个分区;唯一的决策点为是要渐变更新还是要刷新数据。大多数日期矢量多维数据集可从一个简单的 DTS 程序包中管理。
按多个矢量分区的多维数据集具有以下额外的挑战和好处:
- 挑战:有大量分区要处理
- 挑战:分区数量可能会更改
- 好处:可并行加载分区
- 好处:选择性强的查询的性能可大大改善。
大多数在多个矢量上分区的应用程序将多维数据集处理系统设计为可以并行地加载分区。并行加载系统能够启动多个同时运行的 DTS 程序包,它们的参数已经用动态属性任务更新过。尽管可行,但这种结构不便于使用,相反许多系统会选择使用本机 DSO 代码来更新分区。可以获得并行处理分区的示例工具。
合并分区
对于沿日期分区的多维数据集,它的分区的数量会随时间流逝而增长。如前面所述,分区数量增加到一定程度后,理论上存在着一个查询性能开始降低的点。我们测试了包括 500 个以上分区的开发项目,但还没有达到过这个极限。由于过多分区的其他缺点,例如源数据操作缓慢等,将给管理数据库带来更多的困难,系统管理员在达到该极限之前往往就已不能忍受它了。
通过 DSO 和分析管理器,分析服务支持合并分区的功能。合并两个分区时,一个分区的数据将合并到另一个分区中。两个分区必须具有相同的存储模式和聚合计划。合并完成后,第一个分区被丢弃,第二个分区则包含合并的数据。合并处理仅发生在多维数据集数据上;合并过程中不访问数据源。两个分区的合并过程的效率很高。
如果系统设计包括合并的分区,合并过程应该通过编程进行,而不是通过分析管理器。合并分区很简单,和其他 DSO 操作一样只需要几行代码。分区合并系统必须负责验证最终合并的分区包含用于源筛选的精确元数据信息,以确保必要时可以重新处理分区。分区合并过程正确地更改分区片定义,也尽可能合并筛选定义。但合并过程不要求从相同的表或数据源填充两个分区,合并两个不能被重新填充的分区是可能的。
第二个要考虑的问题是:与所有分区一样,已合并的分区不能被重新命名。
通过使用以下良好的系统设计方法可以避免这些问题:
- 使用清晰的命名约定。
- 遵循一致的分区合并计划。
- 匹配分区多维数据集和关系分区时要小心,或不对关系型数据仓库进行分区。
例如,考虑按星期分区数据的“销售额”多维数据集。本周按日进行分区,然后在本周末合并。将分区命名为 Sales_yyyymmdd,其中名称中的日期是分区中数据的第一天。2000 年 11 月,我们将会有 Sales_20001105、Sales_20001112、Sales_20001119 和 Sales_20001126 周分区。下周里,我们通过 Sales_20001209 创建和处理 Sales_20001203、Sales_20001204 等。在星期天的处理窗口期间(那时系统使用率很低),我们可以把从 20001204 到 20001209 合并至 Sales_20001203,仅留下周分区。或者,您可以通过新建一个有您想要的名称的空分区,将其他分区合并进去,从而有效地重新命名一个分区。
丢弃旧的分区
删除按日分区的多维数据集中的旧数据与丢弃最老的分区(集)一样简单。与我们讨论过的其他操作相似,这个过程应该通过编程来管理,而不是通过分析管理器个别进行。如果您理解了这一点,您会乐意花几小时编写和测试这个模块。
总结
建议中到大型分析服务多维数据集(包含 10 亿行以上的事实)使用本地分区。分析服务数据库的查询性能可以通过分区得到改善。维护分区多维数据集很容易,尤其在从多维数据集中丢弃旧数据的情况下。然而,对多维数据集进行分区需要一个管理这些分区的应用程序。
在概念上,在关系型数据仓库中分区与在分析服务中分区相似。和分析服务一样,必须创建应用程序来管理关系分区。有关应在关系型数据仓库中进行分区的观点并不是强制性的。分区解决了某些维护问题,例如修剪旧数据,但这是以系统复杂性为代价的。与建立良好索引的单个表相比,查询性能并没有得到改善。
分析服务和 SQL Server 关系型数据库都支持分布式分区,即分区驻留在不同的服务器上。关于分析服务中分布式分区的问题将留到另一篇文章中讨论。我们不推荐为支持特定查询的 SQL Server 2000 数据仓库系统划分分布关系分区。
使用大量分区可改善分区多维数据集的查询性能。大型多维数据集的开发人员应该考虑按多个矢量进行分区,以尽可能改善用户查询的选择性,同时通过提供并行处理的机会来改善处理性能。
极力推荐对大型分析服务系统进行分区。尽管对于某些特定的仓库维护问题,对关系型数据仓库进行分区是有效而性能良好的解决方案,但通常不推荐您这样做。
更多信息
Microsoft SQL Server Books Online 包含关于索引视图的信息。要获得更多信息,请参阅以下资源。
- Microsoft SQL Server Web 站点 http://www.microsoft.com/sql/(英文)。
- Microsoft SQL Server 开发人员中心 http://msdn.microsoft.com/sqlserver(英文)。
- SQL Server 杂志 http://www.sqlmag.com/(英文)。
- news://news.microsoft.com/ 的 microsoft.public.sqlserver.server 和 microsoft.public.sqlserver.datawarehouse 新闻组(英文)。
- SQL Server 上的 Microsoft Official Curriculum 课程。有关最新课程信息,请参阅 http://www.microsoft.com/trainingandservices(英文)。
附录:复制分区的 VBScript 代码示例
'/********************************************************************* ' 文件:ClonePart.vbs ' '说明:根据 FoodMart 2000 Sales 多维数据集中的最新分区, ' 这个脚本示例会在该多维数据集中创建新的分区。 ' 此脚本的目的是显示用于复制一个分区的 DSO 呼叫类型。 ' 产生的分区将经过处理,但多维数据集中 ' 不会增加任何数据 ' ' 脚本用户可在运行脚本和查看结果后 ' 删除产生的分区。 ' ' 参数: 无 '*********************************************************************/ Call ClonePart Sub ClonePart() On Error Resume Next Dim intDimCounter, intErrNumber Dim strOlapDB, strCube, strDB, strAnalysisServer, strPartitionNew Dim dsoServer, dsoDB, dsoCube, dsoPartition, dsoPartitionNew ' 初始化服务器、数据库和多维数据集名变量。 strAnalysisServer = "LocalHost" strOlapDB = "FoodMart 2000" strCube = "Sales" ' VBScript 不支持直接使用枚举常量。. ' 然而,可定义常量以取代枚举。 Const stateFailed = 2 Const olapEditionUnlimited = 0 ' 连接到分析服务器。 Set dsoServer = CreateObject("DSO.Server") dsoServer.Connect strAnalysisServer ' 如果连接失败,则结束枚举。 If dsoServer.State = stateFailed Then MsgBox "Error-Not able to connect to '" & strAnalysisServer _ & "' Analysis server.", ,"ClonePart.vbs" Err.Clear Exit Sub End if ' 某些分区管理功能只有在分析服务的 ' Enterprise 和 Developer 发行版中 ' 才可使用。 If dsoServer.Edition <> olapEditionUnlimited Then MsgBox "Error-This feature requires Enterprise or " & _ "Developer Edition of SQL Server to " & _ "manage partitions.", , "ClonePart.vbs" Exit Sub End If ' 确定数据库中有有效的数据源。 Set dsoDB = dsoServer.mdStores(strOlapDB) If dsoDB.Datasources.Count = 0 Then MsgBox "Error-No data sources found in '" & _ strOlapDB & "' database.", , "ClonePart.vbs" Err.Clear Exit Sub End If ' 查找多维数据集。 If (dsoDB.mdStores.Find(strCube)) = 0 then MsgBox "Error-Cube '" & strCube & "' is missing.", , _ "ClonePart.vbs" Err.Clear Exit Sub End If ' 将 dsoCube 变量设置到所要的多维数据集。 Set dsoCube = dsoDB.MDStores(strCube) ' 查找分区 If dsoCube.mdStores.Count = 0 Then MsgBox "Error-No partitions exist for cube '" & strCube & _ "'.", , "ClonePart.vbs" Err.Clear Exit Sub End If ' 将 dsoPartition 变量设置到所要的分区。 Set dsoPartition = dsoCube.MDStores(dsoCube.MDStores.Count) MsgBox "New partition will be based on existing partition: " _ & chr(13) & chr(10) & _ dsoDB.Name & "." & dsoCube.Name & "." & _ dsoPartition.Name, , "ClonePart.vbs" ' 从数据源获得引用字符,因为 ' 不同的数据源使用不同的引用字符。 Dim sLQuote, sRQuote sLQuote = dsoPartition.DataSources(1).OpenQuoteChar sRQuote = dsoPartition.DataSources(1).CloseQuoteChar '********************************************************************* ' 根据所要的分区创建新分区。 '********************************************************************* ' 创建新的暂时分区。 strPartitionNew = "NewPartition" & dsoCube.MDStores.Count Set dsoPartitionNew = dsoCube.MDStores.AddNew("~temp") ' 从所要的分区复制属性到 ' 新分区。 dsoPartition.Clone dsoPartitionNew ' 将 "~temp" 分区名更改为 ' 想要供新分区使用的名称。 dsoPartitionNew.Name = strPartitionNew dsoPartitionNew.AggregationPrefix = strPartitionNew & "_" ' 设置新分区事实表。 dsoPartitionNew.SourceTable = _ sLQuote & "sales_fact_dec_1998" & sRQuote ' 设置新分区的 FromClause 和 JoinClause ' 属性。 dsoPartitionNew.FromClause = Replace(dsoPartition.FromClause, _ dsoPartition.SourceTable, dsoPartitionNew.SourceTable) dsoPartitionNew.JoinClause = Replace(dsoPartition.JoinClause, _ dsoPartition.SourceTable, dsoPartitionNew.SourceTable) ' 将受影响的层次和维度的 SliceValue 属性 ' 更改为所要的值,以更改新分区使用的 ' 数据切片定义。 dsoPartitionNew.Dimensions("Time").Levels("Year").SliceValue = "1998" dsoPartitionNew.Dimensions("Time").Levels("Quarter").SliceValue = "Q4" dsoPartitionNew.Dimensions("Time").Levels("Month").SliceValue = "12" ' 估计行数。 dsoPartitionNew.EstimatedRows = 18325 ' 添加另一筛选。SourceTableFilter 提供另外的 ' 机会,可将 WHERE 从句加入要填充的 SQL 查询。 ' 我们使用此筛选程序以确保新分区中未包含 ' 任何数据行。基于此示例代码的目的,我们不想 ' 更改 FoodMart 多维数据集中的数据。如果您想要新分区中 ' 看数据,请去掉此行。 dsoPartitionNew.SourceTableFilter = dsoPartitionNew.SourceTable _ & "." & sLQuote & "time_id" & sRQuote & "=100" ' 将分区定义保存在元数据知识库中。 dsoPartitionNew.Update '检查新分区结构的有效性。 IF NOT dsoPartitionNew.IsValid Then MsgBox "Error-New partition structure is invalid." Err.Clear Exit Sub End If MsgBox "New partition " & strPartitionNew & " has been created and " _ & "processed. To see the new partition in Analysis Manager, you " _ & "may need to refresh the list of partitions in the Sales cube " _ & "of FoodMart 2000. The new partition contains no data.", , _ "ClonePart.vbs" ' 下一语句已去掉,将处理分区。 ' 在实际分区管理系统中,这可能是一个单独的 ' 过程,可能由 DTS 管理。 ' dsoPartitionNew.Process ' 清理。 Set dsoPartition = Nothing Set dsoPartitionNew = Nothing Set dsoCube = Nothing Set dsoDB = Nothing dsoServer.CloseServer Set dsoServer = Nothing End Sub