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;
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ć. - 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?
Edit: Sprawdź komentarz Fabricio o możliwych problemach z blobem.
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:
- ADO vs adoexpress time trials. Niedobrze dla ADOExpress (6/7/2005)
- ADO vs ADO Express Time Trials (redux) (12/30/2007)
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
naTADOQuery
przed wywołaniemOpen
Odkryłem
- użyj
Query.DisableControls
, aby wykonać każde połączenie.Next
50x szybciej - użyj
Query.Recordset.Fields.Items['columnName'].Value
zamiastQuery.FieldByName('columnName')
, aby każda wartość była 2,7 x szybsza -
Za pomocą
TADODataSet
(TADOQuery
) tworzy brak różnicyLoop 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;
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.
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