|
|
|||||||||||||||||||||||||||||
|
Переход с Paradox на InterBase за 30 днейСкип Роуланд (Skip Rowland), президент r3 Software, Inc.
Настоящий документ представляет собой техническую статью, подготовленную на основе выступления Скипа Роуланда (Skip Rowland), президента фирмы r3 Software Inc., на 10-ой ежегодной конференции Inprise & Borland. Подавляющее большинство Paradox-приложений (или других файл-серверных приложений, предназначенных для тех же целей) включает в себя не более 50 таблиц. В данной статье мы хотим рассмотреть, что же произойдет, если небольшое приложение вырастет и выйдет за пределы возможностей Paradox. Вначале я кратко опишу саму ситуацию, а затем затрону проблемы, решения, меры предосторожности, трудности и достижения, которые сопровождают такой сложный, но неизбежный переход на лучшую систему. Обзор Рассмотрим ситуацию на примере многофункциональной морской компании, занимающейся перевозками пассажиров, грузов и буксировкой по всему миру и являющейся в течение уже долгого времени моим клиентом. В 1993 году у них была установлена электронная система выписки счетов и определения загрузки на базе Paradox для Dos. В январе 1997 года они обратились ко мне с просьбой предложить что-нибудь, более полно отвечающее их требованиям к базе данных. Они чувствовали как внутреннее давление, вызываемое возросшими потребностями в управлении, так и внешнее, связанноес возросшими требованиями со стороны промышленности. Им требовалась система, которая могла бы объединить деятельность всех отделов (продаж, отправки, расчетного, операционного, отдела кадров, HR, и отдела управления рисками) в одну центральную базу данных. В это время они имели сеть на базе Novell 3.11, включающую около 30 рабочих станций и использовали Paradox для Dos, Professional File, Excel, Word Perfect, а также ряд других инструментов. Тогда же были определены пять основных приложений:
Позднее было выявлено еще три приложения:
И я уверен, что по мере продвижения вперед будут всплывать все новые приложения. Первоначально был предложен переход на Windows95, что, в свою очередь, потребовало обновления почти всего аппаратного и программного обеспечения компании. И хотя это было абсолютно неизбежно, в компании пытались максимально отодвинуть сроки модернизации. Мы предложили архитектуру клиент/сервер, но предложение было отвергнуто как нечто преждевременное; в результате в качестве хранилища данных был выбран Paradox v7. Разработка началась в феврале 1997 года и шла обычным чередом: мы создавали базу данных, строили прототип, а затем снова возвращались к чертежам. И на каждом витке мы все ближе подходили к тому, что заказчики хотели получить, но не могли четко сформулировать. По мере разработки в нее включались все новые направления, каждое из которых вызывало появление все новых и новых таблиц соответствий. База данных, включавшая изначально 25 таблиц, выросла до 400 таблиц. Сначала все шло гладко. Приложение по распределению/расчетам было самой простой задачей, так как оно было полностью определено и работало уже в течение 4 лет. К несчастью, дела пошли значительно хуже в июне 1998 года, когда мы приступили к работе над HR-приложением. Проблемы Каждое приложение могло выполняться автономно и работало вполне приемлемо. Однако, как только приложения начинали работать параллельно, возникали противоречия, и все начинало разваливаться. Проблема усугублялась, когда пользователи с 16 Мб- ОЗУ пытались одновременно запускать Word, Excel, электронную почту и ряд других приложений. В результате иногда блокировались только приложения с базами данных, а иногда вся система. Иногда приложения с базами данных загружались полностью, а иногда загрузка не удавалась вообще. А сколько мы получили этих ужасных сообщений "insufficient memory to complete operation" (недостаточно памяти для завершения операции)! В общем, мы были свидетелями самых различных случаев несообразного, непонятного и абсолютно неприемлемого поведения. Band-Aids Как я уже упоминал, каждое приложение могло выполняться самостоятельно в качестве решения на уровне отдела. Но так как это разрушало целостное представление решения на уровне предприятия, ни о каком разбиении и слиянии данных на хоть сколько-нибудь регулярной основе не могло быть и речи. Первоначально я сам выполнял всю работу, используя Delphi вместо Paradox. Я проработал каждое приложение вдоль и поперек, исключая все внешние накладные расходы и урезая все, что было возможно. Базовую архитектуру я взял без изменения из KnowledgeBase:
Но этого было не достаточно. Сначала мы обновили BDE, с 3.51 на 4.01, а затем изменили установки BDE-конфигурации.
В течение некоторого времени это работало. К сожалению, из-за циклов тестирования и создания прототипов, полностью использовалось только приложение по распределению/расчетам. Однако к сентябрю 1998 начали оперативно функционировать и использовать приложения HR- и операционный отделы. И вот наступило "Черное воскресенье". 21 сентября, около 10 часов утра, произошел сбой во всех четырех приложениях одновременно. Очевидно, что меры предосторожности были явно недостаточными. Решения Единственным приемлемым решением был бы переход на архитектуру клиент/сервер. InterBase была при этом логичным выбором, исходя из ее стоимости, доступности, сопровождения, а также моей предрасположенности к ней. Так как многие люди зависели от работы приложений, все необходимо было сделать в максимально короткие сроки. Фактически, сначала мне было дано на это 30 дней. Кроме того, были некоторые дополнительные соображения, касающиеся клиента, а именно, обновление их сетевой операционной системы. Они использовали NetWare 3.11 и знали, что придется перейти на новую ОС. Рассматривались варианты NT4, ожидаемая NT5, NW4.11, или NW5. В конце концов, единственным жизнеспособным вариантом оказалась NW4.11. Так как у фирмы не было собственного сетевого администратора, вариант NT4 казался слишком рискованным; что касается NT5, то они опасались, что не успеют развернуть ее до 2000. На тот момент не было сертифицированной версии InterBase для NW5, следовательно, оставалась только NW4.11. Некоторые дополнительные Band-Aids Прежде чем начать перенос, от меня, как минимум, требовалось сделать текущие приложения доступными. Самая большая проблема заключалась в том, что размер блокировочных файлов увеличился до такой степени, что их стало просто невозможно переносить с помощью BDE (более 2.5 Мбайт). Рассматривались следующие промежуточные решения:
Все это выглядело очень неуклюже, но, по крайней мере, позволяло служащим получить доступ к данным. Фактический процесс переноса Можно выделить четыре фазы переноса Delphi-приложения с Paradox на InterBase:
Установка и настройка включает инсталляцию InterBase, ознакомление и использование WISQL, а также конфигурирование BDE. Для "чистого" запуска я демонтировал InterBase, установленную при инсталляции Delphi. Я выполнил полную инсталляцию IBLocal с установками по умолчанию. За тем я создал каталог под именем PDox2IB, в котором разместил "горячие" клавиши для InterBase Server Manger, WISQL, Database Desktop и BDEAdministrator. В первую очередь я хотел изменить пароль входа в InterBase на более короткий. Для этого я:
Примечание: для просмотра изменений паролей необходимо выйти из системы, а затем снова войти в нее Затем, чтобы создать новую базу данных в InterBase, в которую затем будет осуществлен перенос, я запустил WISQL.
После этого, с помощью Windows Explorer, был создан новый каталог данных со всеми таблицами (помните, что одна из мер предосторожности заключалась в разбиении данных на директории с присвоением им псевдонимов, специфических для приложений). Затем, чтобы создать новые псевдонимы для исходной и целевой баз данных, я запустил BDEAdmin.
Теперь все было настроено для переноса данных. Перенос базы данных В первую очередь хочу отметить, что это НЕ одношаговый процесс. Если Вы предполагаете, что все произойдет мгновенно каким-либо сказочным образом, то Вас ожидает глубокое разочарование. В состав Delphi C/S входит программа с именем Datapump.exe; она установлена в BDE-каталоге и называется "Data Migration Wizard" ("Мастер переноса данных"). Datapump - хороший инструмент, но он имеет свои ограничения - не ожидайте от него невозможного и не воспринимайте как "панацею". Иногда вам придется закатать рукава и самим написать тот или иной инструмент. Если у вас нет Datapump, то придется написать его самим. Но в данном случае, предположим, что есть уже готовый. Самые большие проблемы: имена полей и типы полей. Совет: сразу планируйте, что для этого потребуется не одна итерация. При использовании Datapump для выявления проблем, советую вам постепенно вносить изменения в исходную базу данных и исходный код базы. Подробнее: вам придется сделать ее, КАК МИНИМУМ, ДВАЖДЫ : один раз для разработки, а второй для эксплуатационной базы данных! Вы обязательно должны документировать весь процесс преобразования. Помните, что процесс переноса эксплуатационной части пройдет гладко только при идеальной отработке этого процесса. Самые большие изменения в базе данных придется вносить в:
Можете попытаться предварительно выполнить эти операции, или же в несколько циклов с использованием datapump. Datapump создает две paradox-таблицы -одну перед "переносом", а вторую после. В первой таблице отображаются изменения, которые будут внесены в вашу базу данных, а во второй - изменения, которые произошли. Давайте рассмотрим изменения в именах файлов Лучше всего, если исходные таблицы, а затем код, будут модифицированы до выполнения окончательного переноса. В этом случае, можно быть уверенным, что ваш код будет работать после переноса.
Возможно, сначала Вы захотите исключить все вторичные индексы, кроме тех, которые требуются для кода. Большинство вторичных индексов Paradox-таблиц используются для того, чтобы пользователи могли изменить порядок сортировки просматриваемых данных. В клиент-серверной среде этого не требуется, т.к. для просмотра данных Вы будете использовать не таблицы, а запросы. Вторичные индексы в клиент-серверной среде используются для ускорения обработки запросов, поэтому до тех пор, пока Вы не будете знать, как предполагается использовать базу данных, нет никакой необходимости увеличивать накладные расходы, создавая и обслуживая огромное количество вторичных индексов, особенно по убыванию. Базовый процесс, выполняемый вручную Выполните:
Чтобы просмотреть результаты, запустите WISQL, затем выберите File/Connect to Database
Чтобы просмотреть, что сделала программа Datapump,
Примечание: если Вы выполнили "select" один раз, программа запросит, следует ли продолжать работу. Можно ответить и "да" и "нет". Преобразование эксплуатационных данных В нашем случае предположим, что datapump работает нормально, хотя в реальной ситуации этого может и не случиться. Datapump создаст требуемые IB-таблицы и индексы, однако, я получил противоречивые результаты, относящиеся к фактическому переносу данных. Поэтому я создал инструмент, который:
Между шагами 2 и 3, я запустил Datapump, чтобы создать IB-базу данных из пустой продублированной базы данных, полученной в шаге 1. Перенос кода базы Здесь перед нами стоят простые задачи:
Даже если Вы законченный оптимист и считаете, что работаете с простейшими компонентами, вам все-таки придется кое-что сделать. Ни одно приложение, даже самое изощренное, не заработает с первого раза; и не обращайте внимания на то, что Вам пообещали. Сейчас я хочу рассказать о тех уроках, которые получил при переносе базы данных. У Вас ситуация может сложиться по-другому, в этом случае, некоторые из описанных мной шагов Вам не потребуются, но ведь никто не знает, что может произойти... Существуют различные модели работы с InterBase-данными; про каждую из них можно было бы написать отдельный доклад. Выбор модели, которую использовал я, был основан на чистой необходимости. И она работала, хотя, возможно, и не была лучшей. Сначала создайте каталог для нового проекта, ibProjects.
В начале работы, с помощью текстового редактора (не Delphi) удалите из файла проекта почти все содержимое. При открытии Delphi, не должны автоматически открываться формы или модули данных. По мере того, как Вы будете приводить модули в надлежащее состояние, можете снова добавлять их в проект. Регистрационное имя приложения В первую очередь Вы должны создать автоматическое соединение со своей IB-базой данных. Это избавит Вас от ввода регистрационного имени при каждом соединении, а ваших пользователей - от ввода их регистрационного имени. В BDE (при закрытом Delphi) измените Paradox-псевдоним своего приложения на другой. Затем, находясь в Delphi,
SERVER NAME=c:apps4eesvm.gdb
Откройте свой проект. Если Вы действительно все из него удалили, то откроется только исходный файл (.dpr). Просмотрите менеджер проекта. Нажмите кнопку Add и добавьте в проект IBKey. В исходном файле (.dpr), в первую очередь следует создать модуль данных. И что действительно замечательно, так это то, что другие модули данных смогут использовать это соединение, НЕ обращаясь к модулю данных в операторе uses!!! Теперь Вы готовы к тому, чтобы начать последовательно добавлять блоки проекта, удаляя из них все, относящееся к Paradox. Я предлагаю начать с dfms, т.к. иногда формы не открываются, если поля не определены правильно. В КОДЕ (И dfms-, и pas-файлы) Проблемы и их решение
Если Вы используете Woll2Woll-компоненты, следует внести еще некоторые изменения. wwDBComboLookup восстановить параметры поиска Lookup Property проверить ВСЕ псевдонимы и имена таблиц в поисковых строках, особенно в DFMs. В противном случае, ваша форма не откроется даже, если будет скомпилирована!
После этого Ваше приложение, возможно, заработает. По собственному опыту я знаю, что некоторые приложения работали вполне приемлемо, используя только TTable, в то время как другие работали абсолютно неприемлемо - просто ужасно. Это приводит к необходимости выполнения следующего шага, т.е. к оптимизации сервера. Оптимизация сервера Лично я выполнял эту работу просто из интереса, т.к. мои приложения работали нормально. Но настало время сделать так, чтобы они заработали замечательно, переместив код из исполняемого приложения на сервер. Во многих отношениях это длительный процесс. В своем докладе я хочу затронуть некоторые наиболее важные области. По мере работы над проектом, я обнаружил много новых подсказок. Предупреждение: Ваша первостепенная задача - сделать так, чтобы приложение заработало; вторая задача - оптимизировать код так, чтобы он мог использовать все преимущества серверной части системы. Очень заманчиво не выполнять эту последовательность и начать оптимизировать код еще во время переноса. Что же, но пусть это не собьет вас с верного пути... Об оптимизации сервера можно сказать очень многое, материала хватит на целую книгу (я сейчас над ней работаю...); однако, объем данной статьи накладывает свои ограничения, поэтому здесь я опишу только основные моменты в оптимизации сервера, а именно:
Общий обзор В первую очередь: размещение кода. Это обязательное практическое правило: если код включает в себя поведение базы данных, установите его на сервере; в противном случае, установите его в Delphi. Например, для оценки данных используйте процедуры предварительной оценки и обновления на сервере. Для обновления экранов после записи используйте Delphi. Помните, что пользователь манипулирует значениями пользовательского интерфейса, а не значениями базы данных. Они станут значениями базы данных только после того, как будут в нее корректно записаны. Поэтому Вам придется полагаться на события Delphi и на события BDE-базы данных. Было бы замечательно, если бы OnNewRecord было событием баз данных, но это невозможно. Это означает, что для инициализаций новых записей вам придется использовать Delphi. Возможно, проще было бы рассматривать интерфейс пользователя просто как шаблон для управления доступом к данным. "On New Record" представляет собой искусственное событие, позволяющее подготовить пользователя к заполнению шаблона ввода данных. Вы можете использовать хранимые процедуры для восстановления значений по умолчанию и тому подобных операций, но все равно, Вы будете просто подготавливать экран для заполнения его пользователем. Сервер активизируется только после того, как Вы начнете передачу отобранных данных. Затем Вы получите результаты (если таковые имеются) с триггера "before post". И снова, к сожалению, нет способа, позволяющего связать их вместе в единое полное сообщение. Сервер может только послать их обратно по одному (если только Вы не найдете время запрограммировать сервер на выполнение предложения "if then if then else else if then"). Кроме того, даже если применять те же правила, события при передаче новой записи отличаются от событий при обновлении старой. Триггер "before Post" используется только для обновления существующей строки. Чтобы применить эти правила к новой записи, потребуется триггер "before insert". Обновление запросов отсутствует (т.к. полученный в результате набор данных не имеет уникального ключа). В случае использования BDE, обновление можно осуществить только в том случае, если установить закладку, закрыть и снова открыть запрос, а затем перейти к закладке. Обратите внимание на эту последовательность действий - она не документирована и получена полностью эмпирически. С помощью BDE, Вы можете задавать программное завершение, которое будет обновлять текущий курсор без повторного выполнения всего запроса. Отрицательной стороной является то, что другие пользователи не увидят изменений, которые Вы внесли в базу данных. При работе с запросами следует уделять особое внимание как поддержанию сбалансированной производительности, так и синхронизации с пользовательским интерфейсом. При работе с TTables, BDE обеспечивает синхронизацию связанных курсоров. При работе с запросами дело обстоит совсем по-другому. Удаление строки не вызывает автоматического обновления другого запроса, которое отразило бы данное изменение. Это особенно верно при работе с окнами просмотра. Здесь обновление приходится выполнять вручную. Связывание запроса через источник данных - это НЕ то же самое, что связывание с TTABLES через MasterSource и MasterField. Если Вы добавите строку в "связанный элемент", то вам также придется задать главные связующие значения. При использовании TTables, BDE сделает это за вас, автоматически добавив связующие значения для связанных ключевых полей. При работе с запросами Вам придется сделать это вручную, чтобы избежать ошибок противоречия ключей. Главное, Вы должны принять для себя следующую установку: сервер должен работать, а Ваша программа должна отражать состояние данных на сервере. Синхронизация базы данных Одним из решений, которое Вам потребуется принять, связано с тем, как Вы будете отбирать данные с сервера. В IB отсутствует одно из свойств, поддерживаемых в Paradox. Это "автообновление" позволяющее всем пользователям почти сразу же видеть внесенные изменения. Для решения этой проблемы Вам придется сконфигурировать BDE. В BDE Administrator, откройте закладку Configuration, затем последовательно откройте Drivers, Native, INTRBASE и установите DriverFlags на 512. Это установка для Repeatable Read/Hard Commit. Такая установка приводит к повышению производительности; однако, необходим компромисс, помогающий избежать взаимоблокировок (когда каждая из двух или более транзакций пытается заблокировать остальные), В результате, другие пользователи смогут видеть внесенные вами изменения и наоборот. Переход от TTABLES к TQueries Главное различие между Paradox- и клиент-серверными приложениями заключается в том, что Paradox-приложения поддерживают перемещение пользователя между таблицей/списком данных в одном окне и панелью с управляющими элементами редактирования, - в другом. Как только пользователь введет новую строку или внесет изменения в уже существующую, эти изменения сразу же отразятся в таблице данных. Обычно в клиент-серверном приложении список отображает результаты одного запроса, а управляющие элементы редактирования связаны с другим запросом. Следовательно, изменения, вносимые во второй запрос, НЕ будут автоматически отображаться в таблице данных. Несмотря на то, что для TQuery может быть скомпилирован Refresh-метод, он не будет работать. Для обновления списка Вам придется закрыть запрос и открыть его снова. А это не так просто. Если Вы проводили редактирование, то можете установить закладку. Если Вы добавили новую строку, то Вам самим придется искать эту новую строку...
Чтобы связать TQueries вместе в отношение Главный/Частный, используйте для Detail TQuery следующий SQL:
Это будет работать, но помните, что "SELECT * FROM ATABLE", по сути, ничем не отличается и не будет работать лучше, чем TTable! С течением времени Вы захотите оптимизировать запросы так, чтобы они выбирали только те строки и поля, которые Вам необходимы. Я предпочитаю создавать ОКНА, данные в которых пользователь может фильтровать, перемещаясь, таким образом, по базе данных; найдя нужные данные, пользователь нажимает кнопку Edit, в результате открывается форма со всеми редактируемыми полями. Поля будут содержать результаты запроса "SELECT * FROM ATABLE WHERE (KEYFIELD = :KEYFIELD)", а KEYFIELD-параметр будет взят из текущей строки окна. Генераторы ключей Это очень просто. Создайте генератор, напишите процедуру получения значения от генератора, а затем получайте это значение в OnNewRecord-событии в Delphi. CREATE GENERATOR ZKEYS; CREATE PROCEDURE SP_NEW_KEY RETURNS (ID INTEGER) AS BEGIN ID = GEN_ID (ZKEYS, 1); END Query1.OnNewRecord begin with Query1 do FieldByname('FIELD1').AsInteger := GetNewKey; end; Модель клиент-серверного события с точки зрения Delphi В Delphi, BDE и IB (или какой-либо другой клиент-серверной базе данных) поддерживаются разные потоки событий.
То есть "BeforeInsert"-событие в Delphi не соответствует IB "Before Insert"-событию. Фактически цепочку событий можно представить следующим образом:
или
В течение многих часов я решал проблему: что же лучше - хранимые процедуры или триггеры. В какой-то момент я решил, всегда нужно использовать хранимые процедуры, всеми силами избегая использования триггеров. К такому решению меня привело следующее: 1) объем работ, необходимых для написания и последующего обслуживания триггеров, и Но это было еще до того, как я понял, как следует работать с триггерами. Позднее я разработал модель, позволившую мне максимально использовать серверную часть системы, при этом сохраняя достаточную функциональность файл-серверного приложения. /* this table's field holds a Carriage Return */ Create Table ZCR (CR CHAR (2)); CREATE TABLE X ( FIELD1 INTEGER NOT NULL, FIELD2 ..., ... VALIDATED INTEGER, PRIMARY KEY (FIELD1)); CREATE TRIGGER TR_X_BI FOR X ACTIVE BEFORE INSERT AS DECLARE VARIABLE CR CHAR(2); DECLARE VARIABLE ERRS VARCHAR (255); BEGIN ERRS = ''; SELECT CR FROM ZCR INTO :CR; IF (NEW.FIELD2 IS NULL) THEN ERRS = 'Field2 is required.'//:CR; IF (NEW.FIELDn IS NULL) THEN ERRS = :ERRS//'Fieldn is required.'//:CR; IF (:ERRS > '') THEN BEGIN NEW.VALIDATED = GEN_ID (ZERRORS, 1); INSERT INTO ZERRORLOG (ERRORID, ERRORMSG) VALUES (NEW.VALIDATED, :ERRS); END ELSE NEW.VALIDATED = NULL; END CREATE TRIGGER TR_X_AI FOR X ACTIVE AFTER INSERT AS BEGIN IF (NEW.VALIDATED) THEN BEGIN END END В своем Delphi-приложении я использую следующее: function GetNewKey: LongInt; var sp: TStoredProc;begin sp := TStoredProc.Create (nil); with sp do begin DatabaseName := Framework.MasterAlias; StoreProcName := 'SP_GET_KEY'; Params.CreateParam (ftInteger, 'ID', ptOutput); Prepare; ExecProc; Result := ParamByName('ID').AsInteger; UnPrepare; Free; end; Query1.OnNewRecord begin with Query1 do FieldByname('FIELD1').AsInteger := GetNewKey; end; Query1.AfterInsert; begin with Query1 do ParamByName('FIELD1').AsInteger := FieldByName('FIELD1').AsInteger; end; Query1.AfterPost; begin with Query1 do begin {you have to close/open in order to see changes made by the server} DisableControls; Close; Open; EnableControls; if not FieldByName('VALIDATED').IsNull then begin SP_GET_ERROR.ParamByName('ID').AsInteger := FieldByName('VALIDATED').AsInteger; SP_GET_ERROR.Prepare; SP_GET_ERROR.ExecProc; Framework.ReportServerError (SP_GET_ERROR.ParamByName('ERRMSG').AsString); SP_GET_ERROR.UnPrepare; end; end; Это только грубая схема моей базовой структуры; однако, и по ней уже ясно видно, что структура разрабатывалась так, чтобы основную часть работы выполнял север. Трудности Выполнение переноса не было полностью автоматизированным. При разработке и инсталляции новой сетевой операционной системы нам приходилось учитывать временные ограничения. Некоторые приложения вполне приемлемо работали с TTABLES, так что их сразу можно было вводить в эксплуатацию, не ожидая оптимизации сервера, но для других приложений это было недопустимо. Кроме того, в то время, когда приложения у нас уже начинали работать, и мы как-то решили проблемы блокировочных файлов, пользователи стали выдвигать дополнительные требования к приложениям. Стало очень трудно поддерживать оптимальное соотношение между требованиями в начале работы и добавлением новых свойств в то время, когда не все старые еще были реализованы. И все это приходилось делать на фоне решения различных небольших проблем, которые несет с собой новое окружение... Что касается данного проекта, IB 4.2.2 является последней версией для NW4.11. В последующих версиях был исправлен ряд ошибок и внесены полезные изменения, которые отсутствуют в предыдущих версиях. К счастью, в первой половине 1999 года ожидается версия IB5.5 для NW4.11. Главные проблемы, с которыми нам пришлось столкнуться:
Достижения Последовательность наших действий и полученные результаты:
Решены проблемы:
Теперь мы готовы фактически отделить функциональные возможности ввода данных от управления. Значительно упростились просмотр и оповещение. В общем, теперь у нас есть надежная база для дальнейшей работы
|
|