Databázové indexy jsou záležitostí vývojářů. Mají potenciál zlepšit výkon funkcí vyhledávání a filtrování, které v backendu používají dotaz SQL. Ve druhé části této série článků ukážu vliv, který má databázový index na zrychlení filtrů pomocí webové aplikace Java vyvinuté pomocí Spring Boot a Vaadin.
Přečtěte si část 1 této série, pokud se chcete dozvědět, jak funguje ukázková aplikace, kterou zde použijeme. Kód najdete na GitHubu. Také, a chcete-li, jsem nahrál video verzi tohoto článku:
Požadavek
Máme webovou stránku s mřížkou, která zobrazuje seznam knih z databáze MariaDB:
Chceme přidat filtr, který uživatelům této stránky umožní vidět, které knihy byly k danému datu vydány.
Implementace dotazu a služby úložiště
Musíme provést nějaké změny v backendu, abychom podporovali filtrování dat podle data zveřejnění. Do třídy úložiště můžeme přidat následující metodu:
@Repository
public interface BookRepository extends JpaRepository<Book, Integer> {
Page<Book> findByPublishDate(LocalDate publishDate, Pageable pageable);
}
To využívá líné načítání, jak jsme viděli v části 1 této série článků. Tuto metodu nemusíme implementovat – Spring Data ji za nás vytvoří za běhu.
Musíme také přidat novou metodu do třídy služeb (což je třída, kterou uživatelské rozhraní používá ke spuštění obchodní logiky). Zde je postup:
@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();
}
}
Přidání filtru na webovou stránku
Díky backendu, který podporuje filtrování knih podle data vydání, můžeme do implementace webové stránky (nebo zobrazení) přidat nástroj pro výběr data:
@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();
}
...
}
Tento kód pouze vytvoří nový DatePicker
objekt, který naslouchá změnám své hodnoty (prostřednictvím posluchače změny hodnoty). Když se hodnota změní, použijeme třídu služby k vydání knih k datu zvolenému uživatelem. Odpovídající knihy jsou pak nastaveny jako položky Grid
.
Testování pomalého dotazu
Implementovali jsme filtr; je však extrémně pomalý, pokud máte v tabulce např. 200 tisíc řádků. Zkus to! Napsal jsem článek, který vysvětluje, jak generovat realistická demo data pro Java aplikace. Při tomto počtu řádků trvalo aplikaci několik sekund, než zobrazila data na webové stránce na mém počítači (MacBook Pro 2,3 GHz Quad-Core Intel Core i5). To zcela ničí uživatelský dojem.
Analýza dotazů pomocí „vysvětlujícího dotazu“
Pokud jste povolili protokolování dotazů, můžete dotaz vygenerovaný Hibernatem najít v protokolu serveru. Zkopírujte jej, nahraďte otazníky skutečnými hodnotami a spusťte jej v databázovém klientovi SQL. Ve skutečnosti vám mohu ušetřit čas. Zde je zjednodušená verze dotazu:
SELECT id, author, image_data, pages, publish_date, title
FROM book
WHERE publish_date = '2021-09-02';
MariaDB obsahuje EXPLAIN
prohlášení, které nám poskytuje užitečné informace o tom, jak motor odhaduje, že bude dotaz spuštěn. Chcete-li jej použít, stačí přidat EXPLAIN
před dotazem:
EXPLAIN SELECT id, author, image_data, pages, publish_date, title
FROM book
WHERE publish_date = '2021-09-02';
Zde je výsledek:
Dokumentace obsahuje vše, co o ní potřebujete vědět, ale důležitá je hodnota v type sloupec:VŠECHNY . Tato hodnota nám říká, že stroj odhaduje, že bude muset načíst nebo přečíst všechny řádky v tabulce. Není to dobrá věc.
Vytvoření indexu
Naštěstí to můžeme snadno opravit vytvořením indexu ve sloupci, který používáme k filtrování dat:publish_date
. Zde je postup:
CREATE INDEX book\_publish\_date_index ON book(publish_date);
Databázový index je datová struktura vytvořená motorem, obvykle b-strom (b pro vyvážené ), a to urychluje proces hledání určitého řádku v tabulce, tedy hledání řádku s hodnotou ve sloupci, na kterém je index postaven. Tento proces je rychlejší díky povaze b-stromů – udržují data uspořádaná a snižují časovou složitost z O(N) na O(log(N)) a v některých případech dokonce na O(log(1)).
Testování vylepšení
S vytvořeným indexem můžeme znovu spustit příkaz EXPLAIN a zjistit, že sloupec type zobrazuje hodnotu ref místo VŠECHNY :
ref hodnota znamená, že motor použije index, když spustíme dotaz. Je důležité, abyste toto zaškrtli, když přidáváte indexy ke složitějším dotazům. Vždy používejte EXPLAIN
dvakrát zkontrolujte, že při zavádění indexu získáváte na výkonu.
Pokud vyzkoušíte webovou aplikaci v prohlížeči a vyberete jiné datum ve výběru data (není třeba restartovat server), uvidíte obrovský rozdíl! Například data jsou na mém počítači načtena za méně než sekundu, na rozdíl od několika sekund před vytvořením indexu!