Datenbankindizes sind Sache der Entwickler. Sie haben das Potenzial, die Leistung von Such- und Filterfunktionen zu verbessern, die eine SQL-Abfrage im Backend verwenden. Im zweiten Teil dieser Artikelserie zeige ich die Auswirkungen, die ein Datenbankindex auf die Beschleunigung von Filtern hat, indem ich eine Java-Webanwendung verwende, die mit Spring Boot und Vaadin entwickelt wurde.
Lesen Sie Teil 1 dieser Serie, wenn Sie erfahren möchten, wie die Beispielanwendung funktioniert, die wir hier verwenden werden. Sie finden den Code auf GitHub. Außerdem, und wenn Sie es vorziehen, habe ich eine Videoversion dieses Artikels aufgenommen:
Die Anforderung
Wir haben eine Webseite mit einem Raster, das eine Liste von Büchern aus einer MariaDB-Datenbank zeigt:
Wir möchten einen Filter hinzufügen, damit Benutzer dieser Seite sehen können, welche Bücher an einem bestimmten Datum veröffentlicht wurden.
Implementieren der Repository-Abfrage und des Dienstes
Wir müssen einige Änderungen im Backend vornehmen, um das Filtern von Daten nach dem Veröffentlichungsdatum zu unterstützen. In der Repository-Klasse können wir die folgende Methode hinzufügen:
@Repository
public interface BookRepository extends JpaRepository<Book, Integer> {
Page<Book> findByPublishDate(LocalDate publishDate, Pageable pageable);
}
Dies verwendet Lazy Loading, wie wir in Teil 1 dieser Artikelserie gesehen haben. Wir müssen diese Methode nicht implementieren – Spring Data erstellt sie zur Laufzeit für uns.
Wir müssen auch eine neue Methode zur Dienstklasse hinzufügen (das ist die Klasse, die die Benutzeroberfläche verwendet, um die Geschäftslogik auszuführen). Hier ist wie:
@Service
public class BookService {
private final BookRepository repository;
...
public Stream<Book> findAll(LocalDate publishDate, int page, int pageSize) {
return repository.findByPublishDate(publishDate, PageRequest.of(page, pageSize)).stream();
}
}
Hinzufügen eines Filters zur Webseite
Da das Backend das Filtern von Büchern nach Veröffentlichungsdatum unterstützt, können wir der Implementierung der Webseite (oder Ansicht) eine Datumsauswahl hinzufügen:
@Route("")
public class BooksView extends VerticalLayout {
public BooksView(BookService service) {
...
var filter = new DatePicker("Filter by publish date");
filter.addValueChangeListener(event ->
grid.setItems(query ->
service.findAll(filter.getValue(), query.getPage(), query.getPageSize())
)
);
add(filter, grid);
setSizeFull();
}
...
}
Dieser Code erstellt lediglich einen neuen DatePicker
Objekt, das auf Änderungen seines Werts lauscht (über einen Wertänderungs-Listener). Wenn sich der Wert ändert, verwenden wir die Serviceklasse, um die Bücher zu dem vom Benutzer ausgewählten Datum zu veröffentlichen. Die passenden Bücher werden dann als Elemente des Grid
gesetzt .
Testen der langsamen Abfrage
Wir haben den Filter implementiert; Es ist jedoch extrem langsam, wenn Sie beispielsweise 200.000 Zeilen in der Tabelle haben. Versuch es! Ich habe einen Artikel geschrieben, der erklärt, wie man realistische Demodaten für Java-Anwendungen generiert. Bei dieser Anzahl von Zeilen brauchte die Anwendung mehrere Sekunden, um die Daten auf der Webseite auf meinem Computer (MacBook Pro 2,3 GHz Quad-Core Intel Core i5) anzuzeigen. Dies ruiniert die Benutzererfahrung vollständig.
Analysieren von Abfragen mit „Explain Query“
Wenn Sie die Abfrageprotokollierung aktiviert haben, finden Sie die von Hibernate generierte Abfrage im Protokoll des Servers. Kopieren Sie es, ersetzen Sie die Fragezeichen durch tatsächliche Werte und führen Sie es in einem SQL-Datenbankclient aus. Tatsächlich kann ich Ihnen etwas Zeit ersparen. Hier ist eine vereinfachte Version der Abfrage:
SELECT id, author, image_data, pages, publish_date, title
FROM book
WHERE publish_date = '2021-09-02';
MariaDB enthält den EXPLAIN
-Anweisung, die uns nützliche Informationen darüber gibt, wie die Engine schätzt, dass sie die Abfrage ausführen wird. Um es zu verwenden, fügen Sie einfach EXPLAIN
hinzu vor der Abfrage:
EXPLAIN SELECT id, author, image_data, pages, publish_date, title
FROM book
WHERE publish_date = '2021-09-02';
Hier ist das Ergebnis:
Die Dokumentation enthält alles, was Sie darüber wissen müssen, aber das Wichtige ist der Wert im Typ Spalte:ALLE . Dieser Wert sagt uns, dass die Engine davon ausgeht, dass sie alle Zeilen in der Tabelle abrufen oder lesen muss. Keine gute Sache.
Index erstellen
Glücklicherweise können wir dies leicht beheben, indem wir einen Index für die Spalte erstellen, die wir zum Filtern der Daten verwenden:publish_date
. Hier ist wie:
CREATE INDEX book\_publish\_date_index ON book(publish_date);
Ein Datenbankindex ist eine von der Engine erstellte Datenstruktur, normalerweise ein B-Tree (b für ausgeglichen ), und das beschleunigt das Auffinden einer bestimmten Zeile in einer Tabelle, d. h. die Suche nach einer Zeile mit dem Wert in der Spalte, auf der der Index aufgebaut ist. Der Prozess ist dank der Natur von b-Bäumen schneller – sie halten die Daten geordnet und reduzieren die Zeitkomplexität von O(N) auf O(log(N)) und in einigen Fällen sogar auf O(log(1)).
Testen der Verbesserung
Mit dem erstellten Index können wir die EXPLAIN-Anweisung erneut ausführen und sehen, dass die Typspalte den Wert ref anzeigt statt ALLE :
Der ref value bedeutet, dass die Engine den Index verwendet, wenn wir die Abfrage ausführen. Es ist wichtig, dass Sie dies überprüfen, wenn Sie Ihren komplexeren Abfragen Indizes hinzufügen. Verwenden Sie immer den EXPLAIN
Anweisung, um zu überprüfen, ob Sie an Leistung gewinnen, wenn Sie einen Index einführen.
Wenn Sie die Webanwendung im Browser ausprobieren und ein anderes Datum in der Datumsauswahl auswählen (kein Neustart des Servers erforderlich), werden Sie einen großen Unterschied feststellen! Beispielsweise werden die Daten auf meinem Computer in weniger als einer Sekunde abgerufen, im Gegensatz zu mehreren Sekunden, bevor wir den Index erstellt haben!