Laden...

Geschwindigkeit des ODBCDataReader

Erstellt von lights vor 6 Jahren Letzter Beitrag vor 6 Jahren 1.799 Views
L
lights Themenstarter:in
3 Beiträge seit 2017
vor 6 Jahren
Geschwindigkeit des ODBCDataReader

Hallo zusammen,

ich habe ein Problem, zu dem ich nach einiger Recherche leider keine verwertbaren Informationen gefunden habe.
Ich programmiere im Moment in C# ein Tool, welches zunächst Daten aus einer ODBC-Datenbank in eine DataTable datDataME einliest. Dann wird diese DataTable Zeile für Zeile durchgegangen. Für jede Zeile muss die Summe von Energiewerten aus einer anderen Datenbank importiert und in die dafür vorgesehene Spalte der DataTable eingetragen werden.
Die Sache passiert in einer foreach-Schleife. Um die Performance zu erhöhen, wird die Datenbankverbindung nur einmal vor der foreach-Schleife geöffnet und danach erst wieder geschlossen. Die foreach-Schleife geht durch alle Zeilen der DataTable datDataME durch. Es werden zunächst zwei Zeitstempel aus der DataTable ausgelesen und in das richtige Datums/Zeitformat konvertiert.

Mittels dieser Zeiten wird der SQL-Befehl sqlEnergy gebastelt. Der Anfang dieses Strings ist immer gleich und wurde bereits vor der foreach-Schleife angelegt.
Als nächstes wird der ODBCCommand EnergyCmd aus dem SQL-Befehl sqlEnergy und der OdbcConnection conEnergyDB generiert.
Danach wird der OdbcDataReader EnergyReader ausgeführt.
Im if wird geprüft, ob ein Wert zurückgekommen ist oder nicht. Wenn ja, wird dieser in die Variable iEnergyBack geschrieben. Sonst wird die Variable iEnergyBack auf 0 gesetzt.

Dann wird der DataReader geschlossen und der Wert in die entsprechende Zeile der DataTable eingetragen. Dann beginnt die foreach-Schleife von vorne mit der nächsten Zeile.

Hier der relevante Codeausschnitt:


				OdbcConnection conEnergyDB = new OdbcConnection(connectionString: "Dsn=FEDB;driverid=25;fil=MS Access;maxbuffersize=2048;pagetimeout=5");
				string sqlEnergyPart = "SELECT SUM(aP) AS EnergySum FROM tp1 WHERE mID = 1";
				private int iEnergyBack;
				OdbcCommand EnergyCmd;
				OdbcDataReader EnergyReader;
				
				try
                {
                    conEnergyDB.Open();

                    foreach (DataRow rowME in datDataME.Rows)
                    {
                        if (rowME["Values"].ToString() == "0" && rowME["Start"].ToString() != "")
                        {
                            dtStart = DateTime.Parse(rowME["Start"].ToString()); //Lesen der Zeiten aus der ME-DB
                            dtEnd = DateTime.Parse(rowME["End"].ToString());
                            sStart = dtStart.ToString(@"MM\/dd\/yy HH:mm:ss"); //Konvertierung in das richtige Zeitformat
                            sEnd = dtEnd.ToString(@"MM\/dd\/yy HH:mm:ss");     //# werden im sql-String hinzugefuegt

                            sqlEnergy = sqlEnergyPart + " AND resourceID = " + rowME["ResourceID"] + " AND timestamp BETWEEN #" + sStart + "# AND #" + sEnd + "#;";

                            EnergyCmd = new OdbcCommand(sqlEnergy, conEnergyDB);
                            EnergyReader = EnergyCmd.ExecuteReader();

                            if (EnergyReader.HasRows && !(EnergyReader.IsDBNull(0)))
                            {
                                iEnergyBack = EnergyReader.GetInt32(0);
                            }
                            else
                            {
                                iEnergyBack = 0;
                            }
                            EnergyReader.Close();

                            rowME["Values"] = iEnergyBack.ToString();
                        }
                    }
                    conEnergyDB.Close();
                }

Bis ca 190 Datensätze funktioniert dies wunderbar, innerhalb < 1ms. Ab dieser Stelle benötigt die Zeile EnergyReader = EnergyCmd.ExecuteReader(); bis zu 6 Sekunden lang, bis etwas zurückkommt. Die betreffenden Datensätze habe ich bereits überprüft. Hier sind keine unnormalen Eigenschaften zu sehen. Was natürlich sein kann ist, dass für den überprüften Zeitraum keine Daten vorliegen. Aber dann müsste ja IsDBNull true werden, oder?
Es wird keine Exception geworfen.
Ich bin hier auch kein Experte, habe eben nur den C#-Hintergrund aus dem E-Technik-Studium..

Hat jemand eine Idee, was da so massiv bremst?
Ich freue mich über Ideen und Verbesserungstipps.

Viele Grüße,
Chris

16.842 Beiträge seit 2008
vor 6 Jahren

Was genau langsam ist, das kannst Du mit Hilfe des in Visual Studio enthaltenen Profilers analysieren.
So ist es Glaskugelraten.
Beginners Guide to Performance Profiling

Hinweis: Dein Code ist extrem anfällig gegenüber SQL Injection.
[Artikelserie] SQL: Parameter von Befehlen

Davon abgesehen ist es keine gute Idee Datenbank-Logik in die UI zu packen. Wird Dir zu 100% irgendwann massiv auf die Füße fallen.
[Artikel] Drei-Schichten-Architektur

PS: Finger weg von Access!
Das ding hat ausgedient. Es gibt bessere Möglichkeiten wie Sqlite, LocalDB, LiteDB!
Und für/im gemeinsamen Zugriff / mehrere Threads ist Access überhaupt nicht geeignet.

D
985 Beiträge seit 2014
vor 6 Jahren

Du erzeugst die für jede Abfrage eine neue Command-Instanz. Das Command-Statement muss jetzt von der Datenbank interpretiert, compiliert, durch den Query-Optimizer geschoben usw. werden.

Dieses kannst du verhindern, wenn du Parameter verwendest. Die Command-Instanz wird einmal erzeugt, du setzt die Werte für die Parameter und führst das Statement aus.

Hier würde ich sogar DbCommand.ExecuteScalar empfehlen, weil du nur einen Wert haben möchtest.

PS Eine weitere Möglichkeit der Performancesteigerung kann die Verwendung von DbCommand.Prepare() sein (geht nur innerhalb einer Transaktion)

L
lights Themenstarter:in
3 Beiträge seit 2017
vor 6 Jahren

Hallo ihr beiden,

vielen Dank zunächst für euere Hinweise und Anregungen 😃

@Abt: Ich werde mir den Profiler mal anschauen. An SQL Injection habe ich seither gar nicht gedacht. Wurde im Studium auch nicht gelehrt, aber gut, dass du es ansprichst. Da werde ich ebenfalls noch nacharbeiten.

@ Sir Rufo: Ich versuche mal bei Gelegenheit, deinen Ansatz zu testen.

Wenn mir nochmal was unklar ist, melde ich mich nochmals.

Viele Grüße,
Chris

286 Beiträge seit 2011
vor 6 Jahren

Noch ein Tipp aus persönlicher Erfahrung:
Gerade wenn du mit Access arbeitest würde ich die Zahl der tatsächlichen Zugriffe möglichst gering halten (gilt allgemein für Datenbanksysteme - für Access aber im besonderen)
Access ist im allgemeinen seeehr langsam und auch nicht unbedingt für den automatisierten Zugriff via C# ausgelegt.

Vor allem wenn du Auswertungen machen willst würde ich mir grundsätzlich möglichst viele wenn nicht alle Daten direkt in ein oder mehrere DataTables laden und dann für deine Auswertung immer via LINQ mir die Daten aus den DataTables ziehen. Ist ungefähr um den Faktor 1000 performanter als jede Abfrage einzeln auf die Access-Datei zu feuern.

Wenn du mit enorm großen Datenmengen arbeitest solltest du natürlich darauf achten, dass du deinen Arbeitsspeicher nicht völlig überlädst, aber wenn du bei der Größenordnung von 190 Datensätzen bist sollte das kein Problem sein.

Und wie Abt schon gesagt hat: Du bist damit anfällig für SQL-Injection. Bestes Mittel dagegen ist die Verwendung von Parametern, macht den Code auch wesentlich einfacher zu warten/ändern
OdbcParameter-Klasse

2+2=5( (für extrem große Werte von 2)

L
lights Themenstarter:in
3 Beiträge seit 2017
vor 6 Jahren

Servus emuuu,

danke für den Tipp! Warum bin ich da selber noch nicht drauf gekommen?
Das werde ich auch mal testen.

Gruss,
Chris