Экранные формы и отчеты, которые используют базу данных и обращаются к ней, являются витриной приложения. Как и витрина, они создают у пользователя первое впечатление, которое затем нелегко изменить. Поэтому очень важно, чтобы экранные формы и отчеты были выполнены на профессиональном уровне, хорошо смотрелись и с ними легко было работать. Пакетные процессы — это "машинное отделение" системы. Никому не интересно, как они выглядят и работают, но все мы ожидаем, что они будут функционировать надежно и эффективно.
Эта глава касается баз данных меньше, чем остальные, однако в ней мы рассмотрим вопросы, от решения которых зависит успех всего проекта. Если в них не разобраться, то в следующий раз у вас может и не быть базы данных! Кроме того, здесь мы также расскажем о проектировании средств обработки ошибок, навигации и оперативной справочной системы.
Проектирование экранных форм
Самый важный вопрос проектирования экранных форм — как сделать, чтобы они были интуитивно понятными и могли, не утомив пользователя, провести его по тому или иному рабочему процессу.
В наши дни практически каждый пользователь БД знаком и с другими продуктами. У большинства людей на столах стоят ПК, и они наверняка пользуются текстовыми процессорами или электронными таблицами. Если их спросить, каким должен быть интерфейсе, то вполне естественно, что они сошлются на знакомые приложения. Вам следует сделать точно так же. Проектируя, старайтесь обеспечить максимальное соответствие стандартам, принятым в рабочей среде пользователя (например, в приложениях Microsoft Windows для вызова справочной системы служит клавиша F1). Следует также добиваться согласованности с другими приложениями, привычными для пользователей.
Однако при этом легко попасть в зависимость от этого требования и в результате получить программу, напоминающую нечто такое, чем она на самом деле не является. Электронные таблицы и текстовые процессоры хороши для выполнения многих задач, однако в качестве внешних систем баз данных они непригодны, особенно если базу данных нужно обновлять. Не пытайтесь сделать так, чтобы экранная форма ввода данных выглядела и работала, как окно Lotus 1-2-3 или Microsoft Excel. На ознакомление пользователей с новым форматом или стилем требуется совсем немного времени. Можно потратить несколько недель на создание перемещаемой панели кнопок, когда достаточно просто приучить пользователя к тому, что эта панель будет постоянно находиться в верхней части экрана. Кроме того, хотя графические пользовательские интерфейсы (ГПИ), скорее всего, будут присутствовать в приложении, полная реализация их возможностей может и не быть лучшим вариантом для всех пользователей.
Полезно разбить всех пользователей на группы. Вот один из вариантов такого разбиения:
• операторы ввода данных, которые пользуются системой часто и интенсивно, но не выдают запросы;
• пользователи, регулярно выдающие запросы, но вводящие мало данных;
• пользователи, задающие нерегламентированные запросы, иногда выполняющие поиск и (еще реже) обновление;
• пользователи, обеспечивающие поддержку принятия решений, которым нужно работать с нерегламентированными запросами, но которые редко выполняют обновление или не выполняют его вообще;
• операторы, планирующие и контролирующие отчеты и пакетные задания.
Давайте сначала рассмотрим операторов ввода данных. Стоит потратить время и понаблюдать за тем, как они работают в действующей системе. Скорость, с которой они вводят данные, может быть просто впечатляющей! Если планируется заменить старую систему, то лучше сделать так, чтобы не повлиять на производительность труда операторов, особенно если их зарплата зависит от объема введенной информации. Вполне вероятно, что они пользуются архаичным символьным экраном, который им так полюбился за долгие годы работы. Тут вдруг приходим мы, этакие рыцари в сверкающих доспехах, и говорим: вам нужно не что иное, как ГПИ!
Проблема состоит в том, что в нашей новой среде они не видят для себя никаких преимуществ — разве что в перерывах можно поиграть в Solitaire или Minesweeper. Меньше всего они хотят снимать руки с клавиатуры и вводить данные мышью. Конечно, это не означает, что операторам ввода данных вообще не подходят экранные формы на базе ГПИ. Мы лишь хотим подчеркнуть, что вы должны проектировать экранные формы и интерфейс, ориентируясь в первую очередь на пользователей.
Теперь рассмотрим пользователей, регулярно выдающих запросы. Возможно, они обнаружат, что мышь идеально подходит для их целей, особенно если в одной руке у них обычно телефон. В этом случае свободной рукой им легче манипулировать мышью, а не клавиатурой.
Это еще одна область, где неоценимую помощь может оказать быстрое макетирование. Пользователям можно предложить макеты экранных форм для "пробной поездки". Основываясь на результатах работы с макетами (которой должно быть охвачено большое число пользователей), можно проектировать интерфейс с учетом требований пользователей. Однако при этом нужно соблюдать осторожность. Для большинства людей характерно естественное сопротивление изменениям, и они высказывают только отрицательные мнения. Новая компьютерная система может быть лишь малой частью перестройки организации, и проектируемые экранные формы могут отражать новые принципы работы. Люди воспользуются обратной связью для того, чтобы выразить свои возражения по поводу этого процесса, а не конкретных системных функций внутри него (особенно если они считают, что перестраивать будут персонально их!). Всегда помните об этом, когда оцениваете мнение пользователей и определяете, какие из их проблем вы можете (и должны) решить.
Вот общие принципы проектирования экранных форм:
• Все экранные формы должны иметь уникальные и информативные заголовки.
• Все поля необходимо снабдить надписями; при вызове справочной с системы должны быть доступны подробные описания полей.
• Курсор по умолчанию, как правило, должен перемещаться слева направо, а затем сверху вниз.
• Обязательные элементы должны находиться в верхней части экрана. Элементы на экране необходимо упорядочить по степени важности.
• Экранная форма должна обнаруживать ошибочно введенные данные и сообщать о них как можно раньше, а не откладывать проверку (если речь не идет об экранных формах, работающих по низкоскоростной сети, например по коммутируемой линии).
• Экранная форма должна использовать непротиворечивые методы блокировки, обнаруживать и разрешать конфликты.
• Экранная форма не должна состоять из множества страниц (Вспомните о мегамодулях!).
• Пользователи должны вводить код только один раз и не должны ничего запоминать или записывать при переходе от одной экранной формы к другой.
• Использование специальных эффектов следует свести к минимуму. Если вы решительно настроены придать экранным формам и отчетам профессиональный вид, обратитесь к специалисту-дизайнеру. Даже дизайнер среднего уровня выполнит эту работу лучше, чем все ваши проектировщики, аналитики и пользователи, и гораздо лучше, чем ваши программисты (даже если этот дизайнер не может писать рекурсивные структуры на C++).
• Размещение на экранной форме дополнительных элементов за счет уменьшения размера символов допустимо только в ограниченной степени.
• Большинство пользователей гораздо лучше справляются с вертикальной, а не с горизонтальной прокруткой, особенно если при прокрутке вправо из левой части экрана исчезают важные данные и условные обозначения.
Проектирование отчетов
Раньше отчетами обычно называли задания, которые прогонялись в конце месяца и выдавали гору распечаток на линованной компьютерной бумаге. Эти распечатки хранились в шкафу и их никто никогда не смотрел. Вот это были деньки! Сейчас результаты можно выводить не только в виде текста на бумагу, но и в графической форме на экран. Это не просто требует от нас более интеллектуального представления данных, но имеет и более глубокие следствия. Раньше программы отчетов могли работать всю ночь и утром предоставить распечатку. Но пользователь вряд ли посчитает приемлемым ждать несколько часов, пока на экран будет выведен график.
Для отчетов существует широкий набор требований. Среди них часто встречаются не только новые, но и "старомодные" требования. Вот основные типы отчетов:
• установленные законом (обязательные);
• периодические (например, по состоянию на конец месяца, на конец года и т.д.);
• отчеты на готовых бланках (например, счета-фактуры);
• письма;
• нерегламентированные и срочные;
• графические;
• мультимедийные со встроенными аудио- и видеоклипами.
Часто в проекте используют две программы генерации отчетов — одна для удовлетворения нерегламентированных требований, а другая для выполнения всех остальных.
Многие отчеты просто перелопачивают введенные данные, применяя к ним соответствующие форматы. Например, мы можем в течение дня принимать заказы на товары, а ночью печатать счета-фактуры. Эти отчеты просто собирают информацию о заказах и ценах и форматируют ее, создавая в итоге счет-фактуру. Проектировать их довольно легко. Все, что нужно сделать, — это спроектировать шаблон формата и указать, в каких полях размещаются данные из базы данных.
Для большинства отчетов требуется та или иная форма агрегирования. Давайте рассмотрим информационно-управленческие отчеты. Даже несмотря на наличие средств, генерирующих нерегламентированные отчеты, как правило, нам все равно приходится создавать эти отчеты как регулярно, так и по требованию. Для этих отчетов обычно требуется иное представление данных по сравнению с тем, как они были введены. Так, часто необходима разбивка по подразделениям, географическим областям и другим категориям. Если база данных рассчитана на оптимизацию процесса ввода данных, то такая разбивка может оказаться довольно сложной задачей. Генерация отчета непосредственно из данных будет проходить медленно, а в некоторых случаях будет невозможна вообще. В результате вы, может быть, начнете думать над тем, чтобы создать хранилище данных (см. главу 13).
Если же хранилище данных — не подходящий вариант для вашей системы, можно рассмотреть возможность применения распространенного метода подготовки отчетов — программы извлечения отчетов. Она извлекает данные из базы данных и загружает их в производные таблицы, оптимизированные для генерации отчетов. Преимущество этого метода состоит в том, что такая программа запускается до генерации отчетов и может заполнить таблицы за один проход. На этом этапе могут вычисляться и агрегированные значения.
Программе, которая пытается "плыть против течения" и старается, пользуясь исходными таблицами, выдать отчет при помощи одного SQL-предложения, возможно, придется для каждой строки отчета повторять операции проецирования и агрегирования с участием нескольких таблиц. Введя в отчеты предварительную обработку, мы можем повысить производительность выполнения на несколько порядков. Если одни и те же агрегатные значения используются несколькими отчетами, то выигрыш окажется еще более существенным.
Как и для всех задач проектирования, мы должны упомянуть о сопутствующих процессу создания отчетов затратах и сделать необходимые предупреждения. В данном случае ограничимся главным образом предупреждениями.
• Можно столкнуться с проблемой противоречивости производных данных, если во время работы программы извлечения выполняется обновление данных и для извлечения требуется более одного SQL-предложения. Конечно, вы всегда можете выбрать непротиворечивый набор данных, указав команду SET TRANSACTION READ ONLY. Однако такой подход вряд ли возможен, если вы задаете процесс извлечения, осуществляющий непосредственную загрузку данных в производные таблицы.
• Таблицы, содержащие извлеченные данные, соответствуют конкретному моменту времени и постепенно устаревают. В этом может заключаться большое преимущество, когда пользователям нужно создать, много согласованных отчетов, но это также может вызвать проблемы при интерпретации этих отчетов.
• Следует соблюдать осторожность при проектировании отчетов, в которых необходимо соединять производные таблицы с активными, поскольку последние могли измениться за время, прошедшее между запуском программы извлечения и запуском отчета. Запуск отчетов сразу же после программы извлечения и генерация отчета ночью сводят этот риск к минимуму, при условии, что отсутствует вероятность вмешательства со стороны выполняемых в это время пакетных заданий.
• Если вы не можете предложить механизм отслеживания изменений, то должны обеспечить очистку производных таблиц перед каждым извлечением. Для этой цели идеально подходит SQL-оператор TRUNCATE...REUSE STORAGE, особенно если производные таблицы не индексированы (желательно избегать массовой загрузки в индексированные таблицы).
• Если извлечение данных выполняется по требованию, необходимо осуществлять контроль за параллельностью. В частности, если каждый пользователь не имеет отдельный набор производных таблиц, следует использовать механизм, гарантирующий, что два пользователя одновременно не выполняют одну и ту же операцию извлечения. Вероятно (а может быть, даже наверняка), это приведет к дублированию данных в производных таблицах. В результате вы получите отчеты с неточными и вводящими в заблуждение данными. Пакет DBMS_LOCK, поставляемый с Oracle7, может помочь в реализации стратегии кооперативной блокировки для обеспечения контроля параллельности.
Примечание
Стратегия кооперативной блокировки работает лишь в случае, когда все пользователи (в данном случае — программы) играют по объявленным правилам. Стратегия обязательной блокировки работает даже тогда, когда некоторые пользователи пытаются игнорировать ее. Так, если один сеанс выдаст SQL-оператор LOCK TABLE contracts IN EXCLUSIVE MODE, то ни один из других сеансов не сможет вносить изменения в эту таблицу до тех пор, пока сеанс, установивший эту блокировку, не выполнит фиксацию или откат транзакции.
Если база данных Oracle не запущена с параметром SERIALIZABLE = TRUE (что мы не рекомендуем делать по соображениям производительности), то у сервера нет механизма обязательной блокировки, который мог бы установить для таблицы транзакционную блокировку по чтению.
• Необходимо регистрировать все запуски процесса извлечения, чтобы программы отчета всегда могли определить точную дату и время извлечения, с которыми они работают. Рекомендуется вставлять дату и время извлечения в заголовок отчета. Отказ от таких действий ничем не оправдан. Всякий раз, когда вы генерируете отчет по данным, которые, может быть, уже устарели, вы обязаны указать пользователю точку согласованности этих данных по чтению.
Проектирование пакетных программ
Даже в наши дни, когда существуют оперативные системы с высокой скоростью реакции, пакетная обработка не сдает своих позиций. В большинстве наборов приложений предусмотрена регулярная или выполняющаяся по мере необходимости массовая обработка. Даже в мире Internet, где (по крайней мере, теоретически) информацией всегда можно обмениваться в оперативном режиме, существует минимум три убедительные причины широкого использования пакетной обработки, поскольку она:
• всегда более эффективна, чем транзакционная обработка;
• считается более легкой для аудита и контроля, чем транзакционная обработка;
• устраняет необходимость наличия ПО, управляемого событиями.
Пакетной обработке часто не уделяют того внимания, которого она заслуживает. Мы считаем, что на визуальных и осязаемых компонентах системы (экранных формах и отчетах) делают слишком большое ударение. Однако пакетная обработка остается важной и сегодня, и никаких признаков исчезновения ее необходимости не наблюдается.
Во многих приложениях пакетные задания являются критическим фактором успеха всего приложения. Например, в системе расчета заработной платы периодически запускается задание, осуществляющее начисление заработной платы. Конечно, очень важно, чтобы это задание выполнялось за один вечер и давало точные результаты, иначе служащие не получат зарплату вовремя. В системе составления счетов также есть периодически выполняемые задания, выдающие счета-фактуры. Если это задание даст сбой, который не будет устранен к следующему вечеру, то деньги поступят позже, что может уменьшить поток денежных средств.
Таким образом, максимально возможное повышение пропускной способности и обеспечение надежности пакетной обработки — очень важные задачи. В любой долго выполняющейся пакетной программе необходимо предусматривать прерывания, сбои и восстановление, и в процессе проектирования пакетной обработки мы должны решить все проблемы с параллельностью. Мы еще не раз в этой главе будем подчеркивать важность повышения пропускной способности пакетных программ. Однако имейте в виду, что для достижения этого вам может понадобиться параллелизм. (Эта тема подробно рассматривается в главе 14.)
Регистрация хода выполнения задания
Приведенные нами рекомендации касаются не только проектирования пакетных программ, но и проектирования оперативной обработки. Однако пакетные процессы (включая отчеты) обладают особыми свойствами, связанными с их объемом и взаимозависимостью. Основная проблема многих пакетных программ, с которыми мы встречались, состоит в том, что они запускаются, выполняются и (в конце концов) завершаются. Когда они выполняются, они больше ничего не делают, кроме как выполняются, а когда завершаются — просто останавливаются. Мы не считаем, что этого достаточно. По нашему мнению, все пакетные процессы должны также делать следующее:
• регистрировать свой запуск в базе данных;
• регистрировать ход своего выполнения в базе данных;
• регистрировать свое завершение в базе данных.
Ниже приведен пример создания простой структуры таблиц главная-подчиненная, обеспечивающей такую регистрацию. Что касается открытых систем, то мы не думаем, что простая запись сообщений в стандартный поток вывода является адекватной альтернативой регистрации в базе данных. На мэйнфрейме, который создает копию информации, выдаваемой на консоль, регистрация в базе данных менее важна, но она все равно полезна и позволяет использовать обычные средства генерации отчетов БД для получения информации о пропускной способности пакетной обработки и сбоях.
CREATE TABLE run_log
( run_id NUMBER NOT NULL /* из запроса */
, task_name VARCHAR2 (16) NOT NULL /* возможно, требуется путевое имя */
, command_line VARCHAR2 (255) NOT NULL /* выдает список аргументов */
, invoked_by VARCHAR2 (30) NOT NULL /* запрашивающий пользователь */
, started_at DATE NOT NULL
, ended_at DATE
, completion_code VARCHAR2(72)
, rows_processed NUMBER /* из управляющей таблицы */
, cpu_used NUMBER /* если есть */
, memory_used NUMBER /* " */
, io_used NUMBER /* " */
, CONSTRAINT run_log_pk PRIMARY KEY (run_id)
);
CREATE TABLE run_step
( run_id NUMBER NOT NULL /* из запроса */
, stage VARCHAR2 (16) NOT NULL
, rows_processed NUMBER NOT NULL
, reached_at DATE NOT NULL
, cpu_used NUMBER /* если есть */
, memory_used NUMBER /* " */
, io_used NUMBER /* " */
, CONSTRAINT run_step_pk
PRIMARY KEY (run_id, stage, rows_processed)
, CONSTRAINT run_step_pk
FOREIGN KEY (run_id) REFERENCES run_log
);
В дополнение к регистрации хода выполнения задания, в задании необходимо предусмотреть выдачу диагностических данных по требованию. Это позволит задать режим более подробной регистрации при возникновении проблем с тем или иным компонентом. У вас должна быть возможность включать такую регистрацию с помощью просто устанавливаемого элемента управления. Это может быть системная переменная среды, атрибут в управляющей таблице или глобальная переменная пакета.
Примечание
Распространенная практика включения отладочного кода по условию, продемонстрированная в приведенном ниже фрагменте, просто не приемлема, потому что она означает, что коды версии, в которой впервые появилась проблема, и версии, используемой для отладки проблемы, будут разными. Этот вопрос становится особенно серьезным при попытках отследить ошибки в указателях для программ на С. В таких случаях любое изменение программы может сместить точку, в которой возникает проблема.
#ifdef DEBUG
/* диагностика */
fprintf(f_logfile, "About to update customer totals");
#endif
Приемлемый подход показан в следующем фрагменте кода. Многие программисты отвергнут его как неэффективный, поскольку считают, что оператор IF при нормальной работе не нужен. Это сущий вздор. Почему? Во-первых, этот оператор совершенно необходим, так как вам может понадобиться проследить, что делает программа, а во-вторых, каждое выполнение условной проверки займет максимум несколько десятков наносекунд. Если нам придется выполнить в ходе пакетного задания десять миллионов этих проверок (предполагая, что у нас по десять точек регистрации на запись), то нагрузка на центральный процессор увеличится всего на одну секунду. Наш опыт свидетельствует, что увеличение на одну секунду затрат времени центрального процессора при выполнении пакетного задания, обрабатывающего миллион записей, практически не повлияет на общее время выполнения задания.
debug_flag = getenv ("DEBUG")
...
...
IF (*debug_flag == 'Y')
{
/* диагностика */
fprintf(f_logfile, "About to call function lb_commit");
}
В PL/SQL диагностику можно включить с помощью переменной пакета:
IF l_degug.debug_mode THEN
-- диагностика
DBMS_OUTPUT.PUT_LlNE("About to insert 100 new orders");
END IF;
Это интересный пример, потому что без проверки пакета l_debug мы не можем определить, является ли debug_mode функцией, возвращающей булево значение, или глобальной переменной пакета типа BOOLEAN. Надеемся, что это функция — по причинам, которые мы уже рассматривали.
Многие пакетные задания часто представляют собой отчеты. Ясно, что регистрировать их в нашей таблице так же полезно, как регистрировать обновляющие задания. Однако, скорее всего, вы обнаружите, что привязаны к средству генерации отчетов, которое просто не поддерживает DML-операции ни в какой форме. Это очень досадно, поскольку наиболее изменяющейся частью пакетного задания является именно нагрузка, связанная с отчетами, и вам могут быть крайне необходимы данные о том, кто и какие компоненты выполняет, какие значения присвоены параметрам, сколько строк обрабатывается и как долго выполняются эти задания.
В этом случае можно вызывать задания-отчеты из простой программы, которая регистрирует запуск задания перед запуском генератора отчетов, а по возвращении из него регистрирует завершение задания. Однако чем больше данных возвращает генератор отчетов на стандартный вывод или консоль, тем больший объем данных придется обрабатывать (и, возможно, регистрировать) вызывающей программе.
В следующих разделах приводится довольно краткое описание приемов пакетной обработки. Такая краткость в основном обусловлена нашим убеждением, что многие вопросы, связанные с успешным управлением пакетной обработкой, не имеют ничего общего с используемым ПО управления данными. Мы отвергаем идею о том, что такие проблемы, как управление заданиями, лучше всего решаются из СУБД, несмотря на все возможные преимущества переносимости. К сожалению, платформы открытых систем не поставляются с системами управления заданиями по типу мэйнфреймовских, и поддержка в этой области на промышленном уровне будет критическим фактором успеха для многих приложений. Но эта тема выходит за рамки книги по проектированию баз данных Oracle.
Пакет DBMS_OUTPUT поставляется вместе с процедурной опцией Oracle7. Он имитирует функции ввода-вывода файлов, позволяя пользователю записывать и читать "рабочий файл". Однако в том виде, в котором он реализован в текущих версиях Oracle7, для буферизации данных он использует не файловую систему, a SGA и позволяет выбирать эти данные только этому же соединению с базой данных.
Такой подход имеет ряд следствий. Два наиболее важных состоят в том, что "помещаемые" данные теряются, если соединение с базой данных разрывается до того, как появляется возможность их выбрать, и что существуют ограничения на объем памяти, выделяемый таким операциям. По умолчанию объем буфера составляет всего 2 Кбайт, и этой величины, как правило, не хватает. Очень неприятно, когда программа дает сбой только из-за того, что вы не выделили (или не смогли выделить) достаточно памяти для буферизации диагностических данных.
Альтернатива состоит в загрузке регистрационной и отладочной информации через канал связи БД с помощью пакета DBMS_PIPE Oracle. Однако это эффективно лишь в случае, когда вы можете гарантировать, что какой-то процесс-демон или серверный процесс будет ждать эти канальные сообщения и постоянно и немедленно делать соответствующую запись.
В версии 7.3 имеется пакет UTL_FILE, позволяющий записывать и читать последовательные файлы операционной системы из PL/SQL. Этот механизм может оказаться привлекательным для выдачи диагностической информации, но используемая при этом модель безопасности очень слаба.
Чего вы никогда не должны делать с диагностическими данными, так это вставлять их прямо в регистрационную таблицу в базе данных. Почему? Да потому, что если будет выполнен откат какой-либо транзакции, то все незафиксированные диагностические данные будут потеряны. Недопустимо также, чтобы программа выполняла фиксацию после вставки каждого диагностического сообщения, поскольку это изменит границы транзакций. При посылке диагностического текста по каналу (pipe) принимающий процесс может спокойно вставлять и сразу же фиксировать каждое сообщение. Эти вопросы более подробно рассматриваются ниже, в разделе "Обработка ошибок".
Выбор инструментального средства
Несмотря на большое число усовершенствований, сделанных в версиях 7.2 и 7.3, мы не рекомендуем использовать PL/SQL как средство реализации высокопроизводительной пакетной обработки. Как вы видите из приведенных в книге примеров, мы — сторонники использования PL/SQL в качестве языка для хранимых процедур и триггеров, но у него есть ряд ограничений, делающих этот язык слишком дорогим для использования при обработки больших объемов данных. Эти недостатки таковы:
• поддержка ввода-вывода файлов в версиях до 7.3 отсутствует;
• модель безопасности для ввода-вывода файлов в версии 7.3 крайне несовершенна;
• несмотря на улучшения в версии 7.3, управление памятью для таблиц PL/SQL не соответствует обработке больших массивов и кэшей данных, а именно такие приемы обычно используются для создания высокопроизводительной пакетной обработки;
• PL/SQL — интерпретируемый язык, хотя перед интерпретацией программа и компилируется в некую промежуточную форму;
• отладка сложного PL/SQL, работающего на сервере, — просто кошмар.
Рекомендуем писать выполняющиеся на сервере программы пакетной обработки на 3GL и осуществлять обмен данными с СУБД при помощи массивного интерфейса Oracle. Кроме того, рекомендуем ограничить операции с базой данных только теми, которые либо должны выполняться внутри нее, либо не должны экспортироваться. Так, если требуется прибавить единицу к какому-то значению в базе данных, но нет необходимости видеть это значение в управляющем коде, следует использовать простое относительное обновление, например:
UPDATE emp
SET sal = round(sal * (1 + :increase_pct/100);
Однако вполне вероятно, что нам придется сообщать не только о том, сколько увеличений было разрешено (это обновление возвратит количество обработанных строк), но и о том, каков общий объем увеличения. Поэтому нам почти наверняка следовало бы экспортировать эти значения. В этом примере также показана одна из весьма распространенных серьезных ошибок: для выполнения арифметических операций здесь используется интерпретатор SQL, хотя гораздо лучше это делать в 3GL (в данном случае мы имеем в виду деление на 100 и прибавление 1). Помните, что Oracle выполняет всю SQL-арифметику с плавающей запятой с точностью до 38 разрядов. На очень многих машинах это нельзя сделать в одной машинной команде.
Для не критичных по времени заданий PL/SQL все же дает определенные преимущества, а именно:
• это строго структурированный язык, который легко читать, причем он тесно интегрирован с SQL;
• этот язык выбрала для себя корпорация Oracle, она вложила в него много денег и очень долго будет им пользоваться;
• этот язык позволяет удобно упаковывать и инкапсулировать скомпилированный код на сервере.
Использование обработки при смене объекта операции
Еще одна важная рекомендация, которую мы обязаны дать относительно проектирования пакетных заданий, — максимально использовать обработку при смене объекта операции (control break processing), сочетая ее с "мягким" отключением триггеров в ходе выполнения процесса.
Если бы мы разносили данные о движении денежных средств по счетам, то разумно было бы отсортировать все входящие записи по номерам счетов чтобы обрабатывать все перемещения денежных средств по одному счету одновременно. Это значит, что при наличии надежной стратегии блокировки нам не требуется обновлять сальдо по счету до тех пор, пока мы не обработаем последнее перемещение средств по данному счету. Изменение номера счета становится точкой смены объекта операции.
Если сальдо по счету обычно ведется триггером, то для уменьшения числа выполняемых обновлений мы хотели бы отключить его для проводок, выполняемых пакетным процессом, потому что этот процесс сам ведет сальдо. Искусство проектирования в подобной ситуации заключается в том, чтобы определить, стоит ли выигрыш в производительности этих усилий и риска (если он есть). В данном случае наши усилия должны быть направлены на то, чтобы избежать любых рисков. Главное — обеспечить, чтобы в процессе обработки счета фиксация не выполнялась до тех пор, пока сальдо не будет обновлено.
Интервал фиксации
Мы обнаружили, что выбор интервала фиксации играет важную роль при проектировании пакетной обработки. Многие проектировщики либо возлагают решение этой задачи на программиста, либо (что еще хуже) указывают, что программа выполняет фиксацию один раз, в конце. Мы рекомендуем, чтобы каждый пакетный процесс, если возможно, выполнял фиксацию минимум каждые пять минут и оставлял в своем журнале достаточно информации для того, чтобы можно было возобновить выполнение с последней точки фиксации.
Если задание выполняет фиксацию один раз, например, через четыре часа обработки, то можно потерять 3 часа 59 минут сделанной работы в случае отказа носителя или аварии экземпляра. Кроме того, в процессе проектирования часто не учитывают и другие факторы — размер сегмента отката, который потребуется для поддержки модели с единичной фиксацией, и время, необходимое для запуска с восстановлением после аварии экземпляра. Если к моменту аварии задания программа использует 10 Гбайт сегмента отката, то при перезапуске Oracle придется обработать 10 Гбайт элементов сегмента отката. Это может существенно увеличить время простоя.
Самый длительный перезапуск после неуправляемого останова, который нам приходилось наблюдать, занял шесть часов. Ряд улучшений архитектуры в последующих версиях Oracle позволил бы значительно сократить это время. В момент выдачи команды аварийного завершения выполнялось большое пакетное задание. Если бы оно выполняло фиксацию, например, каждые пять минут, то время восстановления можно было бы сделать примерно таким же или меньшим (и не пришлось бы повторять пятичасовую обработку!).
Наличие большего числа точек фиксации в обычной ситуации лишь незначительно повлияет на пропускную способность. При каждой фиксации процесс будет ждать записи в журнал, что должно занять всего несколько десятков миллисекунд. В расчете на каждые несколько минут это немного. Однако там, где процесс закодирован так, как показано ниже, возникают сложности.
INSERT TABLE live_orders
SELECT *
FROM orders
WHERE dead_flag IS NULL
UNRECOVERABLE;
Несомненно, это самый быстрый способ перемещения строк из одной таблицы в другую. В самой последней версии предложение CREATE TABLE...AS SELECT... можно снабдить атрибутом UNRECOVERABLE, предполагая, что проблемы с использованием сегмента отката и временем восстановления экземпляра преодолены. Однако все равно существует риск того, что авария экземпляра, операционной системы или аппаратных средств может привести к потере большого объема работы и, конечно, эта операция невосстановима, если требуется восстановление с повтором транзакций. Поэтому вы, возможно, решите не использовать опцию UNRECOVERABLE в заданиях промышленного уровня, а примените ее для основных служебных операций, за которыми сразу же следует резервное копирование. В приведенном ниже предложении продемонстрирован второй самый быстрый способ перемещения строк:
INSERT TABLE live_orders
SELECT *
FROM orders
WHERE dead_flag IS NULL
/* UNRECOVERABLE нам нужна возможность восстановления */;
Однако в этом случае объем используемого пространства сегмента отката прямо пропорционален числу копируемых строк. Во многих случаях можно совершенно свободно превратить эту операцию в многошаговую, применяя управляющую таблицу, в которую помещаются записи заказов, соответствующие определенному диапазону значений ключей. Затем, используя цикл, мы можем пересылать записи, соответствующие каждому диапазону значений ключей, и регистрировать конец каждого шага и выполнять фиксацию в конце каждого диапазона. Ниже показан пример реализации этого решения на PL/SQL:
DECLARE
CURSOR c1 IS
SELECT deptno
, dname
FROM dept
ORDER BY dname; -- благодаря этому журнал регистрации выглядит логичнее
BEGIN
INSERT INTO run_log (id, start_at)
VALUES ('XX',SYSDATE);
FOR this_dept IN c1
LOOP
INSERT INTO new_emp
SELECT * FROM emp WHERE deptno = this_dept.deptno;
INSERT INTO run_steps (id, step, ended_at)
VALUES ('XX', this_dept.dname, SYSDATE);
COMMIT;
END LOOP;
UPDATE run_log SET ended_at = SYSDATE
WHERE id = 'XX';
COMMIT;
END;
/
Это один из тех немногих случаев, когда мы можем рекомендовать реализацию основного пакетного процесса на PL/SQL, поскольку почти все время выполнения будет затрачено на работу интерпретатора SQL и требования к процедурному языку минимальны (и потому, что PL/SQL очень облегчает реализацию курсорных циклов FOR).
Продемонстрированный нами подход связан с некоторым увеличением затрат на создание задания, и для его реализации необходимо использовать PL/SQL или 3GL, а не просто создать скрипт на SQL*Plus. Однако он позволит сэкономить большой объем пространства сегмента отката и обеспечит быстрое восстановление.
Вообще говоря, нас не впечатляют организации, где пакетная обработка реализована средствами SQL*Plus.
Если этот пример преобразовать в хранимую процедуру (что мы рекомендуем делать), то в этом сеансе должен быть включен режим COMMIT IN PROCEDURE. Собственные средства Oracle, главным образом Oracle Forms, тяготеют к выдаче команды ALTER SESSION, отключающей этот режим. Они делают это для того, чтобы можно было рассчитывать на то, что установленные ими блокировки все еще действуют. Если режим COMMIT IN PROCEDURE включен, то любая хранимая процедура или функция, вызываемая процессом, может выдать команду COMMIT или ROLLBACK, что приведет к снятию всех транзакционных блокировок, установленных сеансом.
Делать все там, где нужно
Конечно, чтобы все сделать правильно, необходимы рассудительность и опыт. Однако мы можем дать ряд общих рекомендаций, которые помогут вам почти во всех случаях достичь приемлемого результата.
• Попробуйте запрашивать каждый элемент данных только один раз, т.е. по возможности используйте кэширование. Одно это требование часто исключает необходимость применения PL/SQL в качестве средства реализации.
• Не бойтесь сортировать файлы и выполнять ORDER BY в запросах, чтобы можно было воспользоваться преимуществом обработки в точке смены объекта операции. Связанные с этим затраты обычно с лихвой компенсируются возможностью перемещения некоторых операций обработки в точку смены объекта операции, с тем, чтобы не выполнять их для каждого элемента управляющей таблицы или файла.
• Не фильтруйте данные в управляющей логике, если соответстующие проверки не должны быть вынесены в программу по другим причинам. Рассмотрим такой пример:
DECLARE
CURSOR c1 IS
SELECT deptno
, dname
, active
FROM dept
ORDER BY dname; -- благодаря этому журнал регистрации выглядит логичнее
BEGIN
INSERT INTO run_log (id, start_at)
VALUES ('XX',SYSDATE);
FOR this_dept IN c1
LOOP
IF this_dept.active = 'Y'
THEN BEGIN
INSERT INTO new_emp
SELECT * FROM emp WHERE deptno = this_dept.deptno;
INSERT INTO run_steps (id, step, ended_at)
VALUES ('XX', this_dept.dname, SYSDATE);
COMMIT;
END;
END IF;
END LOOP;
UPDATE run_log SET ended_at = SYSDATE
WHERE id = 'XX';
COMMIT;
END;
/
В данном случае гораздо проще и эффективнее поместить условие (подразделение активно) в предложение WHERE.
Обработка ошибок
Большинство из нас, конечно, сталкивались с "радостными" сообщениями вроде Unexpected condition has occurred ("Возникла непредвиденная ситуация") или даже 04FD63-unknown reference ("Неизвестная ссылка"). Мы ломали голову над тем, что они означают, и расстраивались, так как не знали, что именно сделали не так — если мы вообще делали что-нибудь не так. Тем не менее, мы считали ошибки такого типа неизбежным злом и обычно повторяли свои попытки. Однако сегодня пользователи требуют сообщений, по которым можно идентифицировать ошибки и на которые можно реагировать. К тому же, если два программиста будут обрабатывать одну и ту же исключительную ситуацию, не имея схемы, указывающей, как обрабатывать эту ситуацию, то вряд ли они сделают это одинаково и наверняка предложат разные тексты сообщения об ошибке. Отсюда и необходимость в общем и согласованном подходе.
Рекомендуем присваивать всем возможным сбойным ситуациям абсолютно бессмысленные коды (мы говорим это совершенно серьезно) и хранить эти коды вместе с уровнем важности и текстовым описанием ошибки либо в базе данных, либо в файле. Очень важно, чтобы выдача новых номеров ошибок производилась просто, поскольку в противном случае программисты будут повторно использовать существующие номера.
Следующий шаг — предоставить стандартные подпрограммы для выдачи сообщений об ошибках. Такой подпрограмме передается номер ошибки и все необходимые параметры, а обработчик ошибок делает все остальное. Если уровень важности — фатальный (или выше), то управление программе не возвращается; во всех остальных случаях управление передается обратно в программу. Выбор места для выдачи сообщения об ошибке — дело подпрограммы обработки ошибок (больше инкапсуляции), но она, вероятно, должна зарегистрировать ее в файле (база данных может не работать) и (если сможет найти экранную форму или процесс, выполняющий отчет) выдать описательный текст пользователю.
Если приложение сталкивается с ошибкой, оно обязательно должно выполнять откат транзакции. Однако в случае, когда требуется откат транзакции, проектировщики иногда делают два ужасных просчета.
Первый просчет встречается в ситуации, когда перед выдачей команды отката транзакции вы делаете что-то еще, в частности помещаете на экран пользователя окно с таким, например, сообщением:
Fatal error encountered
Press OK to abort process
или, что еще хуже:
Severe error encountered
Press Abort or Retry
Если вы потратили десять минут на ввод данных и получили второе сообщение, то что вы сделаете? Вы станете нажимать кнопку Retry снова и снова в надежде, что программа все-таки заработает и вам удастся избежать потери сделанной работы. К сожалению, если программа перед выдачей этих сообщений пользователю сняла ряд важных блокировок, результатом может быть полное блокирование приложения.
Второй, еще более забавный, просчет заключается в регистрации действий приложения в базе данных для содействия устранению фатальной ошибки. Очевидно, что когда программа производит откат транзакции, то она выполняет откат и для всех элементов журнала регистрации. Решения просты: выполнить фиксацию вместо отката транзакции (разве можно без шуток?) или выполнять регистрацию в файлах, а не в базе данных. В версии 7.3 реализовать файловое решение несколько проще, чем в предыдущих, так как из PL/SQL теперь можно выполнять запись в файлы на сервере. Хотя безопасность при этом оставляет желать лучшего, поскольку эти файлы могут содержать в диагностической информации важные данные, на которые может случайно натолкнуться пользователь. В более ранних версиях регистрацию в файлы можно выполнять, посылая сообщения посредством DBMS_PIPE в выделенный серверный процесс, если обработка ошибок производится в основном через хранимые процедуры. Не поддавайтесь искушению использовать DBMS_ALERT, поскольку соответствующие механизмы имеют транзакционный характер, т.е. подвержены фиксации и откату.
Некоторые думают, что решение с фиксацией приемлемо в тестовых системах, но мы считаем его неоправданным в любых обстоятельствах. Если вы не завершили транзакцию (например, выполнили только три из пяти операций вставки, а потом получили сообщение Duplicate value in index), то фиксация незавершенной транзакции и нарушение целостности базы данных — непрофессиональное решение.
Примечание
Вы можете создать прослушивающий процесс, написанный полностью на PL/SQL, который будет ожидать сообщения, передаваемые с помощью пакета DBMS_PIPE. Когда процесс получает такое сообщение, он регистрирует его в базе данных, выполняет фиксацию и возвращается в состояние ожидания, готовясь к следующему сообщению. Следующий уровень сложности — организация поддержки сообщения Stop, чтобы можно было дать прослушивающему процессу команду на выход.
Обеспечивая регистрацию событий в базе данных, можно извлекать и сортировать эти данные с помощью любого генератора отчетов, что может принести существенную выгоду. Однако учтите, что перед тем как применять этот метод для регистрации большого числа событий в приложениях, в которых предъявляются высокие требования к времени реакции или пропускной способности, необходимо проверить последствия передачи более одного-двух канальных сообщений в секунду на конкретной платформе и версии Oracle7.
Навигация
Удобство при перемещении по экранным формам и доступе к функциям, обеспечиваемое системой, может быть решающим фактором ее успеха. Если пользователям приходится проходить мучительный путь, чтобы попасть из одной экранной формы в другую, вводить данные несколько раз или при переходе из одного диалогового окна в другое запоминать код либо элемент информации, то главным их впечатлением от такой системы будет ее неповоротливость.
Вопросы навигации особенно важны при создании приложения, ориентированного на использование ГПИ. ГПИ предоставляет очень много элементов управления, при помощи которых можно выполнять навигацию. Важно создать набор обязательных стандартов, обеспечивающих определенный уровень согласованности. Вот некоторые элементы управления, которые можно использовать для навигации:
• меню;
• линейки пиктограмм или кнопок;
• пиктограммы;
• пиктограммы внутри окон;
• непиктограммные кнопки в окнах;
• диалоговые окна с вкладками (где щелчок на вкладке открывает другую панель окна);
• горячие клавиши.
Как мы говорили, пользователи не должны запоминать или повторно вводить информацию. Это приводит нас к понятиям навигации с контекстом и без контекста. Навигация без контекста — это навигация, при которой вы переходите из одной экранной формы в другую, совершенно не связанную с первой. Например, вы обрабатываете транзакцию, но хотите быстро просмотреть очередь на печать и проверить, не печатается ли еще ваше задание. Навигация с контекстом подразумевает переход из одной экранной формы в форму, связанную с первой. Типичный пример — следование по связи в базе данных, в частности переход к элементам заказа из экранной формы заказов. Вряд ли можно ожидать, что пользователь захочет запоминать, какой заказ он смотрит, и вводить его вновь.
В качестве примера рассмотрим систему обработки заявлений о выплате страхового возмещения. Пользователю, работающему с заявлением, при определении обоснованности требования может понадобиться много разных экранных форм. В частности, интерес могут представлять общие сведения о полисах, исключающие положения, предыстория, данные о владельце полиса и др. Гибкая система навигации даст возможность пользователям вызывать эти экранные формы по порядку в контексте заявления, над которым они работают. Тем не менее, иногда контекст может быть ограничивающим фактором. Допустим, работу пользователя прерывает запрос по телефону и он хочет ознакомиться с другим полисом (принадлежащим тому, кто звонит). Естественно, навигация с контекстом уже не помогает, а становится помехой. Пользователь хотел бы "отложить" куда-нибудь текущее заявление, поработать над другим заявлением (которое становится контекстом), а потом вызвать отложенное заявление в контекст и продолжить над ним работу с того места, где работа над ним была прервана.
Чтобы правильно организовать перемещение между окнами приложения, необходимо иметь полное представление о методах работы пользователей, особенно о документообороте. Мы настоятельно рекомендуем вам протестировать (отмакетировать) все интерфейсы между различными средствами, чтобы обеспечить их техническую реализуемость, оценить производительность и решить вопросы с лицензированием ПО. Если вы вызываете одно средство из другого, то столкнетесь со следующей проблемой: каждое из них потребует соединения с Oracle. Даже если это не превышает лицензионные лимиты, возможен значительный расход оперативной памяти на сервере БД.
Необходимо также решить вопросы безопасности навигационной подсистемы. Механизм навигации должен обладать информацией о существующих привилегиях в плане безопасности и быть способен воспрепятствовать нарушению прав доступа. Кроме того, по возможности следует избегать экспортирования пользовательских имен и паролей. Другими словами, рискованно запускать другую программу путем динамического создания файла скрипта с командой запуска, содержащей имя и пароль пользователя.
Оперативная справочная система
В наши дни хорошее программное обеспечение, особенно настольные продукты, как правило, поставляется с мощной оперативной справочной системой. Сейчас очень распространена практика, когда ПО распространяется без документации (или с документацией только в машинно-читаемой форме), а пользователи почти всецело полагаются на оперативную справочную систему. Правда, пользователям приходится привыкать к поиску информации в такой системе, но если средства поиска достаточно мощные, то все равно она лучше, чем руководство. Справочная подсистема должна быть хорошо спроектированной, логичной и простой в использовании. В большинстве случаев она также должна быть контекстно-зависимой, т.е. доступной из любой точки приложения и по умолчанию выдавать справку о той части приложения, из которой ее вызвали.
При проектировании оперативной справочной системы приложения необходимо учитывать следующее:
• Находясь в какой-либо экранной форме и нажимая для вызова справки клавишу либо щелкая на кнопке, пользователь рассчитывает, что появится окно контекстно-зависимой справки с информацией о поле, в котором находится курсор. Это так называемая справка на уровне полей. В некоторых системах есть только справка на уровне экранных форм, в которой контекстом является вся экранная форма, а не поля. Независимо от вида справки справочный текст должен предоставлять пользователю необходимую информацию об экранной форме. В этом тексте должны содержаться деловое или функциональное описания назначения данной экранной формы и каждого поля. Кроме того, в тексте должны быть гипертекстовые ссылки на родственные темы.
• Для сложных систем и приложений необходимо рассмотреть возможность разработки учебников, знакомящих с основами использования системы, поскольку не все работающие пользователи будут иметь хорошую подготовку. Во многих случаях содержание этих учебников в большей степени касается порядка работы предприятия, чем принципов функционирования приложения.
• Существует еще одна популярная форма справки — карточки-подсказки, рассчитанные на неопытного пользователя. Эти карточки появляются в верхней части экрана и описывают действия, которые должен выполнить пользователь для достижения желаемых результатов. Например, можно создать набор карточек-подсказок для процедуры ввода заказа.
• Должна существовать взаимосвязь между ошибками и справкой. Большинство ошибок (за исключением системных) возникает в результате неправильных действий пользователя. Проинформировав пользователя о том, что он ввел номер заказа в неправильном формате, интегрированное приложение пойдет дальше и даст справку о том, как составляются номера заказов.
• Контекстно-зависимая справка предполагает, чтобы справочный текст имел хотя бы некоторые свойства гипертекста. Гипертекст позволяет осуществлять доступ к родственным темам внутри справочной подсистемы. Это хороший способ получения информации о системе. Приложение вызывает справочную систему, передавая контекст, в котором требуется справка. В простой системе на базе файлов контекст может строиться на заголовках разделов внутри справочного текста.
• Справочная система должна быть знакома пользователям. Если бы мы писали эту главу два-три года назад, то почти наверняка включили бы в нее советы по проектированию таблиц справочного текста, но мир изменился и мы уже не считаем, что справочные системы нужно реализовывать на уровне проекта. Они должны быть похожи на справочные системы, с которыми пользователь работает в других приложениях. Самый лучший путь — использовать "родное" для клиентской платформы справочное средство, даже несмотря на то, что при этом приходится распространять справочные файлы минимум до уровня файлового сервера в пределах предприятия. Например, на клиентах с Microsoft Windows следует использовать Microsoft Help. Если вы используете стандартную справочную систему, то сможете работать с мощными и простыми в эксплуатации средствами создания справок, которые помогут вам разработать справочные файлы профессионального уровня.
• Возможно, вам потребуется спроектировать промежуточное ПО, расположенное на уровне между приложением и справочной системой. Это ПО интерпретирует или получает контекст (например, имя формы и имя поля), преобразует его в допустимую ссылку и вызывает справочную систему, цитируя эту ссылку. Очевидное место для хранения соответствия между экранным контекстом и ссылкой на справочную систему — база данных. Однако если стандарты на именование контекстов соблюдаются не строго, то полученная в результате справочная система будет неудобной и непредсказуемой.
• В Oracle Designer/2000 версии 1.2А и выше есть генератор, который создает справки в формате Microsoft Help на базе описаний таблиц и модулей. Но если эти описания не составлены так, чтобы они были понятны для пользователей, то такая система, конечно, будет классическим образцом системы КЗТО ("каков запрос, таков ответ"). В большинстве проектов много сил тратится на то, чтобы описания были понятыми для программистов!
• Лучше привлечь к подготовке справочного текста пользователя, а еще лучше — профессионального технического писателя. Если его напишет разработчик, то в этом тексте будет слишком сильный системный уклон и он будет основан на понимании разработчиком принципов использования системы. Справочный текст следует писать простым языком, используя стандартную терминологию бизнеса. То, что разработчик может объяснить блоками и функциями, пользователь лучше объяснит бизнес-процессами.