Предложения по оптимизации, необходимые для статуса SQL UPDATE. Используются две таблицы записей ~5 миллионов

Я ищу любые предложения по оптимизации следующего оператора 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;   

person whitespy9    schedule 28.01.2010    source источник
comment
В обеих таблицах есть простые индексы для PK_ID, 2ND_ID и 3RD_ID, а также составной индекс для обеих, содержащий все три.   -  person whitespy9    schedule 28.01.2010
comment
дайте мне знать, когда вы найдете рабочее решение   -  person Pentium10    schedule 28.01.2010


Ответы (6)


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

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

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

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

person Rog    schedule 28.01.2010
comment
Спасибо. Я, наконец, укусил пулю и разбил этот waaaaaaaaaay на как можно меньшие наборы данных. Слейте, а затем, наконец, обновите. Теперь все идет гладко. К сожалению, мне приходится выполнять эти типы дополнительных шагов данных, которые мне не нравятся при использовании SAS. Мой код теперь занимает больше экрана, чтобы выполнить простой оператор обновления. Если бы мне пришлось делать все заново, я бы просто использовал составные хэши во всей программе. - person whitespy9; 29.01.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 уже гарантирует, что эти два столбца не равны нулю.

Однако на двух таблицах с 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 миллионов строк), поможет.

person ErikE    schedule 28.01.2010
comment
+1 за пакетный подход (разделение). Обновление очень больших наборов может создать огромные накладные расходы на транзакции/блокировки и/или операции ввода-вывода. - person Arvo; 28.01.2010
comment
+1 также за разделение. Одна транзакция из 5 миллионов строк — это кошмар для механизма базы данных. Скорее всего, буферы памяти заполнены, и изменения сбрасываются на диск до того, как будет известно, будет ли транзакция зафиксирована или отменена. - person Lluis Martinez; 29.01.2010
comment
По какой-то странной причине SAS Proc SQL не разрешает соединения с обновлениями. Такой огромный недостаток. - person Ville Koskinen; 29.01.2010
comment
@Ville Koskinen: Вы уверены, что это не работает с немного другим синтаксисом, чем я здесь дал? - person ErikE; 01.02.2010
comment
@Emtucifor, к сожалению, см., например. bit.ly/bnYXgU. Однако, если данные находятся на rdbms, вы, конечно, можете передать sql в базу данных (в этом случае ваши предложения очень актуальны). А если данные родные SAS, есть хорошие не-sql методы для обновления данных. - person Ville Koskinen; 01.02.2010
comment
@Ville Koskinen: Спасибо за разъяснение. - person ErikE; 02.02.2010

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

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

Что-то типа:

insert into NEW_TABLE (field1,field2,NEW_2ND_ID)  
select field1, field2, (SELECT 2ND_ID FROM OLD_TABLE....) from NEW_TABLE
  • Пересоздайте индексы после завершения запроса.
  • Повторное создание триггеров после завершения запроса.

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

person Pentium10    schedule 28.01.2010
comment
Я вижу, вы публикуете комментарии к другим предложенным ответам, а к моему пока нет. Вы пробовали предложенное мной решение? - person Pentium10; 28.01.2010
comment
Я уже использовал часть вашего решения. Я создал таблицу подмножества из содержимого, необходимого из старой таблицы. Я постараюсь создать вставку, если не смогу найти решение для оптимизации обновления. - person whitespy9; 28.01.2010
comment
Учтите, что update работает медленно из-за использования блокировок и операций ввода-вывода по сравнению с select all, insert all. Иногда лучше сводить данные в одну отдельную таблицу. - person Pentium10; 28.01.2010

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

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

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

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

person Nitin Midha    schedule 28.01.2010
comment
Не каждая СУБД обрабатывает коррелированные подзапросы как отдельные запросы. SQL Server 2005 и более поздние версии довольно хорошо преобразовывают большинство коррелированных подзапросов в обычные соединения. - person ErikE; 28.01.2010

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

person Lluis Martinez    schedule 28.01.2010

person    schedule
comment
Будет работать, только если pk_id покрыт индексом PRIMARY KEY или UNIQUE в old_table. - person Quassnoi; 28.01.2010
comment
@Quassnoi: Если это проблема, то первоначальный запрос плаката тоже был неверным. - person ErikE; 28.01.2010
comment
@Emtucifor: исходный запрос работал бы, если бы значения pk_id были уникальными, даже если столбец не был объявлен как UNIQUE. Этот запрос требует, чтобы old_table.pk_id был объявлен как UNIQUE, иначе он завершится с печально известным ORA-01779. - person Quassnoi; 29.01.2010
comment
@Quassnoi: Спасибо за объяснение! Наверное, я был в неведении. Я редко использую Oracle, но столкнулся именно с этой проблемой, и после долгих исследований я так и не нашел хорошего способа сделать это (я думаю, что в конечном итоге использовал один из массивоподобных типов данных в Oracle). Что бы это ни стоило, в SQL Server вы можете без проблем выполнить неуникальное обновление, но исходный запрос отправителя завершится ошибкой, если подзапрос когда-либо вернет более одной внутренней строки для любой внешней строки. - person ErikE; 29.01.2010
comment
@Emtucifor: цель сохранения ключа - убедиться, что каждая строка в целевой таблице будет возвращена не более одного раза. - person Quassnoi; 29.01.2010
comment
@Quassnoi, да, я это понимаю, но иногда либо не имеет значения, какая строка используется для обновления, либо обновление УНИКАЛЬНО, но его нельзя легко доказать серверу. - person ErikE; 29.01.2010