Наиболее серьезная проблема с подходом “фиксации перед завершением логической транзакции” заключается в том факте, что он часто оставляет вашу базу данных в неопределенном состоянии, если оператор UPDATE прерывается на полпути. Если только вы не позаботитесь об этом заранее, будет очень трудно перезапустить неудавшийся процесс, и позволить ему найти место, в котором он был прерван. Например, предположим, что мы не применяем функцию LOWER() к столбцу, как в предыдущем примере, а некоторую другую функцию столбца, как показано ниже:
last_ddl_time = last_ddl_time + 1;
Если прервать цикл UPDATE на полпути, как мы сможем перезапустить его? Мы даже не сможем повторно запустить его сначала, потому что в этом случае некоторые данные будут увеличены на 2, а другие — на 1. Если мы опять потерпим неудачу, то при следующем запуске, некоторые увеличатся на 3, другие — на 2, а остальные — на 1. Значит, нужна более сложная логика — какой-то способ “разбиения” данных. Например, можно обработать сначала каждое значение OBJECT_NAME, начинающееся с A, затем — с B и так далее:
ops$tkyte@ORA10G> create table to_do
2 as
3 select distinct substr( object_name, 1,1 ) first_char
4 from T
5 /Table created. Таблица создана.
ops$tkyte@ORA10G> begin
12 end;
13 /22257 rows updated
22257 строк обновлено.
1167 rows updated
1167 строк обновлено
135 rows updated
135 строк обновлено
1139 rows updated
1139 строк обновлено
2993 rows updated
2993 строк обновлено
691 rows updated
691 строк обновлено
... 2810 rows updated
2810 строк обновлено
6 rows updated
6 строк обновлено
10 rows updated
10 строк обновлено
2849 rows updated
2849 строк обновлено
1 rows updated
1 строка обновлена
2 rows updated
2 строк обновлено
7 rows updated
7 строк обновлено
PL/SQL procedure successfully completed.
Процедура PL/SQL успешно завершена.
Теперь мы можем перезапустить процесс, если он завершится сбоем, поскольку уже не станем обрабатывать имена объектов, которые были успешно обработаны. Проблема с этим подходом, однако, заключается в том, что если только у нас нет атрибута, позволяющего равномерно распределить данные, то мы столкнемся с очень широким распределением строк. Первому UPDATE придется выполнить намного больше работы, чем всем прочим вместе взятым. Вдобавок если другие сеансы обращаются к этой же таблице и модифицируют данные, они также могут обновить поле object_name. Предположим, что некоторый другой сеанс обновляет объект по имени Z, переименовывая его в A, после того, как мы уже обработали все объекты на A. В этом случае мы потеряем эту запись. Более того, это очень неэффективный процесс по сравнению с UPDATE T SET LAST_DDL_TIME = LAST_DDL_TIME+1. Возможно, мы воспользуемся индексом для чтения каждой строки таблицы или выполним полное сканирование n раз — и то, и другое нежелательно. О таком подходе можно сказать очень много плохого.
И поэтому лучший подход — первый, который описан в начале главы 1: делать это просто. Если это можно сделать на SQL — делайте это на SQL. Что невозможно сделать на SQL, делайте на PL/SQL. Делайте это, используя минимально возможный объем кода. Выделяйте достаточные ресурсы. Всегда думайте о том, что может произойти в случае ошибки. Мне очень часто приходилось видеть людей, кодирующих циклы обновления, которые великолепно работали с тестовыми данными, но терпели крах при обработке реальных данных. Они попадали впросак, поскольку не имели понятия, где именно прервалась обработка. Намного проще выставить правильно размер сегмента отката, нежели писать перезапускаемую программу. Если у вас есть действительно большие таблицы, которые нужно обновлять, то вы должны использовать секционирование (подробнее о нем рассказывается в главе 10), которое позволит вам обновить каждую секцию индивидуально. Тогда вы можете даже применять параллельный DML для выполнения обновлений.
< Предыдущая | Следующая > |
---|