繁体   English   中英

SQL Server 更新查询非常慢

[英]SQL Server Update query very slow

我对前几年的数据运行了以下查询,用了 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_天

Date1   smalldatetime   PRIMARY KEY & INDEXED
DayType nvarchar(50)

执行计划在此处输入图像描述

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

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

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

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

查看日志文件
日志文件是否在快速磁盘上?
日志文件是否在同一个物理磁盘上?
日志文件是否需要增长?
将日志文件的大小调整为数据文件大小的 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 很奇怪
为什么 n - 你真的有一些 unicode 吗?
为什么不只是 char(17) 并让它分配空间?

为什么有 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 中添加一个索引。

看看这是否有任何改善。

如果你要更新一个表,你需要一个唯一的标识符,所以尽快把它放在 ABCJan2014 上,尤其是因为它太大了。 没有理由不能在共同构成唯一记录的字段上创建唯一索引。 以后永远不要设计没有唯一索引或主键的表。 这只是在处理时间和更重要的数据完整性方面自找麻烦。

当您需要对一个大表进行大量更新时,有时分批工作会更有效。 您不会长时间将表锁定在锁中,有时由于数据库内部如何处理问题,它甚至更快。 考虑在一个循环或游标中一次处理 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 为空或 ABCJan2014.link_id <> LT.link_id 的记录来节省大量时间。 您有一个 7000 万条记录表,您不需要更新不需要更改的记录。 当然,同样的事情也适用于您的其他更新。

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

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

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

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

[中央] 服务器在哪里? 可以在本地复制 [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

希望它能帮助你...

之前所有建议改进表结构和查询本身的答案对您来说都很高兴,对此有疑问。

但是,您的问题是为什么相同的数据/结构和相同的查询会产生如此巨大的差异。

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

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

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

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

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

除了上面的所有答案。

i) 即使 3 小时也很多。我的意思是即使任何查询需要 3 小时,我首先检查我的需求并修改它。提出问题。当然我会优化我的查询。 就像在您的查询中一样,没有任何更新似乎是严重的问题。

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

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

iii) 重建索引。

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

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

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

尝试使用别名而不是在 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

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

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 告诉你的一切,但你至少应该试一试 =)

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

就您的查询而言,您可以将所有 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] & [LookUp_ABC_20142015]),但它们似乎不是唯一的(并不总是聚集的)。 假设他们患有“我们知道它是唯一的但服务器没有”的疾病:出于数据完整性和性能原因,建议还向您加入的字段上的那些表添加主键!

祝你好运。

Update data
set
data.abcKey=surrogate.abcKey
from [MyData].[dbo].[fAAA_Stage] data with(nolock)
join [MyData].[dbo].[dBBB_Surrogate] surrogate with(nolock)
on data.MyKeyID=surrogate.MyKeyID

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM