Bonjour,
Au sein de mon nouvel employeur, j'ai la mission de préparer à la transition vers le provider "MSOLEDBSQL.1" au lieu de "SQLOLEDB.1" qui est déprécié
Certaines applications utilisent "SQLNCLI11.1" et ne devrait pas changer de Provider
Durant mes essais
Delphi Seatle, 64 Bits, SQL Server 10.50.1600.1 (Distant sur un Windows Server 2008 R2 64bits)
TADOQuery
J'ai constaté que le mode CursorLocation à clUseClient avait une courbe de parcours "While not EOF do Next" absolument catastrophique
C'est le mode par défaut, je n'ai pas pas vraiment utilisé ADO (hormis trois mois plutôt pénible)
Plus l'on récupère d'enregistrement plus c'est lent, pas linéairement mais exponentiellement![]()
![]()
Au départ la boucle ne faisait rien d'autre que le Next
J'ai ajouté une progession et on voit nettement que cela ralenti au fur et à mesure comme si une opération était faite sur l'ensemble du DataSet devenant de plus en plus longue à chaque Next (réallocation mémoire ? un tri local inutile ? rebalayage interne)
La courbe théorique est en fait basé sur le temps à 10000, c'est déjà un temps pessimiste
Face à ce constat, j'ai tenté Prepared à True, j'ai tenté différent type de CursorType malgré cette note dans la doc de Delphi : "Quand le propriété CursorLocation du composant ensemble de données ADO a la valeur clUseClient, seule la valeur ctStatic est gérée" confirmé par MSDN : "Seule la valeur adOpenStatic est pris en charge si le CursorLocation est définie sur adUseClient" malgré de nombreux exemples qui tentent des combinaisons impossibles
J'ai essayé CacheSize à différente valeur, je n'ai pas constaté de changement
J'ai souvenir PacketRecords mais du FetchRows de ODAC qui avait un vrai impact
Rien, tout ça ne change rien, c'est le temps de Fetch qui est couteux
Même pas de lecture des données, juste avancé vers l'avant !
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11 GetBeginTime(BeginTime); FetchCount := 0; while not FQuery.Eof do begin Inc(FetchCount); if not ByteBool(FetchCount) then DoProgress(FetchCount, System.Math.Max(ASQLCycleQuery.ExpectedRowCount, FQuery.RecordCount)); FQuery.Next(); end; DelayFetch.Inc(ElapsedTime(BeginTime));
J'ai tenté donc CursorLocation à clUseServer
Et la miracle, c'est 2x fois plus rapide que même la courbe "théoric", c'est une progression linéaire entre RecordCount et temps, ce qui est bien plus cohérent
Et c'est 2x plus rapide en faisant cette fois, un export dans un fichier, car sans la partie export qui représente 80% du temps
C'est donc 10x plus rapide pour le simple fetch
Des temps donc totalement incomparable, le jour et la nuit
C'est ma première question : Quelqu'un a-t-il déjà remarqué ce phénomène où plus l'on va loin dans la DataSet, plus il éprouve de difficultés à avancer ?
Avec les paramètres par défaut clUseClient, ctKeyset et ltOptimistic avec un SELECT de 10000 à 90000 lignes
Voir le zip avec SQL et Contexte d'execution, mon programme de test utilise des jeux de scénarios en fichier (télécharger MDR500.zip )
Je pourrais ainsi intégrer toute sorte de requête venant du logiciel qu'il faut particulièrement étudié
Je n'ai pas encore joué sur LockType, ce que je vais faire aujourd'hui
Delphi Seatle, 64 Bits, SQL Server 10.50.1600.1 (Distant sur un Windows Server 2008 R2 64bits)
Cette fois en OLE avec ADODB.Command et ADODB.Recordset
Je voulais donc constater si ce problème venait de ADO
J'ai donc repris l'essai en OLE sur ADODB
J'ai inclu ADODB dans mon programme de test, j'ai tout reproduit y compris le passage de paramètre et de macro
Tout fontionne parfaitement
Le temps de Fetch et d'export est un peu plus long (5x) qu'en TADOQ clUseServer, je mets ça sur la complexité de l'exécution d'un code en Late Binding (EDIT confirmé)
Aucune différence entre adUseServer et adUseClient, les temps sont aussi proportionnnels à RecordCount selon une courbe linéaire
Pour l'export complet des 250000 lignes, c'est 21 secondes pour ADODB contre presque 4 minutes pour TADOQuery en clUseClient (versus 5 secondes en clUseServer)
EDIT : 10 secondes pour ADODB en réduisant stockant les "rec.Fields.Item(I)" dans un array of Variant réduisant le code généré en LateBinding
voici le premier code vite fait
adUseServer et Prepared sont incompatible
J'ai vu des codes de ADO sur MySQL qui combine ces options c'est que SQL Server refuse le mélange, je ferais d'autres essais en variant aussi de CursorType
C'est ma seconde question : Quelqu'un a-t-il déjà remarqué ce phénomène d'erreur en utilisant adUseServer et Prepared sur SQL Server
CursorLocation clUseServer ne peut pas être Prepared
sinon cela déclenche une EOleException avec le message 'Les propriétés requises ne peuvent être prises en charge' / 'The requested properties cannot be supported'.
Aussi bien TADOQuery que ADODB en OLE
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126 unit TestADODB_MainForm; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, System.Types, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Déclarations privées } public { Déclarations publiques } end; var Form1: TForm1; implementation uses System.Win.ComObj, Winapi.ADOInt, System.StrUtils; {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); const SQL = ' SELECT TOP 10000 M.*, D.*, R.R_NUMBER, R.R_TEXT ' + ' FROM EZV_ADMIN.Z_SP_MASTER_100 M ' + ' INNER JOIN EZV_ADMIN.Z_SP_RELATION_100 R ON R.M_ID = M.M_ID ' + ' INNER JOIN EZV_ADMIN.Z_SP_DETAIL_100 D ON D.D_ID = R.D_ID '; const FORMAT_MILLI_SECONDE = '%.2d:%.2d:%.2d:%.3d'; FORMAT_MICRO_SECONDE = '%.2dh%.2dm%.2ds%.3dms%.3dµs'; //------------------------------------------------------------------------------ (*class*) function (*TSLTTimeDiagnostics.TLaps.*)FormatElapsedTime(ADelay: Int64; const AFormat: string = FORMAT_MILLI_SECONDE): string; var Hour, Min, Sec, MilliSecond: Int64; begin Hour := ADelay div 3600000000; ADelay := ADelay mod 3600000000; Min := ADelay div 60000000; ADelay := ADelay mod 60000000; Sec := ADelay div 1000000; ADelay := ADelay mod 1000000; MilliSecond := ADelay div 1000; ADelay := ADelay mod 1000; Result := Format(AFormat, [Hour, Min, Sec, MilliSecond, ADelay]); end; procedure OleTest(); var cnx, cmd, rec: Variant; BeginTime, EndTime, TickPerSec: Int64; FS: TFileStream; Header, Line: string; BOM, Buffer: TBytes; I, K: Integer; begin FS := TFileStream.Create('ADODB.Export.txt', fmCreate); try BOM := TEncoding.UTF8.GetPreamble(); FS.WriteBuffer(BOM, Length(BOM)); QueryPerformanceCounter(BeginTime); cnx := System.Win.ComObj.CreateOleObject('ADODB.Connection'); cnx.Open('Provider=MSOLEDBSQL.1;Persist Security Info=False;User ID=...;Password=...;Initial Catalog=...;Data Source=...'); try cmd := System.Win.ComObj.CreateOleObject('ADODB.Command'); cmd.ActiveConnection := cnx; cmd.CommandText := SQL; cmd.CommandType := Winapi.ADOInt.adCmdText; cmd.Prepared := True; // Incompatible avec rec.CursorLocation := Winapi.ADOInt.adUseServer; rec := System.Win.ComObj.CreateOleObject('ADODB.Recordset'); rec.CursorLocation := Winapi.ADOInt.adUseServer; // Incompatible avec cmd.Prepared := True; rec.CursorType := Winapi.ADOInt.adOpenForwardOnly; rec.LockType := Winapi.ADOInt.adLockOptimistic; rec.Open(cmd); try K := rec.Fields.Count - 1; for I := 0 to K do Header := Header + rec.Fields.Item(I).Name + IfThen(I < K, Tabulator, sLineBreak); Buffer := TEncoding.UTF8.GetBytes(Header); FS.WriteBuffer(Buffer, Length(Buffer)); while not rec.EOF do begin Line := ''; for I := 0 to K do Line := Line + VarToStr(rec.Fields.Item(I).Value) + IfThen(I < K, Tabulator, sLineBreak); Buffer := TEncoding.UTF8.GetBytes(Line); FS.WriteBuffer(Buffer, Length(Buffer)); rec.MoveNext; end; finally rec.Close; end; finally cnx.Close; end; finally FS.Free(); end; QueryPerformanceCounter(EndTime); QueryPerformanceFrequency(TickPerSec); Button1.Caption := FormatElapsedTime(Round((EndTime - BeginTime) / TickPerSec * 1000000)); end; begin OleTest(); end; end.
Partager