Oracle для профессионалов

         

Независимость от СУБД?


Вы, наверное, уже поняли направление моей мысли. Я ссылался на другие СУБД и описывал различия реализации одних и тех же возможностей в каждой из них. Я убежден: за исключением некоторых приложений, исключительно читающих из базы данных, создать полностью независимое от СУБД и при этом масштабируемое приложение крайне сложно и даже практически невозможно, не зная особенностей работы всех СУБД.

Например, давайте вернемся к первому примеру планировщика ресурсов (до добавления конструкции FOR UPDATE). Предположим, это приложение было разработано на СУБД с моделью блокирования/обеспечения одновременного доступа, полностью отличающейся от принятой в Oracle. Я собираюсь продемонстрировать, что при переводе приложения с одной СУБД на другую необходимо проверять, работает ли оно корректно в новой среде.

Предположим, что первоначально приложение по планированию ресурсов работало в СУБД, использующей блокирование на уровне страниц и блокировку чтения (чтение блокируется при изменении считываемых данных), и для таблицы SCHEDULES был создан индекс:

create index schedules_idx on schedules(resource_name, start_time);

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

и START_TIME, очень вероятно, что транзакции будут выполняться строго последовательно. Система будет выполнять вставки поочередно, поскольку страница индекса блокируется (все близкие значения по полю START_TIME для одного ресурса RESOURCE_NAME будут находиться на той же странице). В такой СУБД с блокированием на уровне страниц наше приложение, вероятно, будет работать нормально, так как перекрытие выделяемых ресурсов будет проверяться последовательно, а не одновременно.

Если просто перенести это приложение в СУБД Oracle, исходя из предположения, что она работает точно так же, можно получить шок. В СУБД Oracle, выполняющей блокирование на уровне строк и не блокирующей чтения, оно окажется некорректным. Как уже было показано, необходимо использовать конструкцию FOR UPDATE для упорядочения доступа. Без этой конструкции два пользователя могут зарезервировать ресурс на одно и то же время. Это будет прямым следствием непонимания особенностей работы используемой СУБД в многопользовательской среде.


С подобными проблемами я сталкивался многократно при переносе приложений из СУБД А в СУБД Б. Когда приложение, без проблем работавшее в СУБД А, не работает или работает весьма странно в СУБД Б, сразу же возникает мысль, что "СУБД Б — плохая". Правда, однако, в том, что СУБД Б работает иначе. Ни одна из СУБД не ошибается и не является "плохой" — они просто разные. Знание и понимание особенностей их работы поможет успешно решить подобные проблемы.

Совсем недавно я помогал перевести код с языка Transact SQL (язык создания хранимых процедур для СУБД SQL Server) на PL/SQL. Разработчик, занимавшийся переводом, жаловался, что SQL-запросы в Oracle возвращают "неправильный" ответ. Запросы выглядели следующим образом:

declare l_some_variable varchar2(25); begin if ( some_condition ) then l_some_variable := f( ... ); end if;

for x in ( select * from T where x = l_some_variable ) loop ...

Целью является получение всех строк таблицы T, которые в столбце X имеют пустое значение, если некоторое условие не выполнено, или определенное значение, если это условие выполнено.

Суть жалобы состояла в том, что, в Oracle этот запрос не возвращал данных, если переменная L_SOME_VARIABLE не получала значения явно (когда у нее оставалось значение NULL). В СУБД Sybase или SQL Server все было не так — запрос находил строки с неопределенным (NULL) значением в столбце X. Я встречался с этим практически при любом переводе приложения с СУБД Sybase или SQL Server на Oracle. Язык SQL предполагает использование трехзначной логики, и СУБД Oracle реализует неопределенные значения так, как того требует стандарт ANSI SQL. По этим правилам сравнение столбца X со значением NULL не дает ни True, ни False — результат фактически неизвестен. Следующий пример показывает, что я имею в виду:

ops$tkyte@ORA8I.WORLD> select * from dual;

D - X



ops$tkyte@ORA8I.WORLD> select * from dual where null=null;

no rows selected

ops$tkyte@ORA8I.WORLD> select * from dual where null<>null;



no rows selected

В первый раз это может показаться странным: в Oracle NULL не равен и не не равен NULL. СУБД SQL Server по умолчанию ведет себя не так: в SQL Server и Sybase NULL равен NULL. Ни Oracle, ни Sybase, ни SQL Server не выполняет операторы SQL неправильно — они просто делают это по-разному. Все эти СУБД якобы соответствуют стандарту ANSI, но все равно работают по-разному. Есть неоднозначности, проблемы совместимости с прежними версиями и так далее, которые необходимо решать. Например, СУБД SQL Server поддерживает метод сравнения со значением NULL, диктуемый стандартом ANSI, но не по умолчанию (это нарушило бы работу тысяч уже существующих приложений, созданных для этой СУБД).

Одним из решений проблемы могло быть переформулирование запроса следующим образом:

select * from t where ( x = l_some_variable OR (x is null and l_some_variable is NULL ))

Однако это привело бы к еще одной проблеме. В СУБД SQL Server при выполнении этого запроса использовался бы индекс по столбцу X. В СУБД Oracle индекс на основе B*-дерева (подробнее о методах индексирования читайте в главе 7) не позволяет индексировать значения ключа NULL. Поэтому, если необходимо найти неопределенные значения, индексы на основе B*-деревьев не сильно помогут.

В рассматриваемом случае, чтобы свести к минимуму изменения в коде, столбцу X присваивалось значение, которого не могло быть в реальных данных. Так, X, по определению, был числом положительным, поэтому было выбрано значение -1. Запрос приобрел следующий вид:

select * from t where nvl(x,-1) = nvl(l_some_variable,-1)

Мы создали индекс по функции:

create index t_idx on t( nvl(x,-1) );

С минимальными изменениями мы добились того же результата. Отсюда можно сделать следующие важные выводы.

  • СУБД — различны. Опыт работы с одной может оказаться полезен в другой, но нужно быть готовым к ряду принципиальных отличий и многим очень мелким.


  • Мелкие различия (вроде обработки NULL-значений) могут иметь такое же влияние, как и принципиальные (например, механизм управления одновременным доступом).




  • Единственный способ справиться с этими проблемами — знать особенности работы СУБД и уметь реализовать предоставляемые ею возможности.


  • Разработчики часто спрашивают меня, как сделать в СУБД что-то конкретное. Например, меня спрашивают: "Как создать временную таблицу в хранимой процедуре?". На такие вопросы я не даю прямого ответа — я всегда отвечаю вопросом: "А для чего вам это нужно?". Неоднократно в ответ я слышал: "Мы создавали временные таблицы в хранимых процедурах в SQL Server, и теперь нам надо это сделать в Oracle". Именно это я и предполагал услышать. В таком случае мой ответ прост: "Вы ошибаетесь, думая, что надо создавать временные таблицы в хранимой процедуре в Oracle". На самом деле в СУБД Oracle это будет крайне неудачным решением. При создании таблиц в хранимых процедурах в Oracle вскоре обнаружится, что:

  • выполнение операторов DDL в этом контексте снижает масштабируемость;


  • постоянное выполнение операторов DDL снижает производительность;


  • выполнение операторов DDL приводит к фиксации транзакции;


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


  • динамический SQL в PL/SQL оптимизируется хуже и работает медленнее статического.


  • Итак, не надо делать в точности так, как в SQL Server (если временная таблица в Oracle вообще понадобится). Делать следует то, что является наиболее оптимальным для Oracle. При обратном переходе из Oracle в SQL Server тоже не стоит создавать одну большую таблицу с временными данными для всех пользователей (как это делается в Oracle). Это приведет к снижению масштабируемости и возможностей одновременного доступа в данной СУБД. Каждая СУБД имеет существенные отличия.


    Содержание раздела