【问题标题】:Why is this UPDATE statement on varbinary(max) on SQL Server so slow?为什么 SQL Server 上的 varbinary(max) 上的 UPDATE 语句这么慢?
【发布时间】:2017-05-25 16:47:33
【问题描述】:

我有一个只有 500 行的表 S 和一个有 120000 行的表 F。两者都使用 GUID 主键,表 F 持有表 S 的外键。表 F 包含一个 varbinary(max)F.Data,每行大约 100 KB(总数据库大小约为 10 GB)。文件流已打开。我正在使用 SQL Server 2014 Express。

当我执行以下 UPDATE 语句(在 SQL Server Management Studio 中)时,它会影响大约 100000 行

UPDATE F
SET F.Data = 0
FROM F
INNER JOIN S
ON S.SID = F.SID
WHERE S.BITFIELD = 1 AND S.Date < DATEADD(DAY,-90,GETDATE())

查询大约需要 30 分钟。这是相当不可接受的,但我对 SQL 的了解还不够,无法知道为什么或如何使这个查询更有效。有哪位大师可以帮忙吗?

仅供参考,等效的 SELECT 语句只需要几秒钟。我搜索了 Stackoverflow 和其他地方,但没有发现任何特别有用的东西(鉴于我对 SQL 的了解有限)。

【问题讨论】:

  • 100,000 个 5Mbytes/行变成 500 GB 的更新信息。那是一个日志。所有这些都需要记录。
  • 我想你已经回答了你自己的问题,你有一张大桌子,你正在更新很多......
  • 它被记录下来并且它也必须最终出现在表格中。建议将您的更新分成更小的块。最终,您只是移动了太多数据,以至于速度会很慢。
  • @Matt,我的错。我更新了问题。每行大约 100 KB,总数据库大小约为 10 GB。
  • @GordonLinoff,我的错。我更新了问题。每行大约 100 KB,总数据库大小约为 10 GB。

标签: sql sql-server filestream


【解决方案1】:

您是否尝试过创建一个只有一个字段( S.SID )和所有与 WHERE S.Date

此外,GUID 上的索引可能不如在 INT 上使用索引好。阅读此GUID vs INT IDENTITY 祝你好运。

类似这样的:

CREATE TABLE [#TEMPTBL1]([SID] uniqueidentifier);
CREATE CLUSTERED INDEX IDX_TEMPTBL1_SID ON [#TEMPTBL1]([SID]);  
INSERT INTO [#TEMPTBL1]([SID])
            SELECT ([SID]) FROM S 
            WHERE S.BITFIELD = 1 
            AND S.Date < DATEADD(DAY,-90,GETDATE());


UPDATE F
SET F.Data = 0
FROM F
INNER JOIN #TEMPTBL1 TMP ON F.SID = TMP.SID

DROP TABLE #TEMPTBL1;

--------- 使用计数器更新代码--------

DECLARE @updtCounter int = 0;

CREATE TABLE [#TEMPTBL1]([SID] uniqueidentifier);
CREATE CLUSTERED INDEX IDX_TEMPTBL1_SID ON [#TEMPTBL1]([SID]);  
INSERT INTO [#TEMPTBL1]([SID])
            SELECT ([SID]) FROM S 
            WHERE S.BITFIELD = 1 
            AND S.Date < DATEADD(DAY,-90,GETDATE());

SELECT @updtCounter = count(*) FROM F
INNER JOIN #TEMPTBL1 TMP ON F.SID = TMP.SID

UPDATE TOP (@updtCounter) F
SET F.Data = 0
FROM F
INNER JOIN #TEMPTBL1 TMP ON F.SID = TMP.SID

DROP TABLE #TEMPTBL1;

【讨论】:

  • 这有点帮助;剃了5分钟。我也阅读了您链接的内容,这使我相信速度缓慢可能是由于 F 中的数据存在集群问题。因为该表上的 GUID 是集群索引(默认 SQL Server 行为未覆盖), UPDATE 必须跳遍整个地方才能更改数据。
  • 您好,您还可以尝试一下。您可以查看是否可以使用计数器来准确评估在开始更新之前必须更新的行数。然后,当您运行更新时,一旦您知道您的更新已实际完成,您就会停止表扫描。请参阅我的答案下方的代码更新。
【解决方案2】:

我看到三件事可以在这里解决:

  1. 您没有提到等效的 select 语句返回需要多少秒,但如果它是相当长的几秒(例如,不低于 10 秒),您可能希望使用变量作为日期而不是运行一个 DATEADD 函数 100k 次。其语法为:

    DECLARE @MyDate as DATETIME = DATEADD(DAY,-90,GETDATE());  
    UPDATE F
    SET F.Data = 0
    FROM F
    INNER JOIN S
    ON S.SID = F.SID
    WHERE S.BITFIELD = 1 AND S.Date < @MyDate
    
  2. 您可以选择以块的形式进行更新,例如 10k 行;这不会锁定太多,可能会更快返回。

  3. 我要检查的另一件事是表 F 上的索引数量。当您选择时,优化器将决定使用哪个索引,您将完成,而在更新中,所有包含受影响字段的索引也需要更新。

评论:作为 PK 的 GUID 对性能没有帮助。如果您有许多非聚集索引,则 GUID 问题会加剧。

【讨论】:

  • 1.为日期添加变量没有任何区别。 2. 这可能是我唯一的选择——只是想确保我没有忽略某些东西。 3. 我不太了解索引,但是 SSMS 说索引是基于 PK 的聚集索引,即 GUID。而且,不出所料,它们非常分散(S 上 >85%,F 上 >99%)。
  • @MichaelRepucci 有一个来自世界级 DBA 的精彩视频,Brent Ozar 解释了索引。我觉得这将极大地帮助你。这是链接:brentozar.com/archive/2016/10/…
【解决方案3】:

更多建议: 1. 这可能是一种罕见的情况,其中游标可以通过将 UPDATE 分成更小的块来提高性能。您提到表 S 有 500 行,表 F 有 120K 行,所以如果它们大致均匀分布,那么 S 中的每一行在 F 中有 240 行。

Declare @SID uniqueidentifier;
Declare c cursor forward_only for
    SELECT ([SID]) FROM S 
            WHERE S.BITFIELD = 1 
            AND S.Date < DATEADD(DAY,-90,GETDATE()); 

Fetch next from c into @SID
While @@fetch_status = 0
    Begin
    UPDATE F
        SET F.Data = 0
        FROM F
        WHERE F.SID = @SID
    Fetch next from c into @SID
    End
Deallocate c
  1. 您还可以在Update 周围使用Begin TransCommit 获得更好的性能。

  2. 根据表 S 中的记录将 BitField 设置为 1 的频率,如果更新不是很频繁,您可以将更新放入 trigger

  3. 另一种方法是仅当 S 中的 BitField 未设置时才从 F 中选择数据:

Select CASE WHEN S.BitField=1 THEN 0 ELSE F.Data END as Data FROM F INNER JOIN S ON S.SID = F.SID

select 语句的目的是让 S 中的 BitField 设置为 1 时 F.Data 包含 0。您可以将 Select 放入视图中,然后在其他中访问 F 时使用视图而不是表查询。尽管 F.Data 字段仍然包含 100KB 值,但无论何时从视图中选择,它都会将 F.Data 显示为 0 或实际值,具体取决于 S.BitField。如果您需要减少正在使用的磁盘空间,您仍然需要执行更新,但您可以将其安排在系统不使用的时间。

【讨论】:

  • 感谢您的建议;我学到了很多东西。 #1 实际上让它稍微长了 2 分钟。 #2 不应该(也没有)工作,因为它已经是一个单一的语句。 #3 是未来改进此过程的好主意。但是,我不明白您对#4 的建议。你能澄清一下吗?
  • 请参阅添加到 #4 的说明。
  • 啊,我明白了。感谢您的澄清。这是一个很酷的技巧。但是你猜对了,这主要是为了通过清除旧数据来减少空间使用。
猜你喜欢
  • 2021-11-07
  • 1970-01-01
  • 2017-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多