我们有以下简单的存储过程作为隔夜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
答案 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)
您可能希望以某种方式处理此分段。 (我假设您展示的查询要复杂得多吗?)在这种情况下,您需要尝试以下方法之一:
LIMIT 100
并处理这些点击。在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