簡體   English   中英

SQL存儲過程臨時表內存問題

[英]SQL stored procedure temporary table memory problem

我們有以下簡單的存儲過程作為隔夜SQL服務器代理作業運行。 通常它在20分鍾內運行,但最近MatchEvent和MatchResult表已經增長到每個超過900萬行。 這導致存儲過程耗時超過2小時,我們的SQL盒上的所有8GB內存都用完了。 這會使數據庫對嘗試訪問它的常規查詢不可用。

我假設問題是臨時表太大並導致內存和數據庫不可用性問題。

如何重寫存儲過程以使其更高效,更少內存密集?

注意:我已編輯SQL以指示存在影響初始SELECT語句的條件。 為了簡單起見,我之前已經將其留下了。 此外,當查詢運行時,CPU使用率為1-2%,但如前所述,memoery最大化


CREATE TABLE #tempMatchResult
(
    matchId VARCHAR(50)
)

INSERT INTO #tempMatchResult SELECT MatchId FROM MatchResult WHERE SOME_CONDITION

DELETE FROM MatchEvent WHERE
MatchId IN (SELECT MatchId FROM #tempMatchResult)

DELETE FROM MatchResult WHERE MatchId In (SELECT MatchId FROM #tempMatchResult)

DROP TABLE #tempMatchResult

這里可能會發生很多事情,並不是你所有的問題。

首先,我同意其他海報。 如果可能的話,嘗試在沒有臨時表的情況下重寫它。

但是假設你需要一個臨時表,你有一個很大的問題,就是你沒有定義PK。 它將大大擴展您的查詢運行所需的時間。 改為創建你的表:

CREATE TABLE #tempMatchResult (
    matchId VARCHAR(50) NOT NULL PRIMARY KEY /* NOT NULL if at all possible */
);

INSERT INTO #tempMatchResult
SELECT DISTINCT MatchId FROM MatchResult;

另外,請確保TempDB的大小正確。 您的SQL服務器可能正在動態地擴展數據庫文件,導致您的查詢吸收CPU和磁盤時間。 此外,請確保您的事務日志大小正確,並且它不會自動增長。 祝好運。

DELETE FROM MatchResult WHERE
MatchId In (SELECT MatchId FROM #tempMatchResult)

可以替換為

DELETE FROM MatchResult WHERE SOME_CONDITION

你能在matchresult和matchevent之間轉換級聯刪除嗎? 然后,您只需要擔心識別要刪除的一組數據,並讓SQL處理另一組數據。

另一種方法是使用OUTPUT子句,但這肯定更加小巧。

這兩個都可以讓你從兩個表中刪除,但只需要說明(並執行)你的過濾謂詞一次。 這可能仍然不如其他海報所建議的批處理方法那樣高效,但值得考慮。 因人而異

看看上面的代碼,為什么需要臨時表?


DELETE FROM MatchEvent WHERE
MatchId IN (SELECT MatchId FROM MatchResult)


DELETE FROM MatchResult
-- OR Truncate can help here, if all the records are to be deleted anyways.

您可能希望以某種方式處理此分段。 (我假設您展示的查詢要復雜得多嗎?)在這種情況下,您需要嘗試以下方法之一:

  • 編寫存儲過程以迭代結果。 (處理時可能仍會鎖定。)
  • 重復選擇N個第一次命中,例如LIMIT 100並處理它們。
  • 通過使用WHERE M <= x AND x <N之類的東西分別掃描表的區域來划分工作。
  • 更頻繁地運行“午夜工作”。 說真的,每隔5分鍾運行這樣的東西就可以創造奇跡,尤其是如果工作非線性增加的話。 (如果沒有,你仍然可以在一天中的幾個小時內完成工作。)

在Postgres中,我使用條件索引取得了一些成功。 如果滿足某些條件,它們通過應用索引來工作。 這意味着您可以在同一個表中保留許多“已解決”和少數未解析的行,但仍然可以獲得僅針對未解析的行的特殊索引。 因人而異。

應該指出,這是使用數據庫變得有趣的地方 您需要密切關注索引並對查詢使用EXPLAIN

(哦,記住, 有趣的是你的愛好是好事,但不是在工作。)

首先,索引必須在這里看到Dave M的回答。

我將在刪除非常大的數據集時使用的另一種方法是創建包含所有數據的影子表,重新創建索引,然后使用sp_rename將其切換。您必須小心處理此處的事務,但取決於數量被刪除的數據可以更快。

注意如果tempdb存在壓力,請考慮使用連接而不是將所有數據復制到臨時表中。

所以舉個例子

CREATE TABLE #tempMatchResult (
    matchId VARCHAR(50) NOT NULL PRIMARY KEY /* NOT NULL if at all possible */
);

INSERT INTO #tempMatchResult
SELECT DISTINCT MatchId FROM MatchResult;

set transaction isolation level serializable
begin transaction 

create table MatchEventT(columns... here)

insert into MatchEventT
select * from MatchEvent m
left join #tempMatchResult t on t.MatchId  = m.MatchId 
where t.MatchId is null 

-- create all the indexes for MatchEvent

drop table MatchEvent
exec sp_rename 'MatchEventT', 'MatchEvent'

-- similar code for MatchResult

commit transaction 


DROP TABLE #tempMatchResult

盡可能避免臨時表

它只是耗盡內存。
你可以試試這個:

DELETE MatchEvent
FROM MatchEvent  e , 
     MatchResult r
WHERE e.MatchId = r.MatchId 

如果你無法避免臨時表

我要把我的脖子伸到這里然后說: 你不需要臨時表上的索引,因為你希望臨時表是等式中最小的表,你想要對它進行表掃描(因為所有的行)是相關的)。 索引對你沒有幫助。

做一點點工作

一次只能處理幾行。
這可能會減慢執行速度,但它應該釋放資源。

- 一次一排
 SELECT @MatchId = min(MatchId) FROM MatchResult WHILE @MatchId IS NOT NULL BEGIN DELETE MatchEvent WHERE Match_Id = @MatchId SELECT @MatchId = min(MatchId) FROM MatchResult WHERE MatchId > @MatchId END 
- 一次幾行
 CREATE TABLE #tmp ( MatchId Varchar(50) ) /* get list of lowest 1000 MatchIds: */ INSERT #tmp SELECT TOP (1000) MatchId FROM MatchResult ORDER BY MatchId SELECT @MatchId = min(MatchId) FROM MatchResult WHILE @MatchId IS NOT NULL BEGIN DELETE MatchEvent FROM MatchEvent e , #tmp t WHERE e.MatchId = t.MatchId /* get highest MatchId we've procesed: */ SELECT @MinMatchId = MAX( MatchId ) FROM #tmp /* get next 1000 MatchIds: */ INSERT #tmp SELECT TOP (1000) MatchId FROM MatchResult WHERE MatchId > @MinMatchId ORDER BY MatchId END 

這個一次最多刪除1000行。
您一次刪除的行越多,您將使用的資源就越多,但運行的速度就越快(直到資源耗盡!)。 您可以嘗試找到比1000更優的值。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM