BDE против ADO в Дельфи - PullRequest
       18

BDE против ADO в Дельфи

13 голосов
/ 15 декабря 2008

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

Недавно мы изменили большое приложение Delphi для использования соединений и запросов ADO вместо соединений и запросов BDE. После этого изменения производительность стала ужасной.

Я профилировал приложение, и узкое место, кажется, при фактическом вызове TADOQuery.Open. Другими словами, я мало что могу сделать с точки зрения кода, чтобы улучшить это, кроме реструктуризации приложения, чтобы фактически использовать базу данных меньше.

У кого-нибудь есть предложения по улучшению производительности приложения Delphi, подключенного к ADO? Я попробовал оба предложения, приведенные здесь , практически безрезультатно.

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

  • Под BDE: 11 секунд

  • Под ADO: 73 секунды

  • Под ADO после изменений, на которые ссылается эта статья: 72 секунды

Мы используем серверную часть Oracle в среде клиент-сервер. Каждый из локальных компьютеров поддерживает отдельное соединение с базой данных.

Для записи строка подключения выглядит следующим образом:

const
  c_ADOConnString = 'Provider=OraOLEDB.Oracle.1;Persist Security Info=True;' +
                    'Extended Properties="plsqlrset=1";' +
                    'Data Source=DATABASE.DOMAIN.COM;OPTION=35;' +
                    'User ID=******;Password=*******';

Чтобы ответить на вопросы, заданные Зендаром:

Я использую Delphi 2007 в Windows Vista и XP.

Серверная часть - это база данных Oracle 10g.

Как указано в строке подключения, мы используем драйвер OraOLEDB.

Версия MDAC на моем тестовом компьютере - 6.0.

Edit:

Под BDE у нас было много кода, который выглядел так:

procedure MyBDEProc;
var
  qry: TQuery;
begin
  //fast under BDE, but slow under ADO!!
  qry := TQuery.Create(Self);
  try
    with qry do begin
      Database := g_Database;
      Sql.Clear;
      Sql.Add('SELECT');
      Sql.Add('  FIELD1');
      Sql.Add(' ,FIELD2');
      Sql.Add(' ,FIELD3');
      Sql.Add('FROM');
      Sql.Add('  TABLE1');
      Sql.Add('WHERE SOME_FIELD = SOME_CONDITION');
      Open;
      //do something
      Close;
    end;  //with
  finally
    FreeAndNil(qry);
  end;  //try-finally
end;  //proc

Но мы обнаружили, что вызов Sql.Add на самом деле очень дорогой в ADO, потому что событие QueryChanged запускается каждый раз, когда вы меняете CommandText. Таким образом, замена вышеупомянутого была НАМНОГО быстрее:

procedure MyADOProc;
var
  qry: TADOQuery;
begin
  //fast(er) under ADO
  qry := TADOQuery.Create(Self);
  try
    with qry do begin
      Connection := g_Connection;
      Sql.Text := ' SELECT ';
        + '   FIELD1 '
        + '  ,FIELD2 '
        + '  ,FIELD3 '
        + ' FROM '
        + '  TABLE1 '
        + ' WHERE SOME_FIELD = SOME_CONDITION ';
      Open;
      //do something
      Close;
    end;  //with
  finally
    FreeAndNil(qry);
  end;  //try-finally
end;  //proc

Еще лучше, вы можете скопировать TADOQuery из ADODB.pas, переименовать его под новым именем и вырвать событие QueryChanged, которое, насколько я могу судить, ничего полезного не делает. Затем используйте новую модифицированную версию TADOQuery вместо собственной.

type
  TADOQueryTurbo = class(TCustomADODataSet)
  private
    //
  protected
    procedure QueryChanged(Sender: TObject);
  public
    FSQL: TWideStrings;
    FRowsAffected: Integer;
    function GetSQL: TWideStrings;
    procedure SetSQL(const Value: TWideStrings);
    procedure Open;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function ExecSQL: Integer; {for TQuery compatibility}
    property RowsAffected: Integer read FRowsAffected;
  published
    property CommandTimeout;
    property DataSource;
    property EnableBCD;
    property ParamCheck;
    property Parameters;
    property Prepared;
    property SQL: TWideStrings read FSQL write SetSQL;
  end;
////////////////////////////////////////////////////////
////////////////////////////////////////////////////////
////////////////////////////////////////////////////////
constructor TADOQueryTurbo.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FSQL := TWideStringList.Create;
  TWideStringList(FSQL).OnChange := QueryChanged;
  Command.CommandText := 'SQL'; { Do not localize }
end;

destructor TADOQueryTurbo.Destroy;
begin
  inherited;
 inherited Destroy;
  FreeAndNil(FSQL);
end;

function TADOQueryTurbo.ExecSQL: Integer;
begin
  CommandText := FSQL.Text;
  inherited;
end;

function TADOQueryTurbo.GetSQL: TWideStrings;
begin
  Result := FSQL;
end;

procedure TADOQueryTurbo.Open;
begin
  CommandText := FSQL.Text;
  inherited Open;
end;

procedure TADOQueryTurbo.QueryChanged(Sender: TObject);
begin
// if not (csLoading in ComponentState) then
//    Close;
// CommandText := FSQL.Text;
end;

procedure TADOQueryTurbo.SetSQL(const Value: TWideStrings);
begin
  FSQL.Assign(Value);
  CommandText := FSQL.Text;
end;

Ответы [ 3 ]

14 голосов
/ 15 декабря 2008

Я не знаю о Delphi 2007, но я сделал то же самое с Delphi 7 и Oracle 8.

Вот что я сделал:

  • Установить TAdoDataSet.CursorLocation в соответствии с запросом:
    • clUseClient , если запрос выбирает записи для графического интерфейса пользователя и запрос относительно «прост» - нет группировки или суммы
    • clUseServer если запрос имеет какую-либо агрегацию (сумма, группировка, подсчет)
  • Установить TAdoDataSet.CursorType в соответствии с запросом:
    • ctForwardOnly для отчетов, для которых не требуется прокрутка набора данных - работает только с clUseServer
    • ctStatic для графического интерфейса. Это единственный режим, который работает с clUseClient
  • Установить TAdoDataSet.LockType в соответствии с запросом:
    • ltReadOnly для каждого набора данных, который не используется для редактирования (сетки, отчеты)
    • ltOptimistic когда записи публикуются в базе данных сразу после изменения (например, пользователь редактирует данные в форме)
    • ltBatchOptimistic при изменении большого количества записей. Это для ситуаций, когда вы выбираете количество записей, затем выполняете их обработку, а затем отправляете обновления в базу данных в пакетном режиме. Это работает лучше всего в сочетании с clUseClient и ctStatic.
  • По моему опыту, поставщик Microsoft OLEDB для Oracle работал лучше, чем поставщик Oracle OleDb. Вы должны проверить это.
    Редактировать: Проверьте комментарий Фабрицио о возможных проблемах с BLOB-объектами.
  • Заменить TAdoQUery на TAdoDataSet . TAdoQuery был создан для преобразования приложений из BDE в ADO, но рекомендацией Borland / Codegear было использование TAdoDataSet
  • Еще раз проверьте строку подключения Oracle, чтобы убедиться, что у вас нет задержки в сети. Как долго длится подключение к Oracle? Как долго длится TnsPing?
4 голосов
/ 16 июля 2011

Я обнаружил проблемы с производительностью ADOExpress несколько лет назад:

Примечание: До того, как ADO стал стандартной частью Delphi, Borland продавал его как дополнение, называемое ADOExpress . Это были просто обертки вокруг COM-объектов Microsoft ActiveX Data Objects (ADO).

Я проверил три сценария

  • с использованием ADO напрямую (т. Е. Непосредственно COM-объектов Microsoft)
  • с использованием ADOExpress (обертки объектов Borland вокруг ADO)
  • указав .DisableControls на TADOQuery перед вызовом Open

я обнаружил

  • используйте Query.DisableControls, чтобы сделать каждый звонок .Next 50x быстрее
  • используйте Query.Recordset.Fields.Items['columnName'].Value вместо Query.FieldByName('columnName') для ускорения поиска каждого значения в 2,7 раза
  • с использованием TADODataSet (стихи TADOQuery) не имеет значения

                                    Loop Results        Get Values 
    ADOExpress:                         28.0s              46.6s 
    ADOExpress w/DisableControls:        0.5s              17.0s 
    ADO (direct use of interfaces):      0.2s               4.7s 
    

Примечание : Эти значения предназначены для зацикливания 20 881 строки и поиска значений в 21 столбце.

Базовый неверный код:

var
   qry: TADOQuery;
begin
   qry := TADOQuery.Create(nil);
   try
      qry.SQL.Add(CommandText);
      qry.Open;
      while not qry.EOF do
      begin
         ...
         qry.Next;
      end;

Используйте DisableControls для ускорения цикла на 5000% :

var
   qry: TADOQuery;
begin
   qry := TADOQuery.Create(nil);
   try 
      qry.DisableControls;
      qry.SQL.Add(CommandText);
      qry.Open;
      while not qry.EOF do
      begin
         ...
         qry.Next;
      end;

Использование коллекции Fields для ускорения поиска значений на 270% :

var
   qry: TADOQuery;
begin
   qry := TADOQuery.Create(nil);
   try 
      qry.DisableControls;
      qry.SQL.Add(CommandText);
      qry.Open;
      while not qry.EOF do
      begin
         value1 := VarAsString(qry.Recordset.Fields['FieldOne'].Value);
         value2 := VarAsInt(qry.Recordset.Fields['FieldTwo'].Value);
         value3 := VarAsInt64(qry.Recordset.Fields['FieldTwo'].Value);
         value4 := VarAsFloat(qry.Recordset.Fields['FieldThree'].Value);
         value5 := VarAsWideString(qry.Recordset.Fields['FieldFour'].Value);
         ...
         value56 := VarAsMoney(qry.Recordset.Fields['FieldFive'].Value);
         qry.Next;
      end;

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

class function TADOHelper.Execute(const Connection: TADOConnection; 
       const CommandText: WideString): TADOQuery;
var
   rs: _Recordset;
   query: TADOQuery;
   nRecords: OleVariant;
begin
   Query := TADOQuery.Create(nil);
   Query.DisableControls; //speeds up Query.Next by a magnitude
   Query.Connection := Connection;
   Query.SQL.Text := CommandText;
   try
      Query.Open();
   except
      on E:Exception do
      begin
         Query.Free;
         raise;
      end;
   end;
   Result := Query;
end;
0 голосов
/ 11 июля 2011

Для лучшей производительности, взгляните на наш Open Source прямой доступ к Oracle .

Если вы обрабатываете много TQuery без использования компонентов БД, у нас есть выделенный псевдокласс для использования прямого соединения OCI, например:

 Q := TQuery.Create(aSQLDBConnection);
 try
   Q.SQL.Clear; // optional
   Q.SQL.Add('select * from DOMAIN.TABLE');
   Q.SQL.Add('  WHERE ID_DETAIL=:detail;');
   Q.ParamByName('DETAIL').AsString := '123420020100000430015';
   Q.Open;
   Q.First;    // optional
   while not Q.Eof do begin
     assert(Q.FieldByName('id_detail').AsString='123420020100000430015');
     Q.Next;
   end;
   Q.Close;    // optional
 finally
   Q.Free;
 end;

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

procedure Test(Props: TOleDBConnectionProperties; const aName: RawUTF8);
var I: ISQLDBRows;
    Customer: Variant;
begin
  I := Props.Execute('select * from Domain.Customers where Name=?',[aName],@Customer);
  while I.Step do
    writeln(Customer.Name,' ',Customer.FirstName,' ',Customer.Address);
end;

var Props: TOleDBConnectionProperties;
begin
  Props := TSQLDBOracleConnectionProperties.Create(
    'TnsName','UserName','Password',CODEPAGE_US);
  try
    Test(Props,'Smith');
  finally
    Props.Free;
  end;
end;

Обратите внимание, что все поставщики OleDB содержат ошибки для обработки больших двоичных объектов: версия Microsoft просто не обрабатывает их, а версия Oracle случайным образом возвращает ноль для 1/4 строк ...

В реальной базе данных я обнаружил, что наши прямые классы OCI работают в 2–5 раз быстрее, чем поставщик OleDB, без необходимости устанавливать этот поставщик. Вы даже можете использовать Oracle Instant Client , предоставляемый Oracle, который позволяет запускать ваши приложения без установки стандартного (огромного) клиента Oracle или наличия ORACLE_HOME. Просто поставьте DLL-файлы в тот же каталог, что и ваше приложение, и оно будет работать.

...