SQL Server

利用表分区简化数据库维护

Noah Gomez

 

概览:

  • 创建已分区表
  • 添加及合并分区
  • 创建和管理已分区索引

下载这篇文章的代码: GomezPartitioning2007_03.exe (156KB)

以前,对于那些管理着含有数百万行数据的表的数据库管理员而言,他们不得不创建多个表。在这些表分区以后,管理员必须在对它们执行大量查询期间,

将表再关联起来。将分区关联起来包括创建一个已分区视图或包装存储过程以指明数据所在位置,然后执行另外一个存储过程以便仅点击所需分区即可返回数据集。

虽然这些方法奏效,但执行起来却比较繁琐。对多个表及其索引的管理以及用于再次将表关联在一起的方法常常会引发管理和维护方面的问题。另外,通过创建多个表将数据分区会造成灵活性不够,因为存储过程、维护工作、数据转换服务 (DTS) 工作、应用程序代码及其他进程必须对分区的特性有所了解。因此,为了允许您在不更改代码的情况下添加或删除这些类似分区,这些元素通常是以非动态方式创建的,从而也导致了其效率比较低下。

SQL Server™ 2005 的 Enterprise 和 Developer 版本可让您将一个表中所含的大量数据划分成多个小分区,以便更加有效地进行管理和维护。这种创建可通过单一入口点进行访问的数据段的能力减少了采用旧方式执行此类操作时所带来的许多管理方面的问题。使用单一入口点(表名称或索引名称)可隐藏应用程序代码中的多个数据段并允许管理员或开发人员根据需要更改分区而无需调整基本代码。

简言之,您根本不必调整应用程序中的代码便可创建多个分区、来回移动这些分区、删除旧分区,甚至更改数据的分区方式。您的应用程序代码只是继续调用同一个基表或索引名称。同时,您可以通过减少每个索引中所包含的数据量来减少这些索引的维护时间,还可以通过将数据加载到空分区来加快数据加载速度。

从技术上讲,每个 SQL Server 2005 表都进行了分区,即每个表中至少包含一个分区。SQL Server 2005 所要做的就是让数据库管理员为每个表创建附加分区。表分区和索引分区是很难定义的,这是因为允许单一入口点(表名称或索引名称)的不带应用程序代码的行级分区(不允许按列分区)需要知道入口点背后的分区数目。分区可以存在于基表以及与该表相关联的索引中。

创建已分区表

需使用分区函数和分区方案创建一个能够包含多个分区的表(不只是仅包含一个默认分区)。这些对象允许您将数据划分成特定的段并控制着这些数据段在您的存储设计中的位置。例如,您可以根据数据的使用寿命或使用其他常用微分函数将数据分布在多个驱动器阵列中。注意,可根据表中的某列对表进行分区,每个分区必须含有数据,且这些数据不能置于其他分区内。

分区函数 在为表分区时,首先要确定您需要如何将数据划分到不同的段中。分区函数用于将每一行数据都映射到不同的分区中。这些单独的数据行可以通过除 text、ntext、image、xml、timestamp、varchar(max)、nvarchar(max)、varbinary(max) 之外的任何列类型、别名数据类型或公共语言运行库 (CLR) 用户定义数据类型进行映射。但分区函数必须能够将一行数据完全置于一个表分区中 - 不允许一行数据同时属于多个分区。

要想进行表分区,必须在目标表中创建分区列。此分区列可在首次创建表时就存在于表架构中,您也可以更改该表并在以后添加该分区列。该列可以接受 NULL 值,但默认情况下,含有 NULL 值的所有行将被置于表最左侧的分区内。要避免发生这种情况,请在创建分区函数时指定将 NULL 值置于表最右侧的分区内。使用左侧分区还是使用右侧分区是一项重要的设计决策,因为您要修改分区方案并添加更多分区或删除现有分区。

在创建分区函数时,可以选择 LEFT 或 RIGHT 分区函数。LEFT 和 RIGHT 分区的不同之处在于,分区方案中边界值所放置的位置。LEFT 分区(默认情况)在分区中包括了边界值,而 RIGHT 分区将边界值放到了下一分区中。

为了更好地理解这一概念,让我们看一下简单的 LEFT 和 RIGHT 分区:

CREATE PARTITION FUNCTION Left_Partition (int) AS RANGE LEFT 
FOR VALUES (1,10,100)

CREATE PARTITION FUNCTION Right_Partition (int) AS RANGE RIGHT 
FOR VALUES (1,10,100)

在第一个函数 (Left_Partition) 中,值 1、10 和 100 分别位于第一、第二和第三分区内。在第二个函数 (Right_Partition) 中,这些值分别位于第二、第三和第四分区内。

在创建已分区表时,要尽可能平均地进行分区,这是很重要的。这有助于您了解分区的空间需求。对 LEFT 和 RIGHT 分区的使用将决定着数据的放置位置,这反过来又决定了分区的大小及针对该分区创建的任何索引的大小。

通过使用 $PARTITION 函数,您能够确定某数据值将要放置到的分区的分区号,如下所示:

SELECT $PARTITION.Left_Partition (10)
SELECT $PARTITION.Right_Partition (10)

在第一个 SELECT 语句中,结果为 2。第二个 SELECT 语句将返回 3。

分区方案 在创建完函数并确定了如何划分数据后,您必须确定要在磁盘子系统的哪个位置上创建各个分区。需使用分区方案来创建此磁盘布局。分区方案通过利用文件组将每个分区放到磁盘子系统的相应位置上来管理各个分区的磁盘存储。您可以将分区方案配置为所有分区都放在一个文件组中、所有分区分别放在不同的文件组中或是多个分区共享文件组。后一种方法使得数据库管理员在分布磁盘 I/O 时能够具有较大的灵活性。

图 1 显示了可将一个或多个文件组分配给分区方案的一些方法。您应注意,在创建分区方案之前,分区方案所使用的文件组必须已经存在于数据库中。

Figure 1 将文件组分配给分区方案

--Place all partitions into the PRIMARY filegroup 
CREATE PARTITION SCHEME Primary_Left_Scheme
AS PARTITION Left_Partition 
--Partition must currently exist in database
      ALL TO ([PRIMARY])

--Place all partitions into the different filegroups 
CREATE PARTITION SCHEME Different_Left_Scheme
AS PARTITION Left_Partition 
--Partition must currently exist in database
      TO (Filegroup1, Filegroup2, Filegroup3, Filegroup4) 
--Filegroups must currently exist in database

--Place multiple partitions into the different filegroups 
CREATE PARTITION SCHEME Multiple_Left_Scheme
AS PARTITION Left_Partition 
--Partition must currently exist in database
      TO (Filegroup1, Filegroup2, Filegroup1, Filegroup2) 
--Filegroups must currently exist in database

如果创建了如图 1 中所示的示例分区函数并利用分区方案创建了一个表,随后便可确定各数据行在新建已分区表中的位置。然后,可在这些数据行插入到已分区表以后查看其分布情况。执行所有这些操作的代码将与图 2 中所显示的那些代码类似。

Figure 2 放置数据行并查看分布情况

--Prepare database
IF OBJECT_ID('Partitioned_Table') IS NOT NULL
DROP TABLE Partitioned_Table
GO

IF EXISTS(SELECT [name] FROM sys.partition_schemes
WHERE [name] = 'Primary_Left_Scheme')
DROP PARTITION SCHEME Primary_Left_Scheme
GO

IF EXISTS(SELECT [name] FROM sys.partition_functions
WHERE [name] = 'Left_Partition')
DROP PARTITION FUNCTION Left_Partition
GO

--Create partitioned table
CREATE PARTITION FUNCTION Left_Partition (int) AS RANGE LEFT 
FOR VALUES (1,10,100)

--Place all partitions into the PRIMARY filegroup 
CREATE PARTITION SCHEME Primary_Left_Scheme
AS PARTITION Left_Partition 
      ALL TO ([PRIMARY])

CREATE TABLE Partitioned_Table
(
col1 INT
,col2 VARCHAR(15)
) ON Primary_Left_Scheme (col1)

--Determine where values will be placed (this is not required)
--You should try to do this before executing the code
SELECT $PARTITION.Left_Partition (1)
SELECT $PARTITION.Left_Partition (2)
SELECT $PARTITION.Left_Partition (3)
SELECT $PARTITION.Left_Partition (4)
SELECT $PARTITION.Left_Partition (10)
SELECT $PARTITION.Left_Partition (11)
SELECT $PARTITION.Left_Partition (12)
SELECT $PARTITION.Left_Partition (13)
SELECT $PARTITION.Left_Partition (14)
SELECT $PARTITION.Left_Partition (100)
SELECT $PARTITION.Left_Partition (101)
SELECT $PARTITION.Left_Partition (102)
SELECT $PARTITION.Left_Partition (103)
SELECT $PARTITION.Left_Partition (104)
 
--Insert data into partitioned table
INSERT INTO Partitioned_Table VALUES (1,'Description')
INSERT INTO Partitioned_Table VALUES (2,'Description')
INSERT INTO Partitioned_Table VALUES (3,'Description')
INSERT INTO Partitioned_Table VALUES (4,'Description')
INSERT INTO Partitioned_Table VALUES (10,'Description')
INSERT INTO Partitioned_Table VALUES (11,'Description')
INSERT INTO Partitioned_Table VALUES (12,'Description')
INSERT INTO Partitioned_Table VALUES (13,'Description')
INSERT INTO Partitioned_Table VALUES (14,'Description')
INSERT INTO Partitioned_Table VALUES (100,'Description')
INSERT INTO Partitioned_Table VALUES (101,'Description')
INSERT INTO Partitioned_Table VALUES (102,'Description')
INSERT INTO Partitioned_Table VALUES (103,'Description')
INSERT INTO Partitioned_Table VALUES (104,'Description')

--View the distribution of data in the partitions
SELECT ps.partition_number
,ps.row_count 
FROM sys.dm_db_partition_stats ps
INNER JOIN sys.partitions p
ON ps.partition_id = p.partition_id
AND p.[object_id] = OBJECT_ID('Partitioned_Table')

修改已分区表

尽管做了详细的预先规划,但有时还是需要您在创建并填充已分区表后对其进行调整。您的分区方案可能已按照预期方式进行工作,但是您可能需要(比如说)在累积了新数据后添加新的分区,或是一次删除大量的已分区数据。幸运的是,已分区表和基本分区结构允许您在表启用并填充有数据后对其进行改动。

添加分区 许多分区计划都囊括了在以后添加新分区的能力。这一时间点可以是某一特定日期,也可以取决于增量标识列中的某个值。但如果没有预先对此进行规划,您还可以在以后进行规划并将新分区添加到已分区表中。请考虑图 2 中所创建的表。您可以向此表中添加一个新分区,使其包含大于 500 的值,如下所示:

--Determine where values live before new partition
SELECT $PARTITION.Left_Partition (501)  --should return a value of 4

--Create new partition
ALTER PARTITION FUNCTION Left_Partition ()
SPLIT RANGE(500)

--Determine where values live after new partition
SELECT $PARTITION.Left_Partition (501)  --should return a value of 5 

这种添加分区的能力给予了极大的灵活性。图 3 显示了如何才能将分区添加到函数的左侧。在本例中,您需要将在何处放置新分区通知给分区方案,因为您已经用完了在首次构建分区方案时所创建的所有文件组。即使对于所有分区,您一直在使用 PRIMARY 文件组,还是要必须将对于新分区再次使用 PRIMARY 文件组这个事实通知给分区方案。

Figure 3 将分区添加到函数的左侧

--Determine where values live after new partition
SELECT $PARTITION.Left_Partition (5)   --should return a value of 2
SELECT $PARTITION.Left_Partition (1)   --should return a value of 1
SELECT $PARTITION.Left_Partition (10)  --should return a value of 2

--Add new filegroups to the partitioning scheme
ALTER PARTITION SCHEME Primary_Left_Scheme
NEXT USED [PRIMARY]

--Create new partition
ALTER PARTITION FUNCTION Left_Partition ()
SPLIT RANGE(5)

--Determine where values live after new partition 
SELECT $PARTITION.Left_Partition (5)   --should return a value of 2
SELECT $PARTITION.Left_Partition (1)   --should return a value of 1
SELECT $PARTITION.Left_Partition (10)  --should return a value of 3

合并两个分区 SQL Server 允许您从表中删除单个分区而保留其中的数据。这可用于将旧的活动数据合并到已存档数据中,或是减少所拥有的分区数,从而简化您对已分区表的管理。还可以利用这种方法合并分区,将数据从一个文件组移动到另一个文件组以释放特定驱动器阵列上的磁盘空间。图 4 中的代码显示了如何将数据从一个分区移动到同一文件组上的另一个分区。

Figure 4 将数据从一个分区移动到另一个分区

--Prepare database
IF OBJECT_ID('multiple_partition') IS NOT NULL
DROP TABLE multiple_partition
GO

IF EXISTS(SELECT [name] FROM sys.partition_schemes
WHERE [name] = 'Primary_Left_Scheme')
DROP PARTITION SCHEME Primary_Left_Scheme
GO

IF EXISTS(SELECT [name] FROM sys.partition_functions
WHERE [name] = 'Left_Partition')
DROP PARTITION FUNCTION Left_Partition
GO


--Create partitioned table
CREATE PARTITION FUNCTION Left_Partition (int) AS RANGE LEFT 
FOR VALUES (1,10,100)

--Place all partitions into the PRIMARY filegroup 
CREATE PARTITION SCHEME Primary_Left_Scheme
AS PARTITION Left_Partition 
--Partition must currently exist in database
      ALL TO ([PRIMARY])

CREATE TABLE multiple_partition
(
col1 INT PRIMARY KEY CLUSTERED
,col2 VARCHAR(15)
) ON Primary_Left_Scheme (col1)

INSERT INTO multiple_partition VALUES (1,'Description')
INSERT INTO multiple_partition VALUES (2,'Description')
INSERT INTO multiple_partition VALUES (3,'Description')
INSERT INTO multiple_partition VALUES (4,'Description')
INSERT INTO multiple_partition VALUES (10,'Description')
INSERT INTO multiple_partition VALUES (11,'Description')
INSERT INTO multiple_partition VALUES (12,'Description')
INSERT INTO multiple_partition VALUES (13,'Description')
INSERT INTO multiple_partition VALUES (14,'Description')
INSERT INTO multiple_partition VALUES (100,'Description')
INSERT INTO multiple_partition VALUES (101,'Description')
INSERT INTO multiple_partition VALUES (102,'Description')
INSERT INTO multiple_partition VALUES (103,'Description')
INSERT INTO multiple_partition VALUES (104,'Description')

--Verify partitions
SELECT OBJECT_NAME(ps.[object_id])
,ps.partition_number
,ps.row_count 
FROM sys.dm_db_partition_stats ps
INNER JOIN sys.partitions p
ON ps.partition_id = p.partition_id
AND p.[object_id] = OBJECT_ID('multiple_partition')

--Check where data would be placed
SELECT $PARTITION.Left_Partition (1)
SELECT $PARTITION.Left_Partition (10)
SELECT $PARTITION.Left_Partition (100)
SELECT $PARTITION.Left_Partition (101)

--Merge two partitions
ALTER PARTITION FUNCTION Left_Partition()
MERGE RANGE (10)

--Verify partitions
SELECT OBJECT_NAME(ps.[object_id])
,ps.partition_number
,ps.row_count 
FROM sys.dm_db_partition_stats ps
INNER JOIN sys.partitions p
ON ps.partition_id = p.partition_id
AND p.[object_id] = OBJECT_ID('multiple_partition')

--Check where data would be placed
SELECT $PARTITION.Left_Partition (1)
SELECT $PARTITION.Left_Partition (10)
SELECT $PARTITION.Left_Partition (100)
SELECT $PARTITION.Left_Partition (101)

将单个分区表移动到已分区表中 在加载例程期间,通常是必须先将大量数据加载到数据库中,然后进行修改或合计,最后再将这些数据移动到实际的数据表中。SQL Server 2005 分区允许您将单个分区表移动到包含多个分区的表中。这意味着您可以先将数据加载到单个加载表中,修改那些数据,然后再将整个表移动到一个现有表中,这样就省去了逐一移动每行数据的开销。此分区层并不包括更改基本分区结构,只是修改已分区表。图 5 中的代码显示了您可以如何来达到这一目的。

Figure 5 将整个表移动到现有表中

--Prepare database
IF OBJECT_ID(‘multiple_partition’) IS NOT NULL
DROP TABLE multiple_partition
GO

IF OBJECT_ID(‘single_partition’) IS NOT NULL
DROP TABLE single_partition
GO

IF EXISTS(SELECT [name] FROM sys.partition_schemes
WHERE [name] = ‘Primary_Left_Scheme’)
DROP PARTITION SCHEME Primary_Left_Scheme
GO

IF EXISTS(SELECT [name] FROM sys.partition_functions
WHERE [name] = ‘Left_Partition’)
DROP PARTITION FUNCTION Left_Partition
GO

--Create single partition table
CREATE TABLE single_partition
(
col1 INT PRIMARY KEY CLUSTERED
,col2 VARCHAR(15)
) 


--Table must have a CHECK Constraint
ALTER TABLE single_partition 
WITH CHECK
ADD CONSTRAINT CK_single_partition
    CHECK (col1 > 100)

INSERT INTO single_partition VALUES (101,’Description’)
INSERT INTO single_partition VALUES (102,’Description’)
INSERT INTO single_partition VALUES (103,’Description’)
INSERT INTO single_partition VALUES (104,’Description’)

--Create partitioned table
CREATE PARTITION FUNCTION Left_Partition (int) AS RANGE LEFT 
FOR VALUES (1,10,100)

--Place all partitions into the PRIMARY filegroup 
CREATE PARTITION SCHEME Primary_Left_Scheme
AS PARTITION Left_Partition 
--Partition must currently exist in database
      ALL TO ([PRIMARY])

CREATE TABLE multiple_partition
(
col1 INT PRIMARY KEY CLUSTERED
,col2 VARCHAR(15)
) ON Primary_Left_Scheme (col1)

INSERT INTO multiple_partition VALUES (1,’Description’)
INSERT INTO multiple_partition VALUES (2,’Description’)
INSERT INTO multiple_partition VALUES (3,’Description’)
INSERT INTO multiple_partition VALUES (4,’Description’)
INSERT INTO multiple_partition VALUES (10,’Description’)
INSERT INTO multiple_partition VALUES (11,’Description’)
INSERT INTO multiple_partition VALUES (12,’Description’)
INSERT INTO multiple_partition VALUES (13,’Description’)
INSERT INTO multiple_partition VALUES (14,’Description’)
INSERT INTO multiple_partition VALUES (100,’Description’)

--Verify partitions
SELECT OBJECT_NAME(ps.[object_id])
,ps.partition_number
,ps.row_count 
FROM sys.dm_db_partition_stats ps
INNER JOIN sys.partitions p
ON ps.partition_id = p.partition_id
AND p.[object_id] IN (OBJECT_ID(‘multiple_partition’), OBJECT_
      ID(‘single_partition’))

--Move the single table into the partitioned table
ALTER TABLE single_partition SWITCH TO multiple_partition PARTITION 4

--Verify partitions
SELECT OBJECT_NAME(ps.[object_id])
,ps.partition_number
,ps.row_count 
FROM sys.dm_db_partition_stats ps
INNER JOIN sys.partitions p
ON ps.partition_id = p.partition_id
AND p.[object_id] IN (OBJECT_ID(‘multiple_partition’), OBJECT_ID(‘single_partition’))

将分区从一个表移动到另一个表 一项常见的管理任务是将旧数据移动到单独的存档表中。存档过程通常会涉及到一系列语句,它们用于在事务日志中创建附加资源使用。但将分区的所有权从一个表转交给另一个表却是存档大量数据而无需任何事务日志开销的简便方法。此功能允许数据库管理员将旧数据段从活动表移动到已存档表中。但由于实际上并未进行数据的移动,所以它所花费的时间要明显少于移动各个数据行所花费的时间。图 6 显示了如何进行此操作。

Figure 6 将旧数据移动到已存档表中

--Prepare database
IF OBJECT_ID('active_data') IS NOT NULL
DROP TABLE active_data
GO

IF OBJECT_ID('archive_data') IS NOT NULL
DROP TABLE archive_data
GO

IF EXISTS(SELECT [name] FROM sys.partition_schemes
WHERE [name] = 'Active_Scheme')
DROP PARTITION SCHEME Active_Scheme
GO

IF EXISTS(SELECT [name] FROM sys.partition_functions
WHERE [name] = 'Active_Partition')
DROP PARTITION FUNCTION Active_Partition
GO

IF EXISTS(SELECT [name] FROM sys.partition_schemes
WHERE [name] = 'Archive_Scheme')
DROP PARTITION SCHEME Archive_Scheme
GO

IF EXISTS(SELECT [name] FROM sys.partition_functions
WHERE [name] = 'Archive_Partition')
DROP PARTITION FUNCTION Archive_Partition
GO

--Create active function
CREATE PARTITION FUNCTION Active_Partition (int) AS RANGE LEFT 
FOR VALUES (1,10,100)

--Create archive function
CREATE PARTITION FUNCTION Archive_Partition (int) AS RANGE LEFT 
FOR VALUES (100,200,300)


--Place all partitions into the PRIMARY filegroup 
CREATE PARTITION SCHEME Active_Scheme
AS PARTITION Active_Partition 
--Partition must currently exist in database
      ALL TO ([PRIMARY])

--Place all partitions into the PRIMARY filegroup 
CREATE PARTITION SCHEME Archive_Scheme
AS PARTITION Archive_Partition 
--Partition must currently exist in database
      ALL TO ([PRIMARY])


CREATE TABLE active_data
(
col1 INT PRIMARY KEY CLUSTERED
,col2 VARCHAR(15)
) ON Active_Scheme (col1)

CREATE TABLE archive_data
(
col1 INT PRIMARY KEY CLUSTERED
,col2 VARCHAR(15)
) ON Archive_Scheme (col1)

INSERT INTO active_data VALUES (1,'Description')
INSERT INTO active_data VALUES (2,'Description')
INSERT INTO active_data VALUES (3,'Description')
INSERT INTO active_data VALUES (4,'Description')
INSERT INTO active_data VALUES (10,'Description')
INSERT INTO active_data VALUES (11,'Description')
INSERT INTO active_data VALUES (12,'Description')
INSERT INTO active_data VALUES (13,'Description')
INSERT INTO active_data VALUES (14,'Description')
INSERT INTO active_data VALUES (100,'Description')


INSERT INTO archive_data VALUES (200,'Description')
INSERT INTO archive_data VALUES (300,'Description')
INSERT INTO archive_data VALUES (400,'Description')

--Verify partitions
SELECT OBJECT_NAME(ps.[object_id])
,ps.partition_number
,ps.row_count 
FROM sys.dm_db_partition_stats ps
INNER JOIN sys.partitions p
ON ps.partition_id = p.partition_id
AND p.[object_id] IN (OBJECT_ID('active_data'),OBJECT_ID('archive_
      data'))

--Switch ownership of partition to another table
ALTER TABLE active_data SWITCH PARTITION 3 TO archive_data PARTITION 1

--Verify partitions
SELECT OBJECT_NAME(ps.[object_id])
,ps.partition_number
,ps.row_count 
FROM sys.dm_db_partition_stats ps
INNER JOIN sys.partitions p
ON ps.partition_id = p.partition_id
AND p.[object_id] IN (OBJECT_ID('active_data'),OBJECT_ID('archive_
      data'))

使用单个分区创建新表 您可以将单个分区从现有的已分区表移动到一个空的未分区表中。这样做之后,数据库管理员可针对单个分区执行索引维护,或是轻松地删除大量数据而不必记录该删除过程。图 7 中的示例显示了如何将一个分区移动到空表中,然后使用该新表来删除数据。

Figure 7 移动和删除数据

--Prepare database
IF OBJECT_ID('active_data') IS NOT NULL
DROP TABLE active_data
GO

IF OBJECT_ID('archive_data') IS NOT NULL
DROP TABLE archive_data
GO

IF EXISTS(SELECT [name] FROM sys.partition_schemes
WHERE [name] = 'Active_Scheme')
DROP PARTITION SCHEME Active_Scheme
GO

IF EXISTS(SELECT [name] FROM sys.partition_functions
WHERE [name] = 'Active_Partition')
DROP PARTITION FUNCTION Active_Partition
GO

--Create active function
CREATE PARTITION FUNCTION Active_Partition (int) AS RANGE LEFT 
FOR VALUES (1,10,100)

--Place all partitions into the PRIMARY filegroup 
CREATE PARTITION SCHEME Active_Scheme
AS PARTITION Active_Partition 
--Partition must currently exist in database
      ALL TO ([PRIMARY])

CREATE TABLE active_data
(
col1 INT PRIMARY KEY CLUSTERED
,col2 VARCHAR(15)
) ON Active_Scheme (col1)

CREATE TABLE archive_data
(
col1 INT PRIMARY KEY CLUSTERED
,col2 VARCHAR(15)
) 

INSERT INTO active_data VALUES (1,'Description')
INSERT INTO active_data VALUES (2,'Description')
INSERT INTO active_data VALUES (3,'Description')
INSERT INTO active_data VALUES (4,'Description')
INSERT INTO active_data VALUES (10,'Description')
INSERT INTO active_data VALUES (11,'Description')
INSERT INTO active_data VALUES (12,'Description')
INSERT INTO active_data VALUES (13,'Description')
INSERT INTO active_data VALUES (14,'Description')
INSERT INTO active_data VALUES (100,'Description')

--Verify partitions
SELECT OBJECT_NAME(ps.[object_id])
,ps.partition_number
,ps.row_count 
FROM sys.dm_db_partition_stats ps
INNER JOIN sys.partitions p
ON ps.partition_id = p.partition_id
AND p.[object_id] IN (OBJECT_ID('active_data'),OBJECT_ID('archive_
      data'))

--Switch ownership of partition to another table
ALTER TABLE active_data SWITCH PARTITION 3 TO archive_data 

--Verify partitions
SELECT OBJECT_NAME(ps.[object_id])
,ps.partition_number
,ps.row_count 
FROM sys.dm_db_partition_stats ps
INNER JOIN sys.partitions p
ON ps.partition_id = p.partition_id
AND p.[object_id] IN (OBJECT_ID('active_data'),OBJECT_ID('archive_
      data'))

--Drop all archive data without logging
DROP TABLE archive_data
GO

--Verify partitions
SELECT OBJECT_NAME(ps.[object_id])
,ps.partition_number
,ps.row_count 
FROM sys.dm_db_partition_stats ps
INNER JOIN sys.partitions p
ON ps.partition_id = p.partition_id
AND p.[object_id] IN (OBJECT_ID('active_data'),OBJECT_ID('archive_
      data'))

已分区索引

在能够对表中数据分区的同时,您也能够创建已分区索引。这样,数据库管理员可根据已划分数据而非整个表的数据来设计索引结构。创建已分区索引会生成单独的已分区索引 B 树。划分索引会创建出更小的索引,这样,在数据修改、添加和删除期间,存储引擎便能够更加轻松地维护这些索引。为了使大型数据集能够得到更好的索引维护,这些小型索引也可由数据库管理员单独维护。

创建已分区索引 创建已分区索引时,您既可以创建对齐索引,也可以创建非对齐索引。对于对齐索引,利用与已分区数据的直接关系创建该索引。(对于非对齐索引,请选择其他分区方案。)

如果您创建了分区表,随后在不指定其他分区方案的情况下创建索引,则对齐是首选方法且会自动执行。使用对齐索引提供了为表创建附加分区的灵活性,并能够将分区所有权转交给另一个表。由于有这些功能存在,因此数据库管理员往往会在一开始就创建已分区表,然后对于索引仅仅使用表分区方案就可能会达到分区目的。

您可以根据表来创建索引,其中,索引中的数据并不与表中的数据对齐。如果数据位于已分区表中,则允许您以不同的方式连接数据(通过查询优化器可将已分区数据与其他已分区数据有效连接起来)。您也可以利用未分区表执行此项操作,此方法可允许您创建已分区索引(根据单个分区表),从而能够方便地进行索引维护。

图 8 中的代码将根据已分区表创建一个已分区的、非群集索引。该非群集索引将与该表对齐并将表的分区列用作非群集索引键。

Figure 8 针对已分区表的已分区、非群集索引

--Prepare database
IF OBJECT_ID('multiple_partition') IS NOT NULL
DROP TABLE multiple_partition
GO

IF EXISTS(SELECT [name] FROM sys.partition_schemes
WHERE [name] = 'Primary_Left_Scheme')
DROP PARTITION SCHEME Primary_Left_Scheme
GO

IF EXISTS(SELECT [name] FROM sys.partition_functions
WHERE [name] = 'Left_Partition')
DROP PARTITION FUNCTION Left_Partition
GO


--Create partitioned table
CREATE PARTITION FUNCTION Left_Partition (int) AS RANGE LEFT 
FOR VALUES (1,10,100)

--Place all partitions into the PRIMARY filegroup 
CREATE PARTITION SCHEME Primary_Left_Scheme
AS PARTITION Left_Partition 
--Partition must currently exist in database
    ALL TO ([PRIMARY])

CREATE TABLE multiple_partition
(
col1 INT 
,col2 VARCHAR(15)
) ON Primary_Left_Scheme (col1)

--Create partitioned non-clustered index
CREATE NONCLUSTERED INDEX cl_multiple_partition ON multiple_
      partition(col1)

INSERT INTO multiple_partition VALUES (1,'Description')
INSERT INTO multiple_partition VALUES (2,'Description')
INSERT INTO multiple_partition VALUES (3,'Description')
INSERT INTO multiple_partition VALUES (4,'Description')
INSERT INTO multiple_partition VALUES (10,'Description')
INSERT INTO multiple_partition VALUES (11,'Description')
INSERT INTO multiple_partition VALUES (12,'Description')
INSERT INTO multiple_partition VALUES (13,'Description')
INSERT INTO multiple_partition VALUES (14,'Description')
INSERT INTO multiple_partition VALUES (100,'Description')
INSERT INTO multiple_partition VALUES (101,'Description')
INSERT INTO multiple_partition VALUES (102,'Description')
INSERT INTO multiple_partition VALUES (103,'Description')
INSERT INTO multiple_partition VALUES (104,'Description')

--Verify partitions
SELECT OBJECT_NAME(ps.[object_id])
,ps.partition_number
,ps.row_count 
FROM sys.dm_db_partition_stats ps
INNER JOIN sys.partitions p
ON ps.partition_id = p.partition_id
AND p.[object_id] = OBJECT_ID('multiple_partition')

--Verify index partitions
SELECT partition_id, index_id FROM sys.partitions pt
WHERE pt.[object_id] = OBJECT_ID('multiple_partition')

图 9 中的代码将根据已分区表创建一个非对齐的、非群集索引。此非群集索引会将不同的列用作其索引键,这可用于与其他已分区表进行对照连接。

Figure 9 针对已分区表的非对齐、非群集索引

--Prepare database
IF OBJECT_ID('multiple_partition') IS NOT NULL
DROP TABLE multiple_partition
GO

IF EXISTS(SELECT [name] FROM sys.partition_schemes
WHERE [name] = 'Primary_Left_Scheme')
DROP PARTITION SCHEME Primary_Left_Scheme
GO

IF EXISTS(SELECT [name] FROM sys.partition_schemes
WHERE [name] = 'Index_primary_Left_Scheme')
DROP PARTITION SCHEME Index_primary_Left_Scheme
GO

IF EXISTS(SELECT [name] FROM sys.partition_functions
WHERE [name] = 'Left_Partition')
DROP PARTITION FUNCTION Left_Partition
GO

IF EXISTS(SELECT [name] FROM sys.partition_functions
WHERE [name] = 'Index_Left_Partition')
DROP PARTITION FUNCTION Index_Left_Partition
GO

--Create partitioned index function
CREATE PARTITION FUNCTION Index_Left_Partition (int) AS RANGE LEFT 
FOR VALUES (10,50,100)

--Create partitioned table
CREATE PARTITION FUNCTION Left_Partition (int) AS RANGE LEFT 
FOR VALUES (1,10,100)

--Place all index partitions into the PRIMARY filegroup 
CREATE PARTITION SCHEME Index_primary_Left_Scheme
AS PARTITION Index_Left_Partition 
--Partition must currently exist in database
      ALL TO ([PRIMARY])

--Place all partitions into the PRIMARY filegroup 
CREATE PARTITION SCHEME Primary_Left_Scheme
AS PARTITION Left_Partition 
--Partition must currently exist in database
      ALL TO ([PRIMARY])

CREATE TABLE multiple_partition
(
col1 INT 
,col2 INT
) ON Primary_Left_Scheme (col1)

--Create non-aligned partitioned nonclustered index
CREATE NONCLUSTERED INDEX cl_multiple_partition ON multiple_
     partition(col2)
ON Index_primary_Left_Scheme (col2)

INSERT INTO multiple_partition VALUES (1,10)
INSERT INTO multiple_partition VALUES (2,10)
INSERT INTO multiple_partition VALUES (3,10)
INSERT INTO multiple_partition VALUES (4,10)
INSERT INTO multiple_partition VALUES (10,50)
INSERT INTO multiple_partition VALUES (11,50)
INSERT INTO multiple_partition VALUES (12,50)
INSERT INTO multiple_partition VALUES (13,50)
INSERT INTO multiple_partition VALUES (14,50)
INSERT INTO multiple_partition VALUES (100,100)
INSERT INTO multiple_partition VALUES (101,100)
INSERT INTO multiple_partition VALUES (102,100)
INSERT INTO multiple_partition VALUES (103,100)
INSERT INTO multiple_partition VALUES (104,100)

--Verify row count on partitioned data
SELECT OBJECT_NAME(ps.[object_id])
,ps.partition_number
,ps.row_count 
FROM sys.dm_db_partition_stats ps
INNER JOIN sys.partitions p
ON ps.partition_id = p.partition_id
AND p.[object_id] = OBJECT_ID('multiple_partition')
AND p.index_id = 0

--Verify row count on partitioned index
--Row counts will not match those found in the data partitions
SELECT OBJECT_NAME(ps.[object_id])
,ps.partition_number
,ps.row_count 
FROM sys.dm_db_partition_stats ps
INNER JOIN sys.partitions p
ON ps.partition_id = p.partition_id
AND p.[object_id] = OBJECT_ID('multiple_partition')
AND p.index_id <> 0

维护已分区索引 在过去,针对含有数百万甚至数十亿行数据的大型表执行索引维护通常会花费大量时间,而数据库管理员却很难抽出这么多时间。而这种维护往往又因为重建索引时数据被锁定而无法执行。使用 SQL Server 2005 以后,数据库管理员可以在线执行索引维护而不必长时间锁定基础表。但即使是这种方法(允许您在用户访问数据时执行索引维护)也会由于资源使用而减慢系统的运行速度。有一种更好的方法是将索引划分成更小的段,然后针对这些小的分区执行索引维护。例如,要针对某个索引分区执行索引维护,只需将以下代码片段附加在图 8 中所示的代码末尾。

ALTER INDEX cl_multiple_partition
ON multiple_partition
REBUILD Partition = 2

请注意,针对单个索引分区的索引维护必须离线执行,且会在索引维护期间导致表锁定。为防止这种情况发生,您可以将此单个分区移动到一个单独的分区中,执行索引维护,然后再将该分区移回主表。在分区移回表中以及更新群集索引时,此过程会引发某些性能上的问题,但由此所产生的问题与锁定整个表相比要少得多,且只需较少的系统资源。

总结

正如您所看到的,SQL Server 2005 表分区为存储和维护大型表中的数据提供了改进的灵活性,从而不必重新修改应用程序代码或 SQL Server 进程。由于拥有上述功能,SQL Server 证明了自己是适用于企业级、重要数据库的合格平台。

Noah Gomez是 Verizon 的高级 SQL Server 开发 DBA,专门研究 VLDB 及大型应用程序。他是 SQL Server 专业协会 (PASS) 的成员,曾在 Verizon DBA 团队工作过,该团队主要研究多 TB VLDB,并于 2003 年获得了 Winter 公司 Top Ten 大奖奖项。

© 2008 Microsoft Corporation 与 CMP Media, LLC.保留所有权利;不得对全文或部分内容进行复制.