2015-11-27 13 views
9

Ich möchte die gesamte Tabelle aus einer MS Access-Datei lesen und ich versuche es so schnell wie möglich zu tun. Beim Testen einer großen Stichprobe stellte ich fest, dass der Schleifenzähler schneller wird, wenn er die obersten Datensätze im Vergleich zu den letzten Datensätzen der Tabelle liest. Hier ist ein Beispielcode, dies zeigt:Warum wird das Scrollen durch ADOTable langsamer und langsamer?

procedure TForm1.Button1Click(Sender: TObject); 
const 
    MaxRecords = 40000; 
    Step = 5000; 
var 
    I, J: Integer; 
    Table: TADOTable; 
    T: Cardinal; 
    Ts: TCardinalDynArray; 
begin 
    Table := TADOTable.Create(nil); 
    Table.ConnectionString := 
    'Provider=Microsoft.ACE.OLEDB.12.0;'+ 
    'Data Source=BigMDB.accdb;'+ 
    'Mode=Read|Share Deny Read|Share Deny Write;'+ 
    'Persist Security Info=False'; 
    Table.TableName := 'Table1'; 
    Table.Open; 

    J := 0; 
    SetLength(Ts, MaxRecords div Step); 
    T := GetTickCount; 
    for I := 1 to MaxRecords do 
    begin 
    Table.Next; 
    if ((I mod Step) = 0) then 
    begin 
     T := GetTickCount - T; 
     Ts[J] := T; 
     Inc(J); 
     T := GetTickCount; 
    end; 
    end; 
    Table.Free; 

// Chart1.SeriesList[0].Clear; 
// for I := 0 to Length(Ts) - 1 do 
// begin 
// Chart1.SeriesList[0].Add(Ts[I]/1000, Format(
//  'Records: %s %d-%d %s Duration:%f s', 
//  [#13, I * Step, (I + 1)*Step, #13, Ts[I]/1000])); 
// end; 
end; 

Und das Ergebnis auf meinem PC: enter image description here

Die Tabelle hat zwei String-Felder, ein Doppel und ein ganze Zahl ist. Es hat weder Primärschlüssel noch Indexfeld. Warum passiert das und wie kann ich es verhindern?

+0

Nein. Ich erstelle die Steuerung programmgesteuert, es gibt nichts mehr als das, was Sie im Beispielcode sehen können. – saastn

+0

Ist die For-Schleife nicht um eins deaktiviert? Wie auch immer, sind Sie überrascht, dass wenn Sie viele Datensätze lesen, es eine Menge Speicherzuweisungen erfordert und dass diese länger dauern, je mehr Speicher zugewiesen wird? – MartynA

+0

@MartynA Sie haben Recht mit der Schleife. Aber ich kann nicht sagen, dass es Speicherzuweisung ist, die es langsamer macht. Scheint, als ob es alle Datensätze in 'Table.Open 'abruft, Task-Manager zeigt nach dem Ausführen dieser Zeile keine Speicherzuweisung an. – saastn

Antwort

17

Ich kann Ihre Ergebnisse mit einer AdoQuery mit einem MS Sql Server-Dataset ähnlicher Größe wie Ihres reproduzieren.

Nachdem ich ein bisschen Linienprofilierung gemacht habe, denke ich, dass ich die Antwort darauf gefunden habe, und es ist etwas kontraintuitiv. Ich bin sicher, dass jeder, der DB-Programmierung in Delphi macht, daran interessiert ist, dass das Durchlaufen eines Datensatzes viel schneller ist, wenn Sie die Schleife durch Aufrufe von Disable/EnableControls umgeben. Aber wer würde das tun, wenn keine datenbanksensitiven Steuerelemente an den Datensatz angehängt sind?

Nun, es stellt sich heraus, dass in Ihrer Situation, obwohl es keine DB-fähigen Steuerelemente gibt, die Geschwindigkeit enorm steigt, wenn Sie Disable/EnableControls unabhängig verwenden.

Der Grund dafür ist, dass TCustomADODataSet.InternalGetRecord in AdoDB.Pas diese enthält:

 if ControlsDisabled then 
     RecordNumber := -2 else 
     RecordNumber := Recordset.AbsolutePosition; 

und nach meiner Linie Profiler, die während nicht AdoQuery1.Next Schleife verbringt 98,8% seiner Zeit AdoQuery1.Eof zu tun Ausführung die Zuordnung

 RecordNumber := Recordset.AbsolutePosition; 

! Die Berechnung von Recordset.AbsolutePosition ist natürlich auf der "falschen Seite" der Recordset-Schnittstelle versteckt, aber die Tatsache, dass die Zeit, um sie aufzurufen, scheinbar zunimmt, je weiter Sie in das Recordset gehen, macht es vernünftig zu spekulieren, dass es berechnet wird indem Sie vom Anfang der Daten des Datensatzes aus zählen.

Natürlich ControlsDisabled gibt true zurück, wenn DisableControls wurde durch einen Aufruf EnableControls genannt und kann nicht rückgängig gemacht. Also, wiederhole es mit der Schleife, die von Disable/EnableControls umgeben ist, und hoffentlich bekommst du ein ähnliches Ergebnis wie meins. Es sieht so aus, als hätten Sie Recht, dass die Verlangsamung nicht mit Speicherzuweisungen zusammenhängt.

Verwendung des folgenden Codes:

procedure TForm1.btnLoopClick(Sender: TObject); 
var 
    I: Integer; 
    T: Integer; 
    Step : Integer; 
begin 
    Memo1.Lines.BeginUpdate; 
    I := 0; 
    Step := 4000; 
    if cbDisableControls.Checked then 
    AdoQuery1.DisableControls; 
    T := GetTickCount; 
{.$define UseRecordSet} 
{$ifdef UseRecordSet} 
    while not AdoQuery1.Recordset.Eof do begin 
    AdoQuery1.Recordset.MoveNext; 
    Inc(I); 
    if I mod Step = 0 then begin 
     T := GetTickCount - T; 
     Memo1.Lines.Add(IntToStr(I) + ':' + IntToStr(T)); 
     T := GetTickCount; 
    end; 
    end; 
{$else} 
    while not AdoQuery1.Eof do begin 
    AdoQuery1.Next; 
    Inc(I); 
    if I mod Step = 0 then begin 
     T := GetTickCount - T; 
     Memo1.Lines.Add(IntToStr(I) + ':' + IntToStr(T)); 
     T := GetTickCount; 
    end; 
    end; 
{$endif} 
    if cbDisableControls.Checked then 
    AdoQuery1.EnableControls; 
    Memo1.Lines.EndUpdate; 
end; 

ich die folgenden Ergebnisse erhalten (mit DisableControls nicht außer genannt, wo angegeben):

Using CursorLocation = clUseClient 

AdoQuery.Next AdoQuery.RecordSet AdoQuery.Next 
       .MoveNext    + DisableControls 

4000:157   4000:16    4000:15 
8000:453   8000:16    8000:15 
12000:687   12000:0    12000:32 
16000:969   16000:15   16000:31 
20000:1250   20000:16   20000:31 
24000:1500   24000:0    24000:16 
28000:1703   28000:15   28000:31 
32000:1891   32000:16   32000:31 
36000:2187   36000:16   36000:16 
40000:2438   40000:0    40000:15 
44000:2703   44000:15   44000:31 
48000:3203   48000:16   48000:32 

======================================= 

Using CursorLocation = clUseServer 

AdoQuery.Next AdoQuery.RecordSet AdoQuery.Next 
       .MoveNext    + DisableControls 

4000:1031   4000:454   4000:563 
8000:1016   8000:468   8000:562 
12000:1047   12000:469   12000:500 
16000:1234   16000:484   16000:532 
20000:1047   20000:454   20000:546 
24000:1063   24000:484   24000:547 
28000:984   28000:531   28000:563 
32000:906   32000:485   32000:500 
36000:1016   36000:531   36000:578 
40000:1000   40000:547   40000:500 
44000:968   44000:406   44000:562 
48000:1016   48000:375   48000:547 

Aufruf AdoQuery1.Recordset.MoveNext Anrufe direkt in die MDac/ADO-Schicht von natürlich, während AdoQuery1.Next den gesamten Overhead des Standardmodells TDataSet umfasst. Wie Serge Kraikov sagte, macht die Änderung der CursorLocation einen Unterschied und zeigt nicht die Verlangsamung, die wir beobachtet haben, obwohl sie offensichtlich wesentlich langsamer ist als clUseClient zu verwenden und DisableControls aufzurufen. Ich nehme an, es hängt genau davon ab, was Sie versuchen zu tun, ob Sie die zusätzliche Geschwindigkeit der Verwendung von clUseClient mit RecordSet.MoveNext nutzen können.

+0

Vielen Dank, 'DisableControls' hat für mich funktioniert. Im Gegensatz zu Ihren Ergebnissen ist 'clUseServer' hier nicht langsamer als' clUseClient'. Obwohl das Dataset keine Datensätze zurückgibt, nachdem 'CursorLocation' auf 'clUseServer' gesetzt wurde, außer ich setze' LockType' auf 'ltReadOnly'. – saastn

+0

@MartynA Aus Neugier, welchen Profiler hast du benutzt? –

+0

@ ChristianHolmJørgensen: Ich habe den Line-Profiler der Nexus Quality Suite (www.nexusdb.com) verwendet, der eine Reinkarnation des alten Turbopower-Produkts mit ähnlichem Namen ist. – MartynA

1

Wenn Sie eine Tabelle öffnen, erstellt ADO-Dataset intern spezielle Datenstrukturen zum Navigieren im Datensatz vorwärts/rückwärts - "Datensatz CURSOR". Während der Navigation speichert ADO die Liste der bereits besuchten Datensätze, um eine bidirektionale Navigation bereitzustellen.
ADO-Cursor-Code verwendet O-Algorithmus (O2) mit quadratischer Zeit zum Speichern dieser Liste.
Aber es gibt Abhilfe - Einsatz serverseitigen Cursor:

Table.CursorLocation := clUseServer; 

ich Ihren Code unter Verwendung dieses Update getestet und linear bekommen Zeit holen - jede nächste Stück Aufzeichnungen nimmt die gleiche Zeit wie in früheren abgerufen werden.

PS Einige andere Datenzugriffsbibliotheken stellen spezielle "unidirektionale" Datenmengen zur Verfügung - diese Datenmengen können nur vorwärts gehen und speichern nicht einmal bereits durchlaufene Datensätze - Sie erhalten konstanten Speicherverbrauch und lineare Abrufzeit.

1

DAO ist in Access integriert und (IMHO) ist in der Regel schneller. Ob Sie wechseln oder nicht, verwenden Sie die GetRows-Methode. Sowohl DAO als auch ADO unterstützen es. Es gibt keine Schleife. Sie können das gesamte Recordset in ein Array mit ein paar Zeilen Code ablegen. Air code: yourrecordset.MoveLast yourrecordset.MoveFirst yourarray = yourrecordset.GetRows(yourrecordset.RecordCount)

+0

Vielleicht, aber das OP fragt nach Delphi-Code, und in Delphi arbeiten Sie normalerweise nicht mit Arrays von db-Datensätzen. – MartynA

+0

Danke MartynA. Ich weiß nichts über Delphi, dachte aber, dass es ähnliche Strukturen wie andere Sprachen haben könnte. – AVG

+0

Nun, es * kann * sie haben (nur indem man ein Array eines geeigneten Typs deklariert), aber es ist einfach nicht die "Delphi" -Methode, Dinge zu tun. Der Punkt ist, in Delphi sind alle unterstützten Dataset-Typen Nachkommen eines Vorfahren (TDataset), der ein verallgemeinertes Modell eines Datasets mit beweglichem logischen Cursor enthält. Und alle db-aware-Steuerelemente sind so konzipiert, dass sie mit diesem Modell und nicht mit Arrays interagieren. Eine Konsequenz ist, dass alle db-fähigen Steuerelemente mit allen unterstützten TDataset-Nachkommen funktionieren. – MartynA

Verwandte Themen