Databaseindekser er et problem for udviklerne. De har potentialet til at forbedre ydeevnen af søge- og filterfunktioner, der bruger en SQL-forespørgsel i backend. I anden del af denne serie af artikler vil jeg vise den indvirkning, som et databaseindeks har for at fremskynde filtre ved hjælp af en Java-webapplikation udviklet med Spring Boot og Vaadin.
Læs del 1 af denne serie, hvis du vil lære, hvordan den eksempelapplikation, som vi vil bruge her, fungerer. Du kan finde koden på GitHub. Også, og hvis du foretrækker det, har jeg optaget en videoversion af denne artikel:
Kravet
Vi har en webside med et gitter, der viser en liste over bøger fra en MariaDB-database:
Vi ønsker at tilføje et filter, så brugere af denne side kan se, hvilke bøger der blev udgivet på en given dato.
Implementering af lagerforespørgsel og service
Vi er nødt til at foretage nogle ændringer i backend for at understøtte filtrering af data efter udgivelsesdatoen. I repository-klassen kan vi tilføje følgende metode:
@Repository
public interface BookRepository extends JpaRepository<Book, Integer> {
Page<Book> findByPublishDate(LocalDate publishDate, Pageable pageable);
}
Dette bruger doven indlæsning, som vi så i del 1 af denne serie af artikler. Vi behøver ikke at implementere denne metode – Spring Data vil oprette den for os under kørsel.
Vi skal også tilføje en ny metode til serviceklassen (som er den klasse, som brugergrænsefladen bruger til at køre forretningslogik). Sådan gør du:
@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();
}
}
Tilføjelse af et filter til websiden
Med backend, der understøtter filtrering af bøger efter udgivelsesdato, kan vi tilføje en datovælger til implementeringen af websiden (eller visningen):
@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();
}
...
}
Denne kode opretter bare en ny DatePicker
objekt, der lytter til ændringer i sin værdi (via en værdiændringslytter). Når værdien ændres, bruger vi serviceklassen til at få bøgerne udgivet på den dato, brugeren har valgt. De matchende bøger indstilles derefter som elementer i Grid
.
Test af den langsomme forespørgsel
Vi har implementeret filteret; det er dog ekstremt langsomt, hvis du for eksempel har 200 tusind rækker i tabellen. Prøv det! Jeg skrev en artikel, der forklarer, hvordan man genererer realistiske demodata til Java-applikationer. Med dette antal rækker tog applikationen flere sekunder at vise dataene på websiden på min maskine (MacBook Pro 2,3 GHz Quad-Core Intel Core i5). Dette ødelægger fuldstændig brugeroplevelsen.
Analyse af forespørgsler med "Forklar forespørgsel"
Hvis du har aktiveret forespørgselslogning, kan du finde den forespørgsel, der genereres af Hibernate, i serverens log. Kopier det, erstat spørgsmålstegnene med faktiske værdier, og kør det i en SQL-databaseklient. Faktisk kan jeg spare dig for noget tid. Her er en forenklet version af forespørgslen:
SELECT id, author, image_data, pages, publish_date, title
FROM book
WHERE publish_date = '2021-09-02';
MariaDB inkluderer EXPLAIN
erklæring, der giver os nyttige oplysninger om, hvordan motoren estimerer, at det vil køre forespørgslen. For at bruge det skal du blot tilføje EXPLAIN
før forespørgslen:
EXPLAIN SELECT id, author, image_data, pages, publish_date, title
FROM book
WHERE publish_date = '2021-09-02';
Her er resultatet:
Dokumentationen har alt, hvad du behøver at vide om det, men den vigtige bit er værdien i typen kolonne:ALLE . Denne værdi fortæller os, at motoren vurderer, at den skal hente eller læse alle rækkerne i tabellen. Ikke en god ting.
Oprettelse af et indeks
Heldigvis kan vi nemt rette dette ved at oprette et indeks på kolonnen, som vi bruger til at filtrere dataene:publish_date
. Sådan gør du:
CREATE INDEX book\_publish\_date_index ON book(publish_date);
Et databaseindeks er en datastruktur skabt af motoren, normalt et b-træ (b for balanceret ), og det fremskynder processen med at finde en bestemt række i en tabel, det vil sige at søge efter en række givet værdien i kolonnen, som indekset er bygget på. Processen er hurtigere takket være arten af b-træer – de holder dataene i orden, hvilket reducerer tidskompleksiteten fra O(N) til O(log(N)) og endda O(log(1)) i nogle tilfælde.
Test af forbedringen
Med indekset bygget kan vi køre EXPLAIN-sætningen igen og se, at typekolonnen viser værdien ref i stedet for ALLE :
ref værdi betyder, at motoren vil bruge indekset, når vi kører forespørgslen. Det er vigtigt, at du tjekker dette, når du tilføjer indekser til dine mere komplekse forespørgsler. Brug altid EXPLAIN
erklæring for at dobbelttjekke, at du vinder i ydeevne, når du introducerer et indeks.
Hvis du prøver webapplikationen i browseren og vælger en anden dato i datovælgeren (ingen grund til at genstarte serveren), vil du se en kæmpe forskel! For eksempel hentes dataene på mindre end et sekund på min maskine i modsætning til flere sekunder før vi oprettede indekset!