SQL Server Update查询速度很慢

时间:2016-01-06 09:33:15

标签: sql sql-server

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

我刚刚测试了旧SQL服务器中的查询,它可以在3小时内完成。因此,问题必须与我创建的新SQL服务器有关。你对这个问题有什么想法吗?

查询:

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在一起是唯一的)

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

Link_ID nvarchar (17) PRIMARY KEY
Link_ref    int INDEXED
Link_metres int

ABC_20142015_days

Date1   smalldatetime   PRIMARY KEY & INDEXED
DayType nvarchar(50)

执行计划 enter image description here

查询的这一部分似乎花了这么长时间。

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

13 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:2)

为什么有3个更新语句可以在一个中完成?

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

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

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。

看看是否有所改善。

答案 2 :(得分:1)

如果您查看执行计划,那么时间就是实际更新

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

至于索引测试和调整这个 如果连接列的编制索引不多,那么

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添加索引)

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

JOIN     [Central].[dbo].[LookUp_ABC_20142015] LT with (nolock)

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

答案 3 :(得分:1)

如果你要更新一个表,你需要一个唯一的标识符,所以特别要把它放在ABCJan2014上,因为它太大了。没有理由不能在一起组成唯一记录的字段上创建唯一索引。将来,不要设计没有唯一索引或PK的表。这只是在处理时间和更重要的数据完整性方面都会遇到麻烦。

如果要对大型表进行大量更新,有时批量工作会更有效。您不会长时间将表绑定在锁中,有时由于数据库内部如何处理问题而更快。考虑一次处理50,000个K记录(您可能需要在循环或游标中进行试验以找到要批量处理的记录的最佳位置,通常有一个更新开始花费更长时间的点)。

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;&gt;的记录,即可节省大量时间。 LT.link_id。您有一个7000万的记录表,您不需要更新不需要更改的记录。同样的事情当然也适用于您的其他更新。

不知道有多少数据被添加到此表或此数字需要更新的频率,请考虑此SumAvJT可能最好定义为持久计算字段。然后当两个值中的一个发生变化时,它会自动更新。如果表是批量加载的话,这将无济于事,但如果记录单独出现,则可能无效。

答案 4 :(得分:1)

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

那就是说,你看到的数据量是多少?如果您正在查看显着不同的数据量,那么服务器生成的执行计划可能会有很大差异。在构建计划时,SQL Server并不总是正确猜测。

另外,您使用预准备语句(即存储过程)吗?如果你是,那么缓存的数据访问计划可能只是过时了。需要更新,或者您需要更新表的统计信息,然后运行过程with recompile,以便生成新的数据访问计划。

答案 5 :(得分:1)

位于[Central]服务器的位置? 可以在本地复制[Central]。[dbo]。[LookUp_ABC_20142015]和[Central]。[dbo]。[ABC_20142015_days]表?

1)做:

  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

跳跃它可以帮助你...

答案 6 :(得分:1)

所有先前的答案表明改进表格的结构和查询本身很高兴知道,对此有疑问。

但是你的问题是为什么SAME数据/结构和SAME查询给出了巨大的差异。

所以在你看优化sql之前,你必须找到真正的原因。真正的原因是硬件或软件或配置。首先将sql server与旧服务器进行比较,然后转移到硬件并对其进行基准测试。最后看一下软件的差异。

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

答案 7 :(得分:0)

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

答案 8 :(得分:0)

我猜有很多页面拆分。你能试试吗?

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

答案 9 :(得分:0)

除了上面的所有答案。

i)即使3个小时也很多。我的意思是即使任何查询花了3个小时,我首先检查我的要求并修改它。提出问题。当然我会优化我的查询。 与您的查询一样,更新似乎都不是严重问题。

像@Devart指出的那样,其中一列可以计算列。

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

iii)重建索引。

iv)在你的加入中使用“with(nolock)”。

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

vi)nvarchar(17)或datetime上的聚簇索引总是一个坏主意。 加入datetime列或varchar列总是需要时间。

答案 10 :(得分:0)

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

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

答案 11 :(得分:0)

坦率地说,我认为你已经回答了自己的问题。

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可能会限制受影响的行数这一事实。因此我会像这样重写它:

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]&amp; [LookUp_ABC_20142015]),但它们似乎是非唯一的(并且不总是聚集在一起)。假设他们正在遭受'我们知道它是唯一的但服务器没有 - 生病:为了数据完整性和性能原因,建议在你加入的字段上为这些表添加主键是明智之举!

祝你好运。

答案 12 :(得分:0)

  <textarea id="mytextarea[nl][9]" class="editor" name="EditorField[nl][9]"><p>NEDERLANDS</p></textarea>

代理表必须具有带唯一键的非聚集索引。必须将myKeyID创建为唯一的非集群键。性能结果的改进是显着的。