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

时间:2008-10-23 00:11:31

标签: sql sql-server stored-procedures

我们有以下简单的存储过程作为隔夜SQL Server代理作业运行。通常它在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

7 个答案:

答案 0 :(得分:3)

这里可能发生了很多事情,并不是你所有的问题。

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

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

答案 1 :(得分:0)

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


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.

答案 2 :(得分:0)

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

  • 编写存储过程以迭代结果。 (处理时可能仍会锁定。)
  • 反复选择N个首次点击,例如LIMIT 100并处理这些点击。
  • 通过使用诸如WHERE M< = x AND x<< Ñ
  • 更频繁地运行“午夜工作”。说真的,每隔5分钟运行这样的东西就可以创造奇迹,尤其是如果工作非线性增加的话。 (如果没有,你仍然可以在一天中的几个小时内完成工作。)

在Postgres中,我使用条件索引取得了一些成功。如果满足某些条件,它们通过应用索引来工作。这意味着您可以在同一个表中保留许多“已解决”和少数未解析的行,但仍然可以获得仅针对未解析的行的特殊索引。因人而异。

应该指出,这是使用数据库获取有趣的的地方。您需要密切关注索引并对查询大量使用EXPLAIN

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

答案 3 :(得分:0)

首先,索引必须在这里看到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

答案 4 :(得分:0)

尽可能避免临时表

只是耗尽记忆 你可以试试这个:

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

答案 5 :(得分:0)

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

可以替换为

DELETE FROM MatchResult WHERE SOME_CONDITION

答案 6 :(得分:0)

你能在matchresult和matchevent之间转换级联删除吗?然后,您只需要担心识别要删除的一组数据,并让SQL处理另一组数据。

另一种方法是使用OUTPUT子句,但这绝对是小提琴。

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