BDE против ADO в Delphi

Обратите внимание на правку ниже, чтобы получить дополнительную информацию и возможное решение

Недавно мы изменили большое приложение 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.

Изменить:

Под 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;

person JosephStyons    schedule 15.12.2008    source источник
comment
Для чего нужен OPTION = 35?   -  person zendar    schedule 16.12.2008
comment
Я не знаю; он был автоматически сгенерирован построителем строки подключения, и теперь мне самому любопытно ....   -  person JosephStyons    schedule 02.03.2009
comment
вообще не делает ничего полезного. На самом деле, цель состоит в том, чтобы проанализировать скрипт и найти параметры, а также заполнить структуру параметров.   -  person Jerry Dodge    schedule 10.05.2017
comment
@JerryDodge, хорошо, это полезно знать, и это объясняет, почему это заняло много времени. Его удаление не сильно повлияло на преобразование ADO, хотя моя память на таком расстоянии нечеткая. Я скучаю по Дельфам ...   -  person JosephStyons    schedule 10.05.2017


Ответы (3)


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

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

  • Set TAdoDataSet.CursorLocation according to query:
    • clUseClient if query fetches records for GUI and query is relatively "simple" - no grouping or sum
    • clUseServer, если в запросе используется какая-то агрегация (сумма, группировка, подсчет)
  • Set TAdoDataSet.CursorType according to query:
    • ctForwardOnly for reports where you don't need scroll back through dataset - works only with clUseServer
    • ctStatic для графического интерфейса. Это единственный режим, который работает с clUseClient
  • Set TAdoDataSet.LockType according to query:
    • ltReadOnly for every dataset that is not used for editing (grids, reports)
    • ltOptimistic, когда записи публикуются в базе данных сразу после изменения (например, пользователь редактирует данные в форме)
    • ltBatchOptimistic при изменении большого количества записей. Это для ситуаций, когда вы выбираете количество записей, затем обрабатываете их, а затем отправляете обновления в базу данных в пакетном режиме. Лучше всего это работает в сочетании с clUseClient и ctStatic.
  • По моему опыту, поставщик Microsoft OLEDB для Oracle работал лучше, чем поставщик Oracle OleDb. Вы должны это проверить.
    Изменить: проверьте комментарий Fabricio о возможных проблемах с blob.
  • Замените TAdoQUery на TAdoDataSet. TAdoQuery был создан для преобразования приложений из BDE в ADO, но Borland / Codegear рекомендовали использовать TAdoDataSet.
  • Еще раз проверьте строку подключения Oracle, чтобы убедиться, что у вас нет задержки в сети. Как долго длится подключение к Oracle? Как долго длится TnsPing?
person zendar    schedule 15.12.2008
comment
Поставщик Microsoft OLEDB для Oracle работает хорошо, если в таблице НЕТ полей больших двоичных объектов. Иначе просто взорвется за исключением. - person Fabricio Araujo; 05.02.2009
comment
Фабрицио, вы можете сказать, какая версия Oracle и провайдер MS OleDB так себя ведет? - person zendar; 05.02.2009
comment
OLEDB: тот, что идет с WinXP Oracle: если моя память не выйдет из строя, Oracle 10 - person Fabricio Araujo; 22.04.2010
comment
Что касается больших двоичных объектов, то поставщик Oracle OleDB, похоже, не возвращает все строки больших двоичных объектов (только около 25%) ... См. orafaq.com/forum/t/38121/0 и stackoverflow.com/questions/6147274/ - person Arnaud Bouchez; 11.07.2011
comment
FWIW Из всех этих предложений я обнаружил, что установка TADODateSet.LockType на ltReadOnly ускорила мои запросы примерно в 10 раз. Это было в базе данных Firebird. - person awmross; 11.04.2012

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

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

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

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

Я узнал

  • используйте Query.DisableControls, чтобы делать каждый вызов .Next в 50 раз быстрее
  • используйте 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;

Используйте коллекцию полей, чтобы ускорить поиск значений на 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;
person Ian Boyd    schedule 16.07.2011
comment
Когда снова включить элементы управления? Я всегда использую блок try ... finally при отключении элементов управления, чтобы убедиться, что элементы управления (сетка данных) повторно включены, что бы ни случилось. - person No'am Newman; 08.02.2012
comment
@ No'amNewman Мы никогда, никогда, никогда не используем элементы управления связью данных. Всегда. Все делать самому намного быстрее и проще. - person Ian Boyd; 08.02.2012
comment
Придется с тобой не согласиться. Я написал несколько несвязанных диалогов; некоторые из них было легко написать, но некоторые были очень сложными, что увеличивало шансы сделать ошибку или что-то упустить. - person No'am Newman; 09.02.2012

Для лучшей производительности обратите внимание на наш Прямой доступ к 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 будет случайным образом возвращает null для 1/4 строк ...

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

person Arnaud Bouchez    schedule 11.07.2011
comment
Я только что добавил пакетное добавление, то есть привязку массива. Теперь производительность офигенная и для прошивки! 55 000 строк в секунду для вставки и 90 000 строк в секунду для чтения. См. blog.synopse.info/post/2012/07/25 / Synopse-mORMot-benchmark - person Arnaud Bouchez; 02.08.2012