|
Программирование >> Oracle
низм AQ, мы не ждем генерации идентификатора транзакции, - мы обращаемся к системе с просьбой сделать это когда-нибудь. Поэтому приложение опять оказалось в тупике. С одной стороны, мы не можем ждать завершения процесса 45 секунд, но, с другой стороны, для перехода к следующему экрану необходим сгенерированный идентификатор, а получить его можно только спустя 45 секунд. Для того чтобы решить эту проблему, пришлось синтезировать собственный поддельный идентификатор транзакции, изменить продолжительный процесс так, чтобы он принимал этот сгенерированный поддельный идентификатор и обновлял таблицу, записывая его по завершении работы, благодаря чему реальный идентификатор транзакции связывался с поддельным. То есть, вместо получения реального идентификатора в результате длительного процесса, этот идентификатор становится для процесса входными данными. Во всех подчиненных таблицах использовался этот поддельный идентификатор транзакции, а не ре-альн1й (поскольку генерации реального надо ждать определенное время). Нам также пришлось пересмотреть использование этого идентификатора транзакции, чтобы понять, как это изменение повлияет на другие модули, и так далее. Еще одна проблема состояла в том, что при синхронной работе, если 45-секундный процесс завершался неудачно, пользователь узнавал об этом сразу. Он мог устранить причину ошибки (обычно путем изменения входных данных) и повторно выполнить запрос. Теперь, когда транзакции выполняются асинхронно с помощью механизма AQ, сделать это невозможно. Для поддержки отсроченного уведомления об ошибке пришлось добавить новые средства. В частности, понадобилось реализовать механизм потоков заданий для отправки информации о неудавшихся транзакциях соответствующему лицу. В результате пришлось существенно пересмотреть структуру базы данных. Пришлось добавить новое программное обеспечение (AQ). Пришлось также создать новые процессы (управление потоками заданий и другие служебные процессы). К положительным последствиям этих изменений можно отнести не только решение проблемы с архитектурой MTS, но и удобство для пользователя (создавалась видимость более быстрой реакции системы). С другой стороны, все эти изменения существенно задержали завершение проекта, поскольку проблемы были выявлены лишь непосредственно перед внедрением, на этапе тестирования масштабируемости. Очень жаль, что приложение сразу не был правильно спроектировано. Если бы разработчики знали, как физически реализован механизм MTS, было бы ясно, что исходный проект не обеспечивает требуемой масштабируемости. Используйте связываемые переменные Если бы мне пришлось писать книгу о том, как создавать немасштабируемые приложения Oracle, первая и единственная ее глава называлась бы Не используйте связываемые переменные . Это - основная причина проблем, связанных с производительностью, и основная помеха масштабируемости. Особенности использования разделяемого пула Oracle (очень важной структуры данных разделяемой памяти) требуют от разработчиков использовать связываемые переменные. Если надо замедлить работу приложения Oracle, вплоть до полного останова, - откажитесь от их использования. Связываемая переменная - это подставляемый параметр запроса. Например, для получения записи доя сотрудника с номером 123, можно выполнить запрос: select * from emp where empno = 123; Но можно задать и другой запрос: select * from emp where empno = :empno; B обычной системе информацию о сотруднике с номером 123 могут запрашивать всего один раз. В дальнейшем будут запрашивать информацию о сотрудниках с номерами 456, 789 и т.д. При использовании в запросе литералов (констант) каждый запрос является для СУБД абсолютно новым, никогда ранее не выполнявшимся. Его надо разбирать, уточнять (определять объекты, соответствующие именам), проверять права доступа, оптимизировать и т.д. - короче, кажд1й выполняемый уникальный оператор придется компилировать при каждом выполнении. Во втором запросе используется связываемая переменная, :empno, значение которой подставляется в запрос при выполнении. Этот запрос компилируется один раз, а затем план его выполнения запоминается в разделяемом пуле (в библиотечном кэше), из которого его можно выбрать для повторного выполнения. Различие между этими двумя вариантами в плане производительности и масштабируемости - огромное, даже принципиальное. Из представленного выше описания вполне понятно, что разбор оператора с явными, жестко заданными константами (так называемый жесткий разбор) выполняется дольше и требует намного больше ресурсов, чем повторное использование уже сгенерированного плана запроса (его называют мягким разбором). Менее очевидным может оказаться, насколько постоянный жесткий разбор сокращает количество пользователей, поддерживаемых системой. Отчасти это связано с повышенным потреблением ресурсов, но в гораздо большей степени - с механизмом защелок, используемых в библиотечном кэше. При жестком разборе запроса СУБД будет дольше удерживать определенные низкоуровневые средства обеспечения последовательного доступа, которые называются защелками (подробнее о них см. в главе 3). Защелки защищают структуры данных в разделяемой памяти сервера Oracle от одновременного изменения двумя сеансами (иначе эти структуры данных Oracle в конечном итоге были бы повреждены) и от чтения этой структуры данных по ходу изменения другим сеансом. Чем чаще и на более продолжительное время на эти структуры данных устанавливаются защелки, тем длиннее становится очередь для установки этих защелок. Точно так же происходит при использовании длинных транзакций в среде MTS, - монополизируются критические ресурсы. Временами машина может казаться минимально загруженной, а СУБД работает очень медленно. Вполне вероятно, что один из сеансов удерживает защелку и формируется очередь в ожидании ее освобождения. В результате работа с максимальной скоростью невозможна. Достаточно одного неверно работающего приложения для существенного снижения производительности всех остальных приложений. Одно небольшое приложение, не использующее связываемые переменные, приводит со временем к удалению из разделяемого пула необходимых SQL-операторов других хорошо настроенн1х приложений. Достаточно ложки дегтя, чтобы испортить бочку меда. При использовании связываемых переменных любой сеанс, выдающий тот же самый запрос, будет использовать уже скомпилированный план выполнения из библиотечного кэша. Подпрограмма компилируется один раз, а используется многократно. Это очень эффективно, и именно такую работу пользователей предполагает СУБД. При этом не только используется меньше ресурсов (мягкий разбор требует намного меньше ресурсов), но и защелки удерживаются значительно меньше времени, и нужны гораздо реже. Это повышает производительность и масштабируемость. Чтобы хоть примерно понять, насколько существенно это может сказаться на производительности, достаточно выполнить очень простой тест: 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
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |