Проблема с памятью временной таблицы хранимой процедуры SQL

У нас есть следующая простая хранимая процедура, которая запускается как ночное задание агента SQL-сервера. Обычно он выполняется за 20 минут, но в последнее время таблицы MatchEvent и MatchResult выросли до более чем 9 миллионов строк каждая. Это привело к тому, что выполнение процедуры сохранения заняло более 2 часов, при этом были израсходованы все 8 ГБ памяти в нашем блоке SQL. Это делает базу данных недоступной для обычных запросов, которые пытаются получить к ней доступ.

Я предполагаю, что проблема в том, что временная таблица слишком велика и вызывает проблемы с недоступностью памяти и базы данных.

Как я могу переписать хранимую процедуру, чтобы сделать ее более эффективной и менее требовательной к памяти?

Примечание: я отредактировал SQL, чтобы указать, что наступило условие, влияющее на начальный оператор SELECT. Раньше я оставил это для простоты. Кроме того, при выполнении запроса загрузка ЦП составляет 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

ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
3
0
12 062
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Глядя на приведенный выше код, зачем вам временная таблица?


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.

Извините, я забыл указать, что мы удаляем только подмножество MatchIds, а не все из них

Robin Weston 23.10.2008 17:00

Вы, вероятно, захотите каким-то образом обработать это кусочно. (Я предполагаю, что запросы намного сложнее, чем вы показали?) В этом случае вам нужно попробовать одно из следующих:

  • Напишите свою хранимую процедуру для перебора результатов. (Может все еще блокироваться во время обработки.)
  • Несколько раз выберите N первых совпадений, например LIMIT 100, и обработайте их.
  • Разделите работу, сканируя области таблицы отдельно, используя что-то вроде WHERE M <= x AND x <N.
  • Чаще выполняйте «полуночную работу». Серьезно, такие занятия каждые 5 минут могут творить чудеса, особенно если объем работы увеличивается нелинейно. (Если нет, вы все равно можете просто распределить работу по часам дня.)

В Postgres я добился определенных успехов в использовании условных индексов. Они творит чудеса, применяя индекс при соблюдении определенных условий. Это означает, что вы можете сохранить многие «разрешенные» и несколько неразрешенных строк в одной и той же таблице, но при этом получить этот специальный индекс только по неразрешенным. Ymmv.

Следует отметить, что именно здесь при использовании баз данных получается интересно. Вам нужно внимательно следить за своими индексами и часто использовать EXPLAIN в своих запросах.

(Да, и помните, интересно - это хорошо для ваших хобби, но не для работы.)

Это может быть полезно, если другие параметры не решают проблемы с блокировкой. Если ваша эксклюзивная блокировка из оператора DELETE по-прежнему выполняется слишком долго, попробуйте выполнить ее большими частями (то есть, если она соответствует бизнес-требованиям, которые не указаны в вашем вопросе). Мне приходилось делать это в магазинах электронной торговли, где проводилось ночное обслуживание, не нарушая взаимодействия с пользователем. Типа приоритезации блокировок по стратегии запроса.

tommy_o 17.07.2013 00:36
Ответ принят как подходящий

Вероятно, здесь происходит много всего, и это еще не все, что вам нужно.

Во-первых, я согласен с другими плакатами. Попытайтесь переписать это без временной таблицы, если это вообще возможно.

Но если предположить, что здесь вам нужна временная таблица, у вас есть БОЛЬШАЯ проблема в том, что для нее не определен 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-сервер вполне может динамически расширять файл базы данных на вас, заставляя ваш запрос отнимать процессорное время и дисковое время. Кроме того, убедитесь, что ваш журнал транзакций имеет правильный размер и не увеличивается автоматически. Удачи.

Было бы немного быстрее добавить индекс к временной таблице ПОСЛЕ того, как она была заполнена.

cjk 14.05.2009 21:20

Кроме того, попробуйте внутреннее присоединить временную таблицу к реальной таблице через PK для удаления вместо использования WHERE [..] IN ([..]), чтобы у вас был DELETE me FROM MatchEvent me JOIN #tempMatchResult tmpmr ON tmpmr.MatchId = me.MatchId.

tommy_o 17.07.2013 00:32

Во-первых, здесь ОБЯЗАТЕЛЬНЫ индексы, см. Ответ Дэйва М.

Другой подход, который я когда-нибудь буду использовать при удалении очень больших наборов данных, - это создание теневой таблицы со всеми данными, воссоздание индексов и последующее использование процедуры 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 

Если вы не можете избежать временного стола

Я собираюсь высунуть здесь шею и сказать: вам не нужен индекс для вашей временной таблицы, потому что вы хотите, чтобы временная таблица была наименьшей таблицей в уравнении, и вы хотите сканировать ее (потому что все строки релевантны). Индекс здесь не поможет.

Делайте небольшие кусочки работы

Работайте над несколькими рядами за раз. Это, вероятно, замедлит выполнение, но должно высвободить ресурсы.

- One row at a time
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
- A few rows at a time
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.

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

можно заменить на

DELETE FROM MatchResult WHERE SOME_CONDITION

Но часть SOME_CONDITION должна повторяться дважды. Если это большой запрос, он будет менее производительным.

tommy_o 17.07.2013 00:34

Можете ли вы просто включить каскадное удаление между matchresult и matchevent? Тогда вам нужно будет только позаботиться об идентификации одного набора данных для удаления, а SQL позаботится о другом.

Альтернативой было бы использование предложения OUTPUT, но это определенно более сложная задача.

Оба они позволят вам удалить из обеих таблиц, но вам нужно будет только один раз указать (и выполнить) свой предикат фильтра. Это может быть по-прежнему не таким эффективным, как пакетный подход, как предлагается другими плакатами, но стоит рассмотреть. YMMV

Другие вопросы по теме