Оптимизация предложений, необходимых для проверки SQL UPDATE. Две ~ 5 миллионов используемых таблиц записей - PullRequest
4 голосов
/ 28 января 2010

Я ищу любые предложения по оптимизации следующего оператора PROC SQL из программы SAS. Две задействованные таблицы содержат около 5 миллионов записей каждая, а время выполнения составляет около 46 часов.

Оператор ищет обновление "новой" версии "старой" таблицы. Отметив столбец, если «старая» таблица для «PK_ID» была указана без значения «3RD_ID» и «CODE», но в «новой» таблице для «PK_ID» она теперь указана с значение для "3RD_ID" и "CODE".

Спасибо за любые предложения ... (Код действительно отформатирован ниже! По некоторым причинам мои пробелы не отображаются для отступов ...)

PROC SQL _METHOD;  
 UPDATE NEW_TABLE AS N  
   SET NEW_2ND_ID=(SELECT 2ND_ID FROM OLD_TABLE AS O  
                WHERE N.PK_ID=0.PK_ID  
                  AND N.2ND_ID<>O.2ND_ID  
                  AND O.3RD_ID IS NULL  
                  AND O.CODE IS NULL  
                  AND N.3RD_ID IS NOT NULL  
                  AND N.CODE IS NOT NULL  
                  AND N.2ND_ID IS NOT NULL)  
        WHERE N.3RD_ID IS NOT NULL  
          AND N.PK_ID IS NOT NULL  
          AND N.CODE IS NOT NULL  
          AND N.2ND_ID IS NOT NULL;  
QUIT;   

Ответы [ 6 ]

3 голосов
/ 29 января 2010

Все ответы до сих пор четко ориентированы в SQL-части вашего вопроса, но в некоторой степени игнорируют SAS-часть. Я настоятельно рекомендую попробовать шаг данных обновить / изменить / объединить вместо proc sql для этого вида обновления. Должна быть возможность сортировать обе таблицы и применять аналогичную логику из вашего SQL, чтобы гарантировать, что правильные строки / столбцы обновляются.

Я видел подобные виды обновлений, которые запускались за считанные минуты на 20 или более миллионах строк.

Кроме того, проверьте http://runsubmit.com, сайт в стиле SAS для стекового потока, для получения дополнительных ответов SAS.

Раскрытие информации: я сотрудник SAS. Я не имею ничего общего с runubmit, который запускается независимо.

3 голосов
/ 28 января 2010

Я не знаком с вариантом SQL, который вы используете. Однако, независимо от того, получаете ли вы лучшую производительность или нет, вы должны использовать синтаксис соединения ANSI. Вот как это будет выглядеть в T-SQL, измените его для своей системы:

UPDATE N
SET N.2ND_ID = O.2ND_ID
FROM
   NEW_TABLE AS N
   INNER JOIN OLD_TABLE AS O ON N.PK_ID = O.PK_ID
WHERE
   N.2ND_ID <> O.2ND_ID
   AND N.3RD_ID IS NOT NULL  
   AND O.3RD_ID IS NULL  
   AND N.CODE IS NOT NULL  
   AND O.CODE IS NULL

Обратите внимание, что лишние условия, которые я удалил, не нужны, например, N.2ND_ID <> O.2ND_ID уже гарантирует, что эти два столбца не равны NULL.

Однако на двух 5-миллионных таблицах строк вы получите ужасную производительность. Вот несколько идей, чтобы ускорить его. Могу поспорить, что вы можете сократить это до часа с правильной комбинацией стратегий.

  1. Разделить обновление на партии (мелкие кусочки, проходя по всему набору). Хотя это звучит вопреки обычному совету базы данных «не зацикливайся, используй наборы», на самом деле это не так: вы просто используете меньшие наборы, а не зацикливаетесь на уровне строк. Лучший способ пакетного обновления, подобного этому, - «пройтись по кластерному индексу». Я не уверен, имеет ли этот термин смысл в СУБД, которую вы используете, но, по сути, это означает, что вы выбираете фрагменты, которые вы обновляете в каждом цикле, на основе порядка, в котором они будут найдены в обновляемом объекте таблицы. PK_ID звучит так, как будто он является кандидатом для использования, но если необработанные данные таблицы не упорядочены в этом столбце, это станет более сложным. В T-SQL пакетный цикл может выглядеть так:

    DECLARE
       @ID int,
       @Count int
    
    SET @ID = 1
    SET @Count = 1
    
    WHILE @Count > 0 BEGIN
       UPDATE N
       SET N.2ND_ID = O.2ND_ID
       FROM
          NEW_TABLE AS N
          INNER JOIN OLD_TABLE AS O ON N.PK_ID = O.PK_ID
       WHERE
          N.2ND_ID <> O.2ND_ID
          AND N.3RD_ID IS NOT NULL  
          AND O.3RD_ID IS NULL  
          AND N.CODE IS NOT NULL  
          AND O.CODE IS NULL
          AND N.PK_ID BETWEEN @ID AND @ID + 4999
       SET @Count = @@RowCount
       SET @ID = @ID + 5000
    END
    

    В этом примере предполагается, что ваш столбец PK_ID плотно упакован, что каждое обновление будет по-настоящему достигать 5000 строк. Если это не так, переключитесь на метод с использованием TOP 5000 и либо выведите обновленные PK_ID в таблицу, либо найдите @StartID и @EndID для следующего обновления за один шаг, а затем выполните его.

    По моему опыту, хорошие размеры партий составляют от 1000 до 20000 рядов. На сервере MS-SQL предпочтительным местом, как представляется, является чуть меньше числа, которое вынуждает переключиться с поиска на сканирование (потому что в конечном итоге механизм обработки БД предполагает, что одно сканирование дешевле, чем множество поисков, хотя это часто бывает неправильно при работе с таблицами из 5 миллионов строк).

  2. Выберите идентификаторы и данные для обновления в рабочую / временную таблицу, а затем присоединитесь к ней. Идея состоит в том, чтобы выполнить простое сканирование с помощью простого оператора INSERT, затем добавить индексы во временную таблицу и выполнить обновление, не прибегая к сложному предложению WHERE. Как только таблица содержит только строки, которые нужно обновить, и необходимые столбцы, предложение WHERE не только может потерять большинство своих условий, но временная таблица имеет намного меньше строк и намного больше строк на странице (поскольку в ней нет лишних столбцов), что значительно улучшит производительность. Это может быть сделано даже на этапах, когда создается «тень» новой таблицы, затем «тень» старой таблицы, затем соединение между ними и, наконец, соединение к новой таблице для ее обновления. Хотя это звучит как большая работа, я думаю, вы будете удивлены совершенно безумной скоростью завершения, которую это может предложить.

  3. Все, что вы можете сделать, чтобы преобразовать чтение из старой таблицы в поиск вместо поиска, поможет. Поможет все, что вы можете сделать, чтобы уменьшить объем диска, используемого для хранения временных данных (например, гигантские хеш-таблицы для 5 миллионов строк).

1 голос
/ 28 января 2010

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

  • Удалить индексы перед запуском запроса.
  • Отбрасывать триггеры перед выполнением запроса.
  • Воссоздание индексов после завершения запроса.
  • Воссоздание триггеров после завершения запроса.

(В конце концов вы замените этой новой таблицей вашу существующую таблицу)

0 голосов
/ 29 января 2010

Вы также можете попробовать поместить новую таблицу в другое устройство, чем старое, чтобы воспользоваться ее параллелизмом. Если вы можете убедить DBA, конечно.

0 голосов
/ 28 января 2010
UPDATE (
    SELECT O.2ND_ID, N.2ND_ID 
    FROM OLD_TABLE AS O  
    INNER JOIN NEW_TABLE AS N on O.PK_ID=N.PK_ID
    WHERE N.2ND_ID <> O.2ND_ID  
    AND O.3RD_ID IS NULL  
    AND O.CODE IS NULL  
    AND N.3RD_ID IS NOT NULL  
    AND N.CODE IS NOT NULL  
    AND N.2ND_ID IS NOT NULL
) t 
set N.2ND_ID = O.2ND_ID
0 голосов
/ 28 января 2010

Я думаю, что сейчас это вложенный подзапрос, поэтому оператор Select будет запущен для количества записей, которое соответствует условию where.

Однако я бы порекомендовал вам перейти на SQL - обновление с помощью соединения. Объяснение можно найти здесь: http://bytes.com/topic/oracle/answers/65819-sql-update-join-syntax.

Если у вас есть обновление с JOIN, примените соответствующие индексы.

Кроме того, не следует использовать те индексы, которые включают 2nd_id, их следует отключить и затем перестроить после обновления, поскольку это может быть массовое обновление данных.

...