BDE vs ADO w Delphi

Prosimy zwrócić uwagę na poniższą edycję, aby uzyskać więcej informacji i możliwe rozwiązanie

Ostatnio zmodyfikowaliśmy dużą aplikację Delphi, aby używała połączeń i zapytań ADO zamiast połączeń i zapytań BDE. Od tej zmiany wydajność stała się straszna.

Sprofilowałem aplikację i wąskie gardło wydaje się być na rzeczywistym wywołaniu do TADOQuery.Open. Innymi słowy, z punktu widzenia kodu niewiele mogę zrobić, aby to poprawić, poza restrukturyzacja aplikacji tak, aby faktycznie korzystała z bazy danych mniej.

Czy ktoś ma propozycje jak poprawić wydajność aplikacji Delphi podłączonej do ADO? Wypróbowałem obie propozycje podane tutaj , praktycznie bez wpływu.

Aby dać wyobrażenie o różnicy wydajności, porównałem tę samą dużą operację:

  • W BDE: 11 sekund

  • Under ADO: 73 seconds

  • Pod ADO po zmianach, o których mowa w tym artykule: 72 sekundy

Używamy zaplecza Oracle w środowisku klient-serwer. Każda z lokalnych maszyn utrzymuje osobne połączenie z bazą danych.

Dla przypomnienia, łańcuch połączeń wygląda tak:

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

Aby odpowiedzieć na pytania zadane przez zendara:

Używam Delphi 2007 Na Windows Vista i XP.

[10]}zapleczem jest baza danych Oracle 10g.

Jak wskazano w connection string, używamy sterownika OraOLEDB.

Wersja MDAC na moim benchmarku to 6.0.

Edit:

Pod BDE mieliśmy dużo kodu, który wyglądał tak:

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

Ale okazało się, że wywołanie Sql.Add jest w rzeczywistości bardzo drogie pod ADO, ponieważ zdarzenie QueryChanged jest wywoływane za każdym razem, gdy zmienisz CommandText. Więc zastąpienie powyższego było znacznie szybsze:

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

Jeszcze lepiej, możesz skopiować TADOQuery z ADODB.pas, zmień jego nazwę pod nową nazwą i wydaj Zdarzenie QueryChanged, które z tego co wiem, w ogóle nie robi nic użytecznego. Następnie użyj nowej, zmodyfikowanej wersji TADOQuery, zamiast rodzimej.

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;
Author: bluish, 2008-12-15

3 answers

Nie wiem jak Delphi 2007, ale zrobiłem to samo z Delphi 7 i Oracle 8.

Oto rzeczy, które zrobiłem:

  • Zestaw TAdoDataSet.CursorLocation zgodnie z zapytaniem:
    • clUseClient jeśli zapytanie pobiera rekordy dla GUI i zapytanie jest stosunkowo "proste" - nie ma grupowania ani sumy
    • clUseServer jeśli zapytanie ma jakiś rodzaj agregacji (suma, grupowanie, liczenie)
  • Zestaw TAdoDataSet.CursorType zgodnie z zapytaniem:
    • ctForwardOnly W przypadku raportów, w których nie trzeba przewijać zbioru danych-działa tylko z clUseServer
    • ctStatic dla GUI. Jest to tylko tryb, który działa z clUseClient
  • Zestaw TAdoDataSet.Loctype zgodnie z zapytaniem:
    • ltReadOnly dla każdego zbioru danych, który nie jest używany do edycji (siatki, raporty)
    • ltOptimistic gdy rekordy są wysyłane do bazy danych natychmiast po zmianie (np. edytowanie danych przez użytkownika w formularzu)
    • ltBatchOptimistic przy zmianie dużej liczby rekordów. Dotyczy to sytuacji, w których pobierasz liczbę rekordów, następnie przetwarzasz je, a następnie wysyłasz aktualizacje do bazy danych w partiach. Działa to najlepiej w połączeniu z clUseClient i ctStatic.
  • Z mojego doświadczenia wynika, że Microsoft OLEDB provider for Oracle działał lepiej niż Oracle OleDb provider. Powinieneś to sprawdzić.
    Edit: Sprawdź komentarz Fabricio o możliwych problemach z blobem.
  • Zastąp TAdoQUery z TAdoDataSet. TAdoQuery został stworzony do konwersji aplikacji z BDE do ADO, ale rekomendacją Borland / Codegear było użycie TAdoDataSet [14]}
  • Sprawdź ponownie łańcuch połączeń Oracle, aby upewnić się, że nie ma opóźnienia sieciowego. Jak długo trwa połączenie z Wyrocznia? Jak długo trwa TnsPing?
 14
Author: zendar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-21 11:19:15

Znalazłem problemy z wydajnością z ADOExpress lat temu:

Uwaga: zanim ADO stał się standardową częścią Delphi, Borland sprzedawał go jako dodatek o nazwie ADOExpress . To było po prostu object wrappers wokół Microsoft ActiveX Data Objects (ADO) COM obiektów.

Przetestowałem trzy scenariusze

    W przeciwieństwie do innych systemów Microsoft Windows, Microsoft Windows nie jest w stanie korzystać z ADO.]} W 2009 roku firma Borland rozpoczęła działalność w branży papierniczej.]}
  • podanie .DisableControls na TADOQuery przed wywołaniem Open

Odkryłem

  • użyj Query.DisableControls, aby wykonać każde połączenie .Next 50x szybciej
  • użyj Query.Recordset.Fields.Items['columnName'].Value zamiast Query.FieldByName('columnName'), aby każda wartość była 2,7 x szybsza
  • Za pomocą TADODataSet (TADOQuery) tworzy brak różnicy

                                    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 
    

Uwaga: te wartości są przeznaczone do zapętlenia 20 881 wierszy i wyszukania wartości 21 kolumn.

Bazowy Zły Kod:

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

Użyj kontrolek DisableControls, aby przyspieszyć pętlę o 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;

Użyj kolekcji pól, aby wyszukiwać wartości szybciej o 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;

Ponieważ jest to dość powszechny problem, stworzyliśmy metodę pomocniczą, aby rozwiązać ten problem:]}
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;
 4
Author: Ian Boyd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-16 15:11:05

Aby uzyskać najlepszą wydajność, należy zapoznać się z naszym Open Source direct access to Oracle.

Jeśli przetwarzasz wiele TQuery, bez użycia komponentów DB, mamy dedykowaną pseudo-klasę do używania bezpośredniego połączenia OCI, jako taką:

 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;

I dodałem jakiś unikalny dostęp za pomocą wariantu późnego wiązania, aby napisać bezpośredni kod jako taki:

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;

Zauważ, że wszyscy dostawcy OleDB mają błędy w obsłudze Blobów: Wersja Microsoftu po prostu ich nie obsługuje, a Wersja Oracle zwróci losowo null dla 1/4 wierszy...

W prawdziwej bazie danych odkryłem, że nasze bezpośrednie klasy OCI są 2 do 5 razy szybsze niż dostawca OleDB, bez konieczności instalowania tego dostawcy. Możesz nawet użyć Oracle Instant Client dostarczonego przez Oracle, który pozwala na uruchamianie aplikacji bez instalowania standardowego (ogromnego) klienta Oracle lub posiadania ORACLE_HOME. Po prostu dostarcz pliki dll w tym samym katalogu, co Twoja aplikacja, i to zadziała.

 0
Author: Arnaud Bouchez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:38