SQL存储过程临时表内存问题

Posted

技术标签:

【中文标题】SQL存储过程临时表内存问题【英文标题】:SQL stored procedure temporary table memory problem 【发布时间】:2008-10-23 00:11:31 【问题描述】:

我们有以下简单的存储过程,它作为夜间 SQL 服务器代理作业运行。通常它会在 20 分钟内运行,但最近 MatchEvent 和 MatchResult 表已经增长到每个超过 900 万行。这导致存储过程需要 2 个多小时才能运行,我们 SQL 盒上的所有 8GB 内存都用完了。这会使数据库对尝试访问它的常规查询不可用。

我认为问题是临时表太大并导致内存和数据库不可用问题。

如何重写存储过程以提高效率并减少内存占用?

注意:我已经编辑了 SQL 以表明存在影响初始 SELECT 语句的条件。为了简单起见,我以前忽略了这一点。此外,当查询运行时,CPU 使用率为 1-2%,但如前所述,内存已用完


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

【问题讨论】:

【参考方案1】:

这里可能发生了很多事情,而不仅仅是您的查询。

首先,我同意其他海报。如果可能的话,尝试在没有临时表的情况下重写它。

但是假设您在这里需要一个临时表,那么您有一个大问题,因为您没有在其上定义 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 和磁盘时间。此外,请确保您的事务日志大小正确,并且它不会自动增长。祝你好运。

【讨论】:

在临时表被填充之后添加索引会稍微快一些。 另外,尝试通过 PK 将临时表内部连接到真实表以进行删除,而不是使用WHERE [..] IN ([..]),这样你就有了DELETE me FROM MatchEvent me JOIN #tempMatchResult tmpmr ON tmpmr.MatchId = me.MatchId【参考方案2】:

看上面的代码,为什么需要临时表?

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.

【讨论】:

抱歉,我忘了说明我们只是删除了一部分 MatchId,而不是全部【参考方案3】:

您可能希望以某种方式分段处理。 (我假设查询比您展示的要复杂得多?)在这种情况下,您可以尝试以下其中一种:

编写存储过程以迭代结果。 (在处理过程中可能仍会锁定。) 重复选择 N 个第一个命中,例如 LIMIT 100 并处理它们。 通过单独扫描表格区域来划分工作,使用类似 WHERE M 更频繁地运行“午夜作业”。说真的,每 5 分钟运行一次这样的东西反而会产生奇迹,尤其是在工作非线性增加的情况下。 (如果没有,您仍然可以将工作分散在一天中的几个小时内。)

在 Postgres 中,我使用条件索引取得了一些成功。如果满足某些条件,它们会通过应用索引来发挥作用。这意味着您可以将许多“已解决”的行和少数未解决的行保留在同一个表中,但仍然可以获得该特殊索引而不是未解决的那些。嗯嗯。

应该指出,这是使用数据库变得有趣的地方。您需要密切关注您的索引,并在查询中经常使用EXPLAIN

(哦,请记住,有趣在你的爱好中是一件好事,但在工作中却不是。)

【讨论】:

如果其他选项不能解决锁定问题,这会很有帮助。如果您的 DELETE 语句中的排他锁仍然运行时间过长,请尝试在相当大的块中执行(即,如果它满足您的问题中未说明的业务要求)。我不得不在电子商务商店执行此操作,在那里进行夜间维护,而不会破坏面向前的用户体验。按查询策略对锁进行排序。【参考方案4】:

首先,索引是必须的,请参阅 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

【讨论】:

【参考方案5】:

尽可能避免使用临时表

它只是用完内存。 你可以试试这个:

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 更优的值。

【讨论】:

【参考方案6】:
DELETE FROM MatchResult WHERE
MatchId In (SELECT MatchId FROM #tempMatchResult)

可以替换为

DELETE FROM MatchResult WHERE SOME_CONDITION

【讨论】:

但这必须执行两次SOME_CONDITION 部分。如果它是一个大查询,这将降低性能。【参考方案7】:

你能在 matchresult 和 matchevent 之间打开级联删除吗?那么您只需要担心识别出要删除的一组数据,而让 SQL 处理另一组。

替代方法是使用 OUTPUT 子句,但这肯定更麻烦。

这两种方法都可以让您从两个表中删除,但只需声明(并执行)一次过滤谓词。这可能仍然不如其他海报所建议的批处理方法那样高效,但值得考虑。 YMMV

【讨论】:

以上是关于SQL存储过程临时表内存问题的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server 内存优化表 - 与临时表相比性能较差

Sql server2014 内存优化表 本地编译存储过程

sql server 临时表占用硬盘吗?

sqlserver 2008 关于存储过程中的临时表。

SQL存储过程多个结果到临时表中

Sql Server内存优化表-与临时表相比性能较差