更改 SQL Server 中的分区列

Lór*_*óda 3 sql-server partitioning sql-server-2012

我创建了一个包含 100 多万行的分区表,并datetime在创建表时不小心将错误的列指定为分区键。

是否有一种简单的方法可以将分区列从一datetime列更改为另一列。就我而言,我们有一个列InsertedDate和一个CompleteDate,我不小心使用了第一个而不是第二个。

Han*_*non 5

更改具有数亿行的表上的分区列可能会有点痛苦,因为该操作需要物理移动表中的所有行,可能还有任何相关联的索引。您需要确保有足够的存储空间来重建每个分区——也许您想将表移动到一组新文件组,然后删除旧的未使用的文件组。

如果表定义了主键或任何唯一索引,则必须删除并重新创建它们,以便在索引键中包含新的分区列。

如果该表有一个主键,并且该主键在其他表中被引用,则您需要破坏参照完整性以更改分区键。由于您当前正在使用该InsertedDate列作为分区键,并且想要移动它以使用该CompletedDate列,这可能是两种日期数据类型,我怀疑这会影响您的情况 - 值得注意。

对于具有主键的表,将表从一个分区键移动到另一个分区键的实际任务包括删除主键约束,然后使用新的分区键作为主键的一部分重建表。

我已经将 10,000,000 行分布在 24 个分区上的小测试台放在一起,所以我们可以看到它的实际效果。

我们将在 中完成工作tempdb,因此请确保您有足够的增长空间。首先,我们将创建分区函数、分区方案,最后创建表本身:

USE tempdb;
GO
IF OBJECT_ID(N'dbo.Tab', N'U') IS NOT NULL
BEGIN
    DROP TABLE dbo.Tab;
END
IF EXISTS (SELECT 1 FROM sys.partition_schemes ps WHERE ps.name = N'PartScheme')
BEGIN
    DROP PARTITION SCHEME PartScheme;
END
IF EXISTS (SELECT 1 FROM sys.partition_functions pf WHERE pf.name = N'PartFun')
BEGIN
    DROP PARTITION FUNCTION PartFun;
END

CREATE PARTITION FUNCTION PartFun (datetime)
AS RANGE LEFT
FOR VALUES (
      N'2012-01-01T00:00:00'
    , N'2012-04-01T00:00:00'
    , N'2012-07-01T00:00:00'
    , N'2012-10-01T00:00:00'
    , N'2013-01-01T00:00:00'
    , N'2013-04-01T00:00:00'
    , N'2013-07-01T00:00:00'
    , N'2013-10-01T00:00:00'
    , N'2014-01-01T00:00:00'
    , N'2014-04-01T00:00:00'
    , N'2014-07-01T00:00:00'
    , N'2014-10-01T00:00:00'
    , N'2015-01-01T00:00:00'
    , N'2015-04-01T00:00:00'
    , N'2015-07-01T00:00:00'
    , N'2015-10-01T00:00:00'
    , N'2016-01-01T00:00:00'
    , N'2016-04-01T00:00:00'
    , N'2016-07-01T00:00:00'
    , N'2016-10-01T00:00:00'
    , N'2017-01-01T00:00:00'
    , N'2017-04-01T00:00:00'
    , N'2017-07-01T00:00:00'
    , N'2017-10-01T00:00:00'
);

CREATE PARTITION SCHEME PartScheme
AS PARTITION PartFun
ALL TO ([PRIMARY]);

IF OBJECT_ID(N'dbo.Tab', N'U') IS NOT NULL
DROP TABLE dbo.Tab;
CREATE TABLE dbo.Tab
(
    TabID int NOT NULL
    , CreateDate datetime NOT NULL
    , UpdateDate datetime NOT NULL
    , Data1 varchar(100) NOT NULL
    , Data2 varchar(100) NOT NULL
    , Data3 varchar(100) NOT NULL
    , CONSTRAINT PK_Tab
        PRIMARY KEY CLUSTERED
        (CreateDate, TabID)
) ON [PartScheme](CreateDate);
Run Code Online (Sandbox Code Playgroud)

这将用 10,000,000 行填充表,这些行在每个分区上有些均匀分布:

;WITH Ten AS
(
    SELECT v.Num
    FROM (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9))v(Num)
)
, TenMillion AS 
(
    SELECT Num = (t7.Num * POWER(10, 6))
        + (t6.Num * POWER(10, 5)) 
        + (t5.Num * POWER(10, 4)) 
        + (t4.Num * POWER(10, 3)) 
        + (t3.Num * POWER(10, 2)) 
        + (t2.Num * POWER(10, 1)) 
        + (t1.Num) 
    FROM Ten t1
        CROSS JOIN Ten t2
        CROSS JOIN Ten t3
        CROSS JOIN Ten t4
        CROSS JOIN Ten t5
        CROSS JOIN Ten t6
        CROSS JOIN Ten t7
)
INSERT INTO dbo.Tab (TabID, CreateDate, UpdateDate, Data1, Data2, Data3)
SELECT m.Num, DATEADD(DAY, m.Num % 1825, N'2012-01-01T00:00:00')
    , DATEADD(DAY, m.Num % 1825, N'2012-02-02T00:00:00')
    , CONVERT(varchar(100), CRYPT_GEN_RANDOM(100))
    , CONVERT(varchar(100), REPLICATE('A', 100))
    , CONVERT(varchar(100), REPLICATE('B', 100))
FROM TenMillion m;
Run Code Online (Sandbox Code Playgroud)

表中行的分布:

SELECT ObjectName = s.name + '.' + o.name
    , p.partition_number
    , p.rows
FROM sys.partitions p
    INNER JOIN sys.objects o ON o.object_id = p.object_id
    INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
WHERE s.name = 'dbo'
    AND o.name = 'Tab'
    AND p.index_id = 1
ORDER BY p.partition_number;
Run Code Online (Sandbox Code Playgroud)
?????????????????????????????????????????????
? 对象名称 ? 分区编号?行?
?????????????????????????????????????????????
? dbo.Tab ? 1 ? 5480?
? dbo.Tab ? 2 ? 498680?
? dbo.Tab ? 3 ? 498680?
? dbo.Tab ? 4 ? 504160?
? dbo.Tab ? 5 ? 504160?
? dbo.Tab ? 6 ? 493200?
? dbo.Tab ? 7 ? 498680?
? dbo.Tab ? 8 ? 504160?
? dbo.Tab ? 9 ? 504160?
? dbo.Tab ? 10 ? 493200?
? dbo.Tab ? 11 ? 498592?
? dbo.Tab ? 12 ? 504068?
? dbo.Tab ? 13 ? 504068?
? dbo.Tab ? 14 ? 493110?
? dbo.Tab ? 15 ? 498589?
? dbo.Tab ? 16 ? 504068?
? dbo.Tab ? 17 ? 504068?
? dbo.Tab ? 18 ? 498589?
? dbo.Tab ? 19 ? 498589?
? dbo.Tab ? 20 ? 504068?
? dbo.Tab ? 21 ? 487631?
? dbo.Tab ? 22 ? 0 ?
? dbo.Tab ? 23 ? 0 ?
? dbo.Tab ? 24 ? 0 ?
? dbo.Tab ? 25 ? 0 ?
?????????????????????????????????????????????

在删除原始主键后,这会在新分区键上重建表。这里的效果是我们将表从聚集索引移动到堆,然后再回到聚集索引。我在事务中这样做是为了确保我不会意外地将表作为堆。

BEGIN TRANSACTION;

BEGIN TRY
    ALTER TABLE dbo.Tab
    DROP CONSTRAINT PK_Tab
    WITH (MOVE TO PartScheme(UpdateDate));

    ALTER TABLE dbo.Tab
    ADD CONSTRAINT PK_Tab
    PRIMARY KEY CLUSTERED
    (UpdateDate, TabID)
    ON PartScheme(UpdateDate);

    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION
END CATCH
Run Code Online (Sandbox Code Playgroud)

当我们查看sys.partitions查询时,我们可以看到行数发生了变化,因为一些行已被移动到不同的分区中:

SELECT ObjectName = s.name + '.' + o.name
    , p.partition_number
    , p.rows
FROM sys.partitions p
    INNER JOIN sys.objects o ON o.object_id = p.object_id
    INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
WHERE s.name = 'dbo'
    AND o.name = 'Tab'
    AND p.index_id = 1
ORDER BY p.partition_number;
Run Code Online (Sandbox Code Playgroud)
?????????????????????????????????????????????
? 对象名称 ? 分区编号?行?
?????????????????????????????????????????????
? dbo.Tab ? 1 ? 0 ?
? dbo.Tab ? 2 ? 328800?
? dbo.Tab ? 3 ? 498680?
? dbo.Tab ? 4 ? 504160?
? dbo.Tab ? 5 ? 504160?
? dbo.Tab ? 6 ? 493200?
? dbo.Tab ? 7 ? 498680?
? dbo.Tab ? 8 ? 504160?
? dbo.Tab ? 9 ? 504160?
? dbo.Tab ? 10 ? 493200?
? dbo.Tab ? 11 ? 498624?
? dbo.Tab ? 12 ? 504068?
? dbo.Tab ? 13 ? 504068?
? dbo.Tab ? 14 ? 493110?
? dbo.Tab ? 15 ? 498589?
? dbo.Tab ? 16 ? 504068?
? dbo.Tab ? 17 ? 504068?
? dbo.Tab ? 18 ? 498589?
? dbo.Tab ? 19 ? 498589?
? dbo.Tab ? 20 ? 504068?
? dbo.Tab ? 21 ? 504068?
? dbo.Tab ? 22 ? 158891?
? dbo.Tab ? 23 ? 0 ?
? dbo.Tab ? 24 ? 0 ?
? dbo.Tab ? 25 ? 0 ?
?????????????????????????????????????????????

仅供参考,在我有限内存的工作站上修改分区键,以及在同一个磁盘上的所有分区,1000 万行需要超过 14 分钟。您需要在非生产系统上进行此练习,以确保您了解其含义并了解这可能需要多长时间。了解在重建过程中不会发生任何客户端活动,因此您需要计划一些停机时间。