Wenn wir mit dem Ruby on Rails-Framework arbeiten, haben wir es in der Regel mit relationalen Datenbanken wie MySQL oder PostgreSQL zu tun. Bei der Definition von Migrationen mit Hilfe der Active Record Migrations stoßen wir auf die so genannten Indizes, aber Anfänger verstehen oft nicht ganz, was Indizes sind und welche Vorteile sie bringen.
Wenn wir mit dem Ruby on Rails-Framework arbeiten, haben wir es in der Regel mit relationalen Datenbanken wie MySQL oder PostgreSQL zu tun. Bei der Definition von Migrationen mit Hilfe der Active Record Migrations stoßen wir auf die so genannten Indizes, aber Anfänger verstehen oft nicht ganz, was Indizes sind und welche Vorteile sie bringen.
In diesem Beitrag möchte ich erklären, was Indizes sind, wofür sie verwendet werden und einige bewährte Verfahren für ihre Verwendung vorstellen.
Datenbank
Es gibt viele Datenbank-Engines, und eine der beliebtesten sind die bereits erwähnten MySQL, PostgreSQL, Oracle oder Microsoft SQL Server. Es handelt sich um relationale Datenbanken, was bedeutet, dass alle Daten miteinander in Beziehung stehen und in Tabellen gespeichert werden. Jede Tabellenzeile wird als Datensatz bezeichnet, und jeder Datensatz hat eine eigene eindeutige Kennung (id). Sie können die Rangliste der beliebtesten Datenbank-Engines unter https://db-engines.com/en/ranking einsehen. Dort finden Sie auch einige nicht-relationale Datenbanken, wie z. B. MongoDB.
Erstellen eines Indexes
Die Tabellen in unseren Datenbanken können einige wenige bis zu mehreren Dutzend - in Extremfällen bis zu mehreren hundert - Spalten haben. Beachten Sie, dass jede Tabelle eine unbegrenzte Anzahl von Zeilen haben kann. Diese Anzahl ergibt sich nicht direkt aus der Struktur der Datenbank, und wir sollten immer davon ausgehen, dass die Anzahl der Datensätze sukzessive ansteigt und damit auch unsere Datenbank wächst. Anfängliche Annahmen und Abfragen, die in bestehenden Anwendungen geschrieben wurden, mögen für eine kleine oder mittlere Anzahl von Datensätzen gut sein, aber mit der Zeit, wenn mehr Daten ankommen, ist die Kommunikation der Anwendung mit der Datenbank nicht mehr effizient.
Die Aufgabe des Programmierers besteht darin, Abfragen zu schreiben, um Daten aus der Tabelle oder den Tabellen abzurufen, aber die optimale Art der Verarbeitung der Abfrage hängt von der Datenbank-Engine ab. Denken Sie daran, dass Datenbank-Engines Daten von der Festplatte in den Speicher laden und sie dann abfragen. Das bedeutet, dass, wenn viele Benutzer gleichzeitig komplexe Operationen durchführen, mehrere von ihnen warten müssen, bis sie an der Reihe sind, weil ihnen die Ressourcen für die Durchführung ihrer Abfragen fehlen. Aus diesem Grund sind relevante Indizes so wichtig.
Wiki: Index - eine Datenstruktur, die die Geschwindigkeit bei der Durchführung von Suchoperationen in einer Tabelle erhöht.
Für jeden Index müssen wir Schlüssel (für eine oder mehrere Spalten) definieren, die für die Suche nach Datensätzen in der Tabelle verwendet werden sollen. Die Daten im Index werden nach dem zuvor definierten Schlüssel sortiert, was die Suche nach Daten in der Tabelle erheblich beschleunigt. Das einfachste Beispiel aus dem Alltag ist ein Telefonbuch, in dem die Personen nach Vor- und Nachnamen sortiert sind. Man kann sagen, dass unser Index in diesem Fall der Vor- und Nachname sein wird.
Wie wählt man den besten Indexschlüssel aus? Das ist nicht schwer - Sie müssen nur ein paar Regeln beachten. Erstellen Sie einen Index, der auf Spalten basiert, die:
- wird oft in unseren Anfragen verwendet (WHERE),
- ergeben in Kombination miteinander einen eindeutigen Wert (d. h. einen, der genau eine Zeile angibt),
- werden als sogenannte Verbindungsspalten (JOIN) verwendet,
- geben die selektivsten Schlüssel an, d. h. diejenigen, die beim Schreiben einer Abfrage die geringste Anzahl von Zeilen ergeben.
Wenn wir bereits wissen, welche Schlüssel für unsere Tabelle optimal sein werden, können wir uns auch fragen, wie viele Indizes wir benötigen. In diesem Fall ist es am besten, die Abfragen, die sich auf unsere Tabelle beziehen werden, bereits in der Entwurfsphase zu kennen.
Wir erstellen Indizes für bestimmte Abfragen, die erscheinen werden, aber schreiben sie nicht für jede Spalte. Indizes müssen ebenso wie Tabellen irgendwo gespeichert werden. Wenn wir also Tabellen mit einem Index für jede Spalte erstellen, müssen wir berücksichtigen, dass der verwendete Speicherplatz erheblich zunehmen kann.
Eindeutigen Index erstellen
Ein weiterer Punkt, über den wir nachdenken müssen, ist die Einzigartigkeit. Es lohnt sich, fünf Minuten mehr Zeit in die Überlegung zu investieren, ob unser Index wirklich einzigartig ist. Auf diese Weise teilen wir dem Abfrageoptimierer mit, dass er keine Duplikate in der Abfrage erwarten muss. Zum Beispiel: E-Mail-Adressen:
frozenstringliteral: true
class CreateUsers < ActiveRecord::Migration[6.0]
def change
createtable :users do |t|
t.string :email, null: false
end
addindex :users, :email, eindeutig: true
end
end
Am Beispiel der PostgreSQL-Engine zeige ich den Unterschied in der Abfragegeschwindigkeit für die E-Mail-Spalte mit einem eindeutigen Index und ohne einen Index.
1. Sie können Muster verwenden Code Snippets in Ihrer eigenen Datenbank, um das folgende Beispiel zu testen. Erstellen wir zunächst eine leere Tabelle mit einer Spalte:
CREATE TABLE users (
email varchar
);
2. Lassen Sie uns 10.000 Datensätze für den Test erzeugen:
DO $
BEGIN FOR i IN 1..10000 LOOP
INSERT INTO users values((select 'user' || i || '@example.com'));
END LOOP; END;
$;
Wir werden EXPLAIN ANALYZE verwenden, um zu prüfen, wie schnell unsere Abfrage bearbeitet wird, wenn wir einen bestimmten Benutzer in der Datenbank finden wollen.
EXPLAIN ANALYZE SELECT email FROM users WHERE email = 'user890example.com';
Unsere Abfrage erzwang eine Iteration durch die gesamte Tabelle auf der Suche nach dem Datensatz, der uns interessiert.
Dieser Vorgang wird als sequentielles Scannen bezeichnet. In diesem Fall ist das Lesen der gesamten Tabelle und das Herausfiltern bestimmter Zeilen der beste Weg, um die Aufgabe zu erledigen.
PostgreSQL filtert die nicht benötigten Zeilen heraus und gibt nur diejenigen zurück, die uns interessieren. Das ist in diesem Fall wirklich das Beste, was man tun kann. Sequentielles Scannen ist nicht immer schlecht, es gibt Fälle, in denen sequenzielles Scannen ideal ist.
4. Jetzt ist es an der Zeit, die Abfrage zu überprüfen, die bereits für die Tabelle mit dem INDEX UNIQUE gemacht wurde. Setzen wir den Index und führen wir die Abfrage aus.
EATE UNIQUE INDEX index_email on users(email);
EXPLAIN ANALYZE SELECT email FROM users WHERE email = 'user890example.com';
Dieses Mal hat PostgreSQL die Vorteile des Index-Scannens genutzt, da alle benötigten Spalten bereits im Index enthalten sind.
Wenn Sie nur wenige Zeilen auswählen, ist die Verwendung des Indexes sehr effizient. Wenn jedoch mehr Daten ausgewählt werden, ist das Scannen des Index und der Tabelle zu zeitaufwändig.
Zusammenfassung
Wie Sie sehen können, ist die Ausführungszeit für eine Abfrage auf eine Spalte mit einem Index viel kürzer (im gezeigten Beispiel ist es eine Verringerung von 1,267 ms auf 0,111 ms, also so viel wie 91,24%!). Der wichtigste Unterschied ist die Art und Weise, wie PostgreSQL nach dem Datensatz sucht, der uns interessiert. Im ersten Fall musste die Datenbankmaschine die gesamte Tabelle nach dem gesuchten Datensatz durchsuchen. Im zweiten Fall ist die Indexstruktur jedoch sortiert und eindeutig, so dass die Engine wusste, wo sich der Datensatz befand, was die Abfrageverarbeitung erheblich beschleunigte.
Bei großen Datenbanken und sehr komplexen Abfragen können richtig eingestellte Indizes die Arbeit Ihrer Anwendung erheblich beschleunigen, ohne dass die Geschwindigkeit der Maschine, auf der Sie die Datenbank durchsuchen, erhöht werden muss.
Es sei daran erinnert, dass die Erstellung von Indizes für jede Spalte keine gute Praxis ist. Etablierte Indizes beschleunigen die Arbeit des Optimierers bei der Suche nach Daten von Interesse, verlangsamen aber gleichzeitig das Einfügen neuer und die Aktualisierung bestehender Indizes.