Database-indexen zijn een zorg van de ontwikkelaars. Ze hebben het potentieel om de prestaties te verbeteren van zoek- en filterfuncties die een SQL-query in de backend gebruiken. In het tweede deel van deze serie artikelen laat ik de impact zien die een database-index heeft bij het versnellen van filters met behulp van een Java-webtoepassing die is ontwikkeld met Spring Boot en Vaadin.
Lees deel 1 van deze serie als je wilt weten hoe de voorbeeldtoepassing die we hier gaan gebruiken werkt. Je vindt de code op GitHub. Ook, en als je wilt, heb ik een videoversie van dit artikel opgenomen:
De vereiste
We hebben een webpagina met een raster dat een lijst met boeken uit een MariaDB-database toont:
We willen een filter toevoegen zodat gebruikers van deze pagina kunnen zien welke boeken op een bepaalde datum zijn gepubliceerd.
De repository-query en -service implementeren
We moeten enkele wijzigingen aanbrengen in de backend om het filteren van gegevens tegen de publicatiedatum te ondersteunen. In de repository-klasse kunnen we de volgende methode toevoegen:
@Repository
public interface BookRepository extends JpaRepository<Book, Integer> {
Page<Book> findByPublishDate(LocalDate publishDate, Pageable pageable);
}
Dit maakt gebruik van lazy loading zoals we zagen in deel 1 van deze serie artikelen. We hoeven deze methode niet te implementeren - Spring Data zal deze tijdens runtime voor ons maken.
We moeten ook een nieuwe methode toevoegen aan de serviceklasse (de klasse die de gebruikersinterface gebruikt om bedrijfslogica uit te voeren). Hier is hoe:
@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();
}
}
Een filter toevoegen aan de webpagina
Met de backend die het filteren van boeken op publicatiedatum ondersteunt, kunnen we een datumkiezer toevoegen aan de implementatie van de webpagina (of weergave):
@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();
}
...
}
Deze code maakt gewoon een nieuwe DatePicker
object dat luistert naar veranderingen in zijn waarde (via een listener voor waardeverandering). Wanneer de waarde verandert, gebruiken we de serviceklasse om de boeken gepubliceerd te krijgen op de door de gebruiker geselecteerde datum. De overeenkomende boeken worden dan ingesteld als items van het Grid
.
De langzame zoekopdracht testen
We hebben het filter geïmplementeerd; het is echter extreem traag als je bijvoorbeeld 200 duizend rijen in de tabel hebt. Probeer het! Ik heb een artikel geschreven waarin wordt uitgelegd hoe u realistische demogegevens voor Java-toepassingen kunt genereren. Met dit aantal rijen duurde het enkele seconden voordat de applicatie de gegevens op de webpagina op mijn computer (MacBook Pro 2,3 GHz Quad-Core Intel Core i5) liet zien. Dit verpest de gebruikerservaring volledig.
Query's analyseren met "Explain Query"
Als u het loggen van query's hebt ingeschakeld, kunt u de query die door Hibernate wordt gegenereerd, vinden in het logboek van de server. Kopieer het, vervang de vraagtekens door werkelijke waarden en voer het uit in een SQL-databaseclient. Sterker nog, ik kan je wat tijd besparen. Hier is een vereenvoudigde versie van de query:
SELECT id, author, image_data, pages, publish_date, title
FROM book
WHERE publish_date = '2021-09-02';
MariaDB bevat de EXPLAIN
statement dat ons nuttige informatie geeft over hoe de engine schat dat de query wordt uitgevoerd. Om het te gebruiken, voegt u gewoon EXPLAIN
. toe voor de vraag:
EXPLAIN SELECT id, author, image_data, pages, publish_date, title
FROM book
WHERE publish_date = '2021-09-02';
Dit is het resultaat:
De documentatie bevat alles wat u erover moet weten, maar het belangrijkste is de waarde in het type kolom:ALLE . Deze waarde vertelt ons dat de engine schat dat hij alle rijen in de tabel moet ophalen of lezen. Geen goede zaak.
Een index maken
Gelukkig kunnen we dit eenvoudig oplossen door een index te maken op de kolom die we gebruiken om de gegevens te filteren:publish_date
. Hier is hoe:
CREATE INDEX book\_publish\_date_index ON book(publish_date);
Een database-index is een gegevensstructuur die door de engine is gemaakt, meestal een b-tree (b voor gebalanceerd ), en dat versnelt het proces van het vinden van een bepaalde rij in een tabel, dat wil zeggen, zoeken naar een rij met de waarde in de kolom waarop de index is gebouwd. Het proces is sneller dankzij de aard van b-trees - ze houden de gegevens geordend, waardoor de tijdcomplexiteit van O(N) naar O(log(N)) en in sommige gevallen zelfs O(log(1)) wordt verminderd.
De verbetering testen
Als de index is opgebouwd, kunnen we de instructie EXPLAIN opnieuw uitvoeren en zien dat de kolom type de waarde ref toont in plaats van ALLE :
De ref waarde betekent dat de engine de index zal gebruiken wanneer we de query uitvoeren. Het is belangrijk dat u dit controleert wanneer u indexen toevoegt aan uw complexere query's. Gebruik altijd de EXPLAIN
verklaring om te controleren of u aan prestatie wint wanneer u een index introduceert.
Als je de webapplicatie in de browser probeert en een andere datum selecteert in de datumkiezer (je hoeft de server niet opnieuw op te starten), dan zie je een enorm verschil! De gegevens worden bijvoorbeeld in minder dan een seconde op mijn computer opgehaald, in tegenstelling tot enkele seconden voordat we de index maakten!