Подтвердить что ты не робот

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=*******';

Чтобы ответить на вопросы, заданные 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;
4b9b3361

Ответ 1

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

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

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

Ответ 2

Я нашел проблемы с работой ADOExpress несколько лет назад:

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

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

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

я обнаружил

  • используйте Query.DisableControls, чтобы сделать каждый вызов .Next быстрее на 50 раз
  • используйте Query.Recordset.Fields.Items['columnName'].Value, а не Query.FieldByName('columnName'), чтобы каждый поиск по индексу был быстрее 2.7x
  • с помощью 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 столбца.

Базовый код Bad Code:

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;

Ответ 3

Для достижения наилучшей производительности следует взглянуть на наш прямой доступ Open Source к 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;

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

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

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