кайт

Oracle.UkrSat.com : Новости : Том Кайт: Expert one-on-one Oracle: Глава 1 "Мир Oracle в Украине"oracle.ukrsat.com О проекте Карта сайта | English Первая Новости Магазин Форум FAQ Объявления Ссылки Том Кайт: Expert one-on-one Oracle: Глава 1 Предисловие переводчика Издательство "ДиаСофт" любезно разрешило мне опубликовать переводы нескольких глав знаменитой книги Тома Кайта "Expert one-on-one Oracle" в формате HTML. Я с удовольствием пользуюсь предоставленной возможностью, и предлагаю вашему вниманию перевод первой главы. Учтите, что в этом тексте используется терминология, соответствующая третьему, исправленному изданию, которое должно выйти в свет в ближайшее время. С наилучшими пожеланиями, В.К. Разработка успешных приложений для Oracle Значительную часть времени я провожу, работая с программным обеспечением СУБД Oracle или, точнее, с людьми, которые это программное обеспечение используют. В течение последних двенадцати лет я работал над многими проектами, как успешными, так кайт закончившимися неудачно, кайт если бы потребовалось обобщить приобретенный при этом опыт несколькими фразами, я бы сказал следующее: успех или неудача разработки приложения базы данных (приложения, зависящего от базы данных) определяется тем, как оно использует базу данных; в команде разработчиков должно быть ядро "программистов базы данных", обеспечивающих согласованность логики работы с базой данных кайт настройку производительности системы. Эти утверждения могут показаться очевидными, но опыт показывает, что слишком многие используют СУБД как "черный ящик", о деталях устройства которого знать необязательно. Они могут использовать генератор SQL, позволяющий не затруднять себя изучением языка SQL. Возможно, они решат использовать ее как обычный файл с возможностью чтения записей по ключу. Как бы то ни было, я могу вам сказать, что подобного рода соображения почти наверняка приводят к неправильным выводам — работать, не понимая устройства СУБД, просто нельзя. В этой главе описано, почему необходимо знать устройство СУБД, в частности, почему необходимо понимать: архитектуру СУБД, ее компоненты кайт алгоритмы работы; что такое средства управления одновременным доступом и каково их значение для разработчиков; как настраивать приложение с момента введения его в эксплуатацию; как реализованы определенные компоненты СУБД кайт чем эта реализация отличается от обычно предполагаемой; какие возможности реализованы в самой СУБД кайт почему, как правило, лучше использовать предоставляемые СУБД функции, кайт не реализовать их самостоятельно; зачем может понадобиться более глубокое знание языка SQL. Этот список тем для начального изучения может показаться слишком длинным, но давайте рассмотрим следующую аналогию: если бы вы разрабатывали масштабируемое, высокопроизводительное приложение для абсолютно новой операционной системы (ОС), с чего бы вы начали? Надеюсь, ваш ответ: "С изучения особенностей функционирования этой новой ОС, работы приложений в ней кайт т.п.". Если ответ принципиально другой, ваша разработка обречена на неудачу. Рассмотрим, например, одну из ранних версий Windows (скажем, Windows 3.x). Она, как кайт ОС UNIX, была "многозадачной" операционной системой. Однако эта многозадачность была не такой, как в ОС UNIX, — использовалась модель невытесняющей многозадачности (т.е., если работающее приложение не возвращает управление, ничто другое работать не может, включая операционную систему). Фактически, по сравнению с UNIX, Windows 3.x вообще не была многозадачной ОС. Для создания эффективных приложений разработчики должны были точно знать, как реализована возможность "многозадачности" Windows. Если необходимо разрабатывать приложение, работающее непосредственно в среде определенной ОС, понимание особенностей этой ОС очень важно. То, что верно в отношении приложений, непосредственно работающих в среде операционной системы, верно кайт для приложений, работающих в среде СУБД: понимание особенностей СУБД является определяющим фактором успеха. Если вы не понимаете, что делает используемая СУБД или как она это делает, создаваемое приложение не будет работать успешно. Предположение о том, что успешно работающее в среде SQL Server приложение так же успешно будет работать кайт в среде Oracle, скорей всего не оправдается. Мой подход Прежде чем начать, хотелось бы объяснить вам мой подход к разработке. Я предпочитаю решать большинство проблем на уровне СУБД. Если что-то можно сделать в СУБД, я так кайт сделаю. Для этого есть две причины. Первая кайт главная состоит в том, что если встроить функциональность в СУБД, то ее можно будет применять где угодно. Я не знаю серверной операционной системы, для которой нет реализации СУБД Oracle. Одна кайт та же СУБД Oracle со всеми опциями работает везде — от Windows до десятков версий ОС UNIX кайт больших ЭВМ типа OS/390. Я часто разрабатываю кайт тестирую программы на моем портативном компьютере, где работает СУБД Oracle8i для Windows NT. А применяются эти программы на различных серверах с ОС UNIX, на которых работает та же версия СУБД. Если приходится реализовать функциональность за пределами СУБД, ее очень сложно переносить на любую другую платформу. Одна из основных особенностей, делающих язык Java привлекательным для многих разработчиков, состоит в том, что программы всегда компилируются в одной кайт той же виртуальной среде, виртуальной машине Java Virtual Machine (JVM), кайт поэтому максимально переносимы. Именно эта особенность привлекает меня в СУБД. СУБД Oracle — это моя виртуальная машина, моя "виртуальная операционная система". Мой подход состоит в том, чтобы делать в СУБД все, что возможно. Если требования выходят за пределы возможностей СУБД, я реализую соответствующие функции на языке Java вне СУБД. В этом случае особенности практически любой операционной системы скрываются. Мне все равно надо понимать, как работают мои "виртуальные машины" (Oracle или JVM) — надо знать используемые инструментальные средства, — но наиболее эффективная реализация соответствующих функций в конкретной ОС остается прерогативой создателей этих виртуальных машин. Таким образом, зная лишь особенности работы одной "виртуальной ОС", можно создавать приложения, демонстрирующие отличную производительность кайт масштабируемость во многих операционных системах. Я не утверждаю, что можно полностью игнорировать базовую ОС, — просто разработчик приложений баз данных достаточно хорошо от нее изолирован, кайт ему не придется учитывать многие ее нюансы. Ваш АБД, отвечающий за поддержку СУБД Oracle, должен знать намного больше об особенностях базовой ОС (если не знает — найдите нового АБД!). При разработке клиент-серверного программного обеспечения, если основная часть кода вынесена из СУБД кайт виртуальной машины (наиболее популярной виртуальной машиной, вероятно, является Java Virtual Machine), разработчику придется учитывать особенности ОС сервера. При разработке приложений баз данных я использую очень простую мантру: если можно, сделай это с помощью одного оператора SQL; если это нельзя сделать с помощью одного оператора SQL, сделай это в PL/SQL; если это нельзя сделать в PL/SQL, попытайся использовать хранимую процедуру на языке Java; если это нельзя сделать в Java, сделай это в виде внешней процедуры на языке C; если это нельзя реализовать в виде внешней процедуры на языке C, надо серьезно подумать, зачем это вообще делать... В книге вы увидите применение этого подхода. Мы будем использовать язык PL/SQL кайт его объектные типы для реализации того, что нельзя сделать в SQL. Язык PL/SQL существует давно, за ним стоит более тринадцати лет настройки, и нет другого языка, настолько тесно интегрированного с языком SQL кайт настолько оптимизированного для взаимодействия с SQL. Когда возможностей PL/SQL оказывается недостаточно, например, при доступе к сети, отправке сообщений электронной почты кайт т.п., мы будем использовать язык Java. Иногда мы будем решать определенные задачи с помощью языка C, но обычно лишь в тех случаях, когда программирование на C — единственно возможный вариант или когда обеспечиваемая компилятором C скорость работы программы действительно необходима. Во многих случаях сейчас последняя причина отпадает при использовании компиляции в машинные коды программ на языке Java (возможности преобразовать байт-код Java в специфический объектный код операционной системы для данной платформы). Это обеспечивает программам на Java такую же скорость работы, как кайт у программ на языке C. Подход с использованием принципа черного ящика У меня есть предположение, основанное на личном опыте, почему так часто разработка приложений баз данных заканчивается неудачно. Позвольте уточнить, что к разряду неудавшихся разработок я отношу также проекты, официально не признанные неудавшимися, но потребовавшие на разработку кайт внедрение намного больше времени, чем планировалось первоначально, поскольку пришлось их существенно "переписывать", "перепроектировать" или "настраивать". Лично я такие не завершенные в срок проекты считаю неудавшимися: очень часто их вполне можно было завершить вовремя (и даже досрочно). Наиболее типичной причиной неудачи является нехватка практических знаний по используемой СУБД — элементарное непонимание основ работы используемого инструментального средства. Подход по принципу "черного ящика" требует осознанного решения: оградить разработчиков от СУБД. Их заставляют не вникать ни в какие особенности ее функционирования. Причины использования этого подхода связаны с опасениями, незнанием кайт неуверенностью. Разработчики слышали, что СУБД — это "сложно", язык SQL, транзакции кайт целостность данных — не менее "сложно". Решение: не заставлять никого делать что-либо "сложное". Будем относиться к СУБД, как к черному ящику, кайт найдем инструментальное средство, которое сгенерирует необходимый код. Изолируем себя несколькими промежуточными уровнями, чтобы не пришлось сталкиваться непосредственно с этой "сложной" СУБД. Такой подход к разработке приложений баз данных я не мог понять никогда. Одна из причин, почему мне трудно это понять, состоит в том, что для меня изучение языков Java кайт C оказалось намного сложнее, чем изучение основ работы СУБД. Я сейчас очень хорошо знаю языки Java кайт C, но для их освоения мне понадобилось намного больше практического опыта, чем для достижения соответствующего уровня компетентности при использовании СУБД. В случае СУБД необходимо знать, как она работает, но детали знать необязательно. При программировании на языке C или Java, необходимо, например, знать все особенности используемых компонентов; кроме того, это очень большие по объему языки. Еще одна причина — то, что при создании приложений базы данных самым важным компонентом программного обеспечения является СУБД. Для успешной разработки необходимо учитывать это кайт доводить до сведения разработчиков, постоянно обращая на это их внимание. Много раз я сталкивался с проектами, где придерживались прямо противоположных воззрений. Вот типичный сценарий такого рода разработки. Разработчики были полностью обучены графической среде разработки или соответствующему языку программирования (например, Java), использованных для создания клиентской части приложения. Во многих случаях они обучались несколько недель, если не месяцев. Команда разработчиков ни одного часа не изучала СУБД Oracle кайт не имела никакого опыта работы с ней. Многие разработчики вообще впервые сталкивались с СУБД. В результате разработчики столкнулись с огромными проблемами, связанными с производительностью, обеспечением целостности данных, зависанием приложений кайт т.д. (но пользовательский интерфейс выглядел отлично). Не сумев обеспечить нужную производительность, разработчики обращались за помощью ко мне. Особенно показателен один случай. Я не мог вспомнить точный синтаксис новой команды, которую надо было использовать, кайт попросил руководство SQL Reference. Мне принесли экземпляр из документации по СУБД Oracle версии 6.0, хотя разработка велась на версии 7.3, через пять лет после выхода версии 6.0! Ничего другого для работы у них не было, но это вообще никого не беспокоило. Хотя необходимое им для трассировки кайт настройки инструментальное средство в то время вообще не существовало. Хотя за пять лет, прошедших после написания имевшейся у них документации, были добавлены такие средства, как триггеры, хранимые процедуры, и многие сотни других. Несложно понять, почему им потребовалась помощь, гораздо труднее было решить их проблемы. Странная идея о том, что разработчик приложения баз данных должен быть огражден от СУБД, чрезвычайно живуча. Многие почему-то считают, что разработчикам не следует тратить время на изучение СУБД. Неоднократно приходилось слышать: "СУБД Oracle — самая масштабируемая в мире, моим сотрудникам не нужно ее изучать, потому что СУБД со всеми проблемами справится сама". Действительно, СУБД Oracle — самая масштабируемая. Однако написать плохой код, который масштабироваться не будет, в Oracle намного проще, чем написать хороший, масштабируемый код. Можно заменить СУБД Oracle любой другой СУБД — это утверждение останется верным. Это факт: проще писать приложения с низкой производительностью, чем высокопроизводительные приложения. Иногда очень легко создать однопользовательскую систему на базе самой масштабируемой СУБД в мире, если не знать, что делаешь. СУБД — это инструмент, а неправильное применение любого инструмента может привести к катастрофе. Вы будете щипцами колоть орехи так же, как молотком? Можно, конечно, кайт так, но это неправильное использование инструмента, кайт результат вас не порадует. Аналогичные результаты будут кайт при игнорировании особенностей используемой СУБД. Я недавно работал над проектом, в котором проектировщики придумали очень элегантную архитектуру. Клиент с помощью Web-браузера взаимодействовал по протоколу HTTP с сервером приложений, обеспечивающим поддержку Java Server Pages (JSP). Алгоритмы работы приложения целиком генерировались инструментальными средствами кайт реализовывались в виде компонентов EJB (с использованием постоянного хранения на базе контейнеров), причем физически они выполнялись другим сервером приложений. В базе данных хранились только таблицы кайт индексы. Итак, мы начали с технически сложной архитектуры. Для решения задачи должны взаимодействовать друг с другом четыре компонента. Web-браузер получает страницы JSP от сервера приложений, который обращается к компонентам EJB, кайт те, в свою очередь, — к СУБД. Для разработки, тестирования, настройки кайт внедрения этого приложения необходимы были технически компетентные специалисты. После завершения разработки меня попросили оценить производительность приложения. Прежде всего я хотел узнать подход разработчиков к СУБД: где, по их мнению, у приложения могут быть узкие места, точки потенциальных конфликтов? каковы, по их мнению, основные препятствия для достижения требуемой производительности? Они не имели ни малейшего представления об этом. На вопрос о том, кто поможет мне переписать код компонента EJB для настройки сгенерированного запроса, ответ был следующий: "О, этот код нельзя изменять, все надо делать в базе данных". То есть, приложение должно оставаться неизменным. В этот момент я был готов отказаться от работы над проектом — ясно, что заставить это приложение нормально работать невозможно: приложение было создано без учета масштабирования на уровне базы данных; приложение нельзя настраивать кайт вообще изменять; по моему опыту, от 80 до 90 процентов всей настройки выполняется на уровне приложения, кайт не на уровне базы данных; разработчики не представляли себе, что делают их компоненты с базой данных кайт где искать потенциальные проблемы. Все это стало понятно уже через час тестирования. Как оказалось, в приложении сначала выполнялся оператор: select * from t for update; Это приводило к строго последовательной работе всех клиентов. В базе данных была реализована такая модель, что перед выполнением любых существенных действий приходилось блокировать весьма большой ресурс. Это моментально превращало приложение в очень большую однопользовательскую систему. Разработчики не верили мне (в другой СУБД, использующей разделяемую блокировку чтения, наблюдалась другая ситуация). После десяти минут работы с инструментальным средством TKPROF (о нем подробно написано в главе 10) я смог продемонстрировать, что именно этот оператор SQL выполнялся приложением (они об этом не знали — просто никогда не видели генерируемых операторов SQL). Я не просто показал, какие операторы SQL выполняются приложением, но кайт с помощью пары сеансов SQL*Plus продемонстрировал, что второй сеанс не начинается до полного завершения работы первым сеансом. Итак, вместо того, чтобы неделю тестировать производительность приложения, я употребил это время на обучение разработчиков настройке, особенностям блокирования в базах данных, механизмам управления одновременным доступом, сравнение их реализаций в СУБД Oracle, Informix, SQL Server, DB2 кайт так далее (во всех этих СУБД они различны). Но сначала мне пришлось понять, однако, почему использовался оператор SELECT FOR UPDATE. Оказалось, что разработчики хотели добиться повторяемости при чтении. Повторяемость при чтении — это такой режим работы СУБД, когда повторное чтение в транзакции строки, которая уже один раз прочитана в этой транзакции, дает тот же результат. Зачем им это было нужно? Они слышали, что "это хорошо". Ладно, предположим, повторяемость при чтении действительно необходима. В СУБД Oracle это делается путем установки уровня изолированности транзакции SERIALIZABLE (что дает не только повторяемость при чтении строки, но кайт повторяемость при выполнении любого запроса — если два раза выполнить один кайт тот же запрос в пределах транзакции с таким уровнем изолированности, будут получены одинаковые результаты). Для обеспечения повторяемости при чтении в Oracle не нужно использовать SELECT FOR UPDATE — это делается только для обеспечения последовательного доступа к данным. К сожалению, использованное разработчиками инструментальное средство это не учитывало — оно было создано для использования с другой СУБД, где именно так повторяемость при чтении кайт достигалась. Итак, в данном случае для установки уровня изолированности транзакций SERIALIZABLE пришлось создать триггер на регистрацию в базе данных, изменяющий параметры сеанса (уровень изолированности транзакций) для данного приложения. Затем мы отключили все установки повторяемости при чтении в использовавшемся инструментальном средстве кайт повторно запустили приложение. Теперь, без конструкции FOR UPDATE, в базе данных определенные действия стали выполняться одновременно. Это была далеко не последняя проблема данного проекта. Нам пришлось разобраться: как настраивать операторы SQL, не изменяя их кода (это сложно — некоторые методы мы рассмотрим в главе 11); как измерять производительность; как находить узкие места; что кайт как индексировать, кайт так далее. В конце недели разработчики, никогда ранее не работавшие с СУБД, были удивлены тем, что в действительности она дает возможность сделать, как легко получить указанную выше информацию и, что наиболее важно, как существенно все это может сказаться на производительности приложения. Тестированием производительности в течение этой недели мы не занимались (им кое-что пришлось переделывать!), но в конечном итоге проект завершился успешно — просто на пару недель позже запланированного срока. Это не критика инструментальных средств или современных технологий, таких как компоненты EJB кайт поддержка постоянного существования на базе контейнеров. Это — критика намеренного игнорирования особенностей СУБД, принципов ее работы и использования. Технологии, выбранные в этом проекте, работали отлично, но лишь после того, как разработчики немного разобрались в самой СУБД. Подводя итоги: СУБД — это краеугольный камень приложения. Если она не работает как следует, все остальное не имеет значения. Если плохо работает черный ящик, что с ним делать? Его нельзя исправить, нельзя настроить (поскольку непонятно, как он устроен), кайт такую позицию вы выбрали сами. Но есть кайт другой подход, который я отстаиваю: разберитесь в используемой СУБД кайт принципах ее работы, поймите, что она может делать, кайт используйте весь ее потенциал. Как надо (и как не надо) разрабатывать приложения баз данных Однако хватит теоретизировать, по крайней мере пока. В оставшейся части главы я использую более эмпирический подход, описывая, почему знание СУБД и особенностей ее работы существенно повышает шансы успешной реализации приложения (без необходимости переписывать его дважды!). Некоторые проблемы решить легко, если известно, как их найти. Для решения других потребуются существенные переделки. Одна из целей этой книги — помочь вам вообще избежать проблем. В следующих разделах я опишу ряд важнейших особенностей СУБД Oracle, не вдаваясь в подробности их реализации кайт использования. Например, я опишу только одно из последствий использования архитектуры многопотокового сервера (Multi-Threaded Server — MTS) — режима, в котором можно (а иногда кайт нужно) конфигурировать сервер Oracle для поддержки множества пользовательских сеансов. Я, однако, не буду детально описывать архитектуру MTS, особенности ее работы кайт т.п. Все это подробно описано в руководстве Oracle Server Concepts Manual (дополнительную информацию можно найти также в руководстве Net8 Administrators Guide). Понимание архитектуры СУБД Oracle Недавно я участвовал в проекте, создатели которого решили использовать только новейшие, самые совершенные технологии: все программное обеспечение было написано на Java в виде компонентов EJB. Клиентское приложение взаимодействовало с СУБД через эти компоненты — никакого протокола Net8. Между клиентом кайт сервером не предавались операторы SQL — только обращения к компонентам EJB с помощью вызовов удаленных методов (Remote Method Invocation — RMI) по протоколу Internet Inter-Orb Protocol (IIOP). Об организации RMI по протоколу IIOP можно узнать на сайте http://java.sun.com/products/rmi-iiop/. Это вполне допустимый подход. Такой способ взаимодействия работает кайт может быть весьма масштабируемым. Те, кто разрабатывал архитектуру, хорошо понимали язык Java, технологию компонентов EJB, знали используемые протоколы, в общем — всю "кухню". Им казалось, что имеются все основания для успешной реализации подобного проекта. Когда же выяснилось, что приложение может поддерживать лишь нескольких пользователей, они решили, что проблема — в СУБД, кайт усомнились в декларируемой корпорацией Oracle "рекордной масштабируемости СУБД". Проблема, однако, была не в СУБД, кайт в незнании особенностей ее работы, и некоторые решения, принятые на этапе проектирования, привели к краху приложения в целом. Чтобы использовать компоненты EJB в базе данных, сервер Oracle должен быть сконфигурирован для работы в режиме многопотокового (MTS), кайт не выделенного сервера. Чего не понимала команда разработчиков в принципе, так это последствий использования режима MTS в сочетании с компонентами EJB для их приложения. При отсутствии этого понимания, как кайт знания основ работы СУБД Oracle вообще, были приняты два ключевых решения. В компонентах будут запускаться хранимые процедуры, работающие по 45 кайт более секунд (иногда — намного дольше). Связываемые переменные использоваться не будут. В условиях всех запросов будут использоваться литеральные константы. Все входные данные для процедур будут передаваться в виде строк. Это "проще", чем использовать связываемые переменные. Эти, казалось бы, непринципиальные решения обусловили неизбежный провал проекта. Все было сделано так, что предельно масштабируемая СУБД не справлялась с нагрузкой даже при небольшом количестве пользователей. Нехватка знаний об особенностях работы СУБД свела на нет все глубокие знания разработчиков по созданию компонентов на Java кайт распределенной обработке. Если бы они нашли время на минимальное изучение особенностей работы СУБД Oracle кайт затем применили два представленных далее простых принципа, шансы на успех проекта уже в первой версии существенно бы повысились. Избегайте длительных транзакций в среде MTS Решение использовать транзакции продолжительностью более 45 секунд в среде MTS выдало недостаточное понимание назначения режима MTS кайт особенностей его работы в Oracle. Если коротко, в режиме MTS используется общий пул серверных процессов, обслуживающий намного больший пул конечных пользователей. Это похоже на пул подключений. Поскольку создание кайт управление процессом — наиболее дорогостоящие операции, выполняемые операционной системой, режим MTS дает большие преимущества для крупномасштабной системы. Можно обслуживать 100 пользователей всего пятью или десятью разделяемыми серверными процессами. Когда разделяемый серверный процесс получает запрос на изменение данных или выполнение хранимой процедуры, он привязывается к этой задаче до ее завершения. Ни одна другая задача не будет использовать разделяемый серверный процесс, пока не будет закончено изменение или не завершится выполнение хранимой процедуры. Поэтому при использовании режима MTS надо использовать как можно быстрее выполняющиеся операторы. Режим MTS создан для обеспечения масштабируемости систем оперативной обработки транзакций (OLTP), для которых характерны операторы, выполняющиеся за доли секунды. Речь идет об изменениях отдельных строк, вставке нескольких строк кайт запросах записей по первичному ключу. Не стоит в этом режиме выполнять пакетные процессы, для завершения которых требуются десятки секунд или минуты. Если все операторы выполняются быстро, архитектура MTS работает отлично. Можно эффективно обслуживать небольшим количеством процессов большое сообщество пользователей. Если же имеются сеансы, монополизирующие разделяемый сервер надолго, то кажется, что СУБД "зависает". Пусть сконфигурировано десять разделяемых серверов для поддержки 100 пользователей. Если в некоторый момент времени десять пользователей одновременно введут оператор, выполняющийся более 45 секунд, то всем остальным транзакциям (и новым подключениям) придется ждать. Если некоторым из ожидающих в очереди сеансов необходимо выполнять оператор такой же продолжительности, возникает большая проблема — "зависание" будет продолжаться не 45 секунд, кайт намного дольше. Даже если желающих выполнить подобный оператор одновременно будет не десять, кайт лишь несколько, все равно будет наблюдаться существенное падение производительности сервера. Мы отберем на длительное время совместно используемый ресурс, кайт это плохо. Вместо десяти серверных процессов, выполняющих быстрые запросы в очереди, остается пять или шесть (или еще меньше). Со временем система станет работать с производительностью, заметно меньше предполагаемой, исключительно из-за нехватки этого ресурса. Простое решение "в лоб" состоит в запуске большего количества разделяемых серверов, но в конечном итоге придется запускать разделяемый сервер для каждого пользователя, кайт это неприемлемо для системы с тысячами пользователей (как та, что создавалась в рассматриваемом проекте). Это не только создает узкие места в самой системе (чем большим количеством процессов приходится управлять, тем больше процессорного времени на это уходит), но кайт просто не соответствует целям создания режима MTS. Реальное решение этой проблемы оказалось простым: не выполнять продолжительные транзакции на сервере, работающем в режиме MTS. А вот реализация этого решения оказалась сложнее. Это можно было сделать несколькими способами, но все они требовали существенных изменений архитектуры. Самым подходящим способом, требующим минимальных изменений, оказалось использование средств расширенной поддержки очередей (Advanced Queues — AQ). AQ — это промежуточное программное обеспечения для обмена сообщениями, реализованное на базе СУБД Oracle кайт позволяющее клиентскому сеансу добавлять сообщения в таблицу очереди базы данных. Это сообщение в дальнейшем (обычно сразу после фиксации транзакции) выбирается из очереди другим сеансом, проверяющим содержимое сообщения. Сообщение содержит информацию для обработки другим сеансом. Оно может использоваться для эмуляции мгновенного выполнения за счет вынесения продолжительного процесса за пределы интерактивного клиента. Итак, вместо выполнения 45-секундного процесса, компонент должен помещать запрос со всеми необходимыми входными данными в очередь кайт выполнять его асинхронно, а не синхронно. В этом случае пользователю не придется ждать ответа 45 секунд, то есть система становится более динамичной. Хотя, судя по описанию, этот подход прост (подключение механизма AQ полностью решает проблему), потребовалось сделать намного больше. Этот 45-секундный процесс генерировал идентификатор транзакции, необходимый на следующем шаге в интерфейсе для соединения таблиц — по проекту интерфейс без этого не работал. Используя механизм AQ, мы не ждем генерации идентификатора транзакции, — мы обращаемся к системе с просьбой сделать это когда-нибудь. Поэтому приложение опять оказалось в тупике. С одной стороны, мы не можем ждать завершения процесса 45 секунд, но, с другой стороны, для перехода к следующему экрану необходим сгенерированный идентификатор, кайт получить его можно только спустя 45 секунд. Для того чтобы решить эту проблему, пришлось синтезировать собственный поддельный идентификатор транзакции, изменить продолжительный процесс так, чтобы он принимал этот сгенерированный поддельный идентификатор кайт обновлял таблицу, записывая его по завершении работы, благодаря чему реальный идентификатор транзакции связывался с поддельным. То есть, вместо получения реального идентификатора в результате длительного процесса, этот идентификатор становится для процесса входными данными. Во всех "подчиненных" таблицах использовался этот поддельный идентификатор транзакции, кайт не реальный (поскольку генерации реального надо ждать определенное время). Нам также пришлось пересмотреть использование этого идентификатора транзакции, чтобы понять, как это изменение повлияет на другие модули, кайт так далее. Еще одна проблема состояла в том, что при синхронной работе, если 45-секундный процесс завершался неудачно, пользователь узнавал об этом сразу. Он мог устранить причину ошибки (обычно путем изменения входных данных) кайт повторно выполнить запрос. Теперь, когда транзакции выполняются асинхронно с помощью механизма AQ, сделать это невозможно. Для поддержки отсроченного уведомления об ошибке пришлось добавить новые средства. В частности, понадобилось реализовать механизм потоков заданий для отправки информации о неудавшихся транзакциях соответствующему лицу. В результате пришлось существенно пересмотреть структуру базы данных. Пришлось добавить новое программное обеспечение (AQ). Пришлось также создать новые процессы (управление потоками заданий кайт другие служебные процессы). К положительным последствиям этих изменений можно отнести не только решение проблемы с архитектурой MTS, но кайт удобство для пользователя (создавалась видимость более быстрой реакции системы). С другой стороны, все эти изменения существенно задержали завершение проекта, поскольку проблемы были выявлены лишь непосредственно перед внедрением, на этапе тестирования масштабируемости. Очень жаль, что приложение сразу не был правильно спроектировано. Если бы разработчики знали, как физически реализован механизм MTS, было бы ясно, что исходный проект не обеспечивает требуемой масштабируемости. Используйте связываемые переменные Если бы мне пришлось писать книгу о том, как создавать немасштабируемые приложения Oracle, первая кайт единственная ее глава называлась бы "Не используйте связываемые переменные". Это — основная причина проблем, связанных с производительностью, кайт основная помеха масштабируемости. Особенности использования разделяемого пула Oracle (очень важной структуры данных разделяемой памяти) требуют от разработчиков использовать разделяемые переменные. Если надо замедлить работу приложения Oracle, вплоть до полного останова, — откажитесь от их использования. Связываемая переменная — это подставляемый параметр запроса. Например, для получения записи доя сотрудника с номером 123, можно выполнить запрос: select * from emp where empno = 123; Но можно задать кайт другой запрос: select * from emp where empno = :empno; В обычной системе информацию о сотруднике с номером 123 могут запрашивать всего один раз. В дальнейшем будут запрашивать информацию о сотрудниках с номерами 456, 789 кайт т.д. При использовании в запросе литералов (констант) каждый запрос является для СУБД абсолютно новым, никогда ранее не выполнявшимся. Его надо разбирать, уточнять (определять объекты, соответствующие именам), проверять права доступа, оптимизировать кайт т.д. — короче, каждый выполняемый уникальный оператор придется компилировать при каждом выполнении. Во втором запросе используется связываемая переменная, :empno, значение которой подставляется в запрос при выполнении. Этот запрос компилируется один раз, кайт затем план его выполнения запоминается в разделяемом пуле (в библиотечном кеше), из которого его можно выбрать для повторного выполнения. Различие между этими двумя вариантами в плане производительности кайт масштабируемости — огромное, даже принципиальное. Из представленного выше описания вполне понятно, что разбор оператора с явными, жестко заданными константами (так называемый полный разбор) выполняется дольше кайт требует намного больше ресурсов, чем повторное использование уже сгенерированного плана запроса (его называют частичным разбором). Менее очевидным может оказаться, насколько постоянный полный разбор сокращает количество пользователей, поддерживаемых системой. Отчасти это связано с повышенным потреблением ресурсов, но в гораздо большей степени — с механизм защелок, используемых в библиотечном кеше. При полном разборе запроса СУБД будет дольше удерживать определенные низкоуровневые средства обеспечения последовательного доступа, которые называются защелками (подробнее о них см. в главе 3). Защелки защищают структуры данных в разделяемой памяти сервера Oracle от одновременного изменения двумя сеансами (иначе эти структуры данных Oracle в конечном итоге были бы повреждены) кайт от чтения этой структуры данных по ходу изменения другим сеансом. Чем чаще и на более продолжительное время на эти структуры данных устанавливаются защелки, тем длиннее становится очередь для установки этих защелок. Точно так же происходит при использовании длинных транзакций в среде MTS, — монополизируются критические ресурсы. Временами машина может казаться минимально загруженной, кайт СУБД работает очень медленно. Вполне вероятно, что один из сеансов удерживает защелку кайт формируется очередь в ожидании ее освобождения. В результате работа с максимальной скоростью невозможна. Достаточно одного неверно работающего приложения для существенного снижения производительности всех остальных приложений. Одно небольшое приложение, не использующее связываемые переменные, приводит со временем к удалению из разделяемого пула необходимых SQL-операторов других хорошо настроенных приложений. Достаточно ложки дегтя, чтобы испортить бочку меда. При использовании связываемых переменных любой сеанс, выдающий тот же самый запрос, будет использовать уже скомпилированный план выполнения из библиотечного пула. Подпрограмма компилируется один раз, кайт используется многократно. Это очень эффективно, кайт именно такую работу пользователей предполагает СУБД. При этом не только используется меньше ресурсов (частичный разбор требует намного меньше ресурсов), но кайт защелки удерживаются значительно меньше времени, кайт нужны гораздо реже. Это повышает производительность кайт масштабируемость. Чтобы хоть примерно понять, насколько существенно это может сказаться на производительности, достаточно выполнить очень простой тест: tkyte@TKYTE816> alter system flush shared_pool; System altered. Здесь я начинаю с "пустого" разделяемого пула. Если потребуется выполнять этот тест многократно, придется очищать разделяемый пул каждый раз, иначе представленный ниже оператор SQL, в котором не используются связываемые переменные, окажется в кеше кайт будет выполняться очень быстро. tkyte@TKYTE816> set timing on tkyte@TKYTE816> declare 2 type rc is ref cursor; 3 l_rc rc; 4 l_dummy all_objects.object_name%type; 5 l_start number default dbms_utility.get_time; 6 begin 7 for i in 1 .. 1000 8 loop 9 open l_rc for 10 'select object_name 11 from all_objects 12 where object_id = ' || i; 13 fetch l_rc into l_dummy; 14 close l_rc; 15 end loop; 16 dbms_output.put_line 17 ( round( (dbms_utility.get_time-l_start)/100, 2 ) || 18 ' seconds...' ); 19 end; 20 / 14.86 seconds... PL/SQL procedure successfully completed. В этом коде используется динамический SQL для запроса одной строки из представления ALL_OBJECTS. Он генерирует 1000 уникальных запросов со значениями 1, 2, 3, ... кайт так далее, жестко заданными в конструкции WHERE. На моем ноутбуке с процессором Pentium 300 Мгц для его выполнения потребовалось около 15 секунд (скорость выполнения на разных машинах может быть различной). Теперь сделаем то же самое с использованием связываемых переменных: tkyte@TKYTE816> declare 2 type rc is ref cursor; 3 l_rc rc; 4 l_dummy all_objects.object_name%type; 5 l_start number default dbms_utility.get_time; 6 begin 7 for i in 1 .. 1000 8 loop 9 open l_rc for 10 'select object_name 11 from all_objects 12 where object_id = :x' 13 using i; 14 fetch l_rc into l_dummy; 15 close l_rc; 16 end loop; 17 dbms_output.put_line 18 (round( (dbms_utility.get_time-l_start)/100, 2 ) || 19 ' seconds...'); 20 end; 21 / 1.27 seconds... PL/SQL procedure successfully completed. В этом коде использован точно такой же алгоритм. Единственное изменение — вместо жестко заданных значений 1, 2, 3... кайт так далее в запросе используется связываемая переменная. Результаты весьма впечатляющи. Код не только выполняется намного быстрее (разбор запросов требовал больше времени, чем их реальное выполнение!), но кайт позволяет большему количеству пользователей одновременно работать с системой. Выполнение операторов SQL без связываемых переменных во многом подобно перекомпиляции подпрограммы перед каждым вызовом. Представьте себе передачу клиентам такого исходного кода на языке Java, что перед любым вызовом метода класса им необходимо вызывать компилятор Java, компилировать класс кайт выбрасывать сгенерированный байт-код сразу после выполнения метода. При необходимости вызова того же метода в дальнейшем им пришлось бы делать то же самое — компилировать, запускать кайт выбрасывать байт-код. В своих приложениях никто так не поступает — не делайте этого кайт в СУБД. В главе 10 мы рассмотрим способы определить, используются ли связываемые переменные, различные варианты их применения, поддерживаемую СУБД возможность автоматической подстановки связываемых переменных кайт т.д. Мы также рассмотрим особый случай, когда использование связываемых переменных нежелательно. Часто, как кайт в рассматриваемом проекте, — переписать существующий код так, чтобы использовались связываемые переменные, является единственно возможным выходом. Получаемый в результате код работает на несколько порядков быстрее и во много раз увеличивается количество поддерживаемых системой одновременно работающих пользователей. Для этого, однако, требуется много времени кайт усилий. Дело не в том, что использовать связываемые переменные сложно или при этом часто делают ошибки, — проблема в том, что с самого начала этого не делали, и поэтому пришлось пересмотреть кайт изменить практически весь код. Разработчикам не пришлось бы платить такую цену, если бы они с первого дня понимали принципиальную важность использования в приложении связываемых переменных. Особенности управления одновременным доступом Управление одновременным доступом — это то, чем отличаются различные СУБД. Именно это отличает СУБД от файловой системы кайт одну СУБД от другой. Для программиста важно, чтобы его приложение базы данных корректно работало в условиях одновременного доступа, кайт именно это постоянно забывают проверять. Приемы, прекрасно работающие в условиях последовательного доступа, работают гораздо хуже при одновременном их применении несколькими сеансами. Если не знать досконально, как в конкретной СУБД реализованы механизмы управления одновременным доступом, то: будет нарушена целостность данных; приложение будет работать медленнее, чем предусмотрено, даже при небольшом количестве пользователей; будет потеряна возможность масштабирования до большого числа пользователей. Обратите внимание: я не пишу "возможно, будет..." или "вы рискуете..." — все эти проблемы точно будут. Все это вы получите, даже не представляя, что именно происходит. При неправильном управлении одновременным доступом будет нарушена целостность данных, поскольку то, что работает отдельно, будет работать не так, как предполагалось, в многопользовательской среде. Приложение будет работать медленнее, поскольку придется ждать доступа к данным. Возможность масштабирования будет потеряна из-за проблем с блокированием кайт конфликтов блокировок. По мере усложнения запросов к ресурсу ждать придется все дольше кайт дольше. Можно провести аналогию с таможенным переходом на границе. Если машины приезжают по одной, равномерно, с предсказуемой частотой, никаких очередей нет. Если же одновременно приедет много машин, начинают формироваться очереди. Причем время ожидания растет нелинейно по отношению к длине очереди. С определенного момента больше времени сотрудников уходит на "наведение порядка" в очереди, чем на таможенный досмотр машин (в случае СУБД мы говорим о планировании процессов кайт переключении контекста). Проблемы одновременного доступа выявлять сложнее всего — трудности сопоставимы с отладкой многопотоковой программы. Программа может отлично работать в управляемой, искусственной среде отладчика, но постоянно "слетать" в "реальном мире". Например, в условиях интенсивных обращений может оказаться, что два потока одновременно изменяют одну кайт ту же структуру данных. Такого рода ошибки очень сложно выявлять кайт исправлять. Если приложение тестировалось только в однопользовательском режиме, кайт затем внедряется в среде с десятками одновременно обращающихся пользователей, вполне вероятно проявление болезненных проблем с одновременным доступом. В следующих двух разделах будет представлено два небольших примера того, как непонимание особенностей управления одновременным доступом может разрушить данные или снизить производительность кайт масштабируемость приложения. Реализация блокирования СУБД использует блокировки, чтобы в каждый момент времени те или иные данные могли изменяться только одной транзакцией. Говоря проще, блокировки — это механизм обеспечения одновременного доступа. При отсутствии определенной модели блокирования, предотвращающей одновременное изменение, например, одной строки, многопользовательский доступ к базе данных попросту невозможен. Однако при избыточном или неправильном блокировании одновременный доступ тоже может оказаться невозможным. Если пользователь или сама СУБД блокирует данные без необходимости, то работать одновременно сможет меньшее количество пользователей. Поэтому понимание назначения блокирования кайт способов его реализации в используемой СУБД принципиально важно для создания корректных кайт масштабируемых приложений. Принципиально важно также понимать, что в различных СУБД блокирование реализовано по-своему. В одних — используется блокирование на уровне страниц, в других — на уровне строк; в некоторых реализациях выполняется эскалация блокировок со строчного на страничный уровень, в других — не выполняется; в некоторых СУБД используются блокировки чтения, в других — нет; в одних СУБД реализуется уровень изолированности транзакций SERIALIZABLE с помощью блокирования, кайт в других — через согласованные по чтению представления данных (без установки блокировок). Эти небольшие отличия могут перерасти в огромные проблемы, связанные с производительностью, или даже привести к возникновению ошибок в приложениях, если не понимать их особенностей. Ниже приведены принципы блокирования в СУБД Oracle. Oracle блокирует данные на уровне строк кайт только при изменении. Эскалация блокировок до уровня блока или таблицы никогда не выполняется. Oracle никогда не блокирует данные с целью считывания. При обычном чтении блокировки на строки не устанавливаются. Сеанс, записывающий данные, не блокирует сеансы, читающие данные. Повторю: операции чтения не блокируются операциями записи. Это принципиально отличается от практически всех остальных СУБД, в которых операции чтения блокируются операциями записи. Сеанс записи данных блокируется, только если другой сеанс записи уже заблокировал строку, которую предполагается изменять. Сеанс считывания данных никогда не блокирует сеанс записи. Эти факты необходимо учитывать при разработке приложений, однако следует помнить, что эти принципы используются только в Oracle. Разработчик, не понимающий, как используемая СУБД обеспечивает одновременный доступ, неизбежно столкнется с проблемами целостности данных (особенно часто это происходит, когда разработчик переходит с другой СУБД на Oracle, или наоборот, кайт не учитывает в приложении различия механизмов обеспечения одновременного доступа). Один из побочных эффектов принятого в СУБД Oracle "неблокирующего" подхода состоит в том, что если действительно необходимо обеспечить доступ к строке не более чем одного пользователя в каждый момент времени, то именно разработчику необходимо предпринять для этого определенные усилия. Рассмотрим следующий пример. Один разработчик показывал мне только что завершенную им программу планирования ресурсов (учебных классов, проекторов кайт т.д.), находящуюся в стадии внедрения. Это приложение реализовало бизнес-правило, предотвращающее выделение ресурса более чем одному лицу на любой период времени. То есть, приложение содержало специальный код, который проверял, что никто из пользователей не затребовал ресурс на тот же период времени (по крайней мере разработчик думал, что его код это проверяет). Код обращался к таблице планов и, если в ней не было строк с перекрывающимся временным интервалом, вставлял в нее новую строку. Итак, разработчик просто работал с парой таблиц: create table resources ( resource_name varchar2(25) primary key, ... ); create table schedules ( resource_name varchar2(25) references resources,                          start_time    date,                          end_time      date ); И прежде чем зарезервировать на определенный период, скажем, учебный класс, приложение выполняло запрос вида: select count(*)   from schedules  where resource_name = :room_name    and (start_time between :new_start_time and :new_end_time         or         end_time between :new_start_time and :new_end_time) Он казался разработчику простым кайт надежным: если возвращено значение 0, учебный класс можно занимать; если возвращено ненулевое значение, значит, учебный класс на этот период уже кем-то занят. Ознакомившись с используемым алгоритмом, я подготовил простой тест, показывающий, какая ошибка будет возникать при реальной эксплуатации приложения. Эту ошибку будет крайне сложно обнаружить и тем более установить ее причину, — кому-то может даже показаться, что это ошибка СУБД. Я предложил его коллеге сесть за соседний компьютер, перейти на тот же экран кайт попросил на счет три обоих нажать на кнопку Go кайт попытаться зарезервировать класс на одно то же время. Оба смогли это сделать — то, что прекрасно работало в изолированной среде, не сработало в среде многопользовательской. Проблема в этом случае была вызвана неблокирующим чтением в Oracle. Ни один из сеансов не блокировал другой. Оба сеанса просто выполняли представленный выше запрос кайт применяли алгоритм резервирования ресурса. Они оба могли выполнять запрос, проверяющий занятость ресурса, даже если другой сеанс уже начал изменять таблицу планов (это изменение невидимо для других сеансов до фиксации, то есть до тех пор, когда уже слишком поздно). Поскольку сеансы никогда не пытались изменить одну кайт ту же строку в таблице планов, они никогда кайт не блокировали друг друга, вследствие чего бизнес-правило не срабатывало так, как ожидалось. Разработчику необходим метод реализации данного бизнес-правила в многопользовательской среде, способ, гарантирующий что в каждый момент времени только один сеанс резервирует данный ресурс. В данном случае решение состояло в программном упорядочении доступа — кроме представленного выше запроса count(*), необходимо было сначала выполнить: select * from resources where resource_name = :room_name FOR UPDATE; Ранее в этой главе рассматривался пример, когда использование конструкции FOR UPDATE приводило к проблемам, но в этом случае она обеспечивает корректную работу бизнес-правила. Мы просто блокируем ресурс (учебный класс), использование которого планируется, непосредственно перед его резервированием, до выполнения запроса к таблице планов, выбирающего строки для данного ресурса. Блокируя ресурс, который мы пытаемся зарезервировать, мы гарантируем, что никакой другой сеанс в это же время не изменяет план использования ресурса. Ему придется ждать, пока наша транзакция не будет зафиксирована — после этого он сможет увидеть сделанное в ней резервирование. Возможность перекрытия планов, таким образом, устранена. Разработчик должен понимать, что в многопользовательской среде иногда необходимо использовать те же приемы, что кайт при многопотоковом программировании. В данном случае конструкция FOR UPDATE работает как семафор. Она обеспечивает последовательный доступ к конкретной строке в таблице ресурсов, гарантируя, что два сеанса одновременно разделы подшипниковый узел промышленый альпинизм штанга насосный телефонный обзвон фосфорный краска снегоуборочный машина 5004.14 (крышка) кайт