簡體   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