Autor |
Beitrag |
Klabautermann
      

Beiträge: 6366
Erhaltene Danke: 60
Windows 7, Ubuntu
Delphi 7 Prof.
|
Verfasst: Mi 09.04.08 16:18
Hallo,
und Entschuldigung, jetzt wird es ein wenig abstrakt und Theoretisch denn ich soll für ein bestehendes Projekt, welches durch häufige Iterative Datenbankzugriffe, doch ziemliche Performance schwächen hat eine Zwischenschicht aus DB Zugriffskomponenten bauen. Hierbei gibt es zwei primäre Ziele 1. eine hohe Geschwindigkeit und 2. hoher Komfort für die Programmierer.
Im Einsatz, soll es so aussehen, das für jede Tabellenstruktur eine eigene Delphi Klasse existiert über welche Komfortabel und Transparent auf die Datenbank zugegriffen werden kann. Ich erarbeite gerade eine Basisklasse für diese, welche die meiste Funktionalität enthält, da diese immer sehr ähnlich sein dürften (aus DB lesen, in DB schreiben, cache verwalten usw.).
Hierbei soll der Performancegewin im wesentlichen durch 2 Strategien erreicht werden 1. wenn immer möglich werden für den Zugriff auf die Datenbank unidirektionale Komponenten verwendet (bisher werden aus Komfortgründen überwiegend bidirektionale genutzt) und 2. sollen, da oft in Schleifen die immer wieder Gleichen Daten abgefragt werden, einmal abgefragte Daten in einem Cache gespeichert werden.
Der letzte Punkt bereitet mir nun einiges an Kopfzerbrechen Insbesondere die Frage, wie erkenne ich wann meine gecacheden Daten veraltet sind? Aufgrund einer persönlichen Antipathie eines Vorgesetzten gegen durch Trigger ausgelöste Events wurde mir die Verwendung dieser untersagt (worüber ich nicht glücklich bin, womit ich aber leben muss), so dass ich die Informationen wohl zwangsweise Pollen muss. Meine Idee war nun, das ich vor jedem Zugriff auf die gecacheden Daten per select count() überprüfe ob die Anzahlt mit den im Chache vorhanden Daten übereinstimmt (so sollte es möglich sein zu erkennen ob Daten hinzugekommen oder gelöscht wurden) und dass ich überprüfe ob im "LastModified" Feld, welches in jeder Tabelle vorhanden ist ein Datumswert hinterlegt ist, der neuer ist als in der gecacheden Tabelle.
Da dies aber eine nicht ganz Triviale Geschichte ist, möchte ich auch euren Hirnschmalz ein wenig mit anzapfen. Daher habe ich folgende Fragen an euch:
- Übersehe ich einen Fall der durch meine "hat sich was geändert" Abfrage nicht abgedeckt wird?
- Wie hoch ist das Risiko, dass die Zeit für die Überprüfung (immerhin zwei Funktionen [count & max] im Query länger Dauert als das pauschale neu einlesen der Daten?
- Habt ihr noch andere Pfiffige Ideen, wie ich den Datenbankzugriff beschleunigen kann? Sowohl lesend als auch schreibend (Massenhaftes schreiben erfolgt natürlich unter Verwendung von Transactions).
- Kennt ihr (Online) Literatur zu dem Thema?
Verwendet wird übrigens eine FireBird Datenbank und zum Zugriff dienen die IBObjects.
Neugierig auf eure Antworten
Klabautermann
|
|
zuma
      
Beiträge: 660
Erhaltene Danke: 21
Win XP, Win7, Win 8
D7 Enterprise, Delphi XE, Interbase (5 - XE)
|
Verfasst: Mi 09.04.08 17:27
Ich hab ähnliche Probleme zu lösen bzw. gelöst und geh dabei wie folgt vor:
Zuerst hab ich meine DB in 'Datenarten' unterteilt und in Stammdaten, kleine Stammdaten und Arbeitsdaten eingeteilt. Kleine Stammdaten sind z.b Länder, Verpackungsarten, Branchen, etc. die alle a) nur Spaltenzahl < 5 haben und relativ selten geändert werden.
Diese lese ich nur bei Programmstart in Clientdataset's und benutzte im Programm nur noch diese (globalen) CDS. Wird so ein 'kleiner Stammdatensatz' geändert, löst die entsprechende Maske ein Update der DB aus und anschließend ein neueinlesen der Tabelle in das CDS. Damit hab ich z.b. den DB-Zugriff auf Ortsnamen um 95% verringert, wird nun kaum noch die DB für gebraucht.
Die Stammdaten sind Datensätze mit eigener Maske und abhängigen Tabellendaten.
Da hab ich die Maske in Pages aufgeteilt und lese erst bei Pagewechsel die entsprechenden Daten nach. Geht natürlich nicht immer, aber in den meisten Fällen konnte ich so arbeiten.
Mit meinen Arbeitsdaten 'kämpfe' ich noch, da gibt's noch keine endgültige Lösung.
Derzeit hilft mir da, was ich dir auch vorschlagen möchte:
Kontrolliere mal die DB-Strukturen bezüglich gesetzter Indexe. Meine Anwendung hab ich auch in eine schon bestehende DB integrieren müssen und dort 'alte' Tabellen mitverwendet. Derjenige, der die damals angelegt hat, hatte leider kaum Indexe gesetzt und meine neuen Abfragen natürlich noch gar nicht gekannt. Der Einbau von ein paar Indexen hat die Performance um z. Teil unglaubliche Werte verschnellert, seitdem mach ich lieber nen Index zuviel, als zu wenig (auch wenn das auch nicht immer die richtige Alternative ist).
_________________ Ich habe nichts gegen Fremde. Aber diese Fremden sind nicht von hier! (Methusalix)
Warum sich Sorgen ums Leben machen? Keiner überlebts!
|
|
baka0815
      
Beiträge: 489
Erhaltene Danke: 14
Win 10, Win 8, Debian GNU/Linux
Delphi 10.1 Berlin, Java, C#
|
Verfasst: Mi 09.04.08 17:33
Wie löst ihr denn das Problem, wenn mehrere Leute gleichzeitig arbeiten?
zuma, wie bekommen die anderen Clients mit, dass die Datenbank geändert wurde?
Oder realisiert ihr die entsprechende DB-Schicht als Server?
|
|
Klabautermann 
      

Beiträge: 6366
Erhaltene Danke: 60
Windows 7, Ubuntu
Delphi 7 Prof.
|
Verfasst: Mi 09.04.08 17:46
Hallo und erst einmal danke für die Hinweise:
zuma hat folgendes geschrieben: | Kleine Stammdaten sind z.b Länder, Verpackungsarten, Branchen, etc. die alle a) nur Spaltenzahl < 5 haben und relativ selten geändert werden.
Diese lese ich nur bei Programmstart in Clientdataset's und benutzte im Programm nur noch diese (globalen) CDS. Wird so ein 'kleiner Stammdatensatz' geändert, löst die entsprechende Maske ein Update der DB aus und anschließend ein neueinlesen der Tabelle in das CDS. |
Das hört sich so an, als würdest du nur mit einer Clientanwendung Arbeiten, in dem Fall wird das gut funktionieren, die Aplication von der ich spreche wird Oft in einer Netzwerkumgebung mit duzenden Clients eingesetzt. In dem Fall wird es schwierig, mit dem Nachladen der "kleinen Stammdaten". Daher auch meine Ausführungen zur "hat sich was geändert" Anfrage.
zuma hat folgendes geschrieben: | Die Stammdaten sind Datensätze mit eigener Maske und abhängigen Tabellendaten.
Da hab ich die Maske in Pages aufgeteilt und lese erst bei Pagewechsel die entsprechenden Daten nach. Geht natürlich nicht immer, aber in den meisten Fällen konnte ich so arbeiten. |
Hmm, das Eigetliche einpflegen und bearbeiten Einzelner Stammdaten sehe ich gar nicht als Problematisch an, da hier ja nur in sehr großen Abständen (immer wenn der user endlich mit seinen Änderungen Fertig ist) gelesen und gepostet wird. Wie machst du es bei zugriffsaufwändigeren Funktionen, wie z.B. Statistischen Auswertungen oder dem Import großer Datenmengen? Oder kommen solche in deinem Programm nicht vor.
zuma hat folgendes geschrieben: | Kontrolliere mal die DB-Strukturen bezüglich gesetzter Indexe. |
Ja, dass könnte ich noch machen, da die meisten Tabellen nicht von mir sind, ein erster Blick offenbart, dass nur durchschnittlich 1,3 Index-Definitionen pro Tabelle vorhanden sind (127 bei 93 Tabellen). Somit währe das ein Ansatzpunkt, aber nur eine zusätzliche Möglichkeit zu den im ersten Topic beschriebenen.
Noch mal danke für deine Vorschläge
Klabautermann
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Mi 09.04.08 17:47
Wichtig bei der Auswahl des Caches ist unter anderem auch die Frage, ob und wie mehrere Instanzen laufen und wie diese untereinander synchronisiert werden. Konkret bedeutet das:
- Wieviele Datenbank-Server laufen?
- Wird Jeder DB-Server von einem Dienst bedient?
- Sind zwischen den Datenbanken Abhängigkeiten, die über die Datenbank-Server-Grenze\den verwaltenden Dienst hinaus gehen?
- Werden asynchron zur zu entwickelnden Schicht Daten in der DB verändert?
- Liegen zwischen der zu entwickelnden Schicht und der Datenbank weitere Schichten?
- Ist ein Mehr-Instanz-Zugriff (Clientseitig) auf die zu entwickelnde Schicht gedacht?
- Welche Arten von Datenmengen werden umgesetzt?
- Gibt es Daten, die sich nur selten ändern, aber häufig benötigt werden?
- Gibt es Daten, die sich häufig ändern, aber nur selten benötigt werden?
- Bei mehreren Verbindungen auf die Datenbank (durch verschiedene Middle-Ware-Systeme): Gibt es stellen, an denen Dirty Reads oder Dirty Writes problematisch werden können (dann tun sie das nämlich später auch *G*)
Generell würde ich aber schauen, dass man das Zugriffskonzept auf die Datenbanken von Iterativ auf "on-Demand" umstellen kann, da du damit die für den Cache nötige Datenmenge stark reduzierst.
Ferner solltest Du dir Strategien überlegen, wie Du einzelnen Clients gezielt Benachrichtigungen über Änderungen zusenden kannst. Hierbei wäre z.B. interessant, ob vor dem Erzeugen von Änderungen an Daten diese Änderung im Voraus ersichtlich ist, um z.B. sinnvoll Locks auf Datensatz\Datenmengen-Ebene im Cache verwalten zu können.
HTH.
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
Reinhard Kern
      
Beiträge: 591
Erhaltene Danke: 14
|
Verfasst: Mi 09.04.08 18:44
Hallo,
cachen kann nur sinnvoll sein, wenn die Daten "hinreichend selten" geändert werden.
Pollen ist für einfache Daten selten sinnvoll - die BLZ einer Bank habe ich genauso schnell gelesen, wie ich brauche um zu bestimmen, ob sie geändert wurde. Höchstens bei grösseren Datenmengen wie den Stammdaten eines Kunden könnte es sich lohnen, eine Prüfsumme zu erzeugen und mit dieser festzustellen, ob sich seit dem letzten Lesen der Daten etwas verändert hat.
Sind Änderungen so selten wie bei BLZ, kann man auch die gesamte Tabelle absichern z.B. mit dem Datum der letzten Änderung. Ist diese jünger als der Cache, wird dieser neu eingelesen, BLZ-Updates gibt es ja nur alle paar Tage.
Auch bei "Push"-Lösungen muss geprüft werden, ob Updates nicht zu oft auftreten, es nützt ja nichts, wenn das System durch getriggerte Änderungsmitteilungen verstopft wird anstatt durch die Abfragen.
Im übrigen verwende ich i.A. nur "Cache on Demand", d.h. ich lese nur in den Cache, was aktuell gebraucht wird, dann kann der Cache auch wesentlich kleiner sein als die Tabelle. Ist auch eine statistische Frage, regelt sich aber bei passenden Algorithmen von selbst.
Gruss Reinhard
|
|
matze
      
Beiträge: 4613
Erhaltene Danke: 24
XP home, prof
Delphi 2009 Prof,
|
Verfasst: Mi 09.04.08 20:02
zu der Prüfung auf Veränderung fällt mir folgendes ein:
Erstelle eine neue Tabelle mit folgenden Spalten:
Tabellenname | letze Änderung
Bei allen anderen Tabellen erstellst du einen Trigger, der bei Insert, Update oder Delete den Wert der letzten Änderung in der "Prüf-Tabelle" aktualisiert. So musst du nur die Prüf-Tabelle anfragen, wenn du wissen willst, ob sich was geändert hat und musst keine aufwändigen Count oder Max Funktionen fahren.
_________________ In the beginning was the word.
And the word was content-type: text/plain.
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Mi 09.04.08 21:34
Problem: Trigger dürfen auf Grund des Chefs nicht verwendet werden ...
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
Agawain
      
Beiträge: 460
win xp
D5, MySQL, devxpress
|
Verfasst: Mi 09.04.08 21:43
Hi,
und Matzes Anregung bringt mich auf die Idee, in jeder Tabelle ein Timestamp mitzuführen.
Du aktualisierst in gleicher Häufigkeit, wie bisher, aber in den Cache, abgerufen werden aber nur jene Daten, die sich auch tatsächlich seit der letzten Aktualisierung geändert haben und zwar in einen 2. Cache, aus dem heraus wird dann Cache no 1 upgedatet.
Geht zwar zu Lasten des Clients, aber da Änderungen und neue Datensätze in eher geringem Umfang entstehen, im Vergleich zu den bereits bestehenden Daten, seh ich das als Performance-Gewinn.
Dann ist da noch die Frage, welche Daten wirklich interessieren und welche Prozesse sie unterstützen.
Um das mal zu veranschaulichen, nenn ich mal ein paar Beispiele aus dem wirklichen Leben
Nehmen wir an, Jemand will Umsatzdaten der letzten Jahre analysieren, dann sollte er von vorneherein den Zeitraum bestimmen müssen, damit nicht Daten gelesen werden müssen, die er sowieso nicht will, dann machste es meinethalben noch wie Excel-Pivot, dort muss man auf Aktualisieren drücken, um neue Daten zu bekommen....ist aber eigentlich nicht erforderlich, denn der Client wird wohl kaum so lange leben, dass es notwendig wird und die Rechnung, die Mitarbeiter XY gerade geschrieben hat, interessiert in dem Anwendungsfall Null.
Ein anderes Beispiel, da geht es um den Prozess, Rechnungen als Bezahlt einzutragen, liegt dann doch nahe, nur Positionen anzubieten, die auch noch tatsächlich den Status offen haben und erst im Falle einer Störung (irgendeiner hat doppelt überwiesen (wie schön  , oder falsche Belegnummer angegeben, weniger schön), werden die übrigen Daten nachgeladen.
Hast Du eine Produktions-Maschine und der Client arbeitet mit einer CAM, die ihm Bilder eines Fertigungsprozesses zwecks Feststellung von Abweichungen liefert und einen 2. Client, der auf Abweichungen außerhalb der Toleranz reagieren muss, kannst Du gar nicht cachen, oder aber Du cachst, bis es den Client 2 interessiert.
Kommt auf die Funktionsverteilung an.
Sind beide Funktionen in einem Client vereint, kannste cachen bis der Arzt kommt, oder der Fertigungsleitstand die Daten anfordert.
Also worauf ich mit diesen 3 popligen Beispielen heraus will, es kommt auf den realen Prozess dahinter an, wie, wann, welche Daten wirklich benötigt werden und da kann man ganz schön abspecken, denk ich mal.
_________________ Gruß Aga
|
|
hazard999
      
Beiträge: 162
Win XP SP2
VS 2010 Ultimate, CC.Net, Unity, Pex, Moles, DevExpress eXpress App
|
Verfasst: Do 10.04.08 08:19
Hallo,
dem Thema musste ich mich auch stellen.
Das mit dem Timestamp lässt sich eleganter lösen.
Der MSSQL-Server stellt zum Beispiel folgendes Konstrukt bereit:
SELECT CHECKSUM_AGG(BINARY_CHECKSUM([FELDER)) FROM [TABLENAME] <WHERE>
Damit erhälst du eine Zahl (Checksum, eine Art Hashwert) der sich, da er recht schnell ist, sehr gut pollen lässt.
r u
René
_________________ MOV EAX, Result;MOV BYTE PTR [EAX], $B9;MOV ECX, M.Data;MOV DWORD PTR [EAX+$1], ECX;MOV BYTE PTR [EAX+$5], $5A;MOV BYTE PTR [EAX+$6], $51;MOV BYTE PTR [EAX+$7], $52;MOV BYTE PTR [EAX+$8], $B9;MOV ECX, M.Code;MOV DWORD PTR [EAX+$9], ECX
|
|
zuma
      
Beiträge: 660
Erhaltene Danke: 21
Win XP, Win7, Win 8
D7 Enterprise, Delphi XE, Interbase (5 - XE)
|
Verfasst: Do 10.04.08 10:49
Klabautermann hat folgendes geschrieben: |
Das hört sich so an, als würdest du nur mit einer Clientanwendung Arbeiten, in dem Fall wird das gut funktionieren, die Aplication von der ich spreche wird Oft in einer Netzwerkumgebung mit duzenden Clients eingesetzt. In dem Fall wird es schwierig, mit dem Nachladen der "kleinen Stammdaten". Daher auch meine Ausführungen zur "hat sich was geändert" Anfrage.
|
Nee, ist keine reine ClientAnwendung, hab je nach Kunde zw. 1 und 150 Clients. Aber ich hab's mir nochmal angesehen und festgestellt, das ich den wichtigen teil zu erwähnen vergessen hab (sorry, der Stress hat mal wieder zugeschlagen). Der Lösungsansatz wurde hier auch schon erwähnt ...
Wenn die DB upgedatet wird, wird dabei gleichzeitig eine Tabelle mit dem Änderungszeitpunkt gefüllt (Tabname, Datum letzte Änderung), die Clients gucken nun per Timer alle x Minuten (einstellbar) in diese Tabelle, ob sie eines oder mehrere ihrer Clientdataset's aktualisieren müssen (Lesezeitpunkt jeder 'CDS-Tabelle' wird sich gemerkt). Klappt eigentlich ganz gut, bislang hat noch keiner gemeckert (Anwendung ist nun ca. 2,5 Jahre 'auf dem Markt')
Klabautermann hat folgendes geschrieben: |
Hmm, das Eigetliche einpflegen und bearbeiten Einzelner Stammdaten sehe ich gar nicht als Problematisch an, da hier ja nur in sehr großen Abständen (immer wenn der user endlich mit seinen Änderungen Fertig ist) gelesen und gepostet wird. Wie machst du es bei zugriffsaufwändigeren Funktionen, wie z.B. Statistischen Auswertungen oder dem Import großer Datenmengen? Oder kommen solche in deinem Programm nicht vor.
|
Klar hab ich Statistiken und Import, sogar einen automatisierten DB-Abgleich (Vertreter kommt wieder ins Büro und klemmt sich ins netz, dann werden seine DB-Daten mit der Server-DB abgeglichen).
Als eine gute Technik hat sich erwiesen, große Datenmengen einfach nur zu lesen und Änderungen daran per 'extra-Sql' zu machen. Hab also oft nen 'Lese- und Anzeige-Dataset' und daneben noch ein oder mehrere 'Schreib-Querys'.
Aber wie Agawain schon sagte: Kommt immer auf den Fall an, hab in meiner Anwwendung beinahe jede 'schweinerei' drin.
Aber noch was zu 'schneller werden':
So mancher Sql kann meistens durch kleinere Änderungen (reihenfolge der Felder, Joins, etc) verbessert werden, da hilft das Tool 'InterBase PLANalyzer' gut.
_________________ Ich habe nichts gegen Fremde. Aber diese Fremden sind nicht von hier! (Methusalix)
Warum sich Sorgen ums Leben machen? Keiner überlebts!
|
|
matze
      
Beiträge: 4613
Erhaltene Danke: 24
XP home, prof
Delphi 2009 Prof,
|
Verfasst: Fr 11.04.08 09:07
BenBE hat folgendes geschrieben: | Problem: Trigger dürfen auf Grund des Chefs nicht verwendet werden ... |
Ich dachte, das bezieht sich nur auf Trigger, die ein Event zum Client senden.
_________________ In the beginning was the word.
And the word was content-type: text/plain.
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Fr 11.04.08 09:27
Keine Ahnung, gegen was der Chef da allergisch ist.
Wenn man auch in der DB keine Trigger verwenden will, kann man sich mit 2 SPs n Locking-System bauen; das Unlock würde dann gleichzeiti in seiner Transaktion auch das Update so einer Veränderungstable mit setzen können.
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
Klabautermann 
      

Beiträge: 6366
Erhaltene Danke: 60
Windows 7, Ubuntu
Delphi 7 Prof.
|
Verfasst: Fr 11.04.08 11:16
Hi,
sorry das ich mich erst so spät (und nur so kurz) Melde, aber wie das so ist wurde mir noch ein anderes Projekt "dazwischen geschoben", so das ich mich erst nächste Woche wieder ausführlicher mit dieser Thematik beschäftigen kann. Dann werde ich mich hier auch wieder ausführlicher äußern können.
Ich denke Trigger innerhalb der DB sollten Ok sein, werde das aber noch abklären, die Idee mit der Last Modified Tabelle gefällt mir prinzipiell recht gut.
Danke auf jeden Fall an alle die bisher geantwortet haben.
Gruß
Klabautermann
|
|
|