我有一个只有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的了解有限)。
答案 0 :(得分:0)
我看到有三件事可以解决:
您还没有提到等效的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
您可以选择以10k行的数据块进行更新;这不会锁定太多,可能会更快地返回。
评论:作为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
使用Begin Trans
周围的Commit
和Update
可以获得更好的效果。
根据表S中记录将BitField设置为1的频率,如果更新频率不高,您可以将更新置于trigger
。
另一种方法可能是仅在未设置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
当S中的BitField设置为1时,select语句似乎使得F.Data包含0。您可以将Select放入视图中,然后在其他访问F时使用视图而不是表查询。即使F.Data字段仍包含100KB值,但只要您从视图中选择,它就会将F.Data显示为0或实际值,具体取决于S.BitField。如果您需要减少正在使用的磁盘空间,您仍然需要执行更新,但是您可以在系统未使用的时候安排更新。