Fragensteller
Suche nach Best practice: fertig berechnete statistische Werte vorrätig halten

Frage
-
Seit vielen Jahren programmiere ich ehrenamtlich für mehrere gemeinnützige Jugendverbände eine Freizeitdatenbank auf Basis eines SQL Servers als Backend und MS Access als Frontend. Das Datenmodell ist so weit wie möglich normalisiert, so dass es u.a. Tabellen zu
Personen Freizeiten Anmeldungen zu Freizeiten Zusatzoptionen zu Freizeiten Gewählte Zusatzoption einer Anmeldung Buchungen
gibt. In den verschiedenen Menüs sind häufig auch berechnete Eckdaten enthalten. So gibt es im Freizeitmenü einen Reiter mit der Auflistung der Teilnehmer. Darunter sind dann statistische Eckdaten aufgeführt: Gesamtzahl, männliche, weibliche Teilnehmer, Begleitpersonen, etc.
Auch beim Reiter „Buchhaltung“ finden sich statistische Eckdaten, die man für einen schnellen Eindruck braucht ohne eigens detaillierte Berichte aufrufen zu müssen: Teilnehmergebühren und Stornogebühren (SOLL), bezahlte Gebühren (IST), Differenz von SOLL und IST, etc.
Die Dauer zum Aufruf des jeweiligen Menüs in Access ist gerade noch OK – ich würde mir aber eine deutlich höhere Performance wünschen. In den letzten Jahren habe ich daher einen Abstecher zur „In-Memory-Technologie“ gemacht. Das hat tatsächlich einiges an Geschwindigkeit gebracht – war aber mit so vielen Nachteilen und Problemen verbunden, dass ich mich letztlich dann doch wieder davon verabschiedet habe.
Ein wesentlicher Punkt war für mich auch die Optimierung der Indices und Abfragen. Hier kann man sicher auch noch etwas rausholen – aus den relevanten Fachbüchern und Internettipps habe ich aber schon vieles umgesetzt und ausprobiert.
Ein Gedanke, der mich schon länger umtreibt und für den ich mit diesem Post eine „Best-Practice“-Lösung suche ist, ob die statistischen Eckdaten nicht schon vorab in irgendeiner Form berechnet werden können und dann beim Abruf eines Menüs viel schneller zur Verfügung stehen. Am Beispiel der Teilnehmerzahl kann diese für eine Freizeit eine längere Zeit konstant bleiben (z.B. weil die maximale Teilnehmerzahl sowieso schon erreicht ist) – trotzdem wird das Menü für die Freizeit X-fach aufgerufen.
Meine Suche hat mich jetzt zu indizierten Sichten geführt. Rein vom beschriebenen Rahmen hätte ich gemeint, dass es genau das ist, was ich suche – Berechnungen können damit „materialisiert“ werden. In meiner Vorstellung ist das wie eine Tabelle und ich muss mich um nichts kümmern. Leider gibt es für diese Sichten keine „OUTER JOINS“, weshalb ich faktisch für jede Unterscheidung eines berechneten Wertes eine eigene Sicht brauche.
CREATE VIEW [dbo].[IS_Freizeiten_Anzahl_TN_M] WITH SCHEMABINDING AS SELECT FZ_ID, COUNT_BIG(*) AS Anzahl_TN_M FROM dbo.PersFZ PFZ INNER JOIN dbo.Personen P ON PFZ.Pers_ID = P.Pers_ID WHERE PersFZ_Warteliste = 0 AND PersFZ_Absage = 0 AND PersFZ_Stornierung = 0 AND PersFZ_Status_Person >= 100 AND PersFZ_Status_Person < 200 AND Pers_Geschlecht = 10 GROUP BY FZ_ID GO
SET ARITHABORT ON SET CONCAT_NULL_YIELDS_NULL ON SET QUOTED_IDENTIFIER ON SET ANSI_NULLS ON SET ANSI_PADDING ON SET ANSI_WARNINGS ON SET NUMERIC_ROUNDABORT OFF GO /****** Object: Index [IX_FZ_ID] Script Date: 28.05.2020 21:59:42 ******/ CREATE UNIQUE CLUSTERED INDEX [IX_FZ_ID] ON [dbo].[IS_Freizeiten_Anzahl_TN_M] ( [FZ_ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] GO
Was mich etwas irritiert ist die Tatsache, dass beim Aufruf der Beispielsicht die Datensätze nicht etwa nach der FZ_ID sortiert sind – obwohl die ID als UNIQUE CLUSTERED INDEX gesetzt ist.
Dann braucht die übergreifende Sicht, in der ich wieder die verschiedenen Einzelwerte der indizierten Sichten zusammenfasse zum Aufruf doch recht lange. Schaue ich mir den Ausführungsplan an, so sehe ich, dass z.B. die Bestimmung des Geschlechtes u.ä. Rechenzeit in Anspruch nehmen. Dabei enthält die zusammenfassende Sicht nur indizierte Sichten nach dem oben aufgezeigten Schema. Ich hätte erwartet, dass auf die bereits berechneten Werte zurückgegriffen wird.
Jetzt bin ich etwas ratlos, was eine bessere Alternative wäre. Ist es „Best Practice“, dass man eine eigene echte Tabelle mit fertig berechneten statistischen Werten erstellt? Nur müsste ich dann überall Trigger setzen, damit jede kleine Änderung dort mitgezogen wird.
Oder setzt man so etwas mit temporären Tabellen um?
Alle Antworten
-
Moin,
ich bin kein SQL-Experte, da werden sich bestimmt bald Wissende äußern, zwei Fragen hätte ich dennoch:
- Ist es zweifelsfrei festgestellt worden, dass die Performance der SQL-Abfragen zur wahrgenommenen Performance der Benutzer-Interaktionen führt? Sprich: Wenn Du einfach nur die Abfragen ausführst, nehmen sie wirklich mehr Zeit in Anspruch als Du erwartet / dir gewünscht hättest?
- In Memory OLTP ist ein Enterprise-Feature. Ich dachte, es ginge um gemeinnützige Organisationen? Wie passt das zusammen (auch wenn Du das schon verworfen hast)?
Insgesamt hört sich Dein Use Case nicht so an, als würdest Du mit Terabytes an hochgradig beweglichen Daten hantieren. Will sagen, wenn Deine SQL-Instanz genug RAM zugewiesen hat, dürften die Abfragen ab dem zweiten Aufruf eh aus dem Cache bedient werden. Ist denn der zweite Aufruf derselben Übersicht deutlich flotter als der erste? Falls nicht, hast Du es vermutlich mit einem Frontend-Problem zu tun, siehe erste Frage.
Evgenij Smirnov
- Bearbeitet Evgenij Smirnov Freitag, 29. Mai 2020 05:02
-
Was mich etwas irritiert ist die Tatsache, dass beim Aufruf der Beispielsicht die Datensätze nicht etwa nach der FZ_ID sortiert sind – obwohl die ID als UNIQUE CLUSTERED INDEX gesetzt ist.
Hallo,
Daten in einer Tabelle haben per Definition keine natürliche Reihenfolge, daran ändert auch ein Clustered Index nichts. Wenn man nur die Felder aus dem CI abfragt, bekommt man meistens die Daten in der Reihenfolge gemäß CI Definition, das ist aber keine garantierte Eigenschaft. Nur über die ORDER BY Klausel kann man die Reihenfolge bestimmen, alles andere ist nur Zufall.
Indizierte Sichten bringen meistens nicht so viel und ist meistens eher Overhead. Nur Abfragen auf die Views können diesen nutzen, alle anderen Abfragen auf die Basis-Tabelle nicht (außer man hat die Enterprise Edition). Indizes auf die Basis sind meistens effektiver.
Zu dem Rest kann ich wenig sagen, da Du viel geschrieben hast, aber nichts über Tabellendesign, gängige Abfragen und deren Ausführungsplan.
Ich habe i.d.R. keine Problem bei Abfragen mit Aggregationen.
Olaf Helper
[ Blog] [ Xing] [ MVP] -
In Memory OLTP ist ein Enterprise-Feature.
Evgenij, das war einmal. MS ist dazu über gegangen, möglichst alle Features in alle Editionen bereit zu stellen, damit man bei der Entwicklung nicht so sehr beachten muss, welche Edition der Kunde einsetzt und welche Features verfügbar sind; macht es uns Entwicklern leichter.
Seit 2016 SP1 gibt es OLTP und andere ehemalige Enterprise Features sogar in der Express Edition, siehe In-Memory OLTP in Standard and Express editions, with SQL Server 2016 SP1
Ist halt nur in der Express auf max. 352 MB beschränkt: Editions and supported features of SQL Server 2017
Olaf Helper
[ Blog] [ Xing] [ MVP] -
Huch, schon wieder was verpasst :-) Danke für die Berichtigung!
Evgenij Smirnov
-
Je nach Datenvolumen liegt es in der Natur der Sache dass alle Abfragen, die auf Views oder Indizes basieren immer den gesamten Bestand durchrechnen müssen.
Dies kann bei kleineren Datenmengen vernachlässigbar sein, bei größeren gibt es halt Antwortzeiten die auch schon mal in die Minuten gehen können.
Ins besonders wenn komplexere Gruppierungen, Verknüpfungen und Aggregate (SUM/AVG/...) verwendet werden.
Um dem Abhilfe zu schaffen arbeitet man da mit statischen Ergebnistabellen die regelmäßig aktualisiert werden.Der Aktualisierungsaufwand hängt vom gewünschten Grad der Aktualität ab.
Entstehen Daten im Laufe des Tages muss der statische Bestand u.U. häufiger aktualisiert werden.Die Vorgehensweise ist dann diese:
Auf Basis deiner Afrage erstellst du eine neue Tabelle.
Über SSIS kannst du dann die Abfrage ausführen und in die statische Tabelle importieren.
Aus Access fragst du dann die neue Tabelle ab.
Diese Aktionen lassen sich dann auch automatisieren.https://docs.microsoft.com/de-de/sql/integration-services/ssis-how-to-create-an-etl-package?view=sql-server-ver15
https://docs.microsoft.com/de-de/analysis-services/instances/automate-analysis-services-administrative-tasks-with-ssis?view=asallproducts-allversions -
Hallo,
schreib doch mal etwas über die Mengen also Zeilenanzahl in deinen Tabellen.
Danach kann man ehere sagen ob es wirklich am SQL Server und der Datenbank hängt.
Wenn MS-ACCESS im Spiel ist, sollte man sich die Abfragen im SQL Management Studio/ SQL Server Profiler anschauen. MS ACCESS verändert die SQL's, abhängig von dem wie man das abfragt.
Es kommt da zu Konstellationen das MS ACCESS die komplette Tabelle selektiert um danach den Join lokal durchzuführen. Das mag ja schnell sein, doch im ersten Schritt wurden halt alle Daten geholt, was in Abhängig der Datenmenge, eher nicht schnell ist. Doch das hängt immer davon ab was und wie abgefragt wird.
pass-through ist hier das Stichwort:
https://support.microsoft.com/en-gb/help/2685090/acc-you-may-encounter-slow-performance-or-hangs-when-designing-executiWie viele Zeilen haben denn diese Tabellen?
und lass uns doch mal das Create Table mit Indizies auf der Tabelle Personen anschauen.
Deine Abfrage welche den View darstellt kennt keine zeitliche Komponente.
Ist das wirklich so?Wenn Du SQL Server Express verwendest sollen die indizierten Views kein Allheilmittel sein. Die Express Version geht damit in einer eigenen Weise um.
Es gibt noch den NOEXPAND Hint, doch das sollte man versuchen zu Umgehen, wenn es nicht sein muss.
https://www.sqlservercentral.com/articles/indexed-views-in-non-enterprise-editions-of-sql-server
Personen Freizeiten Anmeldungen zu Freizeiten Zusatzoptionen zu Freizeiten Gewählte Zusatzoption einer Anmeldung Buchungen
Grüße Alexander
-
Hallo,
Danke erst einmal für Eure Rückmeldungen. Zwischenzeitlich war ich auch über den "NOEXPAND"-Hint gestolpert, den ich aber nicht sofort ausprobieren konnte (https://www.sqlservertutorial.net/sql-server-views/sql-server-indexed-view/)
Das habe ich jetzt nachgeholt und tatsächlich läuft die Sache auf meiner Entwicklungsumgebung deutlich schneller. Ich habe den "Abfragespeicher" aktiviert - dort sieht man ja, wie viele ms eine Abfrage in Anspruch nimmt und welchen Ausführungsplan das Ding verwendet. Alle umgestellten Abfragen mit indexed views und dem "NOEXPAND"-Hint tauchen dort nicht mehr auf.
Trotzdem ist die Aufrufzeit in Access leider noch nicht das, was ich mir vorstelle. Hier muss ich mal prüfen, woran das liegen könnte. Von der Programmierung so viel: alle Daten werden nur via Stored-Procedures und Pass-Through abgerufen. Ich habe keine einzige Tabelle direkt in Access eingebunden.
Von der Anzahl der Zeilen / Datensätzenist es sehr davon abhängig, welcher Jugendverband die Datenbank nutzt. Unser Spitzenreiter hat nach 10 Jahren Nutzung z.B.
Buchungen 44.709 Freizeiten 494 Freizeitoptionen 64 PersFZ 24.061 (= Freizeitanmeldungen) PersFZRes 5.581 (= gewählte Auswahlmöglichkeit) Personen 7.523 Ressourcen 622 (= Auswahlmöglichkeiten der Optionen) Es gibt aber auch mehrere kleine Jugendverbände - die haben viel weniger:
Buchungen 639 Freizeiten 21 Freizeitoptionen 18 PersFZ 702 (= Freizeitanmeldungen) PersFZRes 655 (= gewählte Auswahlmöglichkeit) Personen 262 Ressourcen 32 (= Auswahlmöglichkeiten der Optionen)
Die einzelnen Abfragen beziehen sich dann aber auf einen Bruchteil aller Datensätze. Zu einer Freizeit sind vielleicht so 50 Kinder angemeldet. Dann hängen noch ein oder zwei Freizeitoptionen mit wenigen Auswahlmöglichkeiten dran (z.B. eine Essen und dort "esse alles" / "Vegetarier" oder eine Option zur Anfahrt).
Die Optimierung der Abfragen ist für mich einmal der erste Schritt. Danach muss ich auch noch schauen, wo vielleicht eine Filterung oder Sortierung der Ergebnisse in Access doch noch zu einem Geschwindigkeitsverlust führen.
Grüße
Stefan
-
Im Abfragespeicher und bei "Performance Insights" von AWS (mit der Datenbank sind wir dort - es gibt nämlich ein spezielles NGO-Programm von AWS) taucht jetzt ein Code auf, der viel Rechenzeit in Anspruch nimmt... aber nicht von mir ist:
SELECT
SCHEMA_NAME(sp.schema_id) AS [Schema],
sp.name AS [Name],
sp.object_id AS [ID],
CAST(
case
when sp.is_ms_shipped = 1 then 1
when (
select
major_id
from
sys.extended_properties
where
major_id = sp.object_id and
minor_id = 0 and
class = 1 and
name = N'microsoft_database_tools_support')
is not null then 1
else 0
end
AS bit) AS [IsSystemObject],
CASE WHEN sp.type = N'P' THEN 1 WHEN sp.type = N'PC' THEN 2 ELSE 1 END AS [ImplementationType],
CAST(CASE WHEN ISNULL(smsp.definition, ssmsp.definition) IS NULL THEN 1 ELSE 0 END AS bit) AS [IsEncrypted]
FROM
sys.all_objects AS sp
LEFT OUTER JOIN sys.sql_modules AS smsp ON smsp.object_id = sp.object_id
LEFT OUTER JOIN sys.system_sql_modules AS ssmsp ON ssmsp.object_id = sp.object_id
WHERE
(sp.type = @_msparam_0 OR sp.type = @_msparam_1 OR sp.type=@_msparam_2)
ORDER BY
[Schema] ASC,[Name] ASC
Wo genau kommt diese Abfrage her und warum verbraucht sie so viel Ressourcen?
Kann das irgendwie mit WITH(NOEXPAND) zusammenhängen?
-
Häufig scheitert es an fehlenden Indizes.
In dem Case-Construct wird ein Select auf sys.extended_properties durchgeführt.
Je nach dem, was sich dahinter verbirgt, wie viele Zeilen enthalten sind und ob es einen Index über die Felder der Where-Klausel gibt, kann es da schon mal dauern.Die Hauptabfrage geht auf sys.all_objects. Dies sind alle Obejte der DB, Tabellen, Views, Prozeduren, usw.
Dies kannn schon mal in die hunderte oder tausende gehen.
Wenn nun über die Felder major_id, minor_id, class und name von sys.extended_properties kein Index besteht, wird für jede Zeile aus sys.all_objects alle Zeilen aus sys.extended_properties durchsucht.Multipliziere die Anzahl Zeilen beider Tabellen miteinander und du weißt, warum das so lange dauert.
Das muss sich aber einer eben ausgedacht haben.
-
Hallo,
die Datenmenge scheidet als Grund wohl aus.
Schreibe doch mal wie lange so eine ACCESS Abfrage braucht. Das was da zu sehen ist soll bei vernünftigen DB-Design und wenn die Indizies vorhanden sind und genutzt werden keine Performance Probleme verursachen.
Dazu soll der Bestand nicht groß genug sein.
Kannst Du über das SQL Server Management Studio deine oben stehende Abfrage absetzten.Führend zusätzlich
Set Statistics io on
Set Statistics time on.
Zusätzlich den Ausführungsplan anzeigen lassen.
Das wäre hilfreich.Grüße Alexander
-
"die Datenmenge scheidet als Grund wohl aus." kann ich nicht stehen lassen.
Dies ist bei fehlenden Indizes sehr wohl ein Grund!Ein "scalarer Subselect" (wie oben) liest immer alle Sätze zu dieser Bedingung.
Wenn also 1000 Zeilen in der Tabelle stehen werden auch 1000 Zeilen gelesen, es könnte ja schließlich mehr als 1 Zeile vorhanden sein, die der Suchbedingung entspricht.
Wenn nun in der From-Tabelle ebenfalls nur 1000 Zeilen stehen, sind das bereits 1.000.000 benötigte Zugriffe!
Bei theoretischen 10000 Zugriffen je Sekunde dauert die Abfrage dann immer noch 100 Sekunden.Ich habe es nun leider oft genug erlebt, dass SQL's in Testumgebungen während der Entwicklung sauschnell sind. Sobald aber in der Echtumgebung immer mehr Daten entstehen, entpuppt sich schlechtes Design immer gravierender als Engpass.
Ursache ist immer die Datenmenge und daraus resultierenden fehlenden Indizes.
-
Hallo,
ja und wenn der Index fehlt, dann ist der Grund, das der Index fehlt, und nicht das die Dauer aufgrund der Datenmenge zustande kommt.
Und um den fehlenden Index auf die Spur zu kommen schrieb ich ja das die SQL mit führendem:
Set Statistics io on
Set Statistics time on.
ausgeführt werden soll. Danach kann man aufgrund des Ausführungsplans wohl erkennen wo und welcher Index fehlt.
Grüße Alexander
-
Wo genau kommt diese Abfrage her und warum verbraucht sie so viel Ressourcen?
Olaf Helper
[ Blog] [ Xing] [ MVP] -
Urlaubsbedingt komme ich nur unregelmäßig zum antworten.
Ich versuche die Performance mit dem Abfragespeicher bzw. der Funktion "Performance Insights" von Amazon AWS im Blick zu behalten. Dorten werden die kostenintensivsten Abfragen immer angezeigt. Nach der Umstellung in der beschriebenen Form (WITH(NOEXPAND)) war plötzlich nicht mehr der Aufruf der besagten Sicht am "teuersten", sondern die andere Abfrage, die aber nicht von mir, sondern vom SSMS kam.
Der Formularaufruf war trotzdem sehr langsam - eine andere Abfrage wurde aber nicht als "Verzögerung" angezeigt.
Ggf. muss ich noch mal die Ausgabe von Timestamps in Access einbauen. Dann sehe ich auch, wo genau es hakt. Schade, dass es keine Möglichkeit zum Debbuging in der Form gibt, dass direkt nachvollzogen werden kann, wo Access für was viel Zeit zum Aufruf benötigt.
-
"die Datenmenge scheidet als Grund wohl aus." kann ich nicht stehen lassen.
Was für Datenmengen?
Erst mal liest der SQL Server keine Zeilen, sondern Extents = 8 Datenseiten = 64 KB.
Die Tabelle "Buchungen" hat 44 Tsd Datensätze, bei sagen wir mal 1 KB pro Datensatz sind das 44 MB; das passt bei jedem System locker in den Hauptspeicher, ob da ein Index vorhanden ist oder nicht, wird hier wirklich nicht der ausschlaggebende Faktor sein.
Olaf Helper
[ Blog] [ Xing] [ MVP] -
Ich hätte jetzt einfach mal den Ausführungsplan hochgeladen. Allerdings zeigt mir der Editor nur die Möglichkeit zum Upload eines Bildes. Kann es sein, dass es die Funktion hier im Forum gar nicht gibt? Oder darf man das erst mit einem entsprechenden Ranking?
-
Bilder darf man erst hoch laden, wenn der Account verfiziert wurde, dazu einfach unter Verify Accounts 43 posten
Für Ausführungspläne gibt es aber eine bessere Möglichkeit des teilens, einfach das XML von Plan hier hochladen: https://www.brentozar.com/pastetheplan/ und danach den Link hier teilen
Olaf Helper
[ Blog] [ Xing] [ MVP] -
Nun ja, die Summe der "costs" macht einen Effekt.
Die jeweiligen Loops, die zwar über Index geregelt werden können, dauern eben auch ihre Zeit, auch wenn jeder Einzelzugriff für sich nur (angenommen) 0,1ms dauert.
Alleine die "costs"-Schätzungen sagen nichts über die Ausführungsdauer der einzelnen Teile aus.
Um diese festzustellen, muss man nun die einzelnen Unions jeweils separat testen um den einen Verursacher zu finden, der das Gesamtergebnis stört.Abfragen wie "upper(field) like @Value" erfordern grundsätzlich einen Tablescan, auch wenn ein clustered Index verwendet wird, der nur den Index durchsuchen muss. Während ein direkter Find im Microsekunden bereich liegt, so erfodert ein Scan dann wiederum Millisekunden.
Wenn das Ganz dann auch noch via Join erreicht werden muss, erfolgt der Scan je Zeile.Man könnte theoretisch mit Index über "Computed columns" die Abfrage zusätzlich beschleunigen. Was angesichts der Systemtabellen allerdings ausscheidet, da man die Tabelle um eine berechnete Spalte ergänzen muss um einen Index bilden zu können und dann diese Spalte in der Abfrage verwenden.
Andere DB's können das besser.Und zu guter Letzt: Ist man dann alleine auf der DB oder laufen da noch zusätzlich 1000de anderer Transaktionen, die durchaus zu einem Verdrängungswettbewerb der Seiten im Cache führen können.
Behandelt der SQL-Server nur 1 DB oder ist er durchaus für mehrere DB's zuständig....