Как сделать ежемесячное обновление больших таблиц БД, не прерывая доступ пользователей к ним - PullRequest
2 голосов
/ 24 июня 2009

У меня есть четыре таблицы БД в базе данных Oracle, которые нужно переписывать / обновлять каждую неделю или каждый месяц. Я пишу этот скрипт на PHP с использованием стандартных функций OCI, которые будут считывать новые данные из XML и обновлять эти четыре таблицы. Четыре таблицы имеют следующие свойства

ТАБЛИЦА A - до 2 миллионов строк, один первичный ключ (одна строка может занимать до 2K данных)

ТАБЛИЦА B - до 10 миллионов строк, один внешний ключ указывает на ТАБЛИЦУ A (одна строка может занимать до 1100 байт данных)

ТАБЛИЦА C - до 10 миллионов строк, один внешний ключ указывает на ТАБЛИЦУ A (одна строка может занимать до 1100 байт данных)

ТАБЛИЦА D - до 10 миллионов строк, один внешний ключ указывает на ТАБЛИЦУ A (одна строка может занимать до 120 байт данных)

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

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

Я хотел создать реплики временных таблиц для всех таблиц и заполнить их. Затем я мог бы УДАЛИТЬ основные таблицы и переименовать временные таблицы. Однако вы не можете выполнять операторы таблиц DROP и ALTER внутри транзакции, поскольку они всегда выполняют автоматическую фиксацию. Это должно быть в состоянии сделать быстро (четыре оператора DROP и четыре оператора ALTER TABLE), но это не может гарантировать, что пользователь не получит ошибку в течение этого короткого периода времени.

Теперь, объединяя эти две идеи, я рассматриваю создание временных таблиц, затем выполнение DELETE FROM для всех четырех исходных таблиц, а затем и INSERT INTO из временных таблиц для повторного заполнения основных таблиц. Поскольку здесь нет операторов DDL, все это будет работать в рамках транзакции. Однако затем я задаюсь вопросом, не вызовет ли меня проблема с памятью, необходимой для обработки около 60 миллионов записей в транзакции (это также касается первой идеи).

Я бы подумал, что это будет общий сценарий. Есть ли стандартный или рекомендуемый способ сделать это? Любые советы будут оценены. Спасибо.

Ответы [ 9 ]

2 голосов
/ 24 июня 2009

Я предполагаю, что ваши пользователи на самом деле не изменяют данные в этих таблицах, поскольку они удаляются из другого источника каждую неделю, поэтому не имеет значения, блокируете ли вы таблицы в течение целого часа. Пользователи по-прежнему могут запрашивать данные, вам просто нужно правильно подобрать размер сегмента отката. Поэтому простой DELETE + INSERT должен работать нормально.

Теперь, если вы хотите ускорить процесс И , если новые данные мало отличаются от предыдущих данных, вы можете загрузить новые данные во временные таблицы и обновить таблицы с помощью delta * 1006. * с комбинацией MERGE+DELETE вот так:

Установка:

CREATE TABLE a (ID NUMBER PRIMARY KEY, a_data CHAR(200));
CREATE GLOBAL TEMPORARY TABLE temp_a (
   ID NUMBER PRIMARY KEY, a_data CHAR(200)
) ON COMMIT PRESERVE ROWS;
-- Load A
INSERT INTO a 
   (SELECT ROWNUM, to_char(ROWNUM) FROM dual CONNECT BY LEVEL <= 10000);
-- Load TEMP_A with extra rows
INSERT INTO temp_a 
   (SELECT ROWNUM + 100, to_char(ROWNUM + 100) 
      FROM dual 
   CONNECT BY LEVEL <= 10000);
UPDATE temp_a SET a_data = 'x' WHERE mod(ID, 1000) = 0;

Этот оператор MERGE вставит новые строки и обновит старые строки, только если они отличаются:

SQL> MERGE INTO a
  2  USING (SELECT temp_a.id, temp_a.a_data
  3           FROM temp_a
  4           LEFT JOIN a ON (temp_a.id = a.id)
  5          WHERE decode(a.a_data, temp_a.a_data, 1) IS NULL) temp_a
  6  ON (a.id = temp_a.id)
  7  WHEN MATCHED THEN
  8     UPDATE SET a.a_data = temp_a.a_data
  9  WHEN NOT MATCHED THEN
 10     INSERT (id, a_data) VALUES (temp_a.id, temp_a.a_data);

Done

Затем вам нужно будет удалить строки, которых нет в новом наборе данных:

SQL> DELETE FROM a WHERE a.id NOT IN (SELECT temp_a.id FROM temp_a);

100 rows deleted

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

2 голосов
/ 24 июня 2009

Вы можете иметь синоним для каждой из ваших больших таблиц. Создайте новые воплощения ваших таблиц, заполните их, удалите и заново создайте синонимы и, наконец, удалите старые таблицы. Преимуществом этого является (1) только один фактический набор DML (вставок), позволяющий избежать генерации повторов для ваших удалений, и (2) удаление / воссоздание синонима происходит очень быстро, сводя к минимуму вероятность «плохого взаимодействия с пользователем». *

Напоминает мне небольшую мозоль о синонимах Oracle: почему нет команды ALTER SYNONYM?

1 голос
/ 25 июня 2009

Являюсь ли я единственным (кроме Винсента), который сначала проверит простейшее из возможных решений , т. Е. DELETE / INSERT, прежде чем пытаться создать что-то более продвинутое?

Тогда, однако, мне интересно, если память, необходимая для обработки около 60 миллионов записей в транзакции, доставит мне неприятности (это будет проблемой и для первой идеи).

Oracle управляет памятью довольно хорошо, она не была написана группой новичков в Java (ой, она только что вышла из моих уст!). Таким образом, реальный вопрос заключается в том, нужно ли вам беспокоиться о снижении производительности при переборе файлов журналов REDO и UNDO ... Другими словами, создайте тестовый тест производительности и запустите его на своем сервере и посмотрите, сколько времени это займет. Во время УДАЛЕНИЯ / ВСТАВКИ система будет работать не так быстро, как обычно, но другие сеансы по-прежнему могут выполнять ВЫБОРЫ, не опасаясь взаимоблокировок, утечек памяти или сбоев системы. Подсказка: серверы БД обычно привязаны к диску, поэтому получение правильного RAID-массива обычно очень хорошая инвестиция.

С другой стороны, если производительность критична, вы можете выбрать один из альтернативных подходов, описанных в этой теме:

  • разметка, если у вас есть лицензия
  • переименование таблицы, если вы этого не сделаете, но помните, что DDL на лету могут вызвать некоторые побочные эффекты, такие как недействительность объекта, ORA-06508 ...
1 голос
/ 24 июня 2009

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

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

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

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

Ваш PHP-скрипт будет использовать два соединения с БД - одно, где вы делаете блокировку, другое, где вы делаете загрузку, переименование и удаление. Таким образом, неявные фиксации в рабочем соединении не завершат блокировку в другой таблице.

Итак, в сценарии вы бы сделали что-то вроде:

Соединение 1: Создавайте временные таблицы, загружайте их, создавайте новые индексы

Соединение 2:

LOCK TABLE Load_Locker IN SHARE ROW EXCLUSIVE MODE;

Соединение 1: Выполнить переименование своп старых и новых таблиц

Соединение 2: Откат;

Соединение 1: Удалите старые таблицы.

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

LOCK TABLE Load_Locker IN SHARE MODE;

Таким образом, у вас может быть столько клиентов, которые блокируют таблицу таким образом - ваш процесс выше будет блокировать их, пока все они не снимают блокировку, после чего последующие клиенты будут блокировать, пока вы не выполните свои операции. Поскольку единственное, что вы делаете в контексте блокировки SHARE ROW EXCLUSIVE, - это переименование таблиц, ваши клиенты будут блокироваться только на мгновение. Кроме того, установка этого уровня детализации позволяет вам контролировать, как долго клиенты будут иметь согласованное чтение для старой таблицы; без этого, если бы у вас был клиент, который выполнял серию чтений, которые занимали некоторое время, вы могли бы в конечном итоге изменить таблицы в середине потока и получить странные результаты, если ранние запросы извлекали старые данные, а последующие запросы извлекали новые данные. Использование SET TRANSACTION SET ISOLATION LEVEL READ ONLY было бы еще одним способом решения этой проблемы, если бы вы не использовали мой подход.

Единственный реальный недостаток этого подхода заключается в том, что, если транзакции чтения вашего клиента занимают некоторое время, вы рискуете заблокировать других клиентов дольше, чем мгновение, поскольку любые блокировки в режиме SHARE, возникающие после того, как процесс загрузки выдает его Блокировка SHARE ROW EXCLUSIVE блокируется до тех пор, пока процесс загрузки не завершит свою задачу. Например:

10:00 user 1 issues SHARE lock
10:01 user 2 issues SHARE lock
10:03 load process issues SHARE ROW EXCLUSIVE lock (and is blocked)
10:04 user 3 issues SHARE lock (and is blocked by load's lock)
10:10 user 1 releases SHARE
10:11 user 2 releases SHARE (and unblocks loader)
10:11 loader renames tables & releases SHARE ROW EXCLUSIVE (and releases user 3)
10:11 user 3 commences queries, after being blocked for 7 minutes

Тем не менее, это действительно довольно глупо. Решение разделить Кинлан, скорее всего, путь. Добавьте дополнительный столбец к исходным таблицам, который содержит номер версии, разделите данные на основе этой версии, а затем создайте представления, похожие на ваши текущие таблицы, в которых отображаются только данные, отображающие текущую версию (определяется значением строки в Таблица "CurrentVersion"). Затем просто загрузите таблицу, обновите таблицу CurrentVersion и удалите раздел для старых данных.

1 голос
/ 24 июня 2009

В Oracle вы можете разбивать ваши таблицы и индексы на основе столбца «Дата» или «Время». Таким образом, чтобы удалить большое количество данных, вы можете просто удалить раздел вместо выполнения команды удаления.

Мы привыкли использовать это для управления ежемесячными архивами более 100 миллионов записей и без простоя.

http://www.oracle.com/technology/oramag/oracle/06-sep/o56partition.html - это очень удобная страница для изучения разбиения на разделы.

0 голосов
/ 14 июля 2009

Я собираюсь использовать метод upsert.

Я добавил дополнительный столбец «удалить» в каждую из таблиц.

Когда я начинаю обработку канала, я устанавливаю поле удаления для каждой записи на «1».

Затем я серьезно проверяю обновления, если запись существует, или вставляет, если ее нет. Для каждой из этих вставок / обновлений поле удаления затем устанавливается на ноль.

В конце процесса я удаляю все записи, которые все еще имеют значение удаления '1'.

Спасибо всем за ваши ответы. Я нашел это очень интересным / образовательным.

0 голосов
/ 24 июня 2009

Оценили ли вы размер дельты (изменений).

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

Рассматривай это как ETL.

0 голосов
/ 24 июня 2009

В некоторых случаях мы делаем две версии таблиц, скажем SalesTargets1 и SalesTargets2 (активную и неактивную.) Обрезать записи из неактивной и заполнить ее. Поскольку никто, кроме вас, не использует неактивный, не должно быть проблем с блокировкой или влияния на пользователей во время его заполнения. Затем имейте представление, что сортирует всю информацию из активной таблицы (она должна называться текущей таблицей, скажем, SalesTargets в моем примере). Затем, чтобы переключиться на обновленные данные, все, что вам нужно сделать, это запустить оператор alter view.

0 голосов
/ 24 июня 2009

Почему бы не добавить столбец версии? Таким образом, вы можете добавить новые строки с другим номером версии. Создайте представление для таблицы, которая указывает текущую версию. После добавления новых строк перекомпилируйте представление с новым номером версии. Когда это будет сделано, вернитесь и удалите старые строки.

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