我必须将2个新列col1 char(1) NULL
,col2 char(1) NULL
添加到行数超过2.5亿的表中。对于现有的2.5亿行,我必须使用值1
更新两列。
然后我的SSIS包将每天以增量顺序更新表。 SSIS包将使用来自源表的任何内容填充这两列。
如何快速完成此操作,因为我必须更新250M行?
谢谢, 男人
答案 0 :(得分:7)
您没有说出您正在使用的SQL Server版本。从SQL Server 2012开始,添加带有默认值的新NOT NULL
列在大多数情况下为instantaneous:仅更改表元数据,并且不更新任何行。感谢Martin Smith提供此信息。所以在这个版本中,你最好放弃并重新创建列。
在以前的版本中,您可以尝试这样的事情:
WHILE 1 = 1 BEGIN
WITH T AS (
SELECT TOP (10000) *
FROM dbo.YourTable
WHERE
T.Col1 IS NULL
AND T.COl2 IS NULL
)
UPDATE T
SET
T.Col1 = '1',
T.Col2 = '1'
;
IF @@RowCount < 10000 BREAK; -- a trick to save one iteration most times
END;
这可能需要很长时间才能运行,但其好处是它不会长时间锁定桌面。索引和通常行大小的确切组合也会影响它的执行效果。要更新的行数的最佳位置永远不会是恒定的。它可能是50,000或2,000。我曾经在这样的分块操作中尝试过不同的计数,发现5,000或10,000通常非常接近最佳尺寸。
根据过滤索引的SQL Server(2008及更高版本)版本,上述查询也可能会受益:
CREATE UNIQUE NONCLUSTERED INDEX IX_YourTable ON dbo.YourTable (ClusteredColumns)
WHERE Col1 IS NULL AND COl2 IS NULL;
完成后,删除索引。
请注意,如果您使用默认值NOT NULL
指定了两个新列,则会在创建列期间添加值 - 之后可以删除默认值:
ALTER TABLE dbo.YourTable ADD Col1 char(1)
NOT NULL CONSTRAINT DF_YourTable_Col1 DEFAULT ('1');
与最后添加NULL
列不同,这可能会进行快速拆分,这可能需要花费大量时间,因此在您的250M行表中这可能不是一个选项。
更新要解决布莱恩的评论:
以10,000个小批量进行此操作的理由是更新的开销和开销的负面影响&#34;在很大程度上改善了。是的,确实,这将是很多活动 - 但它不会长时间阻止,这就是这样一项活动的第一个影响性能的因素:长时间阻塞。
我们很多了解此查询的锁定潜力:UPDATE EXCLUSIVE锁定,并且先前的点应该将此类的任何有害影响保持在最低限度。如果我有其他锁定问题,请分享。
过滤后的索引会有所帮助,因为它只允许读取索引的几页,然后在巨型表中读取搜索。由于更新,为真,因此必须维护过滤的索引以删除更新的行,因为它们不再符合条件,这确实增加了更新的写入部分的成本。这听起来很糟糕,直到你意识到上面批量UPDATE
的最大部分,没有某种索引,每次都是表扫描。给定250M行,这需要与整个表的12,500次完整扫描相同的资源!所以我建议使用索引DOES工作,并且是手动遍历聚集索引的一种简单易用的快捷方式。
索引的基本规律&#34;对于那些有大量写入动作的表而言,它们不好用。您正在考虑正常的OLTP访问模式,其中可以使用搜索找到正在更新的行,然后对于写入,表上的每个附加索引确实会创建之前不存在的开销。将此与我之前的观点中的解释进行比较。即使过滤后的索引使得UPDATE
部分占用每行I / O的5倍(可疑),仍然会将的I / O减少超过2,500次!
评估更新对性能的影响非常重要,特别是如果表格非常繁忙并且不断使用。如果需要,在非工作时间(如果存在的话)安排它就像你建议的那样基本意义。
我的建议中的一个潜在弱点是,在SQL 2008及更低版本中,添加过滤后的索引可能需要很长时间 - 尽管可能不是,因为它是一个非常窄的索引并且将以群集顺序编写(可能与一次扫描!)。因此,如果创建时间过长,则可以选择:手动遍历聚簇索引。这可能是这样的:
DECLARE @ClusteredID int = 0; --assume clustered index is a single int column
DECLARE @Updated TABLE (
ClusteredID int NOT NULL
);
WHILE 1 = 1 BEGIN
WITH T AS (
SELECT TOP (10000) *
FROM dbo.YourTable
WHERE ClusteredID > @ClusteredID -- the "walking" part
ORDER BY ClusteredID -- also crucial for "walking"
)
UPDATE T
SET
T.Col1 = '1',
T.Col2 = '1'
OUTPUT Inserted.ClusteredID INTO @Updated
;
IF @@RowCount = 0 BREAK;
SELECT @ClusteredID = Max(ClusteredID)
FROM @Updated
;
DELETE @Updated;
END;
你去:没有索引,一直寻找,只有一个有效的整个表扫描(处理表变量的一小部分开销)。如果ClusteredID
列密集,您甚至可以省去表变量,只需在每个循环结束时手动添加10,000个。
您提供的更新表明您的聚集索引中有5列。这是一个更新的脚本,用于说明您可以如何适应:
DECLARE -- Five random data types seeded with guaranteed low values
@Clustered1 int = 0,
@Clustered2 int = 0,
@Clustered3 varchar(10) = '',
@Clustered4 datetime = '19000101',
@Clustered5 int = 0
;
DECLARE @Updated TABLE (
Clustered1 int,
Clustered2 int,
Clustered3 varchar(10),
Clustered4 datetime,
Clustered5 int
);
WHILE 1 = 1 BEGIN
WITH T AS (
SELECT TOP (10000) *
FROM dbo.YourTable
WHERE
Clustered1 > @Clustered1
OR (
Clustered1 = @Clustered1
AND (
Clustered2 > @Clustered2
OR (
Clustered2 = @Clustered2
AND (
Clustered3 > @Clustered3
OR (
Clustered3 = @Clustered3
AND (
Clustered4 > @Clustered4
OR (
Clustered4 = @Clustered4
AND Clustered5 > @Clustered5
)
)
)
)
)
)
)
ORDER BY
Clustered1, -- also crucial for "walking"
Clustered2,
Clustered3,
Clustered4,
Clustered5
)
UPDATE T
SET
T.Col1 = '1',
T.Col2 = '1'
OUTPUT
Inserted.Clustered1,
Inserted.Clustered2,
Inserted.Clustered3,
Inserted.Clustered4,
Inserted.Clustered5
INTO @Updated
;
IF @@RowCount < 10000 BREAK;
SELECT TOP (1)
@Clustered1 = Clustered1
@Clustered2 = Clustered2,
@Clustered3 = Clustered3,
@Clustered4 = Clustered4,
@Clustered5 = Clustered5
FROM @Updated
ORDER BY
Clustered1,
Clustered2,
Clustered3,
Clustered4,
Clustered5
;
DELETE @Updated;
END;
如果您发现某种特殊方式无效,请尝试另一种方法。在更深层次上理解数据库系统将带来更好的想法和卓越的解决方案。我知道深层嵌套的WHERE
条件是一种愚蠢的行为。您也可以尝试以下尺寸 - 这完全相同,但很多更难理解,所以我不能真正推荐它,即使添加额外的列非常容易。
WITH T AS (
SELECT TOP (10000) *
FROM
dbo.YourTable T
WHERE
122 <=
CASE WHEN Clustered1 > @Clustered1 THEN 172 WHEN Clustered1 = @Clustered1 THEN 81 ELSE 0 END
+ CASE WHEN Clustered2 > @Clustered2 THEN 54 WHEN Clustered1 = @Clustered2 THEN 27 ELSE 0 END
+ CASE WHEN Clustered3 > @Clustered3 THEN 18 WHEN Clustered3 = @Clustered3 THEN 9 ELSE 0 END
+ CASE WHEN Clustered4 > @Clustered4 THEN 6 WHEN Clustered4 = @Clustered4 THEN 3 ELSE 0 END
+ CASE WHEN Clustered5 > @Clustered5 THEN 2 WHEN Clustered5 = @Clustered5 THEN 1 ELSE 0 END
ORDER BY
Clustered1, -- also crucial for "walking"
Clustered2,
Clustered3,
Clustered4,
Clustered5
)
UPDATE T
SET
T.Col1 = '1',
T.Col2 = '1'
OUTPUT
Inserted.Clustered1,
Inserted.Clustered2,
Inserted.Clustered3,
Inserted.Clustered4,
Inserted.Clustered5
INTO @Updated
;
我有很多次使用这个确切的&#34;小批量的聚集索引来对巨型表进行更新&#34;策略对生产数据库没有影响。