|
|
|||||||||||||||||||||||||||||
|
Том Кайт: об игнорировании, блокировании и разборахИсточник: oracle Том Кайт
Наш эксперт рассматривает программную конструкцию WHEN OTHERS, блокирование, каскадные обновления и разборы. В языке PL/SQL есть крайне нежелательная для меня программная конструкция WHEN OTHERS. Когда это предложение используется в блоке обработки исключительных ситуаций, то захватываются все необработанные исключительные ситуации. Иногда это предложение полезно, например, для протоколирования ошибок: exception when others then log_error(....); raise; end; Проблема заключается в том, что многие используют предложение WHEN OTHERS без последующего инициирования исключительных ситуаций (вызовы RAISE или RAISE_APPLICATION_ERROR). Это фактически скрывает ошибку. На самом деле ошибка происходит, но она не обрабатывается каким-то осмысленным образом, а просто игнорируется - молча. Вызывающий вашего кода не имеет никакого понятия, что случилось нечто чрезвычайное и ваш код сбился, обычно он думает, что на самом деле все работает успешно. На сайте Ask Tom (asktom.oracle.com) я беспрестанно получаю вопросы об этом. Например, недавно я получил вопрос: Я создал пакет, который запускает 10 заданий для массовой загрузки данных в плоский файл. Мне нужно использовать пакет UTL_FILE, поскольку в середине процесса я вызываю три процедуры, которые извлекают некоторые данные. Общее время массовой загрузки 9 500 000 строк - шесть часов. Мне нужен ваш совет, как снизить это время. Мой код: PROCEDURE prcl_MakeFile(...) IS ... variables ... BEGIN l_FileID := UTL_FILE.FOPEN (...); OPEN cur; LOOP ... здесь обработка записей ... ... много кода ... END LOOP; CLOSE cur; UTL_FILE.FCLOSE(l_FileID); EXCEPTION WHEN OTHERS THEN IF (UTL_FILE.IS_OPEN(l_FileID)) THEN UTL_FILE.FCLOSE(l_FileID); END IF; END prcl_MakeFile; Мой ответ был простым: я могу беспредельно убыстрить этот код. Все, что должна делать эта процедура: PROCEDURE prcl_MakeFile(...) IS ... variables... BEGIN Return; END prcl_MakeFile; Эти две процедуры логически эквивалентны, но моя работает намного быстрее! Итак, почему же они логически эквивалентны? Из-за предложения WHEN OTHERS, за котором не следует вызов RAISE или RAISE_APPLICATION_ERROR. Предположим, при вызове UTL_FILE .FOPEN возникает ошибка - что тогда произойдет? Весь код будет пропущен, но никто не узнает об этом. Когда в блоке обработки исключительных ситуаций используется предложение WHEN OTHERS, а повторное инициирование исключительной ситуации отсутствует, весь код по-моему мнению можно безболезненно удалить. Он же вам не нужен, поскольку вы игнорируете тот факт, что этот код не выполняется, если при его выполнении возникает ошибка. Если вы допускаете, что код иногда может не выполняться, вы фактически можете разрешить этому коду никогда не выполняться. Вы даже не можете полагаться, что этот код на самом деле работает, поэтому вам никогда не нужно выполнять его. Я также утверждаю, что безопаснее не выполнять этот код, чем выполнять. По крайней мере, если вы его не выполняете, вы знаете, какое будет состояние базы данных. Например, рассмотрим следующую процедуру: procedure p is begin insert into t1 values(1); insert into t2 values(2); insert into t3 values(3); exception when others then dbms_output.put_line ('something bad happened!'); end; Вызывающий эту процедуру никогда не узнает, что:
У этой процедуры есть четыре возможных результата, но вызывающий никогда не узнает, какой же результат получился на самом деле. По крайней мере, если из кода удалить конструкцию WHEN OTHERS, вызывающий будет знать, что произошло во время выполнения процедуры - особенно, если ничего не произошло. Дополнительную информацию по этой важной теме см. на сайтах:
Блокирование в веб-среде Вопрос. Я недавно натолкнулся на .NET-приложение, работающее с сервером Oracle Database 10g, разработчики этого приложения использовали оптимистическое блокирование (извлекали из таблицы идентификатор версии, обновляли требуемую строку, а затем обновляли идентификатор версии), поскольку это единственный способ гарантировать невозможность одновременного обновления одной и той же записи многими пользователями. Я полагаю, что вместо излишнего кода, в котором реализован искусственный механизм блокирования, можно делать тоже самое с помощью предложения FOR UPDATE. Прав ли я? Ответ. У n-звенных приложений есть два способа доступа к базе данных: 1. С сохранением состояния: подключения к серверу хранятся на протяжении длительного времени, в течении которого генерируется много веб-страниц. 2. Без сохранения состояния: подключения хранятся на протяжении очень короткого периода времени, может быть даже меньше времени генерации одной HTML-станицы. В большинстве современных приложений, по моему опыту, используется метод 2. Таким образом, конечные пользователи потребляют ресурсы только тогда, когда они "активны" в сервере базы данных. Они захватывают подключение, используют и освобождают его. Если у вас подключение без сохранения состояния, вы не можете использовать пессимистическое блокирование (предложение FOR UPDATE). Вы будете терять такую блокировку после генерации каждой страницы. Для такого типа приложений подходит только оптимистическое блокирование. Итак, разработчики, с которыми вы работаете, делают, вероятно, то, что надо. Эту тему я широко изложил в книге Expert Oracle Database Architecture: 9i and 10g Programming Techniques and Solutions (Apress, 2005). Приведем из нее небольшой отрывок. Для подгонки к объему и формату колонки оригинальный текст был модифицирован. Оптимистическое блокирование или пессимистическое блокирование? Какой метод наилучший? По моему опыту, пессимистическое блокирование очень хорошо работает в сервере Oracle Database (но, возможно, это не так в других СУБД), и оно имеет много преимуществ по сравнению с оптимистическим блокированием. Однако для него требуется подключение к серверу базы данных с сохранением состояния, такое как в среде "клиент-сервер", поскольку между подключениями блокировки не сохраняются. Один этот факт во многих случаях сегодня делает пессимистическое блокирование нереалистичным. В прошлом при работе с клиент-сервисными приложениями и с несколькими дюжинами или сотнями пользователей это был мой первый и единственный выбор. Сейчас же, однако, я рекомендую для большинства приложений оптимистическое управление конкурентным доступом. За удерживание подключения на протяжении всего сеанса приходится платить слишком высокую цену. Существует много способов реализации оптимистического управления конкурентным доступом, включая:
Итак, что же использую я? Я предпочитаю использовать подход со столбцом версии и со столбцом отметки времени. Это дает мне дополнительную информацию о времени обновления конкретной строки. Этот подход менее дорогостоящий по сравнению с вычислением контрольной суммы или хеш-значения, и он не подвергается риску встречи с данными типа LONG, LONG RAW, CLOB, BLOB и другими очень большими столбцами. Если таблица по-прежнему используется в схеме пессимистического блокирования, например, в клиент-серверных приложениях, то при добавлении оптимистического управления конкурентным доступом для веб-приложений я предпочитаю подход с использованием псевдостолбца ORA_ROWSCN. Дело в том, что существующее унаследованное приложение может не "понимать" появления нового столбца, и даже если я приму дополнительные меры для сокрытия дополнительного столбца, я не могу не принимать во внимание накладные расходы на триггер, необходимый для поддержки этого. В отношении этого механизм ORA_ROWSCN будет ненавязчивым и облегченным (конечно же после пересоздания таблицы). Подход с вычислением хеш-значений или контрольных сумм существенно не зависит от сервера базы данных, особенно, если эти вычисления выполняются за пределами сервера. Тем не менее, выполнение этих вычислений не в сервере, а в промежуточном звене, означает большее потребление ресурсов, таких как использование центрального процессора и передачи данных по сети. Каскадные обновления Вопрос. У меня есть таблица EMP с дочерними таблицами, каждая из которых имеет свои собственные дочерние таблицы. Я хочу в таблице EMP обновить табельный номер служащего и хочу, чтобы все дочерние таблицы (включая дочерние таблицы дочерних таблиц) обновились автоматически. Как это сделать? >Ответ. Предполагается, что первичные ключи неизменяемые - постоянные. По моему мнению, у вас проблема с моделью данных, а не с SQL. Если вы полагаете, что нужно обновить первичный ключ и сделать это в каскаде, вам нужно, на самом деле, пересмотреть свой подход. Вы должны понимать, что табельный номер в вашем примере не может быть первичным ключом таблицы EMP - нет, если он изменяется. Вам нужно для первичного ключа выбрать что-то другое (может быть даже искусственный ключ). Тем не менее, вы можете использовать каскадное обновление, если оно действительно требуется (например, для единовременного обновления данных, которые были слиты с данными таблицы). Для этого используйте откладываемые ограничения и хранимую процедуру. Например, предположим у вас есть следующая схема: create table p ( x int primary key ); create table c1 ( x constraint c1_fk_p r references p deferrable, y int, primary key(x,y) ); create table c2 ( x int, y int, z int, constraint c2_fk_c1 foreign key(x,y) references c1 deferrable, primary key(x,y,z)); Теперь, ограничения в таблицах C1 и C2 могут быть отложены - в этом случае они проверяются либо при фиксации транзакции, либо при изменения состояния ограничений на немедленное (IMMEDIATE). Это позволяет написать хранимую процедуру, как показано на листинге 1.
А теперь вы можете вызвать эту процедуру, и она успешно выполнит каскад обновлений родительской таблицы P, дочерней таблицы C1 и ее дочерней таблицы C2. Но опять-таки, вы должны делать это в редчайших случаях - это не то, что должно стать частью вашей постоянной стратегии проектирования и реализации. Есть разбор и есть разбор Вопрос. У меня проблемы с защелками библиотечного кеша. Я попытался идентифицировать причину этих проблем с помощью пакета STATSPACK: Per Second Per Transaction У вас есть какие-нибудь предложения? Ответ. В вашей системе выполняется разбор массы SQL-операторов - примерно 390 раз в секунду. Хорошая новость - эти разборы в основном частичные (soft parse). Только сами разработчики могут уменьшать количество разборов. Сервер Oracle Database разбирает SQL-оператор каждый раз, когда приложение попросит об этом, и здесь приложение просит очень часто. За 12.98 минут по вашему отчету пакета STATSPACK выполняется примерно 303 669 разборов. Для каждого разбора нужна защелка библиотечного кеша. Оптимальное решение: понимайте, что единственно хорошим разбором является несуществующий разбор, и если в коде, который обращается к базе данных, соблюдается этот подход, у вас не будет слишком много разборов. Оставляйте курсоры открытыми. Не закрывайте их до тех пор, пока они не будут больше нужны. Вы можете легко реализовать этот подход, разместив все SQL-операторы в хранимых процедурах (машина PL/SQL автоматически кеширует курсоры - не закрывает их - так что, если вы потребуете: "PL/SQL закрой этот курсор", сервер базы данных проигнорирует вас и поместит курсор в кеш). Если у вас используется язык Java, используйте для кеширования операторов интерфейс Java DataBase Connectivity (JDBC), так что, этот интерфейс будет игнорировать попытки разработчиков закрыть курсоры. Единственный способ уменьшать количество защелок состоит в том, чтобы уменьшить количество раз, когда вы делаете то, для чего требуются защелки. И разбор - огромный пользователь защелок. Еще нужно рассмотреть следующее: используется ли у вас механизм кеширования курсоров в сеансах (параметр инициализации session_cached_cursors). Он может сделать ваши частичные разборы более "мягкими." Установка этого параметра поможет, если ваше приложение многократно выполняет следующее: 1. Разбор. Рассмотрим пример, мониторинг которого я выполняю, используя небольшой набор средств тестирования runstats (asktom.oracle.com/tkyte/runstats.html). Я начну с небольшой процедуры, в которой многократно выполняются только разборы, для этого используется динамический SQL (эти разборы будут в основном мягкими разборами). Процедура показана на листинге 2.
Теперь, если я сравню разницу в количестве защелок, когда я выполняю эту процедуру с кешированием курсоров в сеансе и без кеширования, я увижу большое различие (см. листинг 3).
Итак листинг 3 показывает, что для выполнения 100 000 мягких разборов потребовалось примерно 11 000 000 защелок, большинство из которых - защелки библиотечного кеша. Теперь, я установлю параметр session_cached_cursors следующим образом: SQL> alter session set session_cached_cursors=100; Session altered. Теперь, я выполню этот же пример (см. листинг 4). Как видно на листинге, количество защелок значительно снизилось, а количество разборов осталось прежним - механизм кеширования курсоров в сеансах начал действовать и сделал частичные разборы более мягкими.
Транспонирование столбца в строку Вопрос. Я хочу представить значения столбца как строки. То есть, я хочу, чтобы результат запроса к таблице EMP выглядел так: DEPTNO ENAME ------------ -------------------- 10 clark king miller 20 adams ford ... ... Можно ли это сделать, используя только язык SQL? Ответ. После появления в сервере Oracle8i Release 2 аналитических функций и функции SYS_CONNECT_BY_PATH() в сервере Oracle9i Database Release 1 сделать это на языке SQL довольно просто. Придерживайтесь следующего подхода:
Функция SYS_CONNECT_BY_PATH() будет возвращать список сцепленных значений столбца ENAME. Этот запрос выглядит так: select deptno, max(sys_connect_by_path (ename, ' ' )) scbp from (select deptno, ename, row_number() over (partition by deptno order by ename) rn from emp ) start with rn = 1 connect by prior rn = rn-1 and prior deptno = deptno group by deptno order by deptno / DEPTNO SCBP --------- ---------------------------------- 10 CLARK KING MILLER 20 ADAMS FORD JONES SCOTT ... 30 ALLEN BLAKE JAMES MARTIN ... Снижение объема генерируемой журнальной информации Вопрос. У меня есть PL/SQL-пакет, который копирует данные в наше хранилище данных из множественных баз данных, используя для этого связь базы данных. В пакете используется массовое связывание (предложение BULK_COLLECT) с ограничением количества строк от 1 000 до 2 500, в зависимости от числа строк в каждой таблице. Транзакцию я фиксирую за пределами цикла, так что у меня только одна операция фиксации. Администраторы базы данных заявляют, что объем журнальной информации немыслим, поэтому они даже должны были увеличить дисковое пространство, доступное серверу Oracle Database. Они определенно не говорили, что проблема связана с моим кодом, но она появилась приблизительно во время реализации моего кода. Как я могу контролировать или гарантировать во время написания кода, что журнализация оптимизирована? Ответ. Насколько это возможно старайтесь использовать одиночные SQL-операторы. Наибольшее влияние, которое вы можете оказать на генерацию журнальной информации - ограничение объема работы, выполняемой во время одного вызова. Кроме того, рассмотрите также возможность выполнения операций прямой вставки (direct-path) с отключенной журнализацией. (Только согласовывайте свою работу с вашими администраторами базы данных! Сразу после выполнения этих операций они должны создать резервную копию.) Сравните разницу в объемах генерируемой журнальной информации при выполнении построчных операций - обработка "мало-помалу" (slow-by-slow) - и одного SQL-оператора, показанную на листинге 5.
То есть, при выполнении построчной вставки мы имеем 59MB журнальной информации, а при выполнении одного эффективного SQL-оператора - 23MB! Ссылки по теме
|
|