BDE против ADO в Delphi

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

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

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

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

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

  • под BDE: 11 секунды!--13-->

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

  • по Адо после изменения ссылки на статью: 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=*******';

ответить на вопросы zendar:

Я использую 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 ответов


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

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

  • Set TAdoDataSet.CursorLocation согласно запросу:
    • clUseClient если запрос извлекает записи для GUI, а запрос относительно "прост" - нет группировки или суммы
    • clUseServer если запрос имеет некоторую агрегацию (сумму, группировку, подсчет)
  • Set TAdoDataSet.CursorType согласно запросу:
    • ctForwardOnly для отчетов, где вам не нужно прокручивать через набор данных - работает только с clUseServer
    • ctStatic для GUI. Это только режим, который работает с clUseClient
  • установить TAdoDataSet.LockType согласно запросу:
    • ltReadOnly для каждого набора данных, который не используется для редактирования (таблицы, отчеты)
    • ltOptimistic когда записи отправляются в базу данных сразу после изменения (например, пользователь редактирует данные в форме)
    • ltBatchOptimistic при изменении большого количества записей. Это для ситуаций, когда вы получаете количество записей, затем выполнить некоторую обработку на них, а затем отправить обновления в базу данных в пакетном режиме. Это работает лучше всего в сочетании с clUseClient и ctStatic.
  • по моему опыту, поставщик Microsoft OLEDB для Oracle работал лучше, чем поставщик Oracle OleDb. Ты должен это проверить.
    Edit: Проверьте комментарий Фабрицио о возможных проблемах blob.
  • заменить TAdoQUery С TAdoDataSet. TAdoQuery был создан для преобразования приложений из BDE в ADO, но Borland/Codegear recomendation должен был использовать TAdoDataSet
  • перепроверьте строку подключения Oracle, чтобы убедиться, что у вас нет задержки сети. Как долго длится подключение к Oracle? Как долго TnsPing?

я нашел проблемы с производительностью с ADOExpress лет назад:

Примечание:ADOExpress. Это были просто обертки предметов вокруг COM-объекты объектов данных ActiveX (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 x быстрее
  • используя 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% быстрее!--29-->:

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;

используйте коллекцию полей, чтобы сделать поиск значений на 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;

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

Если вы обрабатываете много TQuery, без использования компонентов DB, у нас есть выделенный псевдо-класс для использования прямого 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 глючат для обработки BLOBs: версия Microsoft просто не обрабатывайте их, и версия Oracle будет случайным образом возвращает null для 1/4 строк...

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