2017-05-25 119 views
2

我有一个只有500行的表S,以及有01200行的表F。两者都使用GUID主键,表F持有表S的外键。表F包含varbinary(max)F.Data,每行约100 KB(总数据库大小约为10 GB)。文件流已打开。我正在使用SQL Server 2014 Express。为什么SQL Server上varbinary(max)的UPDATE语句如此之慢?

当我提出以下UPDATE语句(在SQL Server Management Studio中),这将影响大约10万行

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的了解有限)。

+6

100,000个5M字节/行变成500G字节的更新信息。这是一个日志。所有这些都需要记录。 –

+0

我认为你已经回答了你自己的问题,你有一张大桌子,你正在更新很多...... – Matt

+2

它被记录下来,它也必须在表格中结束。建议将更新分解为更小的块。最终,你只是在移动这么多的数据,以至于它会变得很慢。 – Donnie

回答

0

您是否尝试过只用一个领域,(S.SID)和所有与WHERE S.Date < 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; 
+0

这有一点帮助;削减5分钟。我读过你链接的内容,这导致我认为缓慢可能是由于F中数据的集群问题。因为该表上的GUID是聚簇索引(默认SQL Server行为未覆盖),更新将不得不跳过所有的地方来改变数据。 –

+0

嗨,再次,这是你可以尝试的另一件小事。您可以查看在开始更新之前是否可以使用计数器来准确评估您需要更新的行数。然后,当您运行更新时,只要知道您的更新实际完成,就会停止表扫描。看到我的答案下面的代码更新。 – levteck

0

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

  1. 您没有提到有多少秒花费相当于select语句返回,但如果这是一个相当几秒钟(如,不在10以下),您可能希望使用变量作为日期而不是运行100000次DATEADD函数。其语法如下:

    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问题会加剧。

+0

1.添加一个变量的日期没有区别。 2.这可能是我唯一的选择 - 只是想确保我没有忽略某些东西。 3.我不太了解索引,但SSMS表示索引是基于PK的聚簇索引,它们是GUID。不出所料,它们非常分散(S> 85%,F> 99%)。 –

+0

@MichaelRepucci世界级的DBA有一个很棒的视频,Brent Ozar解释索引。我觉得这会对你有所帮助。这是链接:https://www.brentozar.com/archive/2016/10/think-like-engine-class-now-free-open-source/ – Eli

0

一些更多的建议: 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 
  • 而且你使用Begin TransCommitUpdate周围可以得到更好的性能。

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

  • 另一种方法可以选择从F中的数据只有在S中的位域没有设置:

  • 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语句的目的是使它看起来F.Data当S中的BitField设置为1时,它包含0。您可以将Select放入视图中,然后在访问其他查询中的F时使用视图而不是表格。即使F.Data字段仍包含100KB值,任何时候您从视图中选择时,都会将F.Data显示为0或实际值,具体取决于S.BitField。如果您需要减少正在使用的磁盘空间,您仍然需要执行更新,但是您可以在系统未使用时安排该时间。

    +0

    感谢您的建议;我学到很多东西。 #1实际上稍长了2分钟。 #2不应该(也不会),因为它已经是一个单一的声明。#3是未来改进这一过程的好主意。不过,我不明白你在用#4来表达什么。你能澄清吗? –

    +0

    请参阅添加到#4的说明。 – chrisuae

    +0

    啊,我明白了。感谢您的澄清。这是一个很酷的伎俩。但你猜对了,这主要是为了通过清除旧数据来减少空间使用量。 –