Laden...

TableAdapter füllen dauert zu lange, in Delphi schneller

Erstellt von FastGarrett vor 14 Jahren Letzter Beitrag vor 14 Jahren 5.712 Views
F
FastGarrett Themenstarter:in
4 Beiträge seit 2009
vor 14 Jahren
TableAdapter füllen dauert zu lange, in Delphi schneller

Verwendete Datenbanksysteme: Delphi mit dBase, C# mit MS SQL 2008 Express

Hallo,

Ich habe eine MS SQL Datenbank(Dienstbasierte Datenbank in C# 2008 erstellt) mit einer Tabelle mit ca. 5 Spalten mit 1.000.000 Datensätzen befüllen lassen. Wenn ich nun die Form starte in der sich ein BindingNavigator befindet, benötigt der Table Adapter ca. 30 Sekunden das Abbild der Tabelle in den Speicher zu laden. Das kann man dem Benutzer natürlich nicht zumuten.

Mein Vater programmiert Datenbankanwendungen in Delphi und dBase(Für die Datenbanken). In .Net wird von der verwendeten Tabelle - soweit ich das richtig verstanden habe - ein Abbild in den Hauptspeicher geladen. In Delphi dagegen wird auf der Platte selbst in die Datenbank eingesehen und dies funktioniert bei Delphi auch mit 1.000.000 noch gut und keine Ladezeiten. Dort gibt es keine Binding Source, man kann aber eine sogenannte TTable erzeugen die ihre Quelle hat und mit Next zum Beispiel den nächsten Datensatz auswählen. Ein DBEdit zeigt dort dann den speziellen Wert der Spalte in diesem Index an, somit also das Gegenstück einer TextBox die an eine BindingSource gekoppelt ist. Somit kommt dies dem BindingNavigator sehr nahe.

Ich wollte meinen Vater von C# überzeugen. Für mich ist Delphi eine aussterbende Sprache. Das was man in Delphi kann, geht auch in C# und C# kann noch viel mehr. Desweiteren muss man bei Delphi alle Versionen teuer bezahlen, C# bietet eine kostenlose alternative. Doch damit er das Potenzial von C# erkennt, müsste ich ihm zeigen, dass das was bei ihm in Delphi geht auch in C# geht und zwar genau so schnell. Mit diesen langen Ladezeiten geht das natürlich nicht.

Daher meine Frage: Ist es auch möglich in C# während der Laufzeit in der Datenbank zu wühlen, während man ein BindingNavigator benutzt? Wenn der BindingNavigator die Position ändert sollte das Programm den nächsten Datensatz also von der Platte lesen. Ist dies so wie in Delphi möglich?

4.506 Beiträge seit 2004
vor 14 Jahren

Hallo FastGarrett,

das ist meines Erachtens kein Problem zwischen Delphi und .Net (naja ein bisschen vielleicht doch). Ich muss hier mal etwas mehr ausholen:

Betrachten wir zunächst die Datenbank:

  • DBase ist eine sehr alte Datenbank. Die ersten Versionen waren reine Dateiendatenbanken, ähnlich wie bei MS Access auch heute. Wie der aktuelle Stand aussieht weiß ich ehrlich gesagt nicht. Wenn Du aber sagst, dass hier direkt auf Festplatte und Records zugegriffen werden kann, dann wäre das definitiv vergleichbar.
  • MS SQL Server (Express ist nur limitiert gegenüber der Vollversion, arbeitet aber genauso) ist aus heutiger Sicht eine vollwertige Datenbank, die über SQL angesteuert wird.

Unterschiede sind hier in Folgenden Bereichen deutlich zu spüren:
Versuche mal mit einer Dateiendatenbank im Multi-User und Multi-Threaded Umfeld zu verwenden. Da kommen Dateidatenbanken schnell an Ihre Grenzen. Ebenfalls werden Dateidatenbanken bei Tabellenverknüpfungen und Filterungen deutlich langsamer. Das sollte im SQL Server erheblich schneller gehen.

Betrachtung der Programmierplattform:
Delphi arbeitet wohl Deiner Aussage nach mit Recordsets, .NET bietet hier eine weitere Abstraktion, das ADO.NET. ADO.NET liest Datenbankabhängig zunächst die Struktur der Datenbank aus (Abfragebezogen) und generiert im Speicher die so genannten DataSets, die wiederum aus DataTables und diese dann DataRows beinhalten. Ein direkter Zugriff auf die Daten ist mittels ADO.NET nicht möglich. Somit ist auch das Arbeiten mit RecordSets nicht möglich. Dafür garantiert diese Arbeitsweise transaktionale Sicherheit und eine Abstraktion der Daten (sollte damit auch flexibler in der Anbindung verschiedenster Datenquellen sein).

Wie bekommt man in Deinem Anwendungsfall eine Performanceverbesserung hin?
Es gibt mehrere Möglichkeiten. Du könntest z.B. auf ADO.NET verzichten und noch die veraltete Technik DAO verwenden, nur weiß ich überhaupt nicht, ob das mit dem SQL Server funktioniert und ich würde solch eine Technik nicht mehr einsetzen. Man kauft sich durch das Recordbasierte Arbeiten zu viele Nachteile ein.
Besser ist es eine Intelligenz bei der Dateninitialisierung einzubauen. 1 Mio Datensätze kann kein Mensch überblicken und schon gar nicht gleichzeitig darstellen. Deshalb ist mein Vorschlag immer nur die Datensätze zu holen, die einmal direkt sichtbar werden, und schon als Vorbereitung die, die per Useraktion als nächstes angezeigt werden können (z.B. die nächsten 10, die vorigen 10, die allerersten 10 und die allerletzten 10 Datensätze) [hängt von der UI Gestaltung ab)

Du könntest hier z.B. für die Performanceunterschiede folgendes ausprobieren:
Versuche mal mit Delphi und DBase einen Filter laufen zu lassen (auf die 1 Mio Datensätze). Sagen wir mal es gibt eine Spalte "Nachname". Du möchtest jetzt alle Datensätze sehen, die mit "ler" enden, wie z.B. "Mueller" oder "Keller" etc. So etwas solltest Du dann mal vergleichen mit SQL Server und ADO.NET.

Ansonsten kann man Dir hier auch sicherlich konkret in einzelnen Performanceoptimierungen zur Seite stehen.

Viele Grüße
Norman-Timo

Edit: Zu früh am Morgen, deshalb Grammatik- und Rechtschreibfehler

A: “Wie ist denn das Wetter bei euch?”
B: “Caps Lock.”
A: “Hä?”
B: “Na ja, Shift ohne Ende!”

F
FastGarrett Themenstarter:in
4 Beiträge seit 2009
vor 14 Jahren

Danke zunächst für diese Ausgezeichnete Ausführung. Habe es nun so probiert, wie du sagtest, habe mit SQL beauftragt von den 1.000.000 Datensätzen nur 10 auszusuchen (ganz einfach mit "WHERE ID > 10 AND ID < 20" ID ist in meinem Fall der Primärschlüssel.)

Das geht schon um einiges Schneller, sind aber immerhin noch 5 Sekunden, eigentlich aber schon sehr erstaunlich für mich das er so schnell 1.000.000 Datensätze durchforstet. Doch für meinen Vater wird das nicht reichen. Hmm, am besten lasse ich mir das mal von meinem Vater vorführen, mit 1.000.000 Datensätzen. Wenn ich keine Lösung finde, hat mein Vater gewonnen und er vertieft sich weiter in (Commerz) Delphi.^^

1.564 Beiträge seit 2007
vor 14 Jahren

Hallo FastGarret

Der Vergleich einer DataTable mit einem Delphi RecordSet sind Äpfel und Birnen. Zusätzlich zu der von Norman-Timo bereits angebrachten Zwischenschicht über den DataReader basiert das RecordSet auf einem Array (oder einer Liste), die DataTable jedoch auf einem RedBlackTree und ermöglicht massiv optimierte Abfragen im Speicher (wobei Norman-Timo recht hat, 1,000,000 Zeilen will kein User sehen 😉 ).

Dass deine SQL Abfrage von 10 Datensätzen über den Primärschlüssel bei gerade mal 1,000,000 in der Datenbank (das ist für SQL Server nichts) 5 Sekunden dauert kann ich mir irgendwie nicht vorstellen. Hast du die Datenbank auf eine Floppy-Disk ausgelagert? 😁
Ich arbeite hier mit deutlich mehr als 1/2 Milliarde Datensätzen und habe Zugriffszeiten von knapp 9 Millisekunden.

Grüße
Flo

Blog: Things about Software Architecture, .NET development and SQL Server
Twitter
Google+

Je mehr ich weiß, desto mehr weiß ich was ich noch nicht weiß.

J
3.331 Beiträge seit 2006
vor 14 Jahren

Kleine Ergänzung: Das ConnectionPooling sorgt dafür, dass der zweite, dritte usw. Zugriff (genauer: das Connection.Open) erheblich schneller gehen als der erste. Jürgen

//sprachlicher Fehler beseitigt

F
FastGarrett Themenstarter:in
4 Beiträge seit 2009
vor 14 Jahren

Wirklich so schnell Florian Reischl?

Wie machst du das. Also ich habe in C# 2008 eine Dienstbasierte Datenbank erstellt, also mit dem Wizzard. Soweit ich weis läuft das dann über den MS SQL Server 2008 Express, möglicherweise auch über den MS SQL Campact Server, das weiß ich nicht genau. Die Datenbank befindet sich natürlich auf meiner lokalen Festplatte. Es ist ein ganz npormaler laptop mit dual Core 2GHz und 2 GB Ram. Wie bekommst du das hin das es so schnell bei dir geht? Kannst du mir ein Beispiel geben, wie man so etwas so schnell bekommt, bzw. was mache ich anders?^^

3.825 Beiträge seit 2006
vor 14 Jahren

Hallo FastGarrett,

30 Sekunden ist viel zu viel, passt nicht zu Deinem Namen !

In meiner Applikation zeige ich 1 Mio. Datensätze an, das dauert ca. 1 - 2 Sekunden. Die Tabelle hat ca. 200 Spalten.

Ich lese mit Datareader, das ist schneller als DataTable. Ich arbeite mit einem virtuellen Listview, deswegen lese ich effektiv nur 30 Datensätze (und ein reccount). Die restlichen 999970 Datensätze lese ich heimlich im Hintergrund 😉

Grüße Bernd

Workshop : Datenbanken mit ADO.NET
Xamarin Mobile App : Finderwille Einsatz App
Unternehmenssoftware : Quasar-3

1.564 Beiträge seit 2007
vor 14 Jahren

Kannst du mir ein Beispiel geben

Jupp 😃

Lege mal im SQL Server Management Studio eine Test-Datenbank an. (Bei mir gibt's immer eine "Sandbox"). Öffne ein Query-Fenster auf der Datenbank und führe folgendes Skript aus. Damit wird eine Tabelle TestData1M angelegt und 1.000.000 Testdatensätze eingefügt. Ich hab's mal ein bisschen kommentiert.

Am Ende wird beispielhaft ein SELECT ausgeführt und die SQL Server eigenen Zeitstatistiken ausgegeben.

--DROP TABLE TestData1M
SET NOCOUNT ON;
GO

---============================================================================
-- create table
IF (OBJECT_ID('TestData1M') IS NULL)
BEGIN
   PRINT ('------------------------------------------------');
   PRINT ('Creating table');

   CREATE TABLE TestData1M
   (
      Id INT IDENTITY(1,1)
         PRIMARY KEY CLUSTERED
      ,SomeInt INT
      ,SomeDate DATETIME
   );

   PRINT ('Table created');
END;
GO

---============================================================================
-- insert some test data
PRINT ('------------------------------------------------');
PRINT ('Insert some test data');

-- some variables
DECLARE 
   @count INT
   ,@i INT
   ,@batchSize INT;

-- configure the count of desired rows and the batch size to create the rows
SELECT
   @count = 1000000
   ,@batchSize = 10000;

-- get the current count of rows
SELECT @i = COUNT(*)
   FROM TestData1M;


-- create the rows
WHILE (@i < @count)
BEGIN

   -- we need some numbers
   WITH Numbers (Num) AS
   (
      SELECT TOP(@batchSize)
         ROW_NUMBER() OVER (ORDER BY (SELECT 1))
      FROM master.sys.all_columns c1
         CROSS JOIN master.sys.all_columns c2
   )
   -- use the numbers to create the test data
   INSERT INTO TestData1M (
         SomeInt
         ,SomeDate
         )
      SELECT TOP(@batchSize)
         Num
         ,DATEADD(HOUR, Num, GETDATE())
      FROM Numbers;

   -- add the count of created rows to the complete count
   SELECT @i = @i + @batchSize;
END;

SELECT @count = COUNT(*)
   FROM TestData1M;

PRINT ('Test table contains ' + CONVERT(VARCHAR(10), @count) + ' rows');
GO

---============================================================================
-- sample select
PRINT ('------------------------------------------------');
PRINT ('sample select (with time statistics)');
DECLARE @someDate DATETIME;

-- activate time stats
SET STATISTICS TIME ON;

-- select a date for a specified Id (PK)
SELECT @someDate = SomeDate
   FROM TestData1M WHERE Id = 600000;

-- deactivate time stats
SET STATISTICS TIME OFF;

PRINT ('');
PRINT ('We found date "' + CONVERT(VARCHAR(40), @someDate, 121) + '"');

Wenn du das Skript ausgeführt hast kannst du aus einer C# Konsolenapplikation folgendes Beispiel ausführen. Das ist jetzt nicht die schnellste Variante mit C#, aber die Performance dürfte als Beispiel durchaus ausreichen 😉:


// ---> CONFIGURE YOUR DATABASE <---
string database = "Sandbox";
string cnStr = string.Format("Server=(local);Database={0};Integrated Security=sspi;", database);

// create a connection, command and an adapter for the DataTable
using (SqlConnection cn = new SqlConnection(cnStr))
using (SqlCommand cmd = new SqlCommand("SELECT * FROM TestData1M WHERE Id BETWEEN 100000 AND 100010", cn))
using (SqlDataAdapter adap = new SqlDataAdapter(cmd))
{
   cn.Open();

   // destination table
   DataTable table = new DataTable();

   // start timing
   Stopwatch watch = Stopwatch.StartNew();

   // get the data from SQL Server
   adap.Fill(table);

   // stop timing and show client duration
   watch.Stop();
   Console.WriteLine("Duration (ms): {0}", watch.ElapsedMilliseconds);

   // show sample output
   Console.WriteLine();
   Console.WriteLine("Result data");
   foreach (DataRow row in table.Rows)
   {
      Console.WriteLine("Date: {0}", row["SomeDate"]);
   }
}

Die Dauer dürfte bei nur 1.000.000 Zeilen deutlich unter 10 Millisekunden liegen. 😉

Grüße
Flo

Blog: Things about Software Architecture, .NET development and SQL Server
Twitter
Google+

Je mehr ich weiß, desto mehr weiß ich was ich noch nicht weiß.

F
FastGarrett Themenstarter:in
4 Beiträge seit 2009
vor 14 Jahren

Riesen Dank für dieses enorme Beispiel. Es übertrifft meine Erwartungen um einiges. Gerne möchte ich es ausprobieren, habe aber ein kleines Problem. Ich habe die gesamte Installation mit allen Services von dem MS SQL Server 2008 Express abgeschlossen und finde doch Partout nicht das Management Studio.

Kann mir jemand sagen, in welchem Verzeichnis genau ich dieses finde? Habe habe den Installationspfad von MS SQL 2008 nach allen .exe Dateien durchsucht, doch kein Erfolg. In der Schnellstartleiste findet sich nur die Option Daten zu Im- oder Exportieren und um Konfigurationen festzustellen.

Habe ich vielleicht die Installation vergeigt? Habe extra noch mal alles was mit SQL Server 2008 gehört runter geschmissen und die komplette Installation gewählt.

5.742 Beiträge seit 2007
vor 14 Jahren

[...] und die komplette Installation gewählt.

Aha - dann hast du vermutlich "nur " den MS SQL Server 2008 Express Edition installiert.
Was du brauchst, nennt sich "Microsoft SQL Server 2008 Express Edition with Advanced Services" bzw. "... with Tools".
Ist auch beides kostenlos, bringt aber das Managementstudio Express mit; ersterer auch Volltextindices.