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) |
执行计划
似乎是查询的这一部分花费了很长时间。
再次感谢您的帮助,我正在拔头发。
一次可以做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并不总是会猜对。
另外,您是否正在使用准备好的语句(即存储过程)?如果是这样,则可能是缓存的数据访问计划已经过时并需要更新,或者您需要更新表上的统计信息,然后运行过程
如果要更新表,则需要一个唯一的标识符,因此请特别快地穿上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)并让它分配空间?
坦白说,我认为您已经回答了自己的问题。
如果您知道组合是唯一的,那么就一定要"强制执行"它。这样服务器也将知道它并可以使用它。
您不应该相信MSSQL告诉您的所有内容,但至少应该尝试一下=)
结合两者,我建议您在字段[date_1]和[Link_ref](按此顺序!)上向表添加
就您的查询而言,您可以将所有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 |