Tipps und Tricks für schnelle Datenbanken

Hier erfahren Sie, wie Sie Ihren Datenbanken einen Performance-Boost verpassen können

Zuerst eine kleine Checkliste mit allgemeinen Fragen bzw. Hinweisen:

Wo könnten Engpässe liegen?

Ein Blick in den Prozess-Monitor hilft Ihnen, Stellen aufzeigen, wo Verbesserungen möglich sind.

Kann der konzeptionelle Aufbau der Datenbank optimiert werden?

Schauen Sie sich Ihr Datenmodell an, um evtl. Optimierungspotential zu erkennen. Große, schwere Datenbanken haben längere Zugriffszeiten.

Besteht die Möglichkeit, die Datenbank in kleinere Tabellen, ggf. mit Untertabellen, aufzuteilen?

Nehmen Sie längere Ladezeiten wahr, wenn Sie bestimmte Ansichten aufrufen?

Könnten Sie die Daten in mehrere Ansichten aufteilen, indem Sie die Ansichten filtern? Oder nicht direkt benötigte Spalten ausblenden? Besteht die Möglichkeit, die Anzahl von Spalten mit Formeln zu reduzieren?

Könnten Daten auch gruppiert werden, damit weniger Daten auf einen Schlag geladen werden müssen?

Sind die Skripte optimiert? Wie ist der Zugriff auf bestimmte Daten aufgebaut?

Prüfen Sie Ihre Skripte. Oft ist es möglich, ein Skript in mehrere Skripte aufzubrechen und so die Abläufe zu beschleunigen.

Im nächsten Abschnitt finden Sie ein paar Anregungen, wie Sie Skripte optimieren können. 🥕

Tipps und Tricks

Hier zeigen wir Ihnen, wie Sie allein durch den Aufbau Ihres Skripts Zugriffs- und Bearbeitungszeiten verbessern können:

Warum verschachtelte Schleifen gefährlich sind

Falls Sie select oder eine for-Schleife innerhalb einer anderen Schleife verwenden, sollten Sie sich fragen, ob dies tatsächlich notwendig ist.

Kann der verschachtelte Teil unabhängig ausgeführt werden? Dann sollten Sie die Schleifen nacheinander ausführen.

Beispiel

Nehmen wir an, dass Tabelle1 und Tabelle2 jeweils 1.000 Datensätze haben.

let anzahl := 0;
for i in select Tabelle1 do
    for j in select Tabelle2 do
        anzahl := anzahl + 1
    end
end;
anzahl

Welchen Wert hat anzahl nach Durchlauf des Skripts (in Zeile 7)?

Antwort: 1.000.000 😱

Denn für jeden der 1.000 Datensätze in Tabelle1 durchlaufen wir jedes Mal alle 1000 Datensätze in Tabelle2.

Beim Verschachteln von Schleifen multiplizieren Sie die Iterationen (m * n).

So geht's besser

Führen Sie – wenn möglich – die Schleifen der Reihe nach aus, dann addieren Sie lediglich die Iterationen (m + n).

let anzahl := 0;
for i in select Tabelle1 do
    anzahl := anzahl + 1
end;
for j in select Tabelle2 do
    anzahl := anzahl + 1
end;
anzahl

Welchen Wert hat anzahl in diesem Fall?

Antwort: 2.000 😁

Denn wir durchlaufen erst alle Datensätze der Tabelle1 und dann die der Tabelle2.

Wie do as ... die Performance verbessern kann

In Ninox unterscheiden wir zwischen lesenden und schreibenden Transaktionen:

  • lesende Transaktionen, wie z.B. das Laden einer Tabelle, können gleichzeitig stattfinden

  • schreibende Transaktionen, wie z.B. das Hinzufügen von Daten, werden der Reihe nach ausgeführt

Mehr zu Transaktionen und Performance der Skripte optimieren.

Eine häufige Ursache für schlechte Performance sind schreibende Transaktionen, die andere schreibende Transaktionen aufhalten.

Beispiel 1

Dieses einfache Skript erstellt 1.000 Datensätze.

for i in range(1000) do
    create Tabelle1
end;

Auf den ersten Blick ist kein Problem erkennbar.

Wenn Sie dieses Skript allerdings in einem Button in der Web-App ausführen, wird für jede Iteration eine eigene Transaktion erstellt.

Beim Erstellen des ersten Datensatzes, warten also 999 weitere Transaktionen darauf, ausgeführt zu werden!

So geht's besser

Bündeln Sie viele kleine schreibende Transaktionen zu 1 Transaktion.

do as transaction
    for i in range(1000) do
        create Tabelle1;
    end
end;

Hier wird die gesamte Schleife innerhalb 1 Transaktion durchgeführt. Das reduziert in unserem Beispiel die Warteschlange auf 0.

Beispiel 2

Es geht aber auch andersrum. Im folgenden Beispiel erhalten alle Kunden aus der Tabelle Kunden eine E-Mail durch ein Skript in einem Trigger nach Änderung.

Streng genommen ergänzen Sie hier die vorangehende schreibende Transaktion (welche den Trigger auslöst) um die Anweisungen aus dem Trigger-Skript.

for kunde in select Kunden do
    sendEmail({
        from: "support@ninox.com",
        to: kunde.'E-Mail-Adresse',
        subject: "Newsletter",
        text: 'E-Mail-Vorlage'
    })
end

Die schreibende Transaktion wird hier unnötigerweise aufgepumpt, sodass der Vorgang länger dauert als nötig.

So geht's besser

Führen Sie nicht schreibende Bestandteile einer großen schreibenden Transaktion in einer separaten Transaktion aus.

do as deferred    
    for kunde in select Kunden do
        sendEmail({
            from: "support@ninox.com",
            to: kunde.'E-Mail-Adresse',
            subject: "Newsletter",
            text: 'E-Mail-Vorlage'
        })
    end
end

Hier wird Ihr Trigger-Skript nicht mehr mit der vorangehenden Transaktion ausgeführt, sondern in einer nachgestellten Transaktion.

Mehr zu do as ... in Performance der Skripte optimieren.

Warum Sie sparsam mit select umgehen sollten

Wenn Sie select verwenden, ist es hilfreich, die gewünschte Auswahl genauer zu definieren.

Sie sollten dabei allerdings select nie direkt mit Klammern [...] verwenden.

Beispiel 1

select Kunden[Firmensitz = "Deutschland"]

Hier werden erst alle Kunden ausgewählt, anschließend wird nach Firmensitz gefiltert.

So geht's besser

Ziehen Sie die Filter-Bedingung aus der Klammer [...] in die select-Anweisung.

select Kunden where Firmensitz = "Deutschland"

Durch where werden von Anfang an nur passende Datensätze ausgewählt.

select-Anweisungen sind super hilfreich, Sie sollten sie aber grundsätzlich als perfomance-lastig betrachten. Jede select-Anweisung, die Sie nicht setzen, ist eine gute select-Anweisung. 😉

Beispiel 2

Sie möchten Kunden aus Deutschland abhängig vom jeweiligen Umsatz in 3 Gruppen einteilen.

let deGross := select Kunden where Firmensitz = "Deutschland" and Umsatz > 10000;
let deMittel := select Kunden where Firmensitz = "Deutschland" and Umsatz <= 10000 and Umsatz > 5000; 
let deKlein := select Kunden where Firmensitz = "Deutschland" and Umsatz <= 5000

Die select-Anweisungen hier sind einzeln betrachtet völlig in Ordnung, das Skript könnte jedoch insgesamt optimiert werden, da die select-Anweisungen viel gemeinsam haben.

So geht's besser

Versuchen Sie mit möglichst wenig select-Anweisungen auszukommen. Speichern Sie wiederverwendbare Rückgabewerte von select-Anweisungen in Variablen.

let deKunden := select Kunden where Firmensitz = "Deutschland";
let deGross := deKunden[Umsatz > 10000];
let deMittel := deKunden[Umsatz <= 10000 and Umsatz > 5000];
let deKlein := deKunden[Umsatz <= 5000];

Hier wird nur eine select-Anweisung verwendet und in einer Variable gespeichert. Diese Variable wird dann verwendet, um die Auswahl zu präzisieren.

Prüfen Sie außerdem, ob es eine Ninox-Funktion gibt, die Sie bei Ihrem Vorhaben unterstützen kann. Insbesondere, wenn Sie dadurch select-Anweisungen und Schleifen reduzieren können.

Beispiel 3

Sie suchen nach dem niedrigsten Umsatz in der Tabelle Kunden.

let kleinsterUmsatz := Infinity;
for kunde in select Kunden do
	if kunde.Umsatz < kleinsterUmsatz then
		kleinsterUmsatz := kunde.Umsatz
	end
end;
kleinsterUmsatz

Dieses Skript ist viel zu kompliziert für einen so einfachen Anwendungsfall. 😟

So geht's besser

Verwenden Sie die entsprechende Ninox-Funktion, hier: min(). Aber es gibt auch andere nützliche Funktionen, wie z.B. max(), sum(), first(), count(), uvm!

min((select Kunden).Umsatz)

Dieses Skript reduziert die 7 Zeilen aus dem vorherigen Skript auf eine und bereitet Ihnen beim Lesen bestimmt weniger Kopfschmerzen. ☺️

Zum Funktionen – Überblick

Last updated