为什么SQL Server上的varbinary(max)上的这个UPDATE语句这么慢?

时间:2017-05-25 16:47:34

标签: sql sql-server filestream

我有一个只有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的了解有限)。

3 个答案:

答案 0 :(得分:0)

我看到有三件事可以解决:

  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上的索引数。当你选择时,优化器将决定使用哪个索引,你会完成,而在更新中,包含受影响字段的所有索引也需要更新。
  4. 评论:作为PK的GUID在这里没有帮助表现。如果您有许多非聚集索引,则GUID问题会更加严重。

答案 1 :(得分:0)

您是否尝试使用一个字段(S.SID)创建临时表,并且所有记录都与WHERE S.Date&lt; DATEADD(DAY,-90,GETDATE()) 然后在更新中加入它,而不是在更新期间在where子句中计算?

此外,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;

答案 2 :(得分:0)

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

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. 使用Begin Trans周围的CommitUpdate可以获得更好的效果。

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

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

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

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