SQL Server更新查询非常慢

SQL Server Update query very slow

我对前几年的数据运行了以下查询,这花费了3个小时,而今年花费了13天。 我不知道为什么会这样。 任何帮助将非常感激。

我刚刚在旧的SQL Server中测试了查询,并且可以在3个小时内正常工作。 因此,问题一定与我创建的新SQL Server有关。 您有什么想法可能是问题吗?

查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
USE [ABCJan]
CREATE INDEX Link_Oct ON ABCJan2014 (Link_ref)
GO
CREATE INDEX Day_Oct ON ABCJan2014 (date_1)
GO

UPDATE   ABCJan2014
SET      ABCJan2014.link_id = LT.link_id
FROM     ABCJan2014 MT
INNER JOIN  [Central].[dbo].[LookUp_ABC_20142015] LT
ON MT.Link_ref = LT.Link_ref

UPDATE   ABCJan2014
SET      SumAvJT  = ABCJan2014.av_jt * ABCJan2014.n

UPDATE   ABCJan2014
SET      ABCJan2014.DayType = LT2.DayType
FROM     ABCJan2014 MT
INNER JOIN  [Central].[dbo].[ABC_20142015_days] LT2
ON  MT.date_1 = LT2.date1

具有以下数据结构:

ABCJan2014(7000万行-无唯一标识符-Link_ref和date_1都是唯一的)

1
2
3
4
5
6
7
Link_ID nvarchar (17)
Link_ref    INT
Date_1  smalldatetime
N       INT
Av_jt       INT
SumAvJT DECIMAL(38,14)
DayType nvarchar (50)

LookUp_ABC_20142015

1
2
3
Link_ID nvarchar (17) PRIMARY KEY
Link_ref    INT INDEXED
Link_metres INT

ABC_20142015_days

1
2
Date1   smalldatetime   PRIMARY KEY & INDEXED
DayType nvarchar(50)

执行计划
enter image description here

似乎是查询的这一部分花费了很长时间。

再次感谢您的帮助,我正在拔头发。


一次可以做3个更新语句?

1
2
3
4
5
6
7
8
9
UPDATE   MT
SET      MT.link_id = CASE WHEN LT.link_id IS NULL THEN MT.link_id ELSE LT.link_id END,
         MT.SumAvJT  = MT.av_jt * MT.n,
         MT.DayType = CASE WHEN LT2.DayType IS NULL THEN MT.DayType ELSE LT2.DayType END
FROM     ABCJan2014 MT
LEFT OUTER JOIN  [Central].[dbo].[LookUp_ABC_20142015] LT
    ON MT.Link_ref = LT.Link_ref
LEFT OUTER JOIN  [Central].[dbo].[ABC_20142015_days] LT2
    ON MT.date_1 = LT2.date1

另外,我只会为连接创建一个索引。更新后创建以下索引。

1
2
CREATE INDEX Day_Oct ON ABCJan2014 (date_1)
GO

在运行之前,通过将上面的更新查询和您的3条更新语句放在一个查询窗口中来比较执行计划,然后执行"显示估计的执行计划"。它将显示估计的百分比,您将能够知道它是否更好(如果新百分比小于50%)。

另外,由于执行哈希匹配,查询看起来很慢。请在[LookUp_ABC_20142015] .Link_ref上添加PK索引。

[LookUp_ABC_20142015]。Link_ID是PK的错误选择,因此请删除该列上的PK。

然后将索引添加到[ABCJan2014] .Link_ref。

看看是否有任何改善。


在ABCJan2014表上创建索引,因为它当前是一个堆


以前所有建议改善表结构和查询本身的答案都很高兴为您所知,对此您有疑问。

但是,您的问题是,为什么SAME数据/结构和SAME查询会带来如此巨大的差异。

因此,在考虑优化sql之前,必须找到真正的原因。 真正的原因是硬件,软件或配置。 首先将sql server与旧的sql server进行组合,然后移至硬件并对其进行基准测试。 最后看一下软件中的差异。

只有解决了实际问题,您才能开始改进sql本身


[中央]服务器在哪里?
是否可以在本地复制[Central]。[dbo]。[LookUp_ABC_20142015]和[Central]。[dbo]。[ABC_20142015_days]表?

1)做:

1
2
  SELECT * INTO [ABC_20142015_days] FROM [Central].[dbo].[ABC_20142015_days]
  SELECT * INTO [LookUp_ABC_20142015] FROM [Central].[dbo].[LookUp_ABC_20142015]

2)在[ABC_20142015_days]和[LookUp_ABC_20142015]上重新创建索引...

3)通过删除" [Central]。[dbo]"来重写您的更新。字首 !

在编写完此解决方案之后,我找到了另一个解决方案,但是我不确定它是否适用于您的服务器:添加" REMOTE"连接提示...我从未使用过,但是您可以在https:/下找到文档/msdn.microsoft.com/en-us/library/ms173815.aspx

跳起来可以帮助您...


在执行计划中,它为添加索引提供建议。您是否创建了这些索引?另外,查看一下旧服务器的数据结构-编写包括索引的表结构的脚本-看看它们之间是否存在差异。在某些时候,可能有人在旧服务器的表上建立了索引,以使其效率更高。

也就是说,您正在查看的数据量是多少?如果查看的数据量大不相同,则可能是服务器生成的执行计划大不相同。在构建计划时,SQL Server并不总是会猜对。

另外,您是否正在使用准备好的语句(即存储过程)?如果是这样,则可能是缓存的数据访问计划已经过时并需要更新,或者您需要更新表上的统计信息,然后运行过程with recompile,以便生成新的数据访问计划。 。


如果要更新表,则需要一个唯一的标识符,因此请特别快地穿上ABCJan2014,因为它太大了。没有理由不能在组成唯一记录的字段上创建唯一索引。将来,永远不要设计没有唯一索引或PK的表。这仅是在处理时间以及更重要的是数据完整性方面提出麻烦。

当您需要对大型表进行大量更新时,分批处理有时会更有效。您不用长时间将表捆绑在锁中,有时由于数据库内部如何解决问题,有时甚至更快。考虑在一次循环或游标中一次处理50,000 K条记录(您可能需要尝试查找要批量处理的记录的最佳位置,通常会有一个开始更新的时间明显更长的时间)。

1
2
3
4
UPDATE ABCJan2014
SET ABCJan2014.link_id = LT.link_id
FROM ABCJan2014 MT
JOIN [Central].[dbo].[LookUp_ABC_20142015] LT ON MT.Link_ref = LT.Link_ref

上面的代码将更新联接中的所有记录。如果某些记录已经具有link_id,则可以通过仅更新link_id为null或ABCJan2014.link_id <> LT.link_id的记录来节省大量时间。您有7,000万条记录表,不需要更新不需要更改的记录。当然,同样的事情也适用于您的其他更新。

不知道向该表中添加了多少数据,或者不知道需要多少次更新此表,请考虑将SumAvJT最好定义为持久的计算字段。然后,当两个值之一更改时,它将自动更新。如果表是批量加载的,这将无济于事,但如果记录是单独输入的,则可能会有所帮助。


如果查看执行计划,则时间在实际更新中

查看日志文件
日志文件是否在快速磁盘上?
日志文件是否在同一物理磁盘上?
需要增长日志文件吗?
将日志文件的大小调整为数据文件大小的1/2

至于索引测试和调整
如果连接列的索引不多,可以在这里做

1
2
3
4
5
6
7
8
9
SELECT   COUNT(*)
FROM     ABCJan2014 MT
INNER JOIN  [Central].[dbo].[LookUp_ABC_20142015] LT
ON MT.Link_ref = LT.Link_ref

SELECT   COUNT(*)
FROM     ABCJan2014 MT
INNER JOIN  [Central].[dbo].[ABC_20142015_days] LT2
ON  MT.date_1 = LT2.date1

从顶部(1000)开始以进行更新调整
对于咧嘴笑,请尝试一下
请发布此查询计划
(请勿将索引添加到ABCJan2014 link_id)

1
2
3
4
5
6
UPDATE   top (1000) ABCJan2014
SET      MT.link_id = LT.link_id
FROM     ABCJan2014 MT
JOIN     [Central].[dbo].[LookUp_ABC_20142015] LT
          ON MT.Link_ref = LT.Link_ref
         AND MT.link_id <> LT.link_id

如果LookUp_ABC_20142015未处于活动状态,则添加nolock

1
JOIN     [Central].[dbo].[LookUp_ABC_20142015] LT WITH (nolock)

nvarchar(17)对于我来说是个PK很奇怪
为什么n-您真的有一些unicode吗?
为什么不只是char(17)并让它分配空间?


坦白说,我认为您已经回答了自己的问题。

ABCJan2014 (70 million rows - NO UNIQUE IDENTIFIER - Link_ref & date_1 together are unique)

如果您知道组合是唯一的,那么就一定要"强制执行"它。这样服务器也将知道它并可以使用它。

Query Plan showing the need for an index on [ABCJAN2014].[date_1] 3 times in a row!

您不应该相信MSSQL告诉您的所有内容,但至少应该尝试一下=)

结合两者,我建议您在字段[date_1]和[Link_ref](按此顺序!)上向表添加PK。注意:添加一个主键-本质上是一个集群的唯一索引-将花费一些时间,并且需要大量空间,因为在此过程中表几乎都是重复的。

就您的查询而言,您可以将所有3个更新放在1条语句中(类似于joordan831的建议),但您应注意JOIN可能会限制受影响的行数这一事实。因此,我会这样重写它:

1
2
3
4
5
6
7
8
9
10
11
UPDATE ABCJan2014
SET    ABCJan2014.link_id = (CASE WHEN LT.Link_ref IS NULL THEN ABCJan2014.link_id ELSE LT.link_id END), -- update when there is a match, otherwise re-use existig value
       ABCJan2014.DayType = (CASE WHEN LT2.date1   IS NULL THEN ABCJan2014.DayType ELSE LT2.DayType END), -- update when there is a match, otherwise re-use existig value
       SumAvJT            = ABCJan2014.av_jt * ABCJan2014.n

FROM     ABCJan2014 MT
LEFT OUTER JOIN  [Central].[dbo].[LookUp_ABC_20142015] LT
             ON MT.Link_ref = LT.Link_ref

LEFT OUTER JOIN [Central].[dbo].[ABC_20142015_days] LT2
             ON MT.date_1 = LT2.date1

与顺序运行原始3个更新的效果相同;但希望可以减少很多时间。

PS:按照查询计划,您已经在要联接到的表上具有索引([LookUp_ABC_20142015]和[LookUp_ABC_20142015]),但它们似乎是不唯一的(并不总是群集的)。假设他们正遭受"我们知道它是唯一的,但服务器却没有"的困扰:出于数据完整性和性能方面的考虑,建议您在要连接的字段的表上也添加主键!

祝好运。


尝试使用别名而不是在UPDATE查询中重新获取表名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
USE [ABCJan]
CREATE INDEX Link_Oct ON ABCJan2014 (Link_ref)
GO
CREATE INDEX Day_Oct ON ABCJan2014 (date_1)
GO

UPDATE   MT
SET      MT.link_id = LT.link_id
FROM     ABCJan2014 MT
INNER JOIN  [Central].[dbo].[LookUp_ABC_20142015] LT
ON MT.Link_ref = LT.Link_ref

UPDATE   ABCJan2014
SET      SumAvJT  = av_jt * n

UPDATE   MT
SET      MT.DayType = LT2.DayType
FROM     ABCJan2014 MT
INNER JOIN  [Central].[dbo].[ABC_20142015_days] LT2
ON  MT.date_1 = LT2.date1

除了以上所有答案。

i)即使是3个小时也很多。我的意思是,即使任何查询需要3个小时,我也要先检查需求并进行修改。提出问题。当然,我会优化查询。
就像您的查询中一样,更新似乎都不是一件大事。

就像@Devart指出的那样,该列之一可以是计算列。

ii)尝试在新服务器中运行其他查询并进行比较。

iii)重建索引。

iv)在您的联接中使用" with(nolock)"。

v)在表LookUp_ABC_20142015列Link_ref上创建索引。

vi)在nvarchar(17)或datetime上聚集索引始终是一个坏主意。
加入datetime列或varchar列始终需要时间。


我猜有很多页面拆分。你可以试试这个吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SELECT

(SELECT LT.link_id FROM [Central].[dbo].[LookUp_ABC_20142015] LT
WHERE MT.Link_ref = LT.Link_ref) AS Link_ID,
Link_ref,
Date_1,
N,
Av_jt,
MT.av_jt * MT.n AS SumAvJT,
(SELECT LT2.DayType FROM [Central].[dbo].[ABC_20142015_days] LT2
WHERE MT.date_1 = LT2.date1) AS DayType

INTO ABCJan2014new
FROM ABCJan2014 MT


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ALTER TABLE dbo.ABCJan2014
    ADD SumAvJT AS av_jt * n --PERSISTED

CREATE INDEX ix ON ABCJan2014 (Link_ref) INCLUDE (link_id)
GO
CREATE INDEX ix ON ABCJan2014 (date_1) INCLUDE (DayType)
GO

UPDATE ABCJan2014
SET ABCJan2014.link_id = LT.link_id
FROM ABCJan2014 MT
JOIN [Central].[dbo].[LookUp_ABC_20142015] LT ON MT.Link_ref = LT.Link_ref

UPDATE ABCJan2014
SET ABCJan2014.DayType = LT2.DayType
FROM ABCJan2014 MT
JOIN [Central].[dbo].[ABC_20142015_days] LT2 ON MT.date_1 = LT2.date1