Hallo zusammen,
ich habe ein ähnliches Problem, wie es jemand auch schon mal
hier beschrieben hat - leider gabs damals keine weitere Rückmeldung des Benutzers und somit auch keine Lösung.
Also, das ganze mal von mir: Ich habe eine Datenbank-Abfrage, bei der es sei kann, dass die Ergebnismenge recht groß wird. In einem Beispiel habe ich hier >500.000 Datensätze, die vom Server zurückkommen. Das Problem ist dann, dass es recht schnell geht, bis die Antwort vom Server zurückkommt, also
Query.Open kehrt nach ca. 17 Sekunden mit seinen 500.000 Datensätzen zurück. Danach allerdings sollen diese Datensätze in einer Schleife verarbeitet werden und eben diese Schleife wird immer langsamer.
Ein bisschen Code kann das Problem sicher veranschaulichen und ihr müsst weniger raten, wo der Fehler liegen könnte.
Zunächst mal das Initialisieren und Aufbauen der Verbindung zum Server - spannend ist ja hierbei vielleicht der ConnectionString und der CursorType und LockType:
Delphi-Quellcode:
procedure TmyThread.InitDB;
var
tempStr:
string;
begin
currConnection.Provider:='
SQLOLEDB.1';
currConnection.LoginPrompt:=configAuthType=vatAlwaysAsk;
currConnection.DefaultDatabase:=StringReplace(configDatabase,'
.','
_',[rfReplaceAll]);
// den Connection-String basteln
tempStr:='
Provider=SQLOLEDB.1;';
if configAuthType=vatWindows
then
tempStr:=tempStr+'
Integrated Security=SSPI;';
tempStr:=tempStr+'
Persist Security Info=False;';
tempStr:=tempStr+'
Initial Catalog='+configDatabase+'
;';
tempStr:=tempStr+'
Data Source='+configServername+'
;';
tempStr:=tempStr+'
Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Use Encryption for Data=False;Tag with column collation when possible=False;';
currConnection.ConnectionString:=tempStr;
// Verbindung mit der Datenbank initialisieren
currDBstate:=vdbConnected;
currErrorText:='
';
try
if (configAuthType=vatAlwaysAsk)
or (configAuthType=vatSavePw)
then
currConnection.Open(configUser,configPassword)
else
currConnection.Open;
except
on e:
Exception do begin
currDBstate:=vdbError;
currErrorText:=e.
Message;
end;
end;
if currDBstate=vdbConnected
then begin
currQuery.Connection:=currConnection;
currQuery.CursorType:=ctOpenForwardOnly;
currQuery.LockType:=ltReadOnly;
end;
end;
Was den ConnectionString angeht, so habe ich mir da mal was vom Delphi-Assistenten basteln lassen und für mich angepasst (also vor allem für die Konfigurierbarkeit).
Nun zum kritischen Teil, der mir Kopfzerbrechen bereitet:
Delphi-Quellcode:
function TmyThread.QueryDB: boolean;
// Die Funktion wird true, falls neue Daten in die Liste eingetragen wurden
var tempDate: TDateTime;
tempStrDate:
string;
oldId, oldFlag:
string;
oldCount: integer;
begin
currQuery.SQL.Text:=configSQLstatement.Text;
currDBstate:=vdbConnected;
currErrorText:='
';
lastQueryTime:=now;
try
currQuery.Open;
except
on e:
Exception do begin
currDBstate:=vdbError;
currErrorText:=e.
Message;
end;
end;
// wenn wir noch connected sind, war die Abfrage wohl erfolgreich und wir können sie
// verarbeiten
if currDBstate=vdbConnected
then begin
oldid:='
';
oldFlag:='
';
// das Füllen der Ergebnis-Struktur darf nur im kritischen Abschnitt stattfinden, da diese
// auch für den Hauptthread veröffentlicht wird
CriticalSec.Acquire;
while (
not currQuery.Eof)
and (
not Terminated)
do begin
// wir erzeugen für jeden Datensatz einen neuen Eintrag in die Ergebnisliste, es sei denn:
// 1. Es gibt keine ID, dann können wir damit nix anfangen
// 2. Der aktuelle und der letzte Datensatz haben das Kennzeichen B und die selbe ID - das wiederholt sich gerne mal, brauchen wir aber nicht jedes Mal zu übernehmen
if (
not currQuery.Fields[1].IsNull)
and ((currQuery.Fields[1].AsString<>oldid)
or (currQuery.Fields[10].AsString<>'
B')
or (currQuery.Fields[10].AsString<>oldFlag))
then begin
// hier würde dann eigentlich das Ergebnis in eine interne Datenstruktur übernommen,
// aber dazu kommt es meist gar nicht - siehe Erklärung unten.
end;
currQuery.Next;
end;
currQuery.Close;
Result:=messageList.Count<>oldCount;
CriticalSec.Release;
end else
Result:=false;
end;
Das eigentliche Erstellen der internen Datenobjekte habe ich mal weggelassen, weil in meinem Testfall die ersten 100.000 Datensätze die if-Abfrage schon nicht erfüllen und daher übersprungen werden, dennoch tritt mein Problem auf. Will heißen: Am Erstellen der internen Objekte kann es definitiv nicht liegen.
So, nun also noch mal das eigentlich Problem: Die while-Schleife wird immer langsamer durchlaufen. Wie gesagt, das currQuery.Open kehrt nach ca. 17 Sekunden zurück. Wenn ich dann nach etwa 5 Sekunden in der while-Schleife einen Breakpoint setze, hat sie schon 25.000 Datensätze durch. Setze ich nach weiteren 5 Sekunden einen, hat sie es gerade mal bis zum Datensatz 40.000 geschafft und nach einer Minute ist man dann beim 80.000ten Datensatz angekommen. Sind keine ganz exakten Werte, sollten aber das Problem beschreiben.
Ich verstehe das nicht. Zum Zeitpunkt dieser Verarbeitung liegen die Ergebnisse doch schon längst im Speicher und werden nicht mehr live von der Datenbank gezogen, oder? Jedenfalls kann ich die Datenbank auch stoppen und die Schleife läuft munter weiter.
Ach so, was euch vielleicht auch noch interessieren könnte: Das
SQL-Statement ist ein ganz einfaches SELECT * FROM view - ich frage in diesem Testfall also einfach alle Datensätze eines Views ab. Der View selbst wiederum ist ein bisschen komplizierter, ein SELECT mit JOINS über mehrere Tabellen, aber letztlich auch nicht weiter wild. Zumal, so wie ich das verstanden habe, da ja das Problem nicht liegen kann, denn der Server hat die Abfrage ja nach den besagten 17 Sekunden ausgeführt.
Irgendwelche Ideen, was hier falsch läuft?
Dankbar für jeden Tipp
Bommel
PS: Bitte keine Hinweise, dass ich die IDs, die NULL sind, doch direkt im
SQL-Befehl wegfiltern könnte - das geht an meinem Problem vorbei.