Einzelnen Beitrag anzeigen

Bbommel

Registriert seit: 27. Jun 2007
Ort: Köln
655 Beiträge
 
Delphi 12 Athens
 
#1

Verarbeiten von großer Ergebnismenge wird immer langsamer

  Alt 6. Feb 2009, 13:23
Datenbank: MSSQL • Version: 2005 • Zugriff über: ADO
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.
  Mit Zitat antworten Zitat