СТАТЬЯ
22.06.01

Переход с Paradox на InterBase за 30 дней

Предыдущая часть

© Скип Роуланд Skip Rowland
Президент r3 Software, Inc.
Подробные данные об авторе

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

Общий обзор

В первую очередь: размещение кода. Это обязательное практическое правило: если код включает в себя поведение базы данных, установите его на сервере; в противном случае, установите его в 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. Delphi Before Insert
  2. Delphi On New Record
  3. Delphi After Insert
  4. Delphi Before Post
  5. InterBase Before Insert
  6. InterBase After Insert
  7. Delphi After Post

или

  1. Delphi Before Edit
  2. Delphi After Edit
  3. Delphi Before Post
  4. InterBase Before Update
  5. InterBase After Update
  6. Delphi After Post

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

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

Но это было еще до того, как я понял, как следует работать с триггерами.

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

 /* 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.

Главные проблемы, с которыми нам пришлось столкнуться:

Достижения

Последовательность наших действий и полученные результаты:

Решены проблемы:

  1. блокировочных файлов

  2. устаревших индексов

  3. низкой производительности

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

Дополнительная информация

Дополнительную информацию Вы можете получить в компании Interface Ltd.

Отправить ссылку на страницу по e-mail
Обсудить на форуме Inprise/Borland


Interface Ltd.
Тel/Fax: +7(095) 105-0049 (многоканальный)
Отправить E-Mail
http://www.interface.ru
Ваши замечания и предложения отправляйте автору
По техническим вопросам обращайтесь к вебмастеру
Документ опубликован: 04.07.01