Проблема памяти временной таблицы хранимой процедуры SQL - PullRequest
3 голосов
/ 23 октября 2008

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

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

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

Примечание: я отредактировал SQL, чтобы указать, что наступило условие, влияющее на начальный оператор SELECT. Ранее я упустил это для простоты. Кроме того, при выполнении запроса загрузка ЦП составляет 1-2%, но максимальная запоминание, как было указано ранее,

<code>
CREATE TABLE #tempMatchResult
(
    matchId VARCHAR(50)
)</p>

<p>INSERT INTO #tempMatchResult
SELECT MatchId FROM MatchResult WHERE SOME_CONDITION</p>

<p>DELETE FROM MatchEvent WHERE<br>
MatchId IN (SELECT MatchId FROM #tempMatchResult)</p>

<p>DELETE FROM MatchResult WHERE
MatchId In (SELECT MatchId FROM #tempMatchResult)</p>

<p>DROP TABLE #tempMatchResult

Ответы [ 7 ]

3 голосов
/ 23 октября 2008

Здесь, наверное, много всего происходит, и это не весь ваш запрос.

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

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

0 голосов
/ 11 февраля 2014

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

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

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

0 голосов
/ 21 ноября 2012
DELETE FROM MatchResult WHERE
MatchId In (SELECT MatchId FROM #tempMatchResult)

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

DELETE FROM MatchResult WHERE SOME_CONDITION
0 голосов
/ 23 октября 2008

Избегайте временной таблицы, если это возможно

Используется только память.
Вы можете попробовать это:

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.

0 голосов
/ 23 октября 2008

Во-первых, индексы ДОЛЖНЫ быть здесь, смотрите ответ Дэйва М.

Другой подход, который я когда-нибудь буду использовать при удалении очень больших наборов данных, - это создание теневой таблицы со всеми данными, воссоздание индексов и последующее использование sp_rename для его включения. Здесь нужно быть осторожным с транзакциями, но в зависимости от объем удаляемых данных может быть быстрее.

Примечание Если есть давление на базу данных tempdb, рассмотрите возможность использования объединений и не копирование всех данных в таблицу temp.

Например,

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
0 голосов
/ 23 октября 2008

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

  • Напишите вашу хранимую процедуру для перебора результатов. (Может все еще блокироваться во время обработки.)
  • Повторно выберите N первых попаданий, например, LIMIT 100 и обработайте их.
  • Разделите работу, отсканировав области таблицы отдельно, используя что-то вроде ГДЕ М <= x И x <N. </li>
  • Чаще запускайте "полуночную работу". Серьезно, запуск таких вещей каждые 5 минут может творить чудеса, особенно если работа увеличивается нелинейно. (Если нет, вы все равно можете просто распределить работу по часам.)

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

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

(О, и помните, интересно - это хорошо в ваших увлечениях, но не на работе.)

0 голосов
/ 23 октября 2008

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


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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...