Сообщений 1    Оценка 70        Оценить  
Система Orphus

Правильный подход к созданию приложений

Глава из книги “Oracle: Эффективное проектирование приложений”

Автор: Т. Кайт
Источник: Oracle: Эффективное проектирование приложений
Материал предоставил: Издательство ''Питер''
Опубликовано: 01.07.2006
Версия текста: 1.0
Работа в команде
Роли администраторов базы данных и разработчиков
Чтение документации
Руководство к руководствам
Путеводитель по чтению
Избегайте синдрома черного ящика
Выбор между независимостью и зависимостью от базы данных
Опасности синдрома черного ящика
Это база данных, а не свалка данных
Использование первичного и внешнего ключей
Проверка издержек ссылочной целостности
Создание тестовой среды
Тестирование с использованием репрезентативных данных
Не стоит производить тестирование с одним пользователем
Не стоит производить тестирование в идеальных условиях
Проектирование производительности: не настраивайте производительность
Не стоит использовать универсальные модели данных
Проектирование эффективной модели данных
Определяйте цели производительности в самом начале
Используйте понятную, специализированную систему показателей
Собирайте и регистрируйте показатели во времени
Не стоит что-то делать только потому, что «все знают, что это нужно»
Об оценке производительности снова и снова
Краткосрочная оценка производительности
Долгосрочная оценка производительности
Инструментирование системы
Трассировка asktom.oracle.com
Инструментальное средство удаленной отладки
Использование DBMS_APPLICATION_INFO
Использование DEBUG.F в PL/SQL
Настройка SQL_TRACE в приложении
Использование стандартных API
Построение собственной подпрограммы
Слово «аудит» не является непристойным
Вопрос авторства
Следует остерегаться универсального «лучше»
Подозрительные коэффициенты и другие мифы
Не стоит искать коротких путей
Чем проще, тем лучше
Рассмотрение альтернативных методов
Стоит позволить базе данных делать то, для чего она предназначена
Использование поставляемой функциональности
Мы слышали, что возможность X работает медленно
Мы слышали, что применять средство Х очень сложно
Мы не хотим...
Мы не знаем...
Мы хотим быть независимыми от базы данных
Итоги

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

Все эти вопросы рассматриваются в данной главе.

Работа в команде

Работа в команде не имеет ничего общего с технологией или программным обеспечением. Это только взаимодействие людей. Обратите внимание, что большинство проблем, возникающих в процессе разработки программного обеспечения, по большей части связаны с политическими аспектами, а не с технологией. Сколько раз я наблюдал, как все труды разработчиков сводились на нет не технической сложностью поставленной задачи, а проблемами, связанными с распределением ресурсов, с организацией рабочего процесса и с политикой.

Нередко отношения между членами команды разработчиков и команды администраторов базы данных (DBA), которые должны оказывать поддержку друг другу, строятся по принципу «мы против них». В подобных условиях администраторы считают необходимым защитить базу данных от разработчиков. С другой стороны, разработчики считают, что администраторы им мешают. Порой, пытаясь заставить трудиться эти две группы вместе, я ощущаю себя в большей степени брачным адвокатом, чем экспертом базы данных.

Мы должны всегда помнить, что работа в команде подобна улице с двухсторонним движением. Разработчики часто жалуются, что администраторы требуют от них слишком много объяснений по поводу предоставления права доступа или разрешений. В действительности, это разумное требование. Раздача прав доступа заслуживает внимательного и обдуманного подхода. Нет ничего неправильного в том, что администратор может попросить разработчика, чтобы тот объяснил необходимость, например, предоставления его учетной записи права CREATE VIEW (см. ниже). Но, с другой стороны, крайне неразумно запрещать использование таких средств базы данных, как представления базы данных, хранимые процедуры и триггеры.

Ниже приведен один из примеров подобных отношений (он взят с сайта AskTom):

«Использование хранимых процедур — плохо ли это?

В чем заключается отрицательная сторона применения хранимых процедур? Вызывает ли это какие-то побочные явления? Я новичок в администрировании базы данных Oracle. Один из программистов SQL попросил меня предоставить ему системные права на СREATE PROCEDURE...»

В своем ответе я объяснил несомненные выгоды от использования хранимых процедур, указал администратору, каким образом они могут помочь ему в настройке приложений (без детального изучения работы приложения). Я подчеркнул, что хранимые процедуры являются механизмом безопасности и позволяют увеличить производительность. Я посоветовал администратору разрешить использование хранимых процедур, поскольку, размещая «плохой» SQL в клиентских приложениях, разработчики могут доставить гораздо больше проблем. К тому же я указал, что размещение SQL-операторов в хранимых процедурах дает администраторам возможность контролировать разработчиков.

Последовавшие ответы были довольно агрессивны и лишний раз демонстрировали наличие пропасти в отношениях между разработчиками и администраторами базы данных. Точку зрения администраторов можно описать следующим образом: «Моя работа заключается в защите базы данных». В то время как разработчики считают: «Моя работа заключается в написании программ, а их задача — позволить мне делать это». Один разработчик высказал мнение, что в обязанности администраторов входит также проверка каждой строчки кода, который используется в базе данных. А администратор заявил, что это дело разработчика.

В действительности, это непосильная для администратора задача — просмотреть каждую строчку кода в базе данных. Более того, администратор — не разработчик, и он не сможет понять, о чем идет речь, даже если просмотрит исходный код.

Очевидно, что разработчики и администраторы выполняют различные задачи. Работа администратора заключается не только в защите базы данных, и он не должен заниматься обслуживанием разработчика.

Подобно чрезмерно заботливому родителю, работа администратора, который придерживается позиции «защитить базу данных от разработчика», не будет продуктивна. С другой стороны, разработчики не программируют назло администраторам (вопреки расхожему мнению администраторов). Разработчики всего лишь пытаются сделать свое дело. Их основная задача — построение функционального приложения, удовлетворяющего требованиям конечного пользователя, производительного, масштабируемого, удобного для эксплуатации и разработанного в разумных стоимостных пределах. До тех пор пока эти две команды не согласятся вместе идти к конечной цели, шансы на успех будут невелики. Пока между ними существует виртуальная стена, многие из задач не смогут быть реализованы.

Роли администраторов базы данных и разработчиков

Обычно администраторы базы данных знают о самой базе и ее работе больше разработчиков, в то время как разработчики знают больше администраторов о том, как создавать программное обеспечение. Это общее понимание их должностных обязанностей.

Администраторы должны разбираться в архитектуре базы данных, знать, как и когда установить обновление и как работает база данных. Они не смогут успешно выполнить процедуру резервного копирования и восстановления данных (например, если их этому не обучали) без глубокого понимания и постоянного изучения архитектуры базы данных Oracle. Если администратор не понимает архитектурных взаимоотношений между управляющими файлами базы данных, файлами с данными и журналами базы данных, он будет совершать ошибки при резервном копировании и восстановлении. Жизнь администратора тесно связана с работой базы данных.

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

И тем не менее есть точки пересечения. Если две эти команды будут трудиться сообща, то разработчики будут знать больше о работе базы данных, а администраторы смогут помочь им в процессе проектирования.

Я составил два списка того, что должны и чего не должны делать разработчики и администраторы баз данных. Это поможет им закрыть вопрос взаимодействия раз и навсегда.

Что должны и чего не должны делать администраторы

Администраторы! Не считайте, что ваша основная цель — защитить базу данных от разработчиков. База данных является их инструментом, и вы должны помогать и советовать им, как лучше ее использовать. Не считайте разработчика вашим противником.

Кроме того, не отказывайтесь от использования тех или иных средств без весомых на то причин. Очень часто подобные решения основаны на опасении и неуверенности или же на однократном неудачном опыте применения. Рассмотрим общие ограничения:

Что должны и чего не должны делать разработчики

Администраторы базы данных и разработчики должны трудиться вместе как единая команда. Они должны встречаться и регулярно обсуждать все вопросы. Они не должны считать себя двумя независимыми подразделениями, перебрасывающими друг другу работу. Подход «они против нас» сделает невозможным создание быстрой, надежной и доступной системы. На мой взгляд, имеет смысл работать единой командой, используя специфические знания и обмениваясь ими друг с другом.

Чтение документации

Документация к базе данных Oracle насчитывает более 100 руководств (108 вместе с Oracle 9i выпуска 2). Это свыше 46 000 страниц текста. Такой объем информации запугает кого угодно и отобьет охоту к изучению. В действительности, люди больше симулируют испуг, чем боятся на самом деле. Необходимо лишь определиться, какая информация нужна, и найти требуемый раздел. Для начала выясним, какие части документации являются ключевыми.

«Я изучил большой объем информации на вашем сайте. Один из моментов, о котором вы постоянно говорите, — это чтение общего руководства (Concepts guide). Пару недель назад я скопировал на мой портативный компьютер PDF-версию этого руководства и начал его изучать. Я очень рад, что поступил так! Многие ранее непонятные вопросы теперь стали ясны. Я работаю консультантом разработчиков и администраторов базы данных и теперь знаю больше них».

Это лучшая награда для меня. Я часто получаю вопросы, начинающиеся со слов «Пожалуйста, не отсылайте меня к документации». Подобные просьбы я игнорирую, если ответ на вопрос хорошо изложен в документации. Нередко я начинаю свой ответ со слов: «Итак, решение своей проблемы вы найдете в общем руководстве (Concepts guide) в разделе...» Прочитайте общее руководство от корки до корки. Пусть даже в голове останется только 10% прочитанного, но знаний о базе данных Oracle и ее работе станет на 90% больше, чем у тех, кто не обращался к этой книге. И в дальнейшем, если вдруг возникнет какая-то проблема, специалист просто скажет себе: «Я помню, что читал об этом. Поищу решение в общем руководстве».

Руководство к руководствам

Несмотря на то, что существует более 100 книг с документацией, руководство к руководствам получилось на удивление сжатым. Руководства, рассмотренные ниже, — это та документация, которую следует прочитать от корки до корки (независимо от принадлежности к группе разработчиков или администраторов базы данных). К остальной документации можно обращаться по необходимости.

Общее руководство

Общее руководство (Concepts guide) — это та часть документации Oracle, которую необходимо читать при появлении каждого нового выпуска базы данных. Она содержит большой объем информации по следующим разделам:

Введение в базу данных, структура памяти, распределенная база данных, управление параллельным доступом, непротиворечивость данных, безопасность, администрирование и другие вопросы.

Углубленный взгляд на хранение данных. Кто, когда, где, зачем и почему разбивал на блоки, работал с экстентом и табличными пространствами, сегментировал базу данных.

Что собой представляет экземпляр Oracle: каковы процессы запуска и остановки, как приложения взаимодействуют с Oracle, как выглядит архитектура памяти и процесса и как управлять ресурсами базы данных.

Краткий обзор множества объектов схемы (таких, как таблицы, представления, индексы и т.п.), все опции, доступные для каждого типа объектов, все типы данных: и предопределенные в базе данных, и определяемые пользователем.

SQL, PL/SQL, взаимодействие Java с базой данных, управление и поддержка зависимостей между объектами схемы, триггеры и управление транзакциями.

Как и когда применяются параллельные операции. Параллельные запросы, параллельный DML, параллельные операции для администрирования и т.д.

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

Одним из достоинств общего руководства (Concepts guide) (помимо того, что оно бесплатно) является то, что оно содержит указания и ссылки на остальную документацию. Очень часто тема заканчивается словами «Смотрите также» и ссылками на документацию Oracle, где вопрос излагается более подробно. Таким образом, общее руководство (Concepts guide) — это книга, занимающая верхний уровень в описании возможностей Oracle и содержащая метаданные для остальной документации. После прочтения общего руководства пользователь сможет плавно перейти к изучению необходимой ему информации, углубляясь в ту или иную тему.

Руководство по новым возможностям

Руководство по новым возможностям (New features guide) — это обзор новых средств, вошедших в пару последних выпусков. В руководстве приводится краткое описание каждого нового средства с указателем на документацию, в которой содержатся более подробные сведения. В нем также дается перечень того, что включено в каждый вариант из семейства Oracle (Персональный, Стандартный, Корпоративный). В Oracle 8i и более ранних версиях это руководство называлось Getting to Know (Получение знаний).

Во многих документах содержится глава «Что нового?». В руководстве по новым возможностям (New features guide) рассматривается большинство новых средств, относящихся к администрированию, разработке, производительности, масштабируемости, работоспособности и т.п. Конкретный же документ содержит более подробный список «Что нового?». Например, в руководстве по администрированию (Administrators guide) будет содержаться глава «Что нового в администрировании?», а в руководстве для разработчиков приложений (Application developers guide) будет глава «Что нового в разработке приложений?».

Руководство для разработчиков приложений

Существует несколько руководств по различным средствам базы данных Oracle, начинающихся с руководства для разработчиков приложений (Application developers guide). К таким средствам относятся, например, организация очередей (Advanced queuing), LOB (большие объекты), особенности реляционных объектов (Object relational features) и управление рабочей областью (Workspace management). Кроме того, всем разработчикам рекомендуется прочитать руководство по основным принципам (Fundamentals guide). В нем рассматриваются такие вопросы, как:

Общее руководство (Concepts guide) рассказывает о новых средствах, в то время как руководство для разработчиков приложений (Application developers guide) объясняет, как этими средствами пользоваться.

Справочное руководство пользователя PL/SQL

PL/SQL является одним из наиболее важных языков для разработчиков, поскольку в процессе проектирования разработчик получает возможность доступа к базе данных. Справочное руководство пользователя PL/SQL (PL/SQL users guide and reference) рассматривает основные принципы PL/SQL, ошибки обработки, синтаксис, пакеты/процедуры и многое другое.

Справочное руководство по настройке производительности

Одним из наиболее часто используемых документов является справочное руководство по настройке производительности (в Oracle 8i и более ранних версиях оно называлось «Руководство по проектированию и настройке производительности» (Designing and tuning for performance guide)). Первая часть руководства обращена к разработчикам. В ней рассказывается о том, как оптимизировать работу, как корректно собрать статистику, как работают различные физические структуры и где их лучше использовать (например, в каком случае следует применять индексные таблицы). В этом документе описаны основные средства, которыми пользуется разработчик: Explain Plan, SQL_TRACE, TKPROF, Autotrace и Statspack (пакет сбора статистики)!

Вторая часть руководства предназначена для администраторов. В ней рассматриваются темы, посвященные построению производительной базы данных, конфигурации памяти, взаимодействию с операционной системой и использованию ресурсов, конфигурированию общих и специализированных серверов, вопросам сбора статистики и применения производительных представлений и других средств. Все это должно быть прочитано перед началом настройки и вводом в действие. Даже если администратор проработал с Oracle уже 100 лет, все равно он найдет для себя что-то новое и полезное в этом руководстве.

Концепция дублирования и восстановления

Дублирование и восстановление — это те функции, нарушения работы которых администратор не должен допустить. Даже если используются средства автоматического дублирования, все равно необходимо прочитать руководство по концепции дублирования и восстановления (Backup and recovery concepts). Четкое и глубокое понимание процессов дублирования и восстановления никогда не будет лишним.

Но только чтения этого руководства будет недостаточно. Нередко поступают вопросы от людей, которые прочитали это руководство, но не понимают, почему им не удается провести восстановление после возвращения в исходное состояние управляющих файлов прошлой недели. У них отсутствуют базовые знания о том, как взаимодействуют файлы и что необходимо для восстановления в данной ситуации. Администратор должен прочитать руководство по концепции дублирования и восстановления (Backup and recovery concepts). Если не все понятно с первого раза, необходимо читать снова и снова. Затем нужно проверить свои знания. И если вы не можете даже найти день обновления, то ваши знания ничего не стоят.

Руководство по управлению восстановлением

В руководстве по управлению восстановлением (Recovery manager reference) рассказывается о средствах, которые используются для резервного копирования базы данных. Можно забыть о старых сценариях и распрощаться с tar, cpio, dd и ocopy. В этом руководстве рассматриваются возможности восстановления на уровне блоков, своевременное восстановление, дублирование сохраняемых методов, быстрое резервирование и другие средства. Оно заслуживает вашего внимания.

Руководство для администратора

В руководстве для администраторов (Administrators guide) всегда можно найти что-то новое. Из этой книги можно узнать о том, что в составе базы данных есть менеджер ресурсов (появившийся в версии 8i и усовершенствованный в версии 9i), а также о том, как настроить аудит (появился в Oracle 9i).

Новые возможности Oracle, касающиеся администрирования базы данных, содержатся только в этом документе, так как они не настолько общие, чтобы размещать их в руководстве по новым возможностям (New features guide).

Путеводитель по чтению

Путеводитель — это документация, которую обязательно нужно прочитать. Я предлагаю три набора: один общий, второй для тех, кто считает себя администратором, и третий для тех, кто считает себя разработчиком.

Книги, необходимые и администраторам, и разработчикам

При выходе каждой новой версии базы данных администраторы и разработчики обязаны прочитать:

Книги, необходимые разработчикам

Разработчики должны также прочитать следующую документацию:

Разработчикам следует прочитать первую часть справочного руководства по настройке производительности и просмотреть на досуге остальные главы. В дополнение к руководству по Oracle 9i я предлагаю просмотреть справочник Performance Method (9i, выпуск 1) или Performance Planning (9i, выпуск 2). В этом руководстве, всего на 60-ти страницах, представлена информация, которая необходима для успешной работы по таким темам, как масштабируемость, архитектура системы, принципы проектирования приложения и т.п.

Книги, необходимые администраторам

Администраторы должны также прочитать следующие книги:

Книги, рекомендуемые для прочтения

После прочтения руководств, рекомендованных выше, можно перейти к специализированной документации. Например, разработчик, которому необходимы знания по XML, может найти не менее трех руководств по XML. Для тех, кого интересуют возможности Java, существуют руководства по этой теме. Если же администратору базы данных необходимо понять, каким образом установить и сконфигурировать стабильно работающую среду, он также сможет воспользоваться соответствующей документацией.

Если глубоко и подробно изучать документацию, всегда можно найти полезные вещи. Я говорю об этом вовсе не потому, что работаю в Oracle. Просто на сегодняшний день большинство моих знаний об Oracle я почерпнул именно из документов. Я читаю общее руководство (Concept guide) для пополнения своих знаний. Настоятельно рекомендую всем обращаться по адресу в Интернете http://otn.oracle.com и работать со ссылками на документацию. Там можно найти абсолютно все.

Избегайте синдрома черного ящика

Без фундаментальных знаний базы данных Oracle и ее функционирования разработчики неизбежно будут совершать ошибки. Долгое время базу данных считали черным ящиком — таким же заменяемым предметом потребления, как, например, аккумуляторы в радио. В соответствии с этим подходом пользователь базы данных любой ценой старается избежать зависимости от нее и поэтому отказывается от эксплуатации новых возможностей. Получается, что на деле большинство функций не применяются. Это означает, что пользователь идет по пути «делать все самому», т.е. пишет больше кода, чем это необходимо для работы, и теряет время.

Компании, выбравшей такой путь, будет не легко. Это стоит денег как с точки зрения оплаты более длительного процесса разработки, так и с точки зрения упущенных возможностей.

Выбор между независимостью и зависимостью от базы данных

Многие могут поспорить, но тем не менее: зависимость от базы данных (не имеет значения, какая база данных используется, речь идет не только об Oracle) должна быть целью, к которой нужно стремиться, а не избегать ее. Если руководство предприятия считает возможным вкладывать в базу данных денежные средства и хочет, чтобы программное обеспечение разрабатывалось за минимально короткое время, то единственный путь — это полностью использовать все возможности базы данных.

Дело в том, что, если не считать самых простых приложений, независимость от базы данных — удовольствие чрезвычайно дорогое и поглощающее огромное количество ресурсов. Да, простой отчет может быть независимым от базы данных. Но возможно ли это в случае масштабируемой системы транзакций? Нет, если только не применяется подход компании PeopleSoft или SAP. Подобно продуктам этих компаний, ваше приложение не будет использовать SQL после простого «закрытого чтения» (может быть применена сортировка подобно VSAM-файлам на мэйнфрейме, файл читается только закрытым — никаких соединений, никакого анализа, только закрытое чтение). Оно не будет пользоваться никакими расширениями производителей и функциями ANSI SQL, так как они реализуются не всеми производителями. Невозможно будет применять базу данных для управления параллельным доступом и для анализа (поскольку все сделано по-разному). В принципе, написание базы данных на этом и закончится. Фактически, SAP поступил следующим образом — написал свою собственную базу данных.

Так что, если не стоит задача создания продукта, который будет работать в качестве готового программного обеспечения с множеством различных баз данных, то независимость базы данных не должна являться конечной целью. В действительности, большая часть программного обеспечения создана именно для работы со стандартной корпоративной базой данных. Представляется сомнительной в подобных обстоятельствах необходимость в независимости от базы данных. Создать приложение быстро и всего лишь с помощью нескольких строчек кода — это то, к чему необходимо стремиться. А работа с условием независимости от базы данных или, что еще хуже, пренебрежение (преднамеренное или как результат недостаточных знаний) возможностями базы данных не должно быть целью.

Лучшим способом, позволяющим достичь некоторого уровня мобильности приложения для множества баз данных, является кодирование всех компонентов базы данных, используемых приложением, в хранимых процедурах. В связи с этим возникает вопрос: если кодирование производится в хранимых процедурах, а каждый производитель имеет свой собственный язык, не возникнет ли зависимость от производителя? И да, и нет. Видимые компоненты приложения в безопасности. Логика приложения (существующая вместе с логикой данных) тоже в безопасности. Логика данных кодируется в процессе работы с базой данных.Так как все это скрыто в хранимой процедуре, пользуясь (на самом деле это всегда нужно использовать) расширенными средствами каждого производителя, можно получить данные более высокого уровня (см. ниже).

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

Опасности синдрома черного ящика

Ниже приводятся причины, по которым не следует рассматривать базу данных как черный ящик:

  1. Невозможность получения корректного ответа
  2. Средства управления параллельным доступом — это главное отличие между базами данных. В зависимости от того, с какой базой данных работают приложения, они получат различные результаты, несмотря на одинаковые входные данные и порядок ввода запроса.
  3. Невозможность получения высокой производительности
  4. Производительность будет всего лишь долей того, что могло и должно было бы быть.
  5. Невозможность быстрого создания программного обеспечения
  6. Придется тратить много времени на то, чтобы делать все самому.
  7. Неэффективное вложение средств
  8. Если есть возможность потратить большую сумму денег на программное обеспечение базы данных, то их нужно тратить. Однако хочется с иронией отметить ситуацию, когда меняют производителя программного обеспечения по той причине, что его приложения работают недостаточно хорошо, в то время как причиной плохой работы является, в первую очередь, неполное использование возможностей программ этого производителя.

Данный список не упорядочен. В зависимости от того, кем является пользователь, пункты могут иметь для него различное значение. Рассмотрим несколько примеров.

Невозможность получения высокой производительности

Предположим, необходимо разработать приложение, осуществляющее сортировку работников или материалов в перечне материалов в определенном порядке. В Oracle любые иерархии строятся с помощью SQL-оператора CONNECT BY. Наиболее эффективным является следующее решение (используется стандартная в Oracle таблица SCOTT.EMP):

scott@ORA920.US.ORACLE.COM> select rpad ('*',2*level,'*') || ename ename
  2  from emp
  3  start with mgr is null
  4  connect by prior empno = mgr
  5  /
ENAME
--------------------------------
**KING
****JONES
******SCOTT
********ADAMS
******FORD
********SMITH
****BLAKE
******ALLEN
******WARD
******MARTIN
******TURNER
******JAMES
****CLARK
******MILLER

14 rows selected.

Оператор CONNECT BY не универсален. Многие базы данных не поддерживают этот синтаксис. В других базах данных необходимо написать некоторую процедуру и поместить результаты во временную таблицу. Можно было бы и в Oracle сделать так же, но зачем? Это будет медленнее и займет больше ресурсов, одним словом, это неверный подход к решению задачи. А подход должен быть верным. Значит, необходимо использовать конструкцию CONNECT BY и «спрятать» ее в хранимую процедуру. Ее можно реализовать по-разному в различных базах данных, например в Microsoft SQL Server, если потребуется (по моему опыту, это бывает редко).

Продолжим. Допустим, что необходимо получить в качестве результата информацию о работниках: имя работника, департамент и заработную плату. Также требуется получить общую сумму заработной платы по департаментам и процент заработной платы конкретного работника в сумме департамента и общей сумме (например, работник X в департаменте Y получает 10% заработной платы от суммы заработной платы его департамента и 1% от общей суммы заработной платы компании). Правильным подходом к решению этой задачи в Oracle является использование аналитических функций:

scott@ORA920.US.ORACLE.COM> column pct_dept format 99.9
scott@ORA920.US.ORACLE.COM> colums pct_overall format 99.9
scott@ORA920.US.ORACLE.COM> break on deptno skip 1

scott@ORA920.US.ORACLE.COM> select deptno,
  2         ename,
  3         sal,
  4         sum(sal) over (partition by deptno order by sal,ename) cum_sal,
  5         round(100*ratio_to_report(sal)
  6              over (partition by deptno), 1 ) pct_dept,
  7         round(100*ratio_to_report(sal) over ( ) , 1 ) pct_overall
  8   from emp
  9  order by deptno, sal
 10 /

DEPTNO-------ENAME---------SAL-------CUM_SAL--------PCT_DEPT--------PCT_OVERALL-----------
10MILLER1300130014.94.5
CLARK2450375028.08.4
KING5000875057.117.2
20SMITH8008007.42.8
ADAMS1100190010.13.8
JONES2975487527.410.2
FORD3000787527.610.3
SCOTT30001087527.610.3
30JAMES95095010.13.3
MARTIN1250220013.34.3
WARD1250345013.34.3
TURNER1500495016.05.2
ALLEN1600655017.05.5
BLAKE2850940030.39.8
14 rows selected.

Однако многие реляционные базы данных не имеют такого средства, как аналитические функции, так что, в сущности, это зависимая от базы данных технология. Существует другой способ решения этой задачи, который будет работать в большинстве баз данных. Этот подход предполагает использование соединений, представлений и т.п.

scott@ORA920.US.ORACLE.COM> select emp.deptno,
  2         emp.ename,
  3         emp.sal,
  4         sum(emp4.sal) cum_sal,
  5         round(100*emp.sal/emp2.sal_by_dept,1) pct_dept,
  6         round(100*emp.sal/emp3.sal_overall,1) pct_overall,
  7    from emp,
  8         (select deptno, sum(sal) sal_by_dept
  9            from emp
 10           group by deptno ) emp2,
 11         (select sum(sal) sal_overall
 12            from emp ) emp3,
 13         emp emp4
 14   where emp.deptno = emp2.deptno
 15     and emp.deptno = emp4.deptno
 16     and (emp.sal > emp4.sal or
 17           (emp.sal = emp4.sal and emp.ename >= emp4.ename))
 18   group by emp.deptno, emp.ename, emp.sal,
 19         round (100*emp.sal/emp2.sal_by_dept,1),
 20         round (100*emp.sal/emp3.sal_overall,1)
 21   order by deptno, sal
 22 /

DEPTNO--------ENAME----------SAL-------CUM_SAL--------PCT_DEPT--------PCT_OVERALL-----------
10MILLER1300130014.94.5
CLARK2450375028.08.4
KING5000875057.117.2
20SMITH8008007.42.8
ADAMS1100190010.13.8
JONES2975487527.410.2
FORD3000787527.610.3
SCOTT30001087527.610.3
30JAMES95095010.13.3
MARTIN1250220013.34.3
WARD1250345013.34.3
TURNER1500495016.05.2
ALLEN1600655017.05.5
BLAKE2850940030.39.8
14 rows selected.

Такой метод работает и является независимым от базы данных. Однако вряд ли в компаниях, где будет использоваться эта функциональность, работает всего 14 человек. Обычно счет идет на сотни и тысячи. Попробуем увеличить масштаб примера и посмотрим, как это повлияет на производительность. Применим эту процедуру к наборам данных разных размеров:

Строк в таблицеПроцессор/аналитические функцииПроцессор/универсальные функцииРазница
20000.052.1342 раза
40000.098.5795 раз
80000.1935.88188 раз

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

Таким образом, если бы я был конечным пользователем, я однозначно не выбрал бы универсальный метод. Этот пример показывает, почему аналитические средства, на упаковке которых написано «мы работаем с вашей родной базой данных», более предпочтительны, чем средства, которые хвастаются: «мы универсально работаем на 15 базах данных». Средства и приложения, которые предназначены для конкретной базы данных, будут более производительны по сравнению с универсальными решениями. Единственные, кто будет рад применению универсальных решений, — это поставщики аппаратных средств, ведь пользователю придется наращивать мощность процессора.

Существуют и другие возможные решения этой проблемы. Можно, например, использовать временные таблицы для размещения данных. Однако все эти подходы обладают теми же недостатками, что и описанные ранее: они являются неверным решением для Oracle (возможно, для других баз данных они являются правильными), и все они требуют написания большого объема кода, что является трудоемким процессом.

А вот другой пример. Я работал с заказчиком, который отказался от применения битового индекса. Его мнение было следующим: «Не во всех программах он работает. Я не хочу помещать его в мою систему, так как индекс, создаваемый в Oracle, может не работать в других базах данных. Мне хотелось бы использовать универсальный сценарий, который будет без труда работать во всех базах данных». При использовании битового индекса выполнение запроса занимало бы меньше минуты, а не часы. Но заказчик решил «наказать» все реализации, поскольку не во всех из них была эта специфическая возможность. Не знаю, как другие, но лично я выбрал бы битовый индекс в Oracle, чем работу вообще без индекса.

Невозможность получения корректного ответа

Если пользователь воспринимает базу данных как черный ящик, возникают трудности не только с достижением высокой производительности, но и с получением корректного ответа. В предыдущем разделе результаты были достаточно очевидны; при рассмотрении производительности обработки данных удалось сразу выявить правильный и неправильный подходы к решению задачи. Однако не всегда сразу можно определить, где была допущена ошибка.

Рассмотрим пример, касающийся управления параллельным доступом (многовариантность, согласованность чтения, блокировка и т.п.), поскольку это те вопросы, на которых защитники идеологии черного ящика часто проваливаются. Применим простую транзакцию к родительской/дочерней таблице. Цель этой задачи — сохранить результат суммирования строк дочерней таблицы в родительской таблице, например, сохранить сумму индивидуальных зарплат работников в таблице департамента. В примере мы используем две простые таблицы:

ops$tkyte@ORA920> create table dept
  2  ( deptno int primary key,
  3    sum_of_salary number
  4  );
Table created.

ops$tkyte@ORA920> create table emp
 2  ( empno int primary key,
 3    deptno references dept,
 4    salary number
 5  );
Table created.

ops$tkyte@ORA920> insert into dept ( deptno ) values (1);
1 row created.

ops$tkyte@ORA920> insert into dept ( deptno ) values (2);
1 row created.

После выполнения транзакций в дочерней таблице EMP можно применить оператор UPDATE к родительской таблице DEPT для того, чтобы синхронизировать столбец SUM_OF_SALARY. Например, включим в транзакцию оператор UPDATE, идущий последним:

ops$tkyte@ORA920> insert into emp (empno, deptno, salary)
  2 values (100, 1, 55);
1 row created.

ops$tkyte@ORA920> insert into emp (empno, deptno, salary)
  2  values (101, 1, 50);
1 row created.

ops$tkyte@ORA920> update dept
  2     set sum_of_salary =
  3     ( select sum (salary)
  4         from emp
  5            where emp.deptno = dept.deptno )
  6   where dept.deptno = 1;
1 row updated.

ops$tkyte@ORA920> commit;
Commit complete.

Это выглядит просто — только вставить дочернюю запись и обновить сумму записей в родительской таблице. Можно ли допустить ошибку в таком простом примере? Если мы сейчас выполним запрос к схеме, то получим:

ops$tkyte@ORA920> select * from emp;

EMPNO-----------DEPTNO-----------SALARY--------
100155
1011 50

ops$tkyte@ORA920> select * from dept;

DEPTNO-----------SUM_OF_SALARY---------------
1105
2

Если добавить строки к дочерней таблице для DEPTNO 1 или DEPTNO 2 и запустить обновление, то все будет хорошо. Но не был рассмотрен вопрос, что произойдет при одновременном (параллельном) доступе к этой таблице. Что, если два пользователя работают с дочерней таблицей EMP в одно и то же время? Один пользователь будет добавлять нового работника в DEPTNO 2. Второй пользователь будет переводить EMPNO 100 из DEPTNO 1 в DEPTNO 2. Рассмотрим случай, когда эти транзакции выполняются синхронно. Проследим за событиями:

ВремяРабота сессии 1Работа сессии 2
Т1Ввод в EMP (EMPNO, DEPTNO, SALARY) значений (102,2,60); добавляется новый работник в DEPTNO 2
Т2Обновление EMP (set DEPTNO=2 where EMPNO=100); перевод работника из одного департамента в другой
Т3Обновление DEPT для департаментов 1 и 2 после модификации записей в обоих департаментах
Т4Обновление DEPT для департамента 2, департамент изменен. Это будет блокировано, поскольку в сессии 2 есть заблокированные строки. Однако компонент запроса UPDATE начнет свою работу в случае, если результирующий набор уже построен. Механизм согласованного чтения в Oracle вернет к оператору UPDATE, что будет зафиксировано в базе данных в момент времени Т4.
Т5Завершение транзакции, сессия 1 разблокируется
Т6Завершение транзакции

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

Ниже представлен пример с двумя сессиями. Командная строка SQLPlus показывает, с какой сессией идет работа в настоящий момент. Можно открыть одновременно два окна SQLPlus при выполнении этого примера:

Session 1> insert into emp ( empno, deptno, salary )
  2  values ( 102, 2, 60 )
1 row created.

Session 2> update emp
  2     set deptno = 2
  3   where empno = 100;
1 row updated.

Session 2> update dept
  2     set sum_of_salary = ( select sum (salary)
  3                             from emp
  4                            where emp.deptno = dept.deptno )
  5   where dept.deptno in ( 1, 2 );
2 rows updated.

Session 1> update dept
  2     set sum_of_salary = ( select sum (salary)
  3                             from emp
  4                            where emp.deptno = dept.deptno )
  5   where dept.deptno =  2;

Теперь сессия 1 будет блокирована. Это происходит после попытки модифицировать строку, которая уже заблокирована сессией 2. Однако чтение фрагмента оператора UPDATE (части запроса) уже произведено. Oracle уже зафиксировал этот результат, используя механизм, называемый «согласованным чтением» (см. главу 5). Вкратце, обновление таблицы DEPT сессией 2 не видит вставку строки сессией 1, и оператор обновления в сессии 1 не будет видеть обновление таблицы EMP в сессии 2. Продолжим пример:

Session 2> commit;
Commit complete.

В этом месте сессия 1 будет разблокирована. В окне сессии 1 немедленно появится сообщение «Обновлена 1 строка». Затем сессия 1 будет завершена:

Session 1> commit;
Commit complete.

Session 1> select * from dept;

DEPTNO-----------SUM_OF_SALARY-------------
150
260
Session 1> select deptno, sum(salary) from emp group by deptno;
DEPTNO-----------SUM(SALARY)-------------
150
2115

Очевидно, что это неверно. Значение для DEPTNO 2 неправильное. Как такое могло произойти? Если запустить этот пример на SQL-сервере, сценарий будет слегка отличаться (исполнение будет производиться в другом порядке), но суммирование будет реализовано. На SQL-сервере последовательность событий будет такой:

ВремяРабота сессии 1Работа сессии 2
Т1Добавление в EMP (EMPNO, DEPTNO, SALARY) значений (102,2,60); добавление нового работника в DEPTNO 2
Т2Обновление EMP (set DEPTNO=2 where EMPNO =100); перевод работника из одного департамента в другой
Т3Обновление DEPT для департаментов 1 и 2 после модификации записей в обоих департаментах. Этот оператор блокирует чтение EMP. Строки, вставленные в момент времени Т1, заблокированы, и SQL-сервер ждет, когда будет реализована блокировка.
Т4Обновление DEPT для департамента 2 (департамент изменен). Этот оператор также блокирует чтение строк, обновленных в момент времени Т2.
Т5Сервер определяет, что обе сессии находятся в тупиковых условиях. Одна из них выбирается в качестве жертвы, и делается откат транзакции (например, сессии 1). Откат.Операторы разблокированы
Т6Завершение транзакции

Из примера видно, что, используя блокировку и механизм управления параллельным доступом, SQL-сервер не позволит транзакциям выполняться совместно. Только одна транзакция будет выполнена, а по остальным будет произведен откат, и результат будет верный с точки зрения SQL-сервера. Так может быть это ошибка Oracle? Вовсе нет.

В Oracle есть средство, называемое «многовариантность и согласованное чтение» (это отличает Oracle от остальных реляционных баз данных). Его методы работы отличаются от работы SQL-сервера (я бы сказал, что они гораздо лучше, так как предлагают большую возможность параллельной работы, дают корректные ответы без ожидания, но все это не относится к рассматриваемому здесь примеру). Поскольку используется модель параллелизма, вторая сессия при обращении к данным, которые были изменены в процессе обновления, не увидит их (не сможет прочитать). Следовательно, обновление не увидит добавления записи. И хотя очередность действий в транзакциях имеет небольшое различие, результаты могут сильно отличаться. (Информацию о согласованном чтении и многовариантности Oracle можно найти в общем руководстве (Concepts guide).)

Мораль этого примера заключается в том, что фундаментальные модели согласованности и управления параллелизмом разных баз данных радикально отличаются друг от друга. Последовательность операторов в одной базе данных может, а иногда и будет, приводить к результату, отличному от результатов в другой базе данных. Это «иногда» относится к тому оператору, с которым возникают сложности. До тех пор пока не будет изучена документация и освоена база данных, будут возникать проблемы с целостностью данных, что будет затруднять отладку. При чтении кода невозможно выявить условия, описанные в этом примере. При применении транзакций к целевой базе данных всегда необходимо помнить, что транзакции, работающие в одной базе, не всегда будут так же работать в другой. При таком подходе обеспечивается страховка от подобных проблем.

Если члены команды разработчиков не имеют представления о работе механизма согласованности и управления параллелизмом в Oracle или, что еще хуже, считают, что он работает подобно механизму SQL-сервера или DB2, то в этом случае самый вероятный исход — это испорченные данные, неверный анализ и некорректные ответы.

Невозможность быстрого создания программного обеспечения

Если разработчик знает все об использовании базы данных и ее возможностях, то процесс создания приложений займет гораздо меньше времени. Обратимся вновь к примеру применения аналитических функций (см. выше). При использовании универсального метода не только производительность была хуже, но и потребовалось больше времени на его разработку! Было сложно найти решение этой задачи. Если бы я использовал временную таблицу для побитового получения результатов, мне пришлось бы потратить много больше времени на написание объемного процедурного кода.

В качестве другого примера рассмотрим ситуацию, когда необходимо создать приложение, способное проверить внесенные изменения. История строки от начала до конца должна сохраняться в базе данных. Можно пойти двумя путями:

Как вам кажется, можно ли быстро реализовать первый вариант? А что если необходимо сделать это для 50-ти таблиц? Придется вручную набирать большой объем кода, а затем тратить время на проверку, отладку и сопровождение этого приложения.

ПРИМЕЧАНИЕ

Решение, о котором идет речь, является общим. Оно может иметь как удачную реализацию, так и определенные проблемы с производительностью, которые могут мешать работе системы. Как уже упоминалось неоднократно и будет подчеркиваться в дальнейшем, прежде чем использовать какое-либо средство, необходимо провести ряд тестов и проверок, чтобы убедиться в отсутствии проблем.

При выборе второго варианта реализация аудита займет около минуты даже при наличии 50-ти таблиц:

ops$tkyte@ORA920> create table emp
  2  as
  3  select empno,  ename,   sal,   comm
  4    from scott.emp;
Table created.

ops$tkyte@ORA920> alter  table emp
  2  add constraint emp_pk
  3  primary key(empno);
Table altered.

ops$tkyte@ORA920>
ops$tkyte@ORA920> begin
  2     dbms_wm.EnableVersioning _
  3     ( 'EMP',  'VIEW_WO_OVERWRITE'  );
  4  end;
  5  /
PL/SQL procedure successfully completed.

Все сделано с помощью одного оператора, являющегося частью встроенного менеджера рабочей области базы данных. (Для более подробного изучения этого средства базы данных смотрите Oracle Application Developers Guide — Workspace Manager.) Ниже представлены некоторые транзакции, выполняемые в таблице EMP после применения этого решения:

ops$tkyte@ORA920> update  emp  set sal = 5000
  2   where ename = 'KING';
1 row updated.

ops$tkyte@ORA920> commit;
Commit complete.

ops$tkyte@ORA920> update emp set comm  = 4000
  2   where ename = 'KING';
1 row updated.

ops$tkyte@ORA920> commit;
Commit complete.

ops$tkyte@ORA920> delete from emp
  2   where ename = 'KING';
1 row deleted.
ops$tkyte@ORA920> commit;
Commit complete.

Теперь можно просмотреть конечный результат. Менеджер рабочей области сформировал серии представлений, одно из которых — представление EMP_HIST, содержащее построчную историю каждой записи. Хорошо прослеживается тип изменений (вставка, обновление или удаление), а также можно определить, в какой момент времени запись была создана и изменена (каким-либо способом модифицирована или удалена).

ops$tkyte@ORA920> select ename, sal, comm, user_name,
  2         type_of_change, createtime,
  3         retiretime
  4    from emp_hist
  5   where ename = 'KING'
  6   order by createtime;

ENAME---------SAL-------COMM-------USER_NAME---------T-CREATETIM---------RETIRETIM---------
KING5000OPS$TKYTEI08-JUN-0308-JUN-03
KING50004000OPS$TKYTED08-JUN-03
KING5000OPS$TKYTEU08-JUN-0308-JUN-03
KING50004000OPS$TKYTEU08-JUN-0308-JUN-03

Выполнив запрос к представлению EMP_HIST, которое создал менеджер рабочей области, можно просмотреть историю каждой строки в таблице. Если сделать запрос к представлению EMP, то появится текущее состояние строки. Может показаться, что мы работаем с таблицей EMP, но менеджер рабочей области переименовал исходную таблицу в EMP_LT и создал для работы представление EMP.

Это представление скрывает дополнительные структуры менеджера рабочей области, добавляя к исходному варианту требуемые данные. Кроме того, можно быстро и легко без изменения приложения или способа решения обеспечить управление версиями и аудит для существующего приложения.

ПРЕДУПРЕЖДЕНИЕ

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

ПРИМЕЧАНИЕ

Для того чтобы просмотреть таблицу EMP, созданную в этом примере, необходимо отключить в таблице управление версиями. Это достигается в SQLPlus с помощью “begin DBMS_WM.DisableVersioning (‘EMP’); end”.

Неэффективное вложение средств

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

Это база данных, а не свалка данных

Этот раздел для тех, кто считает, что ограничения необходимо проверять на стороне клиента или в промежуточном программном обеспечении, что внешние ключи замедляют работу базы данных, а первичные ключи доставляют неприятности.

«У нас есть таблица City, содержащая названия различных городов, в которых находятся офисы наших клиентов. А также у нас есть форма для ввода подробных данных о сотрудниках. В таблице Employee внешним ключом является столбец City, в качестве родительского ключа которой выступает таблица City. Один из наших консультантов рекомендовал отменить проверку ввода городов и посоветовал нам проводить проверку правильности всех городов с помощью внешнего кодирования. Аргументировал он это тем, что проверка ссылочной целостности отнимет слишком много времени и замедлит процесс ввода данных. Я не совсем уверен в правильности его доводов. Обоснованы ли его аргументы?»

Я порекомендовал избавиться от этого консультанта как можно быстрее.

Использование первичного и внешнего ключей

Есть немало причин, по которым наихудшим решением является отказ от внешнего ключа и проверки правильности базы данных. Ниже приводятся три вопроса, которые следует задавать в случае, если кто-то советует от них отказаться:

Это риторический вопрос. История доказывает, что ответом на этот вопрос всегда является «нет». Данные будут многократно использоваться многими приложениями и различными командами разработчиков (в противном случае эти данные бесполезны). Если спрятать все правила, а особенно основные, такие как первичный или внешний ключ, глубоко внутрь приложения, что произойдет через два года, когда кто-нибудь еще начнет использовать эти данные? Как приложение будет защищаться от искажения данных? Что произойдет с существующими приложениями, у которых нет соответствующего первичного ключа, когда они начнут выполнять соединение или запрашивать данные? Возможно, что первое приложение остановится, второе станет неверным, а данные будут потеряны. Реальность этого примера доказывается революцией и эволюцией Интернета. Что бы было, если бы в компании представленные выше системы уступили бы место системам, основанным на Интернет-технологиях? Вероятнее всего, что в компании реализовали бы набор приложений, вся логика данных которых была бы внедрена на стороне клиента.

Для того чтобы начать работу, клиент должен обратиться к базе данных за исходной информацией. Клиент может поместить информацию в кэш, но логика данных при этом будет испорчена. Информация может быть размещена в кэше без повреждений только в том случае, если она будет помещена в кэш и на клиенте, и на сервере, а не только на клиенте. Работа пользователей была бы приятнее, если бы при табуляции полей их уведомили сообщением «Извините, пропущен или поврежден внешний ключ». Но это не означает, что можно переместить эту проверку с базы данных на клиента. Конечно же, можно ее продублировать, но никак нельзя переносить.

И это действительно может быть так — они больше пишут, больше делают, им больше приходится сопровождать и отлаживать. Как было продемонстрировано выше, с помощью средств базы данных можно одной строкой кода сделать то же, что при использовании другого способа потребует написания сотни строк. Разница заключается в том, что с помощью средств базы данных можно решить задачу лучше, быстрее и дешевле, чем в случае самостоятельной разработки.

Проверка издержек ссылочной целостности

Для проверки производительности при работе со ссылочной целостностью создадим небольшую таблицу CITIES, используя таблицу словаря данных ALL_USERS. Включим в эту таблицу ограничение, требующее наличия первичного ключа. Затем создадим две дочерние таблицы, связанные доверительными отношениями с данными таблицы CITIES. Таблица Т1 содержит объявленный внешний ключ. Oracle не разрешит создание строки в этой таблице, если не существует соответствующей строки в родительской таблице CITIES. У второй таблицы нет такого ограничения. Это вынуждает приложение встраивать целостность данных.

ops$tkyte@ORA920> create  table  cities
  2  as
  3  select username city
  4    from all_users
  5   where rownum<=37;
Table created.

ops$tkyte@ORA920> alter table cities
  2  add constraint
  3  cities_pk  primary key (city);
Table altered.
ops$tkyte@ORA920>
ops$tkyte@ORA920> create table with_ri
  2  ( x   char(80),
  3    city references cities
  4  );
Table created.

ops$tkyte@ORA920> create table without_ri
  2  ( x   char(80),
  3    city varchar2(30)
  4  );
Table created.

Теперь можно провести тест. Для анализа результата используем встроенное средство базы данных SQL_TRACE.

ПРИМЕЧАНИЕ

TKPROF является частью встроенного в Oracle средства профилирования SQL. Это неоценимое средство анализа производительности. Использование SQL_TRACE и TKPROF подробно рассматривается в главе 2.

Проведем эксперимент, в ходе которого оценим эффективность вставки одной строки в обе таблицы, используя простой цикл. В каждую таблицу будет вставлено 37 000 строк.

ops$tkyte@ORA920> alter session set sql_trace=true;
Session  altered.

ops$tkyte@ORA920> declare
  2      type array is table of varchar2(30) index by bihary_integer;
  3      l_data array;
  4  begin
  5      select * BULK COLLECT into l_data from cities;
  6      for i in 1 .. 1000
  7      loop
  8          for j in 1 .. l_data.count
  9          loop
 10              insert into with_ri
 11              values ('x', l_data(j) );
 12              insert into without_ri
 13              values ('x', l_data(j) );
 14          end loop;
 15      end loop;
 16  end;
 17  /

PL/SQL procedure successfully completed.

Теперь посмотрим отчет TKPROF по результатам проведенных операций:

INSERT into with_ri values ('x', :bl )

call--------count-----cpu-----elpsed------disk-----query-----current-------rows-----
Parse10.000.020200
Execute370009.4913.5105667887337000
Fetch--------0-----0.00-----0.00------0-----0-----0-------0-----
Total370019.5013.5305687887337000

********************************
INSERT into without_ri values ('x1', :bl )

call--------count-----cpu-----elpsed------disk-----query-----current-------rows-----
Parse10.000.030000
Execute370008.0712.2505674188237000
Fetch--------0-----0.00-----0.00------0-----0-----0-------0-----
Total370018.0712.2905674188237000

Как выяснилось в процессе эксперимента, при вставке 37 000 строк тратилось 0.000256 секунды работы процессора на каждую строку (9.50/37000) в случае применения ссылочной целостности. Без ссылочной целостности требовалось 0.000218 секунды процессорного времени на строку. Будут ли осознавать конечные пользователи, что колоссальные 0.00004 секунды были возложены на них в качестве штрафа?

На основании этого можно сделать вывод, что ссылочная целостность в базе данных добавляет от 10% до 15% издержек. Это невысокая плата за то, чтобы спокойно спать по ночам, зная, что целостность данных защищена и что был использован самый быстрый способ разработки приложения. И не имеет значения, какое новое приложение будет добавлено в систему, поскольку, столкнувшись с этим правилом, оно не сможет его нарушить. Этот же принцип работает и в больших масштабах.

Проверка промежуточного программного обеспечения не является панацеей

В последнее время нередко можно услышать совет: используйте промежуточное программное обеспечение в приложении для контроля данных и проверки безопасности. Применение промежуточного программного обеспечения (ПО), возможно, звучит привлекательно. Кажется, что пользователь действительно получает выгоду и делает приложение более быстрым, гибким, независимым от базы данных и надежным. Но так ли это? Давайте более подробно рассмотрим каждое из этих утверждений.

«Несколько консультантов создают для нас приложение. Они будут работать с базой данных Oracle, которая содержит только таблицы, представления и индексы. Такие действия, как проверка ограничений, будет проводиться в промежуточном ПО. Согласно их утверждениям, это позволит приложению работать быстрее. Также они говорят, что приложение станет более гибким, сможет работать с различными базами данных, поскольку большая часть кода находится в приложении. И, наконец, проверка безопасности будет также проводиться на уровне приложения (промежуточное программное обеспечение). Кроме того, у них есть собственные средства аудита, которые создают свои таблицы в базе данных Oracle».

Вкратце, я бы посоветовал этим людям не связываться с приложением, которое работает медленнее, чем могло бы, требует на разработку больше времени, чем должно, и содержит намного больше кода для поддержки, чем необходимо. К тому же оно не будет гибким и независимым от базы данных. В итоге, заказчик будет вынужден всегда пользоваться услугами этой консалтинговой фирмы. И в довершение вышесказанного, безопасность этого приложения будет далеко не такой простой, как кажется.

Это быстрее?

По мнению некоторых консультантов, приложение будет работать быстрее, если все проверки ограничений будут выполняться промежуточным программным обеспечением, написанным обычно на Java. Допустим. Но это означает, что для работы с данными необходимо написать загружающую их программу (такую, которая сможет осуществлять проверку ограничений). Будет ли самостоятельно написанный загрузчик работать быстрее, чем встроенное средство прямой загрузки? Их ограничения будут проверяться с помощью Java. Будет ли это быстрее, чем при использовании встроенного кода, написанного на C? Если есть родительская таблица с миллионом записей и дочерняя таблица с десятью миллионами записей и необходимо провести проверку ссылочной целостности — что в таком случае будет быстрее: запрос базы данных по сети для проверки и блокировки родительской строки или проверка базы данных в момент ввода данных? Это риторический вопрос, поскольку очевидно, что созданное самостоятельно приложение будет проводить проверку во много раз медленнее.

Утверждения о быстроте работы приложения нуждаются в подкреплении статистикой, основанной на реальном опыте. Я еще не встречал ничего подобного. Однако я видел множество примеров того, что работа самостоятельно написанных приложений гораздо медленней, чем работа встроенных средств базы данных.

Это гибче?

Допустим, что нужно изменить ограничение — например, в таблице Т диапазон изменения Х должен поменяться с 25—100 на 50—100. Выполнение этого средствами базы данных намного гибче. Это можно сделать с помощью всего лишь двух команд: одной добавить новое ограничение, а другой удалить старое. База данных проверит существующие данные и сообщит о несоответствиях, чтобы можно было их исправить. Если же пытаться решить эту задачу с помощью процедуры промежуточного программного обеспечения, то придется редактировать процедурный код в зависимости от конкретного случая, а затем извлекать данные из базы, читать, проверять и возвращать их обратно в базу данных по сети.

Даже если сделать это правило «управляемым таблицей» или «управляемым параметром», все равно это сложнее, чем обновление ограничений в базе данных. Все равно придется проверять данные. А что, если нет необходимости в проверке? Мои оппоненты могут заявить, что ограничения, созданные не базой данных, более эффективны в применении, поскольку могут не проверять каждую строку, если в этом нет необходимости. Но ведь и база данных может так действовать. Можно создать ограничение данных в базе и без проверки основных данных, что означает, что база не будет проводить проверку существующих данных, если в этом нет необходимости.

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

К тому же при таком подходе спустя некоторое время база данных станет менее гибкой, менее соответствующей потребностям компании. Почему? Допустим, что доступ к базе данных можно получить только с помощью приложения. Это приложение принимает специализированные запросы SQL, или оно делает только то, что разработчики запрограммировали к исполнению? Конечно же, оно не будет принимать специализированный SQL. Приложение не будет работать гибко в той степени, в которой это может потребоваться через какое-то время.

Используя эту же аргументацию, вернемся во времени лет на 5-6 назад и заменим понятие «промежуточное программное обеспечение» понятием «клиентское приложение». Есть ли у вас самостоятельно написанный в 1996 г. код, который основан на технологии клиент-сервер и у которого все бизнес-правила спрятаны внутрь приложения? Если да, то вы, вероятно, все еще ищете пути выхода из этой ситуации. Не стоит поступать также с новыми технологиями (сервер приложений). Можно смело утверждать, что в течение 2 лет появятся новые, более мощные разработки. Если данные зависят от некоторого приложения, то это вредно и негибко с точки зрения их последующего использования.

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

Я никогда не видел, чтобы при обращении только через приложение набор данных полностью удовлетворял бы конечных пользователей. Множество вопросов, адресуемых данным, фактически бесконечно, и разработанное приложение не может их все предвидеть.

Это независимо от базы данных?

Рассмотрим утверждение о том, что использование промежуточного программного обеспечения делает базу данных независимой. Речь идет о системе транзакций? Если да, то в этом случае начнутся проблемы в отношении управления параллельным доступом и согласованности данных. Если нет необходимости в применении разного кода для работы с разными базами данных, то почему бы PeopleSoft и SAP не использовать только Open Database Connectivity (ODBC) или Java Database Connectivity (JDBC) везде и всюду?

Приложение создано по заказу для конкретной организации? У этой компании есть база данных, которая стоит больших денег. Менеджеры компании не должны платить кому-то еще за переписывание функциональности своей базы данных в промежуточное программное обеспечение. Это подобно обложению налогом одного и того же чека дважды.

Это более безопасно?

Что касается безопасности, то утверждение, что «данные защищены, потому что у приложения хорошая защита», вообще не имеет смысла. Только безопасность самих данных может обеспечить их сохранность. Например, можно ли провести аудит прямого административного доступа к данным? Нет. А вот если использовать базу данных для аудита, то можно, например:

Одним словом, можно обнаружить, что данные несанкционированно раскрыты или потеряны, и внести поправки. А если аудит используется на уровне приложения, то сделать это будет непросто.

Создание тестовой среды

Нередко мне приходится участвовать в таком диалоге:

Сколько людей злится лично на меня за то, что я посоветовал им что-то попытаться сделать, а они без первоначальной проверки сразу стали делать это в рабочей среде? Я только предположил, а они сделали. Кто-то обновил свою базу данных без тестирования этого процесса, а кто-то обновил свою операционную систему без предварительной проверки!

Отсутствие тестовой среды — прямой путь к неприятностям. Существует рабочая среда. Также должна существовать тестовая среда. Тестовая среда предназначена для проверки того, что приложение работает должным образом и что конечным пользователям не придется сталкиваться с проблемами. Кроме того, тестовая среда используется для следующих действий:

Я часто слышу: «Мы не можем позволить себе иметь тестовую систему». Лично я не понимаю этого. Если посчитать стоимость рабочей системы, не функционирующей полдня, я уверен, что деньги на тестовую систему сразу же найдутся вне зависимости от размера организации. Если организация небольшая, то и тестовая система должна быть небольшой и недорогой. Если же компания большая, то и система будет больше и стоить дороже. Но при этом не стоит забывать о том, что и количество людей, зависящих от работы системы, гораздо больше. Также не нужно забывать, что стоимость измеряется не только в финансовых терминах, но и в терминах стресса, гнева заказчика и репутации разработчика (которая может быть непоправимо утрачена).

Тестовая системы должна быть хорошим зеркалом целевой системы. Неужели необходимо, чтобы все в точности соответствовало? Нет, но тестовая система должна быть настолько близка к рабочей системе, насколько это возможно. Операционная система должна быть точно такой же, той же версии и с тем же уровнем обновлений. База данных также должна быть в точности такой же (тестовая система базы данных может быть уровнем выше, поскольку фактически обновление будет проверяться там). Объем, тип и скорость памяти должны быть такими же, если это возможно.

Ниже представлены некоторые моменты, о которых нужно помнить в процессе разработки тестовой системы:

Рассмотрим более подробно каждый пункт.

Тестирование с использованием репрезентативных данных

Тестирование с использованием репрезентативных данных является критически важным. Если необходимо получить полное представление о том, как работает система, то тестовая система должна загрузить как можно больше данных из реальной системы. Запрос, который замечательно работает в тестовой системе с тысячей строк, может стать хуже ночного кошмара при его запуске в рабочей системе с миллионом строк. Некоторые используют стратегию импорта статистики из рабочей системы в тестовую. Они считают, что таким образом заставят оптимизатор генерировать планы, которые будут эффективно работать с этими данными как в рабочей, так и в тестовой системах. Их теория строится на том, что нет необходимости в использовании одного миллиона строк в таблице, а достаточно только сказать оптимизатору, что там находится один миллион строк. Такой подход работает только в том случае, если есть возможность прочитать план запросов, и вы на 100% уверены, что план правильно сформирован и будет давать наименьшее время ответа. Я видел много планов запросов, но не могу выступать в роли арбитра. Большинство людей стараются получить планы запросов, используя индексы, но при возрастании количества данных индексы не всегда являются хорошим решением. Единственным способом проверки надлежащей работы планов является их тестирование на репрезентативных данных.

Это не означает, что DBMS_STATS с его способностью экспортировать и импортировать статистику является бесполезным. Некоторые используют (с большим успехом) возможности импорта/экспорта статистики, но не при настройке теста. Они берут результаты статистики, накопленные в процессе теста, и импортируют их в рабочую систему! Используют функции DBMS_STATS с точностью наоборот! Большинство же людей берут последнюю резервную копию их рабочей системы, восстанавливают ее в тестовой системе (для тестирования) и собирают статистику, используя дополнительную мощность тестовых машин, которую рабочая система не имеет. Кроме того, статистику, полученную с помощью DBMS_STATS, бывает полезно посмотреть, чтобы проследить, каким образом меняются планы запроса с течением времени.

Использование разбиения данных

Нужно ли загружать 100% рабочих данных в тестовую систему? Такой необходимости нет. Можно загрузить некоторый их горизонтальный срез. Например, если система оперативной обработки транзакций (OLTP) использует декомпозицию и есть уверенность в том, что все запросы выполняются с учетом разбиения (например, декомпозиция была произведена так, что каждый запрос будет обращаться только к одному разделу сегмента), то будет производиться загрузка и проверка только одного или двух разделов в каждой таблице. Это происходит потому, что проводится тестирование запросов к такому же количеству данных, как и в рабочей системе. Средство устранения разделов удаляет другие разделы из рассмотрения точно так же, как они будут исключены из запроса в рабочей среде.

Изменение плана запросов

Использование репрезентативных данных не гарантирует идентичности планов запросов в рабочей и тестовой системах. Даже физическое размещение данных в таблице имеет значение для разработки оптимизатором плана запроса. Например, мы создаем две таблицы: в одну из них данные загружаются в отсортированном порядке (строки в таблицу вводятся в соответствии со значением первичного ключа), а во второй порядок сортировки произвольный. Эти две таблицы содержат одинаковые данные, но отсортированные по-разному:

ops$tkyte@ORA920> create table clustered ( x int, data char(255) );
Table created.

ops$tkyte@ORA920> insert /*+ append */
  2 into clustered  (x, data)
  3 select rownum, dbms_random.random
  4 from all_objects;
29315 rows created.
ops$tkyte@ORA92Q> alter table clustered
  2 add constraint clustered_pk primary key (x);
Table altered.
ops$tkyte@QRA920> analyze table clustered compute statistics;
Table analyzed.
ops$tkyte@ORA920> create table non_clustered ( x int, data char(255) );
Table created.
ops$tkyte@ORA920> insert /*+ append */
  2    into non_clustered (x, data)
  3  select x, data
  4    from clustered
  5   ORDER  BY data;
29315 rows  created.
ops$tkyte@ORA920> alter table non_clustered
  2  add constraint non_clustered_pk primary key (x);
Table altered.
ops$tkyte@ORA920> analyze table non_clustered compute statistics;
Table analyzed.

Таблицы CLUSTERED и NON_CLUSTERED идентичны, за исключением физического расположения данных на диске. В одной данные отсортированы по значению первичного ключа, а во второй сортировка отсутствует. Оптимизатор узнает об этом из CLUSTERING_FACTOR:

оps$tkyte@ORA920>  select  index_name,   clustering_factor
  2    from user_indexes
  3  where  index_name like   '%CLUSTERED_PK';

INDEX_NAME----------------------------------CLUSTERING_FACTOR-------------------------
CLUSTERED_PK1106
NON_CLUSTERED_PK29291
ops$tkyte@ORA920> show parameter optimizer_index
NAME---------------------------TYPE-------------VALUE-------------------
optimizer_index_cachinginteger0
optimizer_index_cost_adjinteger100

ops$tkyte@ORA920> set autotrace traceonly explain
ops$tkyte@ORA920> select * from clustered where x between 50 and 2750;
Execution Plan
 0        SELECT STATEMENT Optimizer=CHOOSE (Cost=109 Card=2702 ...)
 1      0   TABLE ACCESS (BY INDEX ROWID) OF 'CLUSTERED' (Cost=109 Card=2702
 2      1     INDEX (RANGE SCAN) OF 'CLUSTERED_PK' (UNIQUE) (Cost=7 Card=2702)
ops$tkyte@ORA920> select * from non_clustered where x between 50 and 2750;
Execution Plan
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=109 Card=2702 ...)
   1    0   TABLE ACCESS (FULL) OF 'NON_CLUSTERED' (Cost=109 Card=2702 ...)

Цель этого примера заключается не в том, чтобы показать, что оптимизатор сделал ошибку, а в том, чтобы показать, что оптимизатор может и будет изменять планы запроса через какое-то время. Это часть его обычной работы. В случае с несортированными (не сортированными в соответствии со значением первичного ключа) данными оптимизатор корректно выбрал сканирование всей таблицы. Это верный план. Что могло бы получиться при тестировании этого примера в меньшей базе данных? Возможно, были бы использованы индексы с совершенно необоснованной верой в то, что применение индексов — это правильное решение, а сканирование всей таблицы — нет. Все бы замечательно работало в крошечной базе данных, но потерпело бы неудачу при запуске в рабочей среде и обработке реальных данных. В данном случае, если бы я принудительно попытался заменить полное сканирование таблицы использованием индекса, я мог бы нарушить функционирование рабочей системы.

ПРИМЕЧАНИЕ

Возможно, что результаты выполнения этого примера будут отличаться в разных системах. Например, можно обнаружить, что оба запроса проводят полное сканирование таблиц или оба используют индексы. Это функция многих переменных (см. главу 6). На результат могут воздействовать такие факторы, как индивидуальные настройки db_file_multiblock_read_count, db_block_size и даже количество объектов в базе данных, у которых значение OBJECT_ID находится в запрошенном диапазоне. Попробуйте использовать в предикате ряд значений (50 и 2750 в этом примере), и вы увидите, что планы различаются.

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

Не стоит производить тестирование с одним пользователем

Общая ошибка заключается в некорректном проектировании приложения в среде транзакций. Непонимание того, как база данных работает в условиях многопользовательского обращения к данным, — прямой путь к проблемам.

«Мы проводим нагрузочное тестирование нашего приложения, разработанного на Java. При этом тесте база данных Oracle зависает. Просмотрев базу данных, мы выяснили, что один процесс блокирует другой. Например, SID55 блокировал SID50. Когда мы удалили SID55, SID50 продолжил работу, но заблокировал SID35, и так далее. Перед тестированием приложение работало замечательно. Это означает, что операторы SQL выполнялись правильно. Но почему тогда при обращении множественных процессов к базе данных она зависает?»

К счастью, они обнаружили эту проблему в тестовой среде! Однако они не поняли, что источником проблемы, несмотря на их вывод о том, что операторы SQL написаны верно, является управление параллельным доступом. Спроектированное ими приложение является сериализованным (один после другого) и не выполняет транзакции корректно. Приходится удалять сессию для того, чтобы следующая сессия могла начать работать, вследствие чего все останавливается.

При нагрузочном тестировании приложения необходимо проводить множественное параллельное обращение к данным таким же образом, как это будет происходить в «реальной жизни». Нужно проверить возможность масштабирования. Команда разработчиков использовала приложение Java, основанное на механизме сохранения, управляемом контекстом (container-managed persistence, CMP). Все коды генерировались на уровне этого инструмента, и разработчики понятия не имели, как это все будет действовать на уровне базы данных. В процессе проверки на масштабируемость они обнаружили, что их приложение немедленно сериализуется. Создавая приложение для немасштабируемой системы, они выполняли одну транзакцию точно после другой. Результатом стало то, что, начав работать, каждый пользователь блокировал строку в единственной таблице. При проектировании маленьких приложений необходимо всегда помнить о возможности подобной ситуации.

Суть в том, что необходимо отлавливать подобные проблемы в тестовой системе, а не в рабочей. Если вы всего лишь правильно перепишете код из среды разработки в рабочую среду, то вы рискуете потерять доверие конечных пользователей и заказчиков.

Не стоит производить тестирование в идеальных условиях

Если в реальной системе работает одновременно свыше 50 приложений, необходимо удостовериться в том, что все это так или иначе учтено либо прошло проверку в тестовой системе.

Я работал над реализацией одной задачи, где тестовая система повторяла все компоненты рабочей системы, за исключением одного. В тестовой системе отсутствовал интерфейс к внешней системе. Команда измеряла, сколько времени будет занимать вызов внешней системы (от 0.1 до 0.5 секунд максимум), и функция, замещающая реальное обращение, выжидала это время, а затем возвращала правильный, но вымышленный ответ. Когда это было запущено в рабочей среде, система немедленно остановилась. Это не было проблемой базы данных (популярный виновник), в действительности это было результатом работы сервера базы данных.

После нескольких дней анализа команда разработчиков обнаружила, что интерфейс внешней системы был построен на основе сериализации запросов. Это было бы не так плохо, если бы это не был общедоступный web-сайт с огромными объемами транзакций, обращающимися к нему ежедневно и в непредвиденном порядке. Сайт был бы разрушен, если бы потенциальные заказчики, сидя на своих местах, пытались бы заказать что-либо с этого сайта.

Тестовая среда должна быть зеркалом реальности — каждый бит и каждый кусочек.

Проектирование производительности: не настраивайте производительность

Этот заголовок довольно многогранный — «настройка» может относиться к SQL-запросу или к модели данных. Основная мысль этой темы заключается в том, что если модель данных плохо спроектирована, то ни одна настройка с помощь SQL не сможет исправить это. В итоге все закончится созданием второй версии схемы. (О «физике» проектирования схемы рассказывается в главе 7.)

Не стоит использовать универсальные модели данных

Довольно часто встречаются приложения, построенные с использованием универсальных моделей данных для «максимальной гибкости», и приложения, построенные так, что они мешают работе. Например, хорошо известно, что можно представить любой объект в базе данных, используя только четыре таблицы:

Create table objects ( oid int primary key, name varchar2(255) );
Create table attributes
( attrId int primary key, attrName varchar2(255),
datatype varchar2(25) );
Create table object_Attributes
( oid int, attrId int, value varchar2(4000),
primary key(oid,attrId) );
Create table Links ( oidl int, oid2 int,
primary key (oidl, oid2) );

Больше не нужен CREATE TABLE! Я могу заполнить таблицу ATTRIBUTES строками, например, так:

insert into attributes values ( 1, 'DATE_OF_BIRTH', 'DATE' );
insert into attributes values ( 2, 'FIRST_NAME',    'STRING' );
insert into attributes values ( 3, 'LAST_NAME',     'STRING' );
commit;

И теперь можно создать запись PERSON:

insert into objects values ( 1, 'PERSON'	);
insert into object_Attributes values( 1, 1, '15-mar-1965' );
insert into object_Attributes values( 1, 2, 'Thomas' );
insert into object_Attributes values( 1, 3, 'Kyte'  );
commit;
insert into objects values ( 2, 'PERSON' );
insert into object_Attributes values( 2, 1, '21-oct-1968' );
insert into object_Attributes values( 2, 2, 'John' );
insert into object_Attributes values( 2, 3, 'Smith' );
commit;

Зная SQL, я могу сделать запрос к этой записи для того, чтобы получить FIRST_NAME и LAST_NAME из всех записей PERSON:

ops$tkyte@ORA920> select
     max( decode(attrName, 'FIRST_NAME', value, null )) first_name,
  2  max( decode( attrName, 'LAST_NAME', value, null ) ) last_name
  3   from objects, object_attributes, attributes
  4  where atttibutes.attrName in ( 'FIRST_NAME',   'LAST_NAME'  )
  5    and object_attributes.attrId = attributes.attrId
  6    and object_attributes.oid = objects.oid
  7    and objects.name = 'PERSON'
  8  group by objeсts.oid
  9 /

FIRST_NAME----------------------------LAST_NAME----------------------------
ThomasKyte
JohnSmith

Выглядит потрясающе, не правда ли? Мне не нужно больше создавать таблицы, потому что я могу добавить столбцы (вставкой в таблицу ATTRIBUTES). Разработчики могут делать все, что им хочется, и администраторы базы данных не смогут остановить их. Это предельная гибкость. Я встречал людей, пытающихся построить полноценную систему, основанную на этой модели.

Но как такая модель работает? Печально, ужасно, страшно. Простой запрос select first_name, last_name from person трансформируется в соединение трех таблиц с агрегированием. Более того, если имеются атрибуты NULLABLE — в таком случае может не быть строки в таблице OBJECT_ATTRIBUTES для некоторых атрибутов, — возможно, возникнет необходимость использовать внешнее соединение вместо простого соединения, которое может исключить оптимальные планы запросов из рассмотрения.

Написание запросов с использованием этой модели кажется простым. Например, если необходимо выбрать всех, кто родился в марте или чья фамилия Смит, можно было бы сделать запрос, чтобы получить FIRST_NAME и LAST_NAME всех записей PERSON, а затем вставить встроенное представление:

оps$tkyte@ORA920> select  *
  2    from (
  3  select
     max(decode(attrName,  'FIRST_NAME', value, null)) first_name,
  4  max(decode(attrName,  LAST_NAME',  value, null)) last_name,
  5  max(decode(attrName,  DATE_OF_BIRTH',  value, null))
                                                      date_of_birth
  6     from objects, object_attributes, attributes
  7    where attributes.attrName in ( 'FIRST_NAME',
                                      'LAST_NAME',  'DATE_OF_BIRTH' )
  8      and object_attributes.attrId = attributes.attrId
  9      and object_attributes.oid = objects.oid
 10      and objects.name =  'PERSON'
 11 group by objects.oid
 12       )
 13 where last_name =   'Smith'
 14    or date_of_birth like   '%-mar-%'
 15 /

FIRST_NAME---------------------LAST_NAME---------------------DATE_OF_BIRTH---------------------
ThomasKyte15-mar-1965
JohnSmith21-oct-1968

Итак, создать запрос несложно, но не надо забывать о производительности! Если есть пара тысяч записей OBJECT и десятки тысяч OBJECT_ATTRIBUTES, Oracle придется провести сначала внутреннюю группировку запроса, а затем применить конструкцию WHERE.

Это реальная модель данных, и я видел, как люди пытались использовать ее. Их целью было достижение предельной гибкости. Они не знали, какие OBJECTS им необходимы и какие ATTRIBUTES у них будут. Но ведь это то, для чего в первую очередь пишется база данных. Oracle решает эту задачу следующим образом: вызывается SQL для определения OBJECTS и ATTRIBUTES, а затем используется SQL для запросов к ним. Те, кто применяет подобную модель данных, пытаются поместить общий слой поверх другого общего слоя и каждый раз терпят неудачу, за исключением случаев, когда речь идет о простейших приложениях.

«У меня есть таблица с полем BLOB, например:

Create table trx (   trxId Number(18),
trxType Varchar2(20),   objValue    Blob )

Поля BLOB содержат сериализованный объект Java, а также различные объекты, основанные на типах, хотя все они используют одинаковый интерфейс. Мы всегда обращались к этому объекту через контейнер J2EE, так как это хорошо работает. Сейчас пользователи хотят создавать отчеты, используя SQL*Plus, Crystal Reports и тому подобное. Так что нужно найти решение этой BLOB-проблемы".

Это очень интересная задача. В данном случае разработчики приложений пошли в использовании общей модели дальше, чем я мог даже предположить! Они преобразовали все атрибуты объектов в нечитаемую строку двоичных битов и байтов. В Java «сериализация» (упорядочение) означает перемещение структуры данных в «плоский» формат, который может быть записан в файл и позже обратно прочитан для заполнения структуры данных Java. В Java Development Kit (JDK) компании Sun дается следующее определение:

«Сериализация объектов расширяет возможности поддержки объектов основных Java-классов ввода/вывода. Сериализация поддерживает кодирование объектов в потоки байтов и дополнительную реконструкцию диаграммы объектов из потока. Сериализация используется для достижения сохраняемости и для связи через сокеты или удаленный вызов метода (Remote Method Invocation, RMI). Заданное по умолчанию кодирование объектов защищает данные при передаче, а также поддерживает изменение классов. Классы могут реализовывать собственное внешнее кодирование и тем самым отвечать за внешний формат».

Таким образом, в данном случае разработчики Java решили использовать базу данных в качестве большой свалки данных, «ведра с битами». Они сохраняют данные в базе, но не используют ее, поскольку не хотят обременять себя такими вещами, как модели данных, и другими формальностями. Они всего лишь написали некоторый код и сохранили некоторые штучки. А теперь конечные пользователи стучатся в их двери за своими данными! Они хотят знать, как будет производиться анализ этих BLOB и поддерживаться SQL-доступ к данным. Но дело в том, что это просто невозможно сделать в эффективном, высокопроизводительном виде.

Мой совет всем: будьте более специализированными и менее универсальными. Несомненно, общий подход более гибкий, но менее производительный, более сложный с точки зрения формирования запросов и сопровождения. Не используются словарь данных и метаданные. Те, кто применяет универсальный подход, загоняют себя в угол. Эксплуатация такого кода подобна ночному кошмару. Этот метод самостоятельно удаляет значения из данных, поскольку данные полностью блокированы в приложении.

Проектирование эффективной модели данных

Необходимо так проектировать модель данных, чтобы можно было эффективно ответить на большинство запросов. Нужно понимать, что если система разработана неэффективно, то при активных запросах конечных пользователей она потерпит поражение.

«У нас есть запрос, и нам необходимо запускать его сотни раз в минуту, что подвешивает нашу систему. Помогите!"

if type = 'A' join to tablea
if type = 'B' join to tableb
...

Подобный запрос делает условное соединение. (Схожая проблема возникает с запросом, который должен выполнить процесс where exists в глубину на четыре или пять уровней.) Очевидно, что модель данных не была спроектирована для ответа на запрос, выполняющийся сотни раз в минуту.

При создании базы данных в первую очередь необходимо подумать над тем, каким образом хранить данные, чтобы часто выполняемые, критические для производительности запросы обрабатывались как можно быстрее. Хорошо бы продумать это до создания таблиц, чтобы затем построить таблицы, отвечающие этому требованию. Затем выбирается определенная технология индексации. И система проектируется для каждодневной работы. Настройка необходима только в том случае, если что-то было сделано некорректно в процессе проектирования и реализации, а вовсе не потому, что «мы всегда проводим настройку после внедрения».

Метод настройки после внедрения не работает. Почему? Да потому что система уже внедрена. Разве возможно изменить физическую структуру после внедрения системы? Конечно же, нет. Потребуется другой вариант продукта, поскольку такие ошибки нельзя исправить за один вечер.

Нередко меня просят: «Создана рабочая система. Код нельзя изменить. Нельзя сбить работу системы. Но нужно, чтобы она работала быстрее. Предложите какую-нибудь палочку-выручалочку, чтобы установить fast=true в init.ora». Но ничего подобного сделать невозможно.

В качестве примера рассмотрим внутренний, основанный на Web системный календарь, над которым не так давно работала моя команда. Это приложение должны были использовать от 10 000 до 20 000 пользователей 24 часа в сутки 7 дней в неделю. Вдобавок к web-интерфейсу он должен был поддерживать синхронизацию с Palm Pilot. Над этим проектом трудилась еще одна группа. Нас попросили помочь.

Мы изучили вопрос и поняли, что система на 90% предназначена для чтения информации — 9 из 10 запросов могли бы быть сформулированы так: «покажите мне мои встречи». Другие 10% обращений предназначались для записи: «создайте эту однократную встречу», «создайте это повторную встречу» и т.д. Наиболее важным запросом был: «покажите мои встречи, где дата встречи находится между START и STOP». START и STOP задавались представлением данных как один день, неделя или месяц.

Проектирование для чтения или для записи?

Итак, какую систему следует разрабатывать? Что сделать наиболее быстрым: запись или чтение? Вторая команда, изучив требования к системе, сказала: «У нас есть синхронизация с Palm Pilot. Мы будем использовать их модель данных. В таком случае синхронизация будет проходить легче».

У нас был свой подход: «Если мы не сконцентрируемся на модели данных, которая будет поддерживать более 90% наших запросов, то получим приложение, медленно работающее и плохо масштабируемое. Кроме того, модель данных Palm спроектирована для устройства, предназначенного для одного пользователя; оно имеет специализированный процессор и небольшой объем оперативной памяти, которая еще должна дублироваться как дисковая (постоянная) память. В нашем же случае работа выполняется на многопользовательском устройстве, имеющем виртуальную неограниченную дисковую память и большую оперативную память. У нас различные критерии проектирования. Мы будем синхронизировать с Palm, но хранить данные в нашей базе данных таким же образом, как и в Palm, невозможно».

Ситуация с моделью данных Palm была простой. Сохранять повторяющуюся встречу пришлось бы как единичную запись. У этой записи были бы атрибуты START_DATE, DESCRIPTION, REPEAT_TYPE (ежедневно, еженедельно или помесячно), INTERVAL (например, если тип повторений дневной и интервал равен 2, повторения будут происходить каждый второй (OTHER) день) и END_DATE. Так что эту единичную запись необходимо будет «прогнозировать» с помощью процедуры каждый раз, когда пользователь захочет посмотреть свой календарь. Например, рассмотрим такие входные данные:

Start date:  04-Jan-2003
Description: Manager Meeting
Repeat_type: Weekly
Interval:    1
End date:    01-Jan-2004

А теперь попросим календарь на Июль 2003 года. Palm будет выполнять цикл, начиная с START_DAY и добавляя по одной неделе до тех пор, пока не будет достигнут Июль 2003 года. Он будет выводить записи для июля и остановится, когда результат прибавления месяцев станет больше, чем конец июля. Затем он обратится к таблице EXCEPTIONS для того, чтобы посмотреть, нет ли каких исключений для этого месяца. Например, встреча, назначенная на 5 июля 2003 года, будет отменена из-за американского праздника — Дня четвертого июля. Эта дата может быть записана как исключение, и одна из четырех встреч будет удалена.

Итак, этот подход замечательно работает для Palm Pilot. Здесь не применяется SQL в качестве языка запросов, процессор обслуживает одного пользователя и память пользуется большим спросом — сохранение каждого байта требует значительных усилий.

Но поместим эту же запись в реляционную базу данных и напишем SQL-запрос, который ответит, что в июле четыре встречи: пятого, двенадцатого, девятнадцатого и двадцать шестого числа, за исключением отмененной встречи пятого июля. Это будет сделано, но получится чрезвычайно дорого и потребует применения так называемой «уловки». Реляционная база данных ненавидит комплектовать данные, и поэтому приходится хитрить, чтобы выполнить этот запрос при использовании модели данных Palm. Есть только одна строка.

Необходимо размножить ее, чтобы синтезировать другие строки. Это тяжело, медленно и не масштабируется.

С другой стороны, можно было бы в процессе создания этого события выполнить цикл и вставить строку для каждого случая. Действительно, мы могли бы ввести 52 (или больше, если событие длится дольше) строки, но ведь база данных создана для хранения данных. Это то, что она умеет делать наилучшим образом.

И у нас есть много памяти на сервере (в противоположность Palm с его экстремально ограниченной памятью). Кроме того, это событие будет запрашиваться гораздо чаще, чем вводиться (интенсивное чтение — 9 из 10 раз требуется читать данные). Если строки существуют, то нетрудно вернуть ответ с требуемыми событиями в заданном месяце. Если же они не существуют и используется модель Palm, то придется выбрать все повторяющиеся события и спрогнозировать их, процедурно генерируя данные.

Предположим также, что web-календарь будет использоваться для координации расписаний встреч людей и т.п. Конечный пользователь задает простой вопрос: «В какое время Боб, Мэри, Джордж и Сью смогут встретиться во вторник?» Если применяется модель Palm (построенная и спроектированная для одного пользователя, устройства с экстремально ограниченной оперативной памятью и дисковым пространством), то невозможно будет использовать базу данных для ответа на этот запрос, поскольку этих данных в ней нет. Их нужно генерировать на основе имеющейся информации. Необходимо просмотреть всю информацию по всем встречам Боба, Мэри, Джоржа и Сью в этот день, получить все повторяющиеся встречи, проанализировать их, отсортировать и затем вычислить, в какой период времени они смогут собраться.

С другой стороны, если есть таблица, в которой хранится время начала и конца событий для каждого пользователя, то можно написать SQL-запрос, в ответе которого будут указаны доступные области. У этого решения есть ряд положительных сторон: требуется меньше кода, базе данных приходится меньше работать (она посылает меньше данных, поскольку может эффективно обработать запрос и вернуть данные всех этих пользователей) и облегчается сопровождение.

Какая модель данных работает?

Другая группа решила оптимизировать процесс записи за счет запросов. Они где-то слышали, что запись работает плохо и медленно, поэтому решили записывать как можно меньше. Они использовали модель Palm для упрощения разработки модуля синхронизации с Palm и написали много процедурного кода, чтобы надлежащим образом спрогнозировать расписания.

Они считали, что мы ошибаемся, и следует использовать то, что они называют «нормализованная схема, подобная Palm». Мы полагали, что ошибаются они, и им необходимо изучить базу данных и то, как она работает. Их главным аргументом против нас было то, что размер нашей базы данных может достигать (согласно их теории) 9 терабайтов (Тбайт). В действительности, даже неоднократно записанная встреча, которая повторяется каждый день в течение 10 лет, занимает всего 1000 байт.

Их решение не совсем «летает», как они сказали. Они выяснили, что могли бы масштабировать систему до 6–12 одновременно работающих пользователей, прежде чем им пришлось бы добавить серверы приложений. Их подход к масштабированию был больше аппаратным. Машина с базой данных «кормила» данными серверы приложений настолько быстро, насколько они могли «поедать» их, но цикла процессора сервера приложений было недостаточно для перемещения страниц. Если физическое проектирование неверно в своей основе, никакие дополнительные технические средства, настройки и «вылизывание» готовой системы не поможет в решении проблемы.

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

Модуль синхронизации с Palm был написан, и координирование двух различных моделей было совсем несложным. Большой скучной работой была только синхронизация. Мы брали содержимое Palm Pilot (мы знали, что оно мало), помещали его в базу данных, объединяли два варианта (синхронизировали их), а затем отправляли изменения обратно в Palm.

Система получилась довольно удачной. Через год нашей базой данных пользовались 28 000 пользователей, занимала она 12.5 Гбайт, и в ней хранилось больше 12 миллионов событий. Несмотря на рост количества событий, время ответа оставалось превосходно низким (мы только делали запросы «между А и В»). Даже если кто-то имел в общей сложности 5000 событий, это было неважно (попробуйте сделать это на модели Palm). У пользователя может быть как угодно много событий в определенный день, неделю или месяц, и даже в этом случае база данных справится с задачей с помощью индексов.

Мораль этой истории такова: необходимо проектировать базу данных в соответствии с поставленной задачей. Если целью является высокая производительность, так и нужно проектировать с учетом этой цели. Тогда не придется модифицировать базу и создавать версию 2.

Определяйте цели производительности в самом начале

Перед созданием приложения необходимо четко и определенно понять значение таких характеристик системы, как производительность и масштабируемость. Эта система показателей включает в себя ожидаемое количество пользователей, количество транзакций в секунду, приемлемое время ответа и т.п.

Рассмотрим типичные сценарии:

Нужно заставить систему работать быстрее.

Это результат того, что при создании системы не ставилось целью повышение производительности. Не были изучены вопросы, касающиеся того, сколько пользователей предполагается поддерживать, как много транзакций будет производиться в секунду, в какой диапазон (x, y) (в миллисекундах) должно укладываться время ответа. Разработчики знают, что их система очень медленная, но не имеют ни малейшего представления о том, как заставить ее работать быстрее.

Система настроена?

Единственный способ узнать, настроена ли система, это поставить некую цель и определить систему показателей. Если цель достигнута или даже превышена, значит, система настроена, в противном же случае она требует доработки.

Все говорят, что это нужно делать.

Некоторые люди выполняют специфические операции на регулярной основе: например, перестраивают индексы каждую неделю. Если спросить их, зачем они это делают, то ответ будет следующий: «Все знают, что нужно перестраивать индексы, вот мы этим и занимаемся». Им даже не приходит в голову оценить, приносят ли их действия больше пользы, чем вреда! Это будет происходить вечно, поскольку люди, как правило, не стараются установить, собрать и проанализировать проблемы, возникающие в их системе.

Необходимо двигаться в определенном направлении, стремясь к намеченной цели, чтобы достичь успеха.

«У меня есть вопрос, и я надеюсь, что Вы сможете ответить на него или хотя бы указать мне правильное направление. Мне необходимо генерировать для руководства помесячные отчеты о производительности базы данных. Я знаю, что показатели производительности можно извлечь из Oracle. Однако, по моему опыту, высшее руководство обычно не вникает в технические детали. Информация, предоставляемая им, должна иметь вид, приятный для просмотра и легкий для понимания. Как Вы считаете, какая им необходима статистика?»

Это классическое затруднение. Высшее руководство на самом деле хочет только знать, что все в порядке. У них нет ясного понимания целей и планов работы. Их отношение можно выразить словами: «только покажите мне, что уровень загрузки кэша хороший» или «покажите мне красивую диаграмму». Такие отчеты абсолютно бесполезны в определении того, насколько достигнута желаемая цель.

Используйте понятную, специализированную систему показателей

Я считаю, что система всегда может работать на 1% быстрее. Теоретически должна существовать возможность сделать наши базы данных бесконечно быстрыми. К сожалению, сделать это не удается по двум причинам. Во-первых, невозможно добраться до нуля; если брать 1% от малого числа, этот 1% будет каждый раз меньше (1% повышения производительности с каждым разом все меньше и меньше). Во-вторых, получить каждый последующий 1% труднее, чем предыдущий. Приходится больше настраивать, и каждая последующая настройка сложнее предыдущей. Достижение каждого 1% (который получается меньше) стоит больше предыдущего.

Таким образом, перед началом разработки необходимо выбрать жесткую и быструю систему показателей, в соответствии с которой будет оцениваться производительность. Если поставить задачу: «мы должны поддерживать 1000 пользователей, 100 из которых будут работать одновременно, с продолжительностью ответа 0.25 секунд при прохождении хорошо известной транзакции», то можно разработать систему, соответствующую этим требованиям. Для доказательства того, что эти цели достижимы, можно провести оценку производительности. Тогда вы будете на 99.9% уверены в том, что система настроена и соответствует поставленным целям. Если же созданная система не отвечает заданным требованиям, об этом будет известно до момента ее внедрения.

В случае если задача ставится так: «система будет применяться большим количеством пользователей (необязательно), и она должна быть быстрой», невозможно разработать систему определенного размера. Я был на многих совещаниях, где заказчики хотели знать, почему мы не можем им сказать, насколько большой компьютер им нужно купить для того, чтобы «большое количество людей могло работать быстро, выполняя некоторые транзакции». Мой ответ обычно был такой: «Сколько денег у вас есть? Покупайте самую большую машину и надейтесь на лучшее».

Собирайте и регистрируйте показатели во времени

Если есть длительная история определенных показателей, можно оценить положение дел. Пакет статистики (см. главу 2 и документацию Oracle “Performance Tuning Guide and Reference”) является отличным средством. Если бы на сайте хранилась статистика (пакет статистики) за последние шесть месяцев и каждый день в моменты максимальной загрузки делался пятнадцатиминутный снимок, то тогда можно было бы точно определить, когда запрос начал работать с низкой производительностью (это можно посмотреть в верхней части SQL-отчета) и какое событие вызвало это снижение. Или можно было бы посмотреть, негативный или позитивный эффект дало добавление нового приложения.

Наличие статистики для некоторых типовых транзакций и записей никогда не помешает. Можно, например, установить хранимую процедуру, которая выполняет запрос к репрезентативной выборке или даже транзакции, модифицирующие данные таким же образом, как это делают конечные пользователи. Периодически, в течение дня можно запускать эту процедуру, делая снимки производительности работы. В результате вы получите записи времени ответа для типовых запросов и транзакций. В целях профилактики вы можете осуществлять текущий контроль производительности и даже выводить ее диаграммы. Это делается не только для того, чтобы иметь что-то, что можно продемонстрировать высшему руководству, но и для того, чтобы точно засечь, в какой момент производительность начала падать и на сколько. Комбинация результатов текущего контроля и отчетов пакета статистики будет неоценима, когда потребуется ускорить работу системы.

Не стоит что-то делать только потому, что «все знают, что это нужно»

Очень часто безоговорочное принятие традиционной мудрости наносит вред системе. Плохо, если администраторы базы данных или разработчики делают что-то только потому, что «все знают, что это нужно делать».

«Почему перестройка индекса является причиной роста генерации протокола? У меня есть таблица с 35 миллионами строк и индексом (разделов нет). Транзакции с этой таблицей постоянны и составляют 500 000 строк в день, что создает 10 записей в журнале базы данных в день. Один раз в месяц индексы перестраиваются (alter index rebuild). На следующий день после перестройки индекса создается 50 записей. На следующий день — 45 записей, затем 40, 35, 30, 25, и опускается до 10. По достижении значения 10 записей в день эта цифра остается постоянной. Просматривая протоколы, мы обнаружили, что возрастает число обновлений внутреннего индекса (INTERNAL INDEX UPDATE). Почему это происходит? Всегда ли так случается?»

Интересно, что администратор определил причину проблемы — перестройка индекса! Подобно людям, индексы стремятся быть определенного размера. Одни из нас круглолицы, другие тощи, кто-то высокий, а кто-то низкий. Безусловно, мы можем сесть на диету, но у каждого из нас своя предрасположенность. Точно так же и с индексами. Каждый месяц администратор перестраивает индекс (сажает на диету), но индекс все равно хочет быть широким и толстым. Его откармливание занимает первую половину месяца, и в это время генерируется много записей в журнале базы данных. В данном случае перестройка индекса в системе имеет такие эффекты:

Это продолжается до тех пор, пока система не получает обратно то, что ей нужно. А затем администратор снова все повторяет! Он нарушает то равновесие, которого с таким трудом добилась система.

В данном случае администратор, к счастью, выявил высокую генерацию записей в протоколах, после чего продолжил расследование. Решение же этой проблемы самое простое — прекратить перестраивать индекс.

Это мой любимый пример: «Мы перестраиваем индексы каждую неделю или месяц. Все знают, что это следует делать на регулярной основе». Если бы только они проверили, что случается после перестройки индексов. Индексы крайне редко нуждаются в перестройке. Лишь в исключительных случаях подобные операции необходимо проводить на регулярной основе. Также редко возникает необходимость в реорганизации таблицы. Пока что я вижу, что люди делают это только в качестве стандартной исполнительной процедуры.

Конечно, бывают экстремальные случаи, когда перестройку рекомендуется сделать. В частности, это относится к битовым индексам после массовой загрузки данных. Но в ежедневных операциях системы транзакций необходимость в перестройке индексов возникает настолько редко, что я за 15 лет никогда этого не делал, за исключением физического перемещения индекса из одного места в другое. Сегодня нет необходимости делать это, поскольку легче использовать логические тома и проводить перемещения за пределами базы данных.

Ответ в данном случае такой: если отслеживать во времени жесткие и быстрые показатели (например, отчет пакета статистики), то можно количественно доказать правильность действий. Если перестройка индекса дает положительный результат, то на следующий день отчет пакета статистики должен быть лучше, чем днем раньше, не так ли? Статистические показатели по обращению в буфер для запроса высокого уровня, который использует этот индекс, должны стать много ниже (если этот индекс не используется запросом высокого уровня, зачем его перестраивать?), и общая производительность системы должна стать до известной степени лучше. Если отчет пакета статистики не показывает никаких изменений и время ответа на запрос остается таким же, это означает, что перестройка индекса, по крайней мере, не нанесла вреда. Или кажется, что эта процедура прошла безвредно, поскольку поглощаются ресурсы, тратится время и уменьшается производительность.

Об оценке производительности снова и снова

Многие представляют себе оценку производительности как что-то очень большое, и иногда это действительно так. Я провожу тесты по оценке производительности в разных формах и размерах. Многие мои тесты проходили в очень маленьких масштабах, я оценивал идею или метод. Помимо этого, я проводил тесты, чтобы посмотреть будет ли работать приложение, спроектированное для 10 000 одновременно работающих пользователей. Эти испытания могут занимать большое количество времени и энергии, но они необходимы.

Третий тип оценочных тестов предполагает наличие специфических показателей с самого начала проекта. Эта оценка производительности представляется в форме отчета пакета статистики или обычных процедур, которые в течение дня измеряют время ответа стандартных и общих транзакций в системе. Активная оценка производительности позволяет выяснить, соответствует ли или превышает система требования, поставленные при ее проектировании. На практике люди, как правило, игнорируют все виды оценочных тестов. Они никогда не тестируют свои методы, чтобы посмотреть, насколько они будут масштабируемыми и будут ли они таковыми вообще. Они никогда не проверяют систему, чтобы узнать, будет ли она масштабируемой после того, как сложатся вместе все биты и байты. И когда не сбываются ожидания, что мы слышим? «О! База данных очень медленно работает». Да, база данных загружается медленно. Но это симптом, а не проблема. Причина заключается обычно в плохо написанной программе или в неправильной архитектуре системы, которая никогда не тестировалась.

Важно понимать, что при оценке производительности должна моделироваться реальность. Оценка должна быть спроектирована, протестирована и применена в соответствии с конкретной спецификацией. Запуск TPC-C на вашей аппаратуре не скажет, как будет работать приложение, если только, конечно, ваша компания не настроена именно на оценку TPC-C (что сомнительно). Оценка производительности является уникальной и для пользователя, и для приложения.

ПРИМЕЧАНИЕ

Информация по оценке производительности по стандартам Transaction Processing Council содержится на сайте http://www.tpc.org/. Область действия оценки производительности так же широка, как эффективны результаты, которые она дает.

Краткосрочная оценка производительности

Команда Oracle применяет краткосрочную оценку производительности для того, чтобы выбрать один из двух различных подходов к проблеме или же доказать, что определенный технический метод более масштабируем, чем другой метод. Этот тип оценки производительности позволяет численно доказать, что подход А лучше, хуже или такой же, как подход В.

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

Использование простого тестового снаряжения

Я очень часто провожу краткосрочную оценку производительности и поэтому установил тестовое снаряжение, которое называю Runstats. Оно измеряет три параметра: время решения (или затраченное время), системные показатели (такие, как анализ вызовов) и блокирование. Системная статистика показывает, как долго с помощью каждого подхода выполняются те или иные действия, а также разницу во времени между двумя подходами. Информация по блокированию является ключевой в выходных данных этого отчета.

Триггер-защелка — это тип взаимного исключения или блокирующий механизм. Взаимное исключение/блокировка — это устройство сериализации. Устройства сериализации препятствуют выполнению параллельного доступа. Такие устройства хуже масштабируются, поддерживают меньшее количество пользователей и требуют больше ресурсов. Нашей же целью является создание масштабируемых приложений, для которых не имеет значения количество пользователей, которых они могут хорошо обслуживать (что 1 пользователь, что 10 000). Чем меньше триггеров-защелок применяется в приложении, тем лучше. Я бы скорее выбрал метод, который работает чуть дольше, чем использовал бы 10% триггеров-защелок. Я знаю, что те методы, в которых применяется меньше защелок, с точки зрения масштабирования гораздо лучше методов, использующих большое количество триггеров-защелок.

Итак, что дает применение Runstats? Детально Runstats обсуждается в главе 2. Здесь мы рассмотрим конечный результат работы Runstats и покажем, насколько важно использовать этот тип оценки производительности.

В качестве примера предположим, что нам необходимо проверить два различных подхода, производящих вставку данных в таблицу. Есть определенные требования к загрузке данных в некоторую таблицу базы данных, но неизвестно, какие таблицы применяются в процессе выполнения (статический SQL не рассматривается). В одном методе используются переменные привязки, которые служат для заполнения оператора SQL и позволяют применять оператор SQL снова и снова. Второй метод не использует переменных привязки. По какой-то причине второй метод широко применяется многими разработчиками ODBC и JDBC. Мы сравним два метода и проанализируем результаты. Начнем с создания тестовой таблицы:

ops$tkyte@ORA920> create table t ( x varchar2(30) ) ;
Table created.

Напишем две процедуры для двух наших методов. Одна процедура использует динамический SQL с переменными привязки. Другая — динамический SQL без переменных привязки. Она выполняет конкатенацию символьных строк для вставки в SQL-оператор.

ops$tkyte@ORA920> declare
  2      procedure method1( p_data in varchar2 )
  3      is
  4      begin
  5          execute immediate
  6          'insert into t(x) values(:x)'
  7          using p_data;
  8      end method1;
  9
 10      procedure method2( p_data  in varchar2 )
 11      is
 12      begin
 13          execute immediate
 14          'insert into t(x) values ( ' ' '  ||
 15          replace( p_data,' ' ' ', ' ' ' ' ' ' )   ||  ' ' ' ) ';
 16      end method2;

Отметим, что кодировать без применения переменных привязки действительно сложнее. Необходимо быть внимательным в использовании специальных символов, подобных кавычкам. Фактически, цель функции REPLACE, вызываемой в method2, заключается в том, чтобы убедиться, что кавычки двойные.

Теперь протестируем два разных метода. Сделаем это в простом цикле. Распечатаем затраченное время и завершим блок PL/SQL.

 17  begin
 18      runstats_pkg.rs_start;
 19      for i  in  1  ..  10000
 20      loop
 21          method1(  'row  '  ||  I ) ;
 22      end loop;
 23      runstats_pkg.rs_middle;
 24      for i in 1 .. 10000
 25      loop
 26          method2(  'row  '  ||  I );
 27      end loop;
 28      runstats_pkg.rs_stop;
 29  end;
 30  /
884 hsecs
2394 hsecs
run 1 ran in 36.93% of the time

Теперь видно, какой из подходов лучше. Понятно, что время работы method1, использующего переменные привязки, составляет 37% от времени работы method2. Просмотрев статистику и использование триггеров-защелок, можно увидеть более очевидные моменты:

NameRun1Run2Diff
...
LATCH.row cache enqueue latch7240,09640,024
LATCH.row cache objects8840,12840,040
LATCH.library cache pin60,166108,56348,397
LATCH.library cache pin alloca11678,49078,374
LATCH.child cursor hash table1979,19479,175
LATCH.shared pool30,181162,931132,750
LATCH.library cache60,363249,568189,205
PL/SQL procedure successfully completed.

Метод, в котором не применяются переменные привязки, использовал значительно больше триггеров-защелок (напомним, что триггеры-защелки — это тип блокировки, а блокирование препятствует масштабированию). Если подсчитать, то получится, что method1 использовал около 275 000 триггеров-защелок, а method2 — около 884 000, и разница составляет свыше 600 000. Это огромная величина! И окончательно доказывает, что использование переменных привязки делает приложение не только более быстрым, но и более масштабируемым. Увеличение числа блокировок в системе уменьшает количество параллельно работающих пользователей. Проблемы блокирования не могут быть решены с помощью мощного процессора или с помощью технических средств. Скорее, нужно удалить источник разногласий, и в данном случае он был найден.

ПРИМЕЧАНИЕ

Информацию о применении Runstats можно найти на сайте http://asktom.oracle.com/. Для поиска используйте строку “run stats” (в кавычках).

Инструменты для оценки производительности

Ниже приводится список средств, которые можно использовать в процессе краткосрочной оценки производительности:

TKPROF, TIMED_STATISTICS и SQL_TRACE

Отличные методы для просмотра того, что делают ваши программы и насколько хорошо.

DBMS_PROFILER

Настройка PL/SQL кода.

Explain Plan

Предназначен для просмотра того, что запрос собирается делать.

Autotrace

Предназначен для просмотра того, что запрос действительно делал.

Все эти средства подробно рассматриваются в главе 2.

Долгосрочная оценка производительности

Под долгосрочной оценкой производительности подразумевается проверка системы в целом, тестирование на масштабируемость. Не существует другого способа узнать, будет ли система так же хорошо работать с 5000 пользователями, как и с 5. Нет другого способа узнать, будет ли система так же хорошо работать с 500 000 строками, как и с 5000. Нужно проводить оценочные тесты, если только вы не хотите в день сдачи проекта обнаружить, что система работает плохо. Необходимо моделирование в реальных условиях. Потребуется тратить на это деньги, время и энергию. Но если действительно нужно создать хорошую, корректно работающую систему, придется проводить долгосрочную оценку производительности.

Первым шагом на пути к успеху является критика. И этот шаг команды разработчиков часто игнорируют. Время от времени мне встречаются системы, недостатки которых обусловлены тем, что:

Их никогда не тестировали.

Тестирование с репрезентативным количеством данных

Этот пример подтверждает необходимость тестирования с реальным количеством данных. Я работал в команде, которая проводила эксплуатационные испытания сайта для одного продукта. Мы все установили, запустили и настроили. Это был самый медленный сайт на планете. Для загрузки главной страницы требовалось от 30 до 45 секунд. И каждая последующая страница загружалась столько же. Мы выполнили экспорт/импорт старой схемы нашей базы данных на их машины, и разработчики не смогли воспроизвести эту ситуацию.

После работы TKPROF проблема была обнаружена. В нашей базе данных было более 40 000 учетных записей, а в их — всего лишь 10. Запрос, который они написали к словарю данных для проверки привилегий, быстро обслуживал 10 пользователей, а 40 000 пользователей обрабатывались очень медленно. Разработчики никогда не проводили проверку с больше, чем одной учетной записью в системе. Когда они проверяли нагрузку в тысячи сессий, все эти сессии запускались одним и тем же пользователем! Их оценка производительности не была реальной, она проводилась в несоответствующей действительности ситуации применения приложения. После того как разработчики создали пару тысяч учетных записей, они обнаружили и впоследствии исправили все проблемы с производительностью.

Тестирование с реальными входными данными

Я принимал участие в оценке трехуровневого, основанного на Java приложения. Промежуточный код создавался как часть программного обеспечения. Разработчики были разочарованы полученной производительностью. Выяснилось, что проблема вовсе не в базе данных и даже не в самом приложении. Дело было в неправильно спроектированной оценке производительности. Было легче написать сценарий с применением учетной записи одного пользователя, что они и сделали. Они не учли, что первой строкой кода в каждом контейнере была:

SELECT * FROM USER_TABLE WHERE USERNAME = :X FOR UPDATE

Контейнер сериализовывал транзакции на уровне пользователя. У них был один пользователь, который обращался к системе (активно), и ему действительно приходилось долго ждать выполнения транзакций. Этот случай подобен описанному выше, но здесь проблема производительности возникает из-за управления параллельной работой. Сложность ситуации заключалась в том, что у одного пользователя были сотни транзакций, идущих параллельно!

Разработчики достигли поставленной цели, потратив больше времени на оценку и постановку теста в реальных условиях. Пугает лишь то, что этот пример является улицей с двусторонним движением. К счастью для этой группы, эталонный тест показал неутешительные результаты, и разработчикам пришлось углубиться в ситуацию. Если заполнить систему нереальными входными данными, она покажет хорошую производительность и масштабируемость, но в действительности все будет не так. Очень важно создать реальные условия тестирования.

Необходимость в проверке результатов

Меня вызвали, чтобы помочь заказчику решить вопросы производительности сервера в рабочей системе. Система работала с трудом. Она была введена в эксплуатацию после того, как успешная оценка производительности «доказала», что ее можно масштабировать до 10 000 пользователей. Но теперь система рушилась даже при одновременной работе чуть больше 200 пользователей. Что произошло?

Когда систему отключили, выяснилось, что числа, полученные разработчиками в процессе оценки, были абсолютно нереальными. Время ответа, зафиксированное в отчете, не отражало реальной ситуации. У каждого экрана, у каждой web-страницы время ответа составляло от 0.01 до 0.05 секунд. Эта система, согласно результатам оценочного теста, могла масштабироваться бесконечно. Остановились на 10 000 пользователях, но могли бы идти и дальше.

Разработчики не проверили, корректно ли была проведена оценка производительности. В действительности, каждая страница возвращала ошибку “404 — Not found”. Они этого не знали, потому что никогда не проверяли. Они не записывали никаких показателей, не оснащали инструментальными средствами свой код (см. ниже), так что они даже не знали, что на самом деле ничего не работает. Они только думали: «Вот какую замечательную систему мы создали!»

Когда мы исправили условия оценочного теста, стало все ясно. В системе не использовались переменные привязки! Если вернуться к теме о краткосрочной оценке производительности, то станет понятно, что произошло. При увеличении числа пользователей увеличивается количество одновременных обращений. Большее количество одновременных обращений означает больше событий ожидания, а возрастание числа событий ожидания ведет к увеличению времени ответа. При росте количества пользователей система стала тратить больше времени на удержание их на линии в ожидании анализа SQL. Компьютер, на котором мы работали, был действительно большой машиной — 48 процессоров. Но никакое дополнительное количество процессоров не могло помочь в данном случае. Проблема заключалась в длительном ожидании доступа к совместно используемой области. Через две долгие недели поиска и исправления кода система была установлена вновь.

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

Не следует относиться к оценке производительности как к рутинной работе

Оценка производительности является одним из наиболее важных шагов в разработке после верного проектирования (спроектировано для обеспечения хорошей производительности). Этот шаг необходим для проверки проекта. При использовании оценки производительности можно все проверить и избежать гнева и презрения заказчика — конечного пользователя.

При разработке программного продукта необходимо проводить его оценку. Если вам не дают добро на проведение теста перед установкой у заказчика, то возникает вопрос: «Действительно ли система так важна для них?»

С помощью проведенной вовремя оценки производительности удастся не только избежать затруднений, но и сэкономить время и деньги. Каким образом? Разве проведение тестов не отнимает время и деньги? Конечно, и не мало, но попробуйте оценить стоимость двухнедельного бездействия 1000 людей, которые ждут исправления ошибок в системе. Какое это количество времени? Какова цена простоя этих 1000 человек и тех, кого они обслуживают? Например, если мы имеем дело с системой заказа продуктов, то речь пойдет уже не о 1000 неработающих сотрудниках, а о компании, доходы которой в первую очередь зависят от деятельности этих людей. Потери будут колоссальными.

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

Инструментирование системы

Под инструментированием системы я понимаю необходимость отладки кода всего приложения. Обработка кода инструментальными средствами означает снабжение системы возможностью генерации большого количества трассировочной информации.

Это позволит сделать следующие вещи:

Отладить код без отладчика.

Когда возникает проблема с базой данных, специалисты службы технической поддержки рекомендуют включить трассировку событий с целью формирования файлов трассировки, содержащих множество диагностической информации. Это даст возможность службе поддержки и команде разработчиков решить проблему, даже не притрагиваясь к системе. Представьте хотя бы на секунду работу Oracle, у которого нет SQL_TRACE или системы событий.

Установить причину плохой производительности системы.

Это чрезвычайно важно в том случае, когда строится N-уровневое приложение с множеством перемещающихся частей. Когда служба поддержки говорит, что «система работает медленно», как выявить проблемное место, замедляющее работу, если нет кода, который может указать, что конкретно отнимает много времени?

Существуют средства, позволяющие решать эти вопросы. «Но увеличатся накладные расходы. А мне этого не нужно». Не стоит относить инструментирование к накладным расходам. Накладные расходы — это нечто, что можно удалить без потери прибыли. Удаление (или отсутствие) инструментальных средств забирает значительную часть функциональности. В инструментальных средствах нет необходимости, если система никогда не прерывает свою работу, никогда не нуждается в диагностике и никогда не страдает от потерь производительности. Если все так, то действительно отсутствует необходимость в инструментировании системы (пришлите мне электронный адрес разработчика — я предложу ему работу).

Трассировка asktom.oracle.com

Чтобы посмотреть инструментальные средства в действии, обратитесь на сайт asktom.oracle.com и щелкните мышью на любой статье на главной странице. Вы увидите URL, подобный следующему:

http://asktom.oracle.com/pls/ask/f?p=...::NO::...

Если набрать этот URL и заменить слово NO на YES, то появится та же самая страница, но с информацией о состоянии и времени.

Каждое приложение, над которым я работал, обладает способность к трассировке в той или иной форме. Если кто-нибудь жалуется на медленную работу страницы, я могу быстро и легко определить, какая часть страницы стала причиной этой проблемы. Если определенный запрос, с помощью которого создается какая-то часть страницы, работает долго, его очень легко вычислить.

В дополнение к этому я проверяю доступ к каждой странице. Это означает, что для каждой страницы, доступной моему приложению, я вставляю строку аудита, содержащую необходимую для меня информацию. На web-сайте asktom я отслеживаю IP-адреса, браузер, время запроса, длительность запроса, какая страница была затребована в этом приложении и другую информацию. Это позволяет мне быстро отвечать на вопросы типа: «Сколько человек используют asktom?» и «Сколько страниц вы обслуживает ежедневно?»

«За последние 24 часа я наблюдал, что доступ к вашему сайту и открытие страниц Ask Question, Read Question, Review Question и т.д. выполнялись очень медленно (примерно от 1 до 3 минут вместо обычных пары секунд). Кто-то еще сталкивался с такой же проблемой?»

Я обратился к моей статистической странице, создаваемой на основе журнала аудита, который является частью моей системы, и увидел следующее:

Последние 24 часаПоследние 60 минутПоследние 60 секунд
Page Views27 3487599
Page Views/Sec0.3170.2110.150
IP Addresses25521304
Views per IP Address10.7165.8382.250
Users29861476
Views per User9.1595.1631.500
Distinct Pages20163
Avg Elap/Page (secs)0.250.400.77
Fastest Page (secs)0.130.10.28

Я сразу же увидел, что сложная ситуация с доступом была вызвана не базой данных. Фактически эта проблема вовсе не была широко распространенной (она возникала только у этого пользователя). Я мог утверждать, что причина не в базе данных, исходя из значения Avg Elap/Page, которое было в норме (создание страниц базой данных в норме). Уровень количества просмотров страниц также был в пределах нормы. (У меня есть другой отчет, который я время от времени просматриваю. В нем содержится информация о том, сколько страниц было посещено за день/неделю и т.д.) В течение недели я имею от 25 000 до 35 000 просмотров страниц в день с 2000–3000 различных IP-адресов. За последние 24 часа эти показатели на asktom были в пределах нормы. Если бы загрузка страницы занимала 2–3 минуты вместо обычных секунд, пользователи перестали бы вызывать их, и уровень числа посещений снизился бы.

В течение пары секунд я мог отключить базу данных, web-сервер и мою внешнюю сеть по причине медленного ответа пользователям. Я попросил посетителей asktom рассказать мне о работе сайта, и все ответили, что производительность в норме, подтверждая, что это был единичный случай.

Впоследствии автор этого вопроса уведомил меня, что проблема была решена. Поскольку он находился где-то глубоко в Австралии, а я на восточном побережье Соединенных Штатов, я могу предположить, что это была досадная сетевая проблема, исправленная со временем.

Инструментальное средство удаленной отладки

В другом примере полезного применения инструментального средства используется часть кода, написанного мной на заре появления Интернета.

Многие из вас знакомы с mod_plsql, Apache-модулем Oracle, позволяющим адресам URL запускать хранимые процедуры PL/SQL; это, например, способ работы asktom. Предшественник mod_plsql назывался OWA (Oracle Web Agent). Это была оригинальная CGI-BIN программа, которая поставлялась с Oracle Internet Server (OIS) версии 1.0, ставшей впоследствии Oracle Web Server (OWS) версий 2.0 и 2.1.

Картридж OWA, как это называлось, был очень прост по своим функциям: он запускал хранимую процедуру. Эту концепцию я повторно применил в 1996 г., но добавил поддержку загрузки и выгрузки файла, гибкую передачу параметров, авторизацию базы данных, тег <Oracle> (подобно PL/SQL Server Pages (PSPs)), компрессию, web-хронометрированную статистику и т.д. Многие из этих возможностей можно увидеть сегодня в модуле mod_plsql. (Успех моей части программного обеспечения подсказал разработчикам включить эти возможности в поддерживаемый код.)

Я назвал мою часть программного обеспечения OWAREPL и выложил ее в Интернет. Эту программу загружали тысячи раз, и, как я слышал, некоторые применяют ее по сей день. Эта часть кода на С занимает около 3500 строк и использует OCI (Oracle Call Centre) для взаимодействия с базой данных.

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

Пожалуйста, установите debugModules = all в конфигурационном файле sv<имя_web-сервера>.app. Это позволит создать файл трассировки после перезагрузки web-сервера. Запустите приложение, воспроизведите проблему и затем пришлите мне по электронной почте результаты файла трассировки.

У меня никогда не было необходимости в регистрации на другой машине за пределами Oracle для сбора диагностической информации. Так как эта часть кода запускалась как CGI-BIN или как динамически загружаемый картридж под OWS/OAS, вопрос использования отладчика просто не возникал. Если бы я не имел возможности получать трассировку, то, скорее всего, моим единственным ответом на все вопросы был бы: «Я не знаю, извините».

Теперь понятно, насколько выгодно использование этих средств. Остался вопрос: «Каким образом можно оснастить инструментальными средствами программу?» Существует множество способов реализации, и они будут исследованы, начиная от базы данных и заканчивая типичными приложениями (клиент-сервер и N-уровневые приложения).

Использование DBMS_APPLICATION_INFO

DBMS_APPLICATION_INFO является одним из многих пакетов базы данных, предлагаемых Oracle. Это один из наиболее редко используемых пакетов.

Известно ли вам, что при выполнении длительной команды в Oracle, такой как CREATE INDEX или UPDATE для миллиона строк (длительная определяется как выполняемая дольше, чем за 3–5 секунд), динамически работающее представление V$SESSION_LONGOPS заполняется информацией? Это представление включает в себя следующую информацию:

DBMS_APPLICATION_INFO позволяет установить значения в представлении V$SESSION_LONGOPS. Это полезно делать при записи течения длительного процесса.

Вероятно, каждому разработчику хотелось бы, чтобы такая возможность существовала и в его собственных длительных хранимых процедурах или в других операциях. Что ж, начиная с версии 8.0 базы данных, DBMS_APPLICATION_INFO поможет найти ответы на вопросы:

DBMS_APPLICATION_INFO позволяет установить три столбца в строке таблицы V$SESSION: CLIENT_INFO, ACTION и MODULE. Функции могут не только устанавливать эти значения, но и возвращать их. К тому же во встроенных функциях USERENV и SYS_CONTEXT существует параметр, обеспечивающий доступ к столбцу CLIENT_INFO из любого запроса. В моем запросе я могу сделать, например, select userenv ('CLIENT_INFO') from dual или использовать where some_column = sys_context ('userenv', 'CLIENT_INFO'). Столбец MODULE должен хранить имя главного процесса, например имя пакета. Столбец ACTION подходит для хранения имени процедуры, которая выполняется в пакете.

ПРИМЕЧАНИЕ

Значения, устанавливаемые в таблицах V$, становятся видимыми немедленно. Нет необходимости фиксировать их для того, чтобы увидеть их и использовать при общении с внешним миром.

Ниже приводятся основные указания по использованию пакета DBMS_APPLICATION_INFO:

Использование DEBUG.F в PL/SQL

Другое средство, которое я часто использую, — это разработанный на заказ пакет DEBUG и содержащаяся в нем функция F. Я применяю это техническое средство в своих программах уже свыше 16 лет. Сначала оно было реализовано на С (и называлось debugf), после я смоделировал его в printf (сейчас оно называется DEBUG.F). Используя этот пакет, можно добавлять операторы регистрации и трассировки в PL/SQL-приложение. Он очень прост в применении. Например, можно написать программу:

Create procedure p (  p_owner  in varchar2, p_object_name  in varchar2)
As
    L_status number := 0;
Begin
    Debug.f( 'Entering procedure, inputs "%s", "%s" ',
              P_owner, p_object_name ) ;
    ...какой-то код...
    debug.f( 'Normal exit, status = %d', l_status );
end;

В операционной системе будет (если пакет отладки скажет, что нужно генерировать файлы отладочной трассировки для этого модуля через вызов DEBUG.INIT) создаваться сообщение файла трассировки примерно такого вида:

12062002 213953(P.PROCEDURE  5) Enter procedure inputs "A", "B"
12062002 213955(P.PROCEDURE  56) Normal exit,  status = 0

Оно содержит следующую информацию:

С помощью этих выходных данных можно решить вопросы, возникающие в процессе отладки. Например, кто-нибудь говорит: «Я получил ошибку от конечного пользователя, запустившего вашу программу. Что я должен делать?» Ответом будет: «В SQL*PLUS вызовите пакет DEBUG.F и попросите его отладить (трассировать) процедуру Р только для этого пользователя.» После того как ошибка будет воспроизведена, в трассировочном файле будет содержаться достаточное количество информации для дальнейшей отладки программы.

Кроме того, процессы имеют временную метку. Можно засечь момент, когда в большом, многопроцедурном процессе началось замедление работы. При получении сообщения о замедлении работы можно установить трассировку для пользователя и просмотреть временные метки (см. выше пример с asktom.oracle.com). Изучив изменение времени, можно определить место, где возникают сложности с производительностью, и решить проблему. Реализацию DEBUG.F можно загрузить с сайта http://asktom.oracle.com/ ~tkyte/debugf.html.

Настройка SQL_TRACE в приложении

Одним из наиболее мощных настроечных средств базы данных является SQL_TRACE, который позволяет в процессе выполнения приложения трассировать все операторы SQL и блоки PL/SQL. Он также предоставляет такую информацию, как количество выполненных операций ввода/вывода, время выполнения запроса, время выполнения SQL и т.д. Без такого удобного средства, как SQL_TRACE, настройка SQL-приложения была бы чрезвычайно сложной. При использовании SQL_TRACE каждую часть программы, которая выполняет SQL в базе данных, можно считать уже частично оснащенной инструментальными средствами. (За более подробной информации о SQL_TRACE обращайтесь к Oracle Performance Tuning Guide and Reference.)

Тогда почему так много внедренных систем не используют возможности SQL_TRACE? Ответ на этот вопрос прост — большинство людей не думают о необходимости трассировки до тех пор, пока у них не появляются сложности с производительностью (как известно, это случается с рабочими системами, которые не были протестированы на масштабируемость). Когда же это происходит, они понимают, что их приложение не способно выполнить трассировку с помощью этого удобного средства.

«У нас есть основанное на web приложение, которое управляется MS-DTC (Microsoft Distributed Transaction Coordinator). Все SQL-операторы (select/insert/update/delete) для всех таблиц располагаются в хранимых процедурах, которые, в свою очередь, находятся в пакетах. Эти хранимые процедуры вызываются с помощью компонентов СОМ+. Компоненты СОМ+ используют для регистрации в базе данных и запуска этих пакетов одно имя пользователя (app_user). Пул соединений управляется IIS и зависит от посещаемости пользователями web-сайта. Я вижу подключенные к базе данных множественные сессии пользователя app_user. Как я могу использовать TKPROF?»

К сожалению, запуск TKPROF далеко не такая легкая вещь. Обычные методы баз данных, которые делают это, например триггер SCHEMA LOGON, способный осуществлять трассировку для сессии с одним пользователем, бесполезны. При этом подходе следует избегать применения единственного имени пользователя для регистрации в базе данных, так как соединительный пул в дальнейшем усугубит проблему. В случае с обычным соединительным пулом сессия базы данных (уровень, на котором осуществляется трассировка) открыта для множества несвязанных сессий конечных пользователей. При отсутствии соединительного пула у приложения есть собственное подключение к базе данных — уровень, на котором Oracle от начала до конца создает трассировку. При наличии соединительного пула это единственное соединение является общим для всех сессий конечных пользователей в системе. Мы получаем файл трассировки, в котором содержится не только интересная для нас трассировочная информация, но и информация от любых сессий конечных пользователей, которые применяли это соединение (одним словом, беспорядок). Результирующий файл трассировки в таком случае будет бессмысленным (ваш SQL смешается и с моим SQL, и с их SQL, и с чьим-нибудь еще SQL).

Итак, что необходимо сделать с этим приложением:

Теперь приложение знает, когда George его запустил или когда модуль Х начал выполняться, и может включить или выключить трассировку. Можно выборочно получить трассировку только для конкретного пользователя или конкретного модуля. Информация может собираться в отдельных файлах трассировки на сервере (поскольку каждый раз берется соединение из пула, можно получать другие сессии, и файлы трассировки будут основаны на сессиях), но эти файлы будут содержать только трассировочную информацию для пользователя или модуля. Теперь можно применить TKPROF к любому из трассировочных файлов и получить необходимую информацию.

Использование стандартных API

Даже языки программирования имеют режим «инструментируйте вашу программу». Например, web-страница http://java.sun.com/j2se/1.4/ docs/guide/util/logging/ описывает API для регистрации приложений, основанных на J2EE. Этот API следует концепциям, которые я поддерживаю: снабжает программу сообщениями протокола. Этот пакет регистрации генерирует сообщения на XML. У Sun для него даже есть Document Type Definition (DTD), и можно использовать файл XSL (Extensible Stylesheet Language) при кодировании в формате XML для получения красивых отчетов. Сообщение протокола может выглядеть примерно так:

<?xml version="l.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2002-12-06 23:21:05</date>
  <millis>967083665789</millis>
  <sequence>1234</sequence>
  <logger>demo.test.foo</logger>
  <leve1>INFO</1eve1>
  <class>demo.test.LogTest</class>
  <method>writeLog</method>
  <thread>10</thread>
  <message>Entered routine, inputs = 5 and 'Hello'</message>
</record>
</log>

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

Пакет регистрации является новым средством, появившимся в J2EE 1.4. Существуют и другие решения, например регистрирующий API Log4J и Jakarta Common Logging Component. Если запустить поиск этих ключевых слов на www.google.com, то можно получить огромное количество информации.

Построение собственной подпрограммы

Если не используется PL/SQL или Java J2EE, то можно создать собственную подпрограмму DEBUG.F. Я делал это на нескольких языках. Например, выше упоминалась OWAREPL — написанная мною часть программного обеспечения, которую по сей день многие загружают и используют. Для этой программы я реализовал debugf на языке С. Эта программа работает по тому же принципу, что и DEBUG.F в PL/SQL и пакет регистрации в Java. Все, кому это интересно, могут загрузить исходный код (хотя сейчас он уже устарел) с сайта http://asktom.oracle.com/~tkyte/owarepl/ doc/ и посмотреть эту программу в работе в startup.c и owarepl.h

Слово «аудит» не является непристойным

Некоторые считают, что аудит — это ненужные накладные расходы. Но, по-моему, аудит чрезвычайно полезен и необходим каждый день. Я часто спрашиваю: «Каким образом мы можем выяснить, кто удалил эту таблицу?» Если аудит не включен, установить это будет тяжело. Журнал аудита содержит полезную информацию, которая позволяет уточнить, где возникают сложности с производительностью, какова частота использования шаблонов и какие объекты чаще всего применяются (и наоборот). Аудит позволит посмотреть, как люди используют приложение или же чем они злоупотребляют.

ПРИМЕЧАНИЕ

В Oracle 9i с дополнительной регистрацией можно использовать Log Miner для того, чтобы узнать, кто удалил таблицу. Но с помощью журнала аудита это сделать легче и эффективнее, потому что можно выбрать — либо журнал создается для каждого удаления, либо не создается вовсе.

Однажды я обратил внимание на внезапно возросшую посещаемость сайта asktom.oracle.com — если обычно было 30 000 просмотров страниц, то теперь их стало больше 150 000. Сначала я удивленно подумал, что резко возросла популярность моего сайта. Однако рост был слишком внезапным и слишком большим. При просмотре журнала аудита я обнаружил новый браузер — Web Whacker. Кто-то не спеша бродил по сайту asktom.oracle.com и загружал все целиком. Проблема была в том, что все время этот пользователь обращался к сайту с помощью Web Whacker, получал новую пользовательскую сессию в URL, что означало полную загрузку сайта каждый раз.

Сайт asktom не был приспособлен к возросшей загрузке. Я добавил код в заголовок asktom.oracle.com (код, который запускался вверху каждой страницы), который просматривал тип браузера. Если это был Web Whacker, код возвращал страницу без ссылок, которая говорила: «Пожалуйста, не пытайтесь использовать средства загрузки». Практически сразу же результаты подсчета просмотров страниц вернулись к их нормальным значениям. С тех пор каждый раз, когда я вижу подобные пики активности, я просматриваю журнал аудита, который сообщает мне о происходящем. Он предоставляет новый тип браузера, который следует отфильтровывать.

Меня спрашивают (я получаю этот вопрос примерно раз в неделю — меняются только слова функция, пакет, процедура, последовательность, таблица, представление...): «Как можно получить следующую информацию из системных таблиц? Как идентифицировать действия DDL, выполняемые за последние 24 часа? Если разработчик переписал некоторую функцию, как определить, кто это сделал?» Единственный известный мне способ — это включение аудита: либо основного аудита, использующего встроенные возможности базы данных, либо написанного на заказ аудита, использующего триггеры событий (BEFORE CREATE, BEFОRE DROP и т.п.).

В приложении, помимо обычного аудита, проводимого в базе данных, необходимо осуществлять аудит действий. Следует проводить аудит в таблицах баз данных. Легко проверять данные в базе данных, но сложно это делать, если данные находятся где-нибудь в файле или в XML-документе. Используйте систему, подобную той, что представлена на asktom.oracle.com: каждый просмотр страницы — это событие аудита, т.е. вставка в таблицу. Необходимо помнить, что базы данных созданы для вставки — это то, что они лучше всего делают.

Не надо бояться добавлять аудит везде и всюду. Выгоды от этого перевешивают снижение производительности.

Вопрос авторства

Не так давно на asktom.oracle.com пришло сообщение, которое начиналось примерно так:

Я прочитал статью о производительности на <некоторый web-сайт> и не могу поверить в то, что узнал об операции фиксации. Я обычно просматриваю ваши ответы на этом сайте и читал вашу книгу. Вы предлагаете избегать частого применения COMMIT. Ниже я привожу выдержку из статьи. Пожалуйста, дайте мне знать, что вы думаете о предложениях этого автора.

Результат частого использования оператора COMMIT

“Следует как можно чаще выполнять в программах оператор COMMIT. При частом использовании этого оператора производительность программы увеличивается и потребности в ресурсах минимизируются, поскольку COMMIT высвобождает следующие ресурсы: 1) Информация, удерживаемая в сегментах отката для отмены транзакций, если это понадобится; 2) Все блокировки, приобретенные в процессе выполнения операторов; 3) Пространство в буферном кэше протокола; 4) Накладные расходы, связанные с внутренними механизмами Oracle, на управление ресурсами из трех предыдущих пунктов».

Помимо всего прочего, в статье содержалось более 50 советов по увеличению производительности. Однако практически каждый совет был либо неверен (как и эта статья), либо обманчив.

Так что после разоблачения одного пункта — мифа об операции фиксации (очень популярный миф) — автор связался со мной. Он попросил меня просмотреть остальные пункты и дать ему знать, если там содержатся ошибки. Я написал опровержение для первых 25 советов, затем, устав печатать, мельком взглянул на остальные и подтвердил, что все они такого же качества. Я отправил ответ автору по электронной почте и получил от него сообщение: «Благодарю вас за подробное описание. Меня действительно удивило, что Tuning Book (книга по настройке) содержит так много ошибок».

Что это? Все 50 советов были взяты из книг по настройке производительности. Но автор даже не пытался проверить, как все это работает. Он просто поверил каждому слову в этих книгах.

Следует остерегаться универсального «лучше»

Все, что связано с настройкой производительности, может быть и должно быть доказано (посредством тестирования, например). Важно помнить: то, что хорошо работает в одних условиях, может потерпеть неудачу в других.

Например, некоторые убеждены, что не следует использовать IN (подзапрос), а лучше всегда применять WHERE EXISTS (коррелированный подзапрос). Другие думают иначе. Как могли сложиться два столь разных мнения? Легко — настройка проводилась в разных условиях. Одна группа пришла к выводу, что не стоит использовать IN в их запросах с их данными, а лучше применять WHERE EXISTS. Другая же группа с иными данными и иными запросами пришла к противоположному выводу. И утверждения ни одной из групп не были абсолютно верными — обе ошибались, и в то же время обе были правы. Определенные методы применимы в определенных условиях. Вот почему я стараюсь не отвечать на вопросы:

Если бы существовал лучший универсальный способ работы, то программное обеспечение продвигало бы только его. Почему тогда существуют альтернативные методы?

Подозрительные коэффициенты и другие мифы

Часто можно слышать такие утверждения: «В хорошо настроенной системе уровень обращений в кэш должен составлять 96%. Ваша цель заключается в регулировке буферного кэша для достижения этого значения». Необходимо уточнить, почему это так. Это доказано? Другие согласны с этим утверждением? Например, буферный кэш — это один из популярных мифов. У меня есть «антимиф».

Я считаю, что система с высоким уровнем обращений в кэш на самом деле нуждается в серьезной настройке! Почему? Потому что выполняется много логических вводов/выводов. Логический ввод/вывод требует триггеров-защелок. Защелки — это блокировки. Блокировки — это устройства сериализации, которые препятствуют масштабируемости. Высокий уровень обращений в кэш является индикатором злоупотребления индексами в системе. Рассмотрим небольшой пример:

SQL-программисту Joe (или Josephine) необходимо запустить следующий запрос:

select tl.object_name, t2.object_name
  from tl, t2
 where tl.object_id = t2.object_id
   and tl.owner =  'WMSYS'

В данном случае Т1 и Т2 — это огромные таблицы с более чем 1.8 миллиона строк в каждой. Joe запускает запрос и использует SQL_TRACE, чтобы посмотреть, как запрос выполняется. Ниже представлен результат:

call--------count------cpu------elapsed-------disk-------query-------current-------rows-------
Parse10.000.000000
Execute10.000.000000
Fetch--------35227------5.63-------9.32-------23380-------59350-------0-------528384-------
total352295.639.3323380593500528384

Rows--------------Row Source Operation----------------------------------------
528384HASH JOIN
8256TABLE ACCESS FULL T1
1833856TABLE ACCESS FULL T2

«Глупый, глупый Cost Based Optimizer (CBO), — говорит Joe. — У меня есть индексы. Почему он их не использует? Все знают, что применение индексов — это всегда быстро! Но это еще не все! Посмотрите на уровень обращений в кэш для этого запроса — он составляет около 50%! Это ужасно! Полностью не соответствует тому, что я читал в книге. Хорошо, попробую использовать более точный Rule Based Optimizer (RBO) и посмотрю, что получится».

select /*+ RULE */ t1.object_name,  t2.object_name
  from tl, t2
 where tl.object_id = t2.object_id
   and tl.owner = 'WMSYS'

Получается следующее:

Execution Plan
   0      SELECT STATEMENT 0ptimizer=HINT: RULE
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T2'
   2    1     NESTED LOOPS
   3    2       TABLE ACCESS (FULL) OF 'Tl'
   4    2       INDEX (RANGE SCAN) OF 'T2_IDX' (NON-UNIQUE)

«Отлично, — говорит Joe, — он все-таки использовал мои индексы. Я же знаю, что индексы — это хорошо!» Joe думает, что проблема решена, но он еще не запускал запрос. TKPROF покажет истинную ситуацию:

call--------count-----cpu-----elapsed-------disk-------query---------current-------rows------
Parse10.000.000000
Execute10.000.000000
Fetch--------35227-----912.07-----3440.70-------1154555-------121367981---------0-------528384------
total35229912.073440.7011545551213679810528384

Будьте аккуратнее в своих запросах! В итоге индекс Joe был использован, но это оказалось неразумным: 10 секунд против одного часа, и все ради применения индексов и высокого уровня обращений в кэш. Администратор был бы доволен:

  1  SELECT phy.value,
  2         cur.value,
  3         con.value,
  4    1-((phy.value)/((cur.value)+(con.value))) "Cache hit ratio"
  5  FROM  v$sysstat cur, v$sysstat con, v$sysstat phy
  6  WHERE cur.name='db block gets'
  7  AND   con.name='consistent gets'
  8* AND   phy.name='physical reads'
ops$tkyte@ORA920.US.ORACLE.COM> /
VALUE---------VALUE---------VALUE---------Cache hit ratio-------------------
127737758486121661490.989505609

Уровень обращений в кэш составляет 98.9% — хорошую работу проделал Joe или нет? Очевидно, что нет. Однако многие сказали бы: «Это хорошо настроенная система, основанная на обращениях в кэш. Вы же знаете, что физический ввод/вывод в 10 000 раз обходится дороже, чем логический ввод/вывод!» (Физический ввод/вывод не всегда в 10 000 раз дороже чтения из кэша.)

Нужно помнить, что не существует универсальных правил, которые можно применять в любых случаях. Скептически относитесь ко всякого рода заявлениям о коэффициентах. Это не означает, что коэффициенты бесполезны, это означает, что они бесполезны сами по себе. Чтобы заключать в себе какой-то смысл, они должны использоваться в совокупности с другими переменными. Те инструменты, которые показывают зеленый свет, когда уровень обращений в кэш составляет 90% и больше, и красный, если он опускается ниже, вводят в заблуждение. Уровень обращений в кэш 99% может свидетельствовать как о наличии проблем в системе, так и о том, что система хорошо работает.

ПРИМЕЧАНИЕ

Я точно знаю один коэффициент, который значим сам по себе и все равно не применим к хранилищам данных. Это соотношение программного и аппаратного анализа, которое должно быть около 100%. Используйте переменные привязки, которые позволяют проводить программный анализ (см. выше).

Не стоит искать коротких путей

Не ищите коротких путей. Многие пытаются найти в init.ora параметр fast=true. Позвольте выдать секрет: его там нет! (Хотя есть параметр slow=yes.)

Часто меня спрашивают об этих недокументированных параметрах Oracle. Я обычно интересуюсь: «Вы прочитали от корки до корки Concepts Guide?» И неизменно слышу ответ: «Нет». Люди, не зная основ, хотят идти вглубь.

Многие думают, что там внутри что-то спрятано, какие-то магические вещи, которые можно добавить к базе данных, и она сразу же начнет работать быстрее и лучше. Они считают, что, следуя этим путем, им не придется тратить время на изучение базы данных.

Однако у этих недокументированных параметров есть побочные эффекты, которые невозможно предвидеть. А, как правило, базовых знаний недостаточно для понимания вариантов настройки этих параметров. Отсутствие знаний по недокументированным параметрам может обернуться большими неприятностями.

Недавно в сетевой конференции Usernet мы обсуждали этот вопрос. Дискуссия на comp.databases.oracle.server затрагивала тему «Почему люди так боятся подчеркнутых параметров?» (Все недокументированные параметры в Oracle начинаются с символа подчеркивания.) Некоторые говорили: «Возьмем в качестве примера _trace_files_public. Что может быть технически более безвредным, чем подчеркнутые параметры?»

Параметр _trace_files_public открывает общий доступ к генерируемым Oracle файлам трассировки. Это очень удобно на стадии разработки, когда разработчикам необходимо получить отчет TKPROF для настройки системы. Однако в производственной системе (где не должно быть никаких настроек) включение этого параметра грозит возникновением проблем с безопасностью.

В эти файлы трассировки может быть помещена такая секретная информация, как пароли. Существует недокументированный, но хорошо известный способ — сброс библиотечного кэша (раздела SGA или глобальной общей области), и этот сброс производится в файл трассировки. Если пользователь имеет привилегии, знает, как применить команду ALTER SESSION, и _trace_files_public включен, то могут возникнуть большие проблемы с безопасностью, и с этим ничего нельзя поделать (существуют и другие проблемы, связанные с привилегиями ALTER SESSION, но это уже другая история). Где гарантия, что не возникнут какие-нибудь еще побочные эффекты с другими «удивительными» недокументированными параметрами?

Одним словом, в производственной системе недокументированные параметры должны устанавливаться только под руководством службы поддержки Oracle. И не принимаются никакие «если», «и» и «но» по этому поводу. Даже если получены все инструкции касаемо недокументированного параметра, необходимо повторно оценить необходимость его использования при обновлении системы. Нередко причиной установки недокументированного параметра является наличие известной ошибки. Ошибка исправляется, и установка старых недокументированных параметров после обновления системы может привести к большим сложностям.

Например, в Oracle 7.3 существовал недокументированный способ получения битового индекса, в котором использовался подчеркнутый параметр для установки события. Этот метод получил огласку, и многие начали устанавливать этот параметр и обучать этому других (если на groups.google.com ввести в поисковой строке “bitmap indexes 7.3 group:comp.database.oracle.*”, то можно узнать, как это сделать). Затем они обновили базу данных до версии 7.3.3, но не отменили установку этого события. И что же произошло? Событие заставило программу 7.3.3 выполнять старый код. Как можно себе представить, это вызвало серьезные проблемы поддержки. Некоторые оставили эти события вплоть до Oracle 8i, что вызывало снижение производительности сервера при поиске текста (специальная техника индексации для текстовой информации в базе данных). Подобная ситуация представляется как ошибка производительности.

В своем опыте я не использовал ни внутренних секретов, ни недокументированных магических заклинаний для того, чтобы приложения базы данных работали быстрее, масштабировались лучше и достигали конечных целей. Я использовал основы — знания, которые я приобрел в процессе чтения стандартной документации, плюс мой большой опыт и здравый смысл. Читайте Concepts Guide. Успех могут принести только базовые знания, а не некоторые волшебные параметры.

Чем проще, тем лучше

Я предпочитаю идти по пути наименьшего сопротивления, т.е. придерживаюсь курса действий, наиболее легкого в достижении цели.

Рассмотрение альтернативных методов

Я часто получаю вопросы, начинающиеся со слов: «Как я могу сделать Y без выполнения Х?» Периодически я встречаю людей, выбирающих самый сложный из возможных методов решения задачи. У них уже сложилось в голове решение, и они будут его применять, несмотря ни на что. Они отвергают любые другие подходы.

Например, кто-то недавно спросил меня, каким образом добавить в таблицу столбец между двумя другими столбцами без удаления и повторного создания этой таблиц. В таблице (T) есть столбцы (по порядку): Сode, Date, Status и Actiondate, и нужно вставить новый столбец New_Column между столбцами Status и Actiondate. Я указал на то, что физический порядок столбцов ни на что не влияет. Достаточно выполнить простые шаги:

Его ответ был: «Я хочу добавить столбец в середину таблицы, но не хочу перестраивать ее и не хочу использовать представления. Может, есть какой-нибудь другой способ?» Что я могу сказать на это? Он хочет добраться из пункта А в пункт С, но отказывается идти через пункт В, несмотря на то, что это единственный логический путь. С помощью представления это заняло бы всего 5 секунд. Никакое приложение не может сделать лучше. Это умное, чистое и корректное решение задачи.

ПРИМЕЧАНИЕ

Говорят, что использование представлений замедляет работу. Это не всегда так. Представление — это ничто иное, как хранимый запрос! Если хранимый запрос выполняется медленно, то, безусловно, представление (или запрос к представлению) будет медленным. Представление не может быть медленнее, чем сам запрос. При использовании в SQL специального запроса, определяемого представлением, производительность будет такой же.

Итак, некто приходит с этой «потрясающей» идеей (это было в версии 8i, в которой нет функции переименования столбцов, иначе шагов могло бы быть меньше). В данном случае Т — название настоящей таблицы, а NEW_COLUMN — это столбец, который необходимо добавить:

Таким образом, столбец NEW_COLUMN добавлен в середину таблицы. Заметьте, он не хотел перестраивать таблицу и не хотел использовать представления, но это решение принял с благодарностью. Он даже не задумался над тем, что таблица будет переписываться снова и снова, перемещая множество строк при обновлениях, что негативно повлияет на производительность. Ему не пришло в голову, что в случае с таблицей другого размера это займет намного больше времени, чем перестройка таблицы, и что это бесконечно сложно по сравнению с применением представления.

Это наихудший подход к решению относительно простой задачи. Перестройка переписывает таблицу один раз. Представление позволяет изменить таблицу за пять секунд. А при многократном переписывании один неверный шаг — и таблица будет уничтожена (что, кстати, довольно часто встречается).

Стоит позволить базе данных делать то, для чего она предназначена

Другой классический пример простого решения — это дать возможность базе данных делать то, для чего она предназначена. Не пытайтесь ее перехитрить.

В качестве примера приведу вопрос о внешних соединениях. Администратор базы данных объяснил, что у него есть большая таблица CUSTOMERS и семь или восемь небольших таблиц, ссылающихся на столбцы CUSTOMERS (COUNTRY, STATE, CITY, CUSTOMER_GROUP и т.д.). Данные в этих таблицах были чувствительны к языку, выбор языка зависел от параметра языка in web-клиента. В некоторых случаях внешнее соединение было необходимо. Требовалось найти наилучший способ управления этим видом запросов, и у администратора было три идеи:

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

select t.*, t2.cl, t3.c2
  from t, t2, t3
 where t.keyl = t2.keyl(+)
   and t.key2 = t3.key2(+)

или

select t.*, (select cl from t2 where t2.keyl = t.keyl) cl,
             (select c2 from t3 where t3.key2 = t.key2) c2
  from t;

Вторая идея администратора с использованием основанного на функции столбца — это попытка перехитрить базу данных: «Я могу создать внешнее соединение, которое будет работать быстрее, чем может делать база данных». Это невозможно. Подход fn_country() заставит SQL вызвать PL/SQL, а PL/SQL самостоятельно вызовет SQL в поисках строки. Это гораздо менее эффективно, чем просто позволить SQL выполнить свою работу, как в первом случае. Третья идея с применением fn_country для каждой выборки еще хуже, потому что заставляет клиента ходить по кругу.

Но администратор был убежден, что его вторая и третья идеи лучше. Он считал, что чем меньше нагрузка на SQL, тем быстрее будет выполнение. После многочисленных «назад-вперед» он, в конце концов, вызвал меня: «Я был бы вам признателен, если бы вы показали один-два примера, подтверждающие ваши утверждения». Браво!

Я достал мое надежное средство Runstats (см. выше) и начал с двух таблиц, сравнивая внешнее соединение и попытку перехитрить базу данных:

create table tl as select * from all_objects;
create table t2 as select * from all_objects where rownum <= 15000;

alter table tl add constraint tl_pk primary key(object_id);
alter table t2 add constraint t2_pk primary key(object_id);

analyze table tl compute statistics
for table for all indexes for all indexed columns;

analyze table t2 compute statistics
for table for all indexes for all indexed columns;
Затем я создал функцию, как предлагал администратор:
create or replace function get_data( p_object_id in number ) return varchar2
is
    l_object_name t2.object_name%type;
begin
    select object_name into l_object_name
      from t2
     where object_id = p_object_id;
    return l_object_name;
exception	
    when no_data_found then
        return NULL;
end;
/

Теперь можно сравнить эти два запроса:

select a.object_id, a.object_name onamel, b.object_name oname2
  from tl a, t2 b
 where a.object_id = b.object_id(+);

select object_id, object_name onamel, get_data(object_id) oname2
  from tl;

Я включил использование теста Runstats, и результаты были невероятны:

ops$tkyte@QRA920> begin
  2      runstats_pkg.rs_start;
  3      for x in ( select a.object_id,
  4                        a.object_name  onamel,
  5                        b.object_name oname2
  6                   from tl a,  t2  b
  7                  where a.object_id=b.object_id(+))
  8      loop
  9          null;
 10      end loop;
 11      runstats_pkg.rs_middle;
 12      for x in  ( select object_id;
 13                         object_name oname1,
 14                         get_data(object_id) oname2
 15                    from tl  )
 16      loop
 17          null;
 18      end loop;
 19      runstats_pkg.rs_stop;
 20  end;
 21 /
84 hsecs
2803 hsecs
run 1 ran in 3% of the time

Позволив базе данных делать то, для чего она была создана, мы потратили гораздо меньше времени. Но и это еще не все. Посмотрите на созданный Runstats отчет по блокировкам/статистике:

NameRunlRun2Diff
STAT...consistent gets - exami2178 15578 134
STAT...session logical reads15 553109 77594 222
STAT...consistent gets 15 524109 74794 223
LATCH.cache buffers chains 31 118141 477110 359
LATCH.library cache pin 167126 460126 293
LATCH.shared pool383126 698126 315
LATCH.library cache380189 780189 400
STAT...session pga memory max196 6080–196 608
STAT...session uga memory max1 767 8800–1 767 880
Runl latches total versus runs -- difference and pct
33 264   590 820   557 556   5.63%
PL/SQL procedure successfully completed.

Как видно, здесь операторов ввода/вывода на 95 000 меньше, на полмиллиона меньше триггеров-защелок (защелки = блокировки, этого не стоит забывать). И снова повторюсь. Все это было достигнуто простым использованием средств базы данных.

Что касается простых решений — не пытайтесь перехитрить базу данных. Намного легче использовать внешнее соединение, чем писать небольшую функцию для каждого и всякого внешнего соединения.

Дело в том, что всегда есть два разных пути достижения одной и той же цели — легкий и сложный. Лучше быть таким же ленивым, как я, и использовать более легкий путь. Легкий путь не только проще, он обычно и более верный.

Использование поставляемой функциональности

Время от времени мне встречаются люди, которые заново придумывают функциональность базы данных. Ниже представлены некоторые возможности, использование которых часто ставится под сомнение:

Аудит

Вместо того чтобы при высокоточном аудите использовать встроенную команду AUDIT, некоторые стараются извернуться и сделать это через триггеры. Самая распространенная причина подобного поведения: «Мы слышали, что аудит медленно работает».

Репликация

Вместо того чтобы использовать двустороннюю репликацию, репликацию, основанную на копировании моментального снимка, или потоки, некоторые делают это самостоятельно по той причине, что: «Мы слышали, что применять встроенное средство очень сложно».

Организация очередей сообщений

Вместо того чтобы использовать программное обеспечение расширенной организации очередей (AQ), встроенное в базу данных, некоторые ищут недокументированные возможности, хитрые способы получения множественных процессов чтения отдельных записей из таблицы «очереди» и пытаются с этим эффективно работать. И объясняют это так: «Мы не хотим использовать AQ».

Ведение истории изменения записей

Вместо того чтобы использовать менеджер рабочего пространства, который может делать это автоматически, некоторые пишут огромное количество кода в триггерах, пытаясь сделать абсолютно то же самое. Причина в том, что: «Мы не знали о существовании этой функциональности».

Последовательности

Вместо того чтобы использовать встроенную нумерацию последовательностей, некоторые создают свой собственный метод, основанный на применении таблиц базы данных. А затем им становится интересно, как же это все можно масштабировать (а это невозможно, потому что при непосредственном использовании таблицы возникает последовательный процесс, а «последовательный» означает «немасштабируемый»). И поступают они так потому, что: «Не во всех базах данных есть последовательности. Мы же производим разработку и для базы Х, и для базы Y, и можем использовать только те возможности, которые есть в обеих базах».

Причины, по которым множество возможностей базы данных не используется, заключаются не только в боязни, неуверенности и сомнениях разработчиков, но также в незнании и в стремлении к независимости от базы данных. Однако подобное поведение не оправданно. Рассмотрим каждый из аргументов более подробно.

Мы слышали, что возможность X работает медленно

Предположение, что встроенные в Oracle возможности работают медленно, является общим заблуждением. Я уверен, что оно проистекает из того, что при включенном аудите система работает медленнее, чем при выключенном (это кажется очевидным). Так что миф строится следующим образом:

И эта история передается дальше — из поколения в поколение разработчиков Oracle. Когда я это слышу, я задаю простые вопросы:

Как можно предположить, никто и не пытается что-то сделать, ведь все слышали, что это работает медленно.

Если провести оценку производительности встроенного аудита, то можно обнаружить следующие выгоды от его применения в системе:

Рассмотрим, как можно выполнить самостоятельно оценку производительности. Я хочу проверить вставку в таблицу. Ниже представлен один из способов (в файле параметров init.ora должен быть установлен AUDIT_TRAIL):

create table tl  ( x int );
audit insert on tl by access;

А вот другой способ:

create table t2 ( x int );
creat  table t2_audit
as
select sysdate dt,  a.*
  from v$session a
 where l=0;
create index t2_audit_idx on t2_audit (sid, serial#);

create trigger t2_audit
after insert on t2
begin
     insert into t2_audit
     select sysdate, a.*
       from v$session a
      where sid = (select sid
                     from v$mystat
                    where rownum=l);
end;
/
ПРИМЕЧАНИЕ

Существует много способов самостоятельного создания аудита. Я выбрал метод, при котором требуется минимальный объем кода, и использовал технику, которую часто вижу в других реализациях. Другой допустимый подход должен был бы заполнить контекст приложения в процессе регистрации в системе, исключая необходимость в запросе таблиц V$SESSION/V$MYSTAT для каждого оператора вставки в T2. Это уменьшит использование процессора, но можно потерять информацию, которая может измениться в таблице V$SESSION в процессе работы этой сессии.

Теперь становится очевидным, какой из путей разработки легче и требует меньше раздумий. Но какой из методов работает быстрее? Чтобы найти ответ на этот вопрос, установим небольшую процедуру:

create table tl_times  ( xstart  timestamp,  xstop timestamp );
create or replace procedure p1 ( n in number )
as
    l_rowid rowid;
begin
    insert into tl_times (xstart) values (systimestamp)
    returning rowid into l_rowid;
    for i in 1 .. n
    loop
        insert into tl values (i);
        commit;
    end loop;
    update tl_times set xstop = systimestamp where rowid = l_rowid;
    commit;
end;
/

Затем делаем то же самое для Т2. После чего запускаем эту процедуру 5 раз, вставляя каждый раз 30 000 строк. Перед и после этого нужно сделать снимок Statspack. Ниже приведены некоторые из полученных цифр:

T1 with Native AuditingT2 with DIY AuditingComment
Transactions/Second38027827% decrease in transactions/second
CPU Time302443146% of the CPU time

Результат понятен. Использование встроенной функциональности всегда легче, быстрее и более эффективно. Так что не стоит принимать все слова на веру. Нужно самому доказать или попросить кого-то доказать, что тот или иной метод работает медленнее (или быстрее).

Мы слышали, что применять средство Х очень сложно

На днях кто-то спросил меня, как работает функция MD5 из DBMS_OBFUSCATION_TOOLKIT. Он хотел оптимизировать обновление медленно меняющихся значений в таблице хранилища данных. Его подход заключался в том, чтобы передать первичный ключ и контрольную сумму остальных данных по ссылкам базы данных, а затем подсчитать и сравнить отправленные контрольные суммы и контрольные суммы локальных данных. Если они будут различны, то будет выполнен возврат, и строки из ссылок базы данных будут считаны для того, чтобы обновить строку в локальной базе данных. Если эта строка не существует, она будет вставлена. После считывания изменений и вставки новых строк все первичные ключи отправляются в удаленную базу данных, чтобы посмотреть, существуют ли они. Если нет, то их нужно удалить локально.

Мое решение заключалось в создании снимка протокола для удаленной таблицы и в создании локального снимка с обновленным интервалом.

Тот, кто обратился ко мне с вопросом, слышал, что репликация очень сложна. Вместо того чтобы попытаться выяснить, может ли это средство помочь ему в решении задачи, он решил все сделать сам. Будет ли работать его метод? Он не предусматривал некоторых ситуаций. Что произойдет, если кто-нибудь изменит удаленную таблицу в процессе работы этого метода (проблемы целостности данных)? Как насчет инфраструктуры, которую придется спроектировать и создать для того, чтобы наблюдать за ходом работы? (Не существует отдельных средств управления системами, которые могли бы в случае отказа просмотреть процесс и страницу администратора.) Были упущены из виду и другие моменты, которые могли возникнуть в рабочей среде.

Да, действительно, репликация сложна. Ее нужно изучать. Но реализовывать все это самому гораздо сложнее. Например, снимки, доступные только для чтения, появились в Oracle версии 7.0, в 1993 г. И они идеально подходят для решения поставленной задачи. Это средство управляемо и реализовано в сотнях установок. С другой стороны, самостоятельное создание требует проектирования, написания большого объема кода и затрат времени.

Любой вопрос кажется сложным до тех пор, пока не начнешь его изучать. В первое время водить автомобиль тоже очень сложно. Но если садишься за руль каждый день, то через какое-то время даже и не думаешь об этом.

Мы не хотим...

Я не могу понять, почему в отношении базы данных люди часто занимают позицию «Я не хочу...» И если они единожды так решили, то изменить их мнение не представляется возможным.

«Привет, Том, не посмотрите ли вы мой SQL-код. Я могу получить более быстрый результат при использовании not in вместо not exists, но я сделал иначе.

\ вырезан большой запрос \
TKPROF REPORT:
call cntcpuelapdiskqueryrows
Parse 1 0.02 0.04 000
Execute 1 0.00 0.00 000
Ftch 1939 ---- 25772.65-------- 25976.95-------- 149793-------29294754--------29061-----
tot 194125772.6725976.99l497932929475429061

Я безуспешно пытался использовать разные советы по поводу соединений в подзапросе. (Существуют соответствующие индексы во всех таблицах, и все проанализировано.) Если я использовал конструкцию:

AND a.x not in  (select b.y from b WHERE b.a = 'X')

то результат возвращался в течение 5 минут, в то время как для оригинала требовалось почти восемь часов. Что вы думаете по этому поводу?"

Что я думаю? Я думаю, что он знает мой ответ и понимает, что нужно делать. Но не хочет. Я бы посоветовал ему поступить так, как он должен поступить.

Программное обеспечение AQ в базе данных — это та технология, которую люди обычно не хотят использовать. Общая сложность заключается в том, что приложение помещает сообщения в таблицы. Эти записи представляют работу, которая выполнена (например, процесс заказа). Техническая проблема заключается в поиске решения с учетом того, что:

Как такое может быть: со строками могут одновременно работать много пользователей притом, что каждое сообщение обрабатывается как минимум и как максимум один раз? Невозможно просто обновить запись и отметить это в процессе. Если поступить так и не сделать фиксацию, то все другие сессии, пытающиеся получить сообщения, будут блокированы при попытке обновления этих строк (сообщения не будут обрабатываться одновременно). Если обновить запись, отметить это в процессе и зафиксировать перед фактической обработкой, то сообщение обрабатываться не будет. Если в процессе произойдет сбой, сообщения будут помечены как «обрабатываемые» (или в процессе обработки). Никто другой не будет когда-либо возобновлять его. Одним словом, это очень неприятная проблема в реляционной базе данных. Хотелось бы, чтобы поддерживалась многопользовательская очередность к таблице. К счастью, база данных может это обеспечить. В AQ имеются:

Таким образом, имеет смысл использовать AQ для решения рассмотренной выше проблемы.

Единственный путь, ведущий к успеху в изменении отношения «Я не хочу...», — это применение давления со стороны коллег или руководства. Может оказаться достаточным вопрос менеджера: «Зачем ты написал сотни строк кода, если мог просто сделать вот так?»

Мы не знаем...

Самая худшая из причин неиспользования возможностей базы данных заключается в незнании об их существовании. Читайте руководства или хотя бы New Features Guide и Concepts Guide (для каждой версии).

Все, о чем неизвестно, стоит времени, денег, энергии и клеток головного мозга. База данных объединяет много средств. Скупой платит дважды. Время, потраченное на хотя бы поверхностное изучение этих руководств, окупит себя через неделю множеством открытий.

Мы хотим быть независимыми от базы данных

Другая причина, по которой люди используют более сложные пути для достижения своих целей, связана с идеей, что необходимо стремиться к открытости и независимости от базы данных. Разработчики стараются избегать применения закрытых, свойственных только этой базе данных возможностей, даже если они проще, чем хранимые процедуры или последовательности, потому что эти возможности ограничивают разработчиков использованием только этой базы данных. Но в действительности, в момент начала разработки приложения для чтения/записи уже накладываются те или иные ограничения.

Все базы данных хоть чем-то, но отличаются друг от друга. Например, в одной базе (не Oracle!) можно столкнуться с тем, что select count(*) from T блокируется при простом обновлении двух строк. В Oracle select count (*) никогда не блокируется для записи. Также можно обнаружить, что при проведении двух абсолютно одинаковых транзакций в двух различных базах данных могут получиться отчеты с разными ответами из-за фундаментальных различий в реализации. Крайне редко можно просто взять приложение с одной базы данных и установить на другую. Всегда будут отличия в реализации и интерпретации SQL.

В качестве примера рассмотрим недавний проект по созданию web-продукта, использующего Visual Basic (VB), ActiveX Controls, Internet Information Services (IIS) Server и Oracle. Разработчики высказали опасение, что поскольку бизнес-логика написана на PL/SQL, продукт станет зависимым от базы данных. Они хотели узнать, каким образом это скорректировать. Я был ошеломлен этим вопросом. Я посмотрел на список выбранных технологий и не смог понять, чем так плоха зависимость от базы данных:

Все выбранные ими технологии ограничивали их очень специфической конфигурацией. База данных была, фактически, единственной технологией, которая предлагала им какой-то выбор. Я думаю, что они предполагали использовать полный потенциал других технологий, но почему же они исключили из этого списка базу данных, и именно тогда, когда ее выбор был очень важен для успеха?

Можно посмотреть на эту аргументацию с точки зрения «открытости». Положим все данные в базу. База данных — открытый инструмент. Она поддерживает доступ к данным через SQL, Enterprise JavaBeans (EJBs), Hypertext Transfer Protocol (HTTP), File Transfer Protocol (FTP), Server Message Block (SMB) и многие другие протоколы и механизмы доступа. Звучит потрясающе — самая открытая вещь в мире.

Затем вы размещаете всю прикладную логику и безопасность за пределами базы данных — возможно, в JavaBeans или Java Server Pages (JSPs), которые обращаются к данным, или в VB-коде, запускаемом в Microsoft Transaction Server (MTS). И как финальный результат — абсолютно закрытая база данных. Никто не сможет использовать существующие технологии для доступа к этим данным. Придется применять специальные методы доступа (или вообще обходить безопасность).

Необходимо помнить, что сегодняшняя супертехнология, такая как EJBs, — это вчерашняя идея и старая технология завтра. База данных как таковая держится уже 20 лет в реляционном мире. Интерфейс к данным меняется почти что ежегодно, и по мере того, как это происходит, приложения со встроенной безопасностью становятся препятствием будущему прогрессу.

Рассмотрим одну из специфических возможностей базы данных Oracle — высокоуровневый контроль доступа (FGAC). В двух словах, эта технология позволяет разработчику внедрять процедуры в базу данных, которые могут изменять представление запросов к базе данных. Эти изменения запросов используются для ограничения строк, которые клиент может получать или модифицировать. Процедура может посмотреть, кто, с какого терминала запускает запрос, когда он был запущен и т.д., и организовать соответствующий доступ к данным. С помощью FGAC можно установить, например, такую безопасность:

Это позволяет размещать управление доступом непосредственно в базе данных. Больше не имеет значения, получает ли пользователь данные через EJB, JSP, VB-приложения, используя ODBC, или же через SQL*Plus — будут применяться одни и те же протоколы безопасности. Таким образом, мы имеем хороший задел для будущих технологий.

Какая реализация более открыта?

Мне еще придется встречать специализированные средства создания отчетов, которые будут запрашивать VB-код. Однако я знаю десятки таких, которые работают на SQL.

Многие борются за независимость от базы данных и общую открытость, но я считаю, что это неверное направление. Не имеет значения, какую базу данных использовать, важно эксплуатировать все ее возможности. В любом случае придется это делать на этапе настройки (который может потребоваться сразу после внедрения). Удивительно, как быстро требование к независимости от базы данных снимается, если приложение начинает работать в пять раз быстрее при использовании возможностей программного обеспечения.

Итоги

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

Я придерживаюсь двух очень важных постулатов. Первое: все является неправдой, пока не будет доказано обратное. Если можно доказать, что это правда (или неправда), это нужно сделать. Задавайте больше вопросов.

Второе: необходимо использовать то, что есть. Используйте имеющиеся операционную систему, язык, промежуточное программное обеспечение, базу данных и все их возможности. Это быстрее, дешевле и позволяет создать лучший продукт. Не жалейте время на проведение оценки производительности, инструментирование и тестирование!

В следующей главе будут рассмотрены средства, которые можно использовать не только для разработки и настройки приложений, но и для оценки производительности. Среди них SQL_TRACE, TKPROF и Runstats.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 1    Оценка 70        Оценить