Tato stránka byla strojově přeložena. Přečtěte si anglický originál. English

Knihovna IBSurgeon

Negativní dopad indexů na výkon INSERT, UPDATE a DELETE v Firebird SQL

Obecně platí, že indexy jsou nezbytné pro každou vážnější databázi, protože jsou klíčové pro zrychlení dotazů s klauzulí WHERE (SELECT, UPDATE, MERGE, DELETE atd.). Každý index však má svou cenu, a ta je zřejmá, když porovnáme rychlost operací INSERT/UPDATE/DELETE na indexovaných a neindexovaných polích.

V tomto článku demonstrujeme dopad indexu s několika unikátními klíči na rychlost operací.

Testovací databáze

Vytvořme jednoduchou Firebird databázi:

Code
CREATE DATABASE “E:\TESTFIREBIRDINDEX.FDB” USER “SYSDBA” PASSWORD “masterkey”;
CREATE TABLE TABLEIND1 (
    I1            INTEGER NOT NULL PRIMARY KEY,
    NAME          VARCHAR(250),
    MANORWOMAN  SMALLINT
);
CREATE GENERATOR G1;
SET TERM ^ ;

create or alter procedure INS1MLN
returns (
    INSERTED_CNT integer)
as
BEGIN
inserted_cnt = 0;
WHILE (inserted_cnt <1000000) DO
BEGIN
 Insert into tableind1(i1, name, manorwoman) values(gen_id(g1,1), 'TEST name', (:inserted_cnt - (:inserted_cnt/2)*2));
 inserted_cnt=inserted_cnt+1;
END
suspend;
END^
SET TERM ; ^
GRANT INSERT ON TABLEIND1 TO PROCEDURE INS1MLN;
GRANT EXECUTE ON PROCEDURE INS1MLN TO SYSDBA;
COMMIT;

Pro demonstraci problému se špatnými indexy provedeme následující operace:

  • Nejprve vložíme 1 milion záznamů do tabulky
  • Poté tyto záznamy aktualizujeme
  • Následně smažeme všechny záznamy
  • A nakonec spustíme SELECT count(*) z tabulky

Následující příkazy provádějí popsané operace:

Code
set stat on;  /*enable display of statistics in isql*/
select * from ins1mln;
update tableind1 SET MANORWOMAN  = 3;
delete from tableind1;
select count(*) from tableind1;

Spustíme to a ponecháme výsledky pro další analýzu.

Poté vytvoříme další tabulku se stejnou strukturou - a přidáme index pro sloupec MANORWOMAN:

Code
CREATE INDEX TABLEIND1_IDX1 ON TABLEIND1 (MANORWOMAN);

Jak vidíte výše ve skriptu, do tohoto sloupce vkládáme pouze celočíselné hodnoty 0 nebo 1. Takový index je pro select dotazy nepoužitelný a teoreticky by se měli všichni vývojáři databází takovým špatným indexům vyhnout (kromě velmi zvláštního případu s nevyváženým rozdělením hodnot v tabulce), ale v praxi existuje mnoho indexů se 2 unikátními hodnotami nebo dokonce s 1 z nich.

Poté zopakujeme skript s tímto indexem a porovnáme výsledky - viz následující tabulka:

Bez indexu pro MANORWOMAN S indexem pro MANORWOMAN
SQL> set stat on; /*show statistics*/
SQL > select * from ins1mln;
INSERTED_CNT
============
1000000
Current memory = 10487216
Delta memory = 80560
Max memory = 12569996
Elapsed time= 13.33 sec
Buffers = 2048
Reads = 0
Writes 18756
Fetches = 7833503
SQL > update tableind1 SET MANORWOMAN = 3;
Current memory = 76551788
Delta memory = 66064572
Max memory = 111442520
Elapsed time= 15.04 sec
Buffers = 2048
Reads = 16166
Writes 15852
Fetches = 6032307
SQL> delete from tableind1;
Current memory = 76550240
Delta memory = -1548
Max memory = 111442520
Elapsed time= 3.27 sec
Buffers = 2048
Reads = 16147
Writes 16006
Fetches = 5032277
SQL> select count(*) from tableind1;
COUNT
============
0
Current memory = 76552064
Delta memory = 1824
Max memory = 111442520
Elapsed time= 1.35 sec
Buffers = 2048
Reads = 16021
Writes 1791
Fetches = 2032278
SQL> set stat on; /*show statistics*/
SQL> select * from ins1mln;
INSERTED_CNT
============
1000000
Current memory = 10484140
Delta memory = 75524
Max memory = 12569996
Elapsed time= 23.94 sec
Buffers = 2048
Reads = 1
Writes 23942
Fetches = 11459599
SQL> update tableind1 SET MANORWOMAN = 3;
Current memory = 76548712
Delta memory = 66064572
Max memory = 111439444
Elapsed time= 29.30 sec
Buffers = 2048
Reads = 16167
Writes 19492
Fetches = 10035948
SQL> delete from tableind1;
Current memory = 76547164
Delta memory = -1548
Max memory = 111439444
Elapsed time= 3.41 sec
Buffers = 2048
Reads = 16147
Writes 15967
Fetches = 5032277
SQL> select count(*) from tableind1;
COUNT
============
0
Current memory = 76548988
Delta memory = 1824
Max memory = 111439444
Elapsed time= 0.69 sec
Buffers = 2048
Reads = 16021
Writes 1901
Fetches = 2032278

Takže špatný index snižuje výkon přibližně 2krát při vkládání nebo aktualizaci. Také vidíme, že neoptimální index výrazně zvyšuje počet zápisů a čtení záznamů.

Získejme statistiky pro tuto vzorovou databázi (se špatným indexem pro MANORWOMAN) a pokusme se najít nějaké podrobnosti. Pro shromáždění statistik spustíme následující příkaz:

Code
gstat -r e:\testfirebirdindex.fdb > e:\teststat.txt

Sekce statistik tabulky TABLEIND1 a indexů vypadá zajímavě, ale jaké užitečné informace nám poskytuje?

Code
TABLEIND1 (128)
    Primary pointer page: 166, Index root page: 167
    Average record length: 0.00, total records: 1000000
    Average version length: 27.00, total versions: 1000000, max versions: 1
    Data pages: 16130, data page slots: 16130, average fill: 93%
    Fill distribution:
       0 - 19% = 1
      20 - 39% = 0
      40 - 59% = 0
      60 - 79% = 0
      80 - 99% = 16129

    Index RDB$PRIMARY1 (0)
      Depth: 3, leaf buckets: 1463, nodes: 1000000
      Average data length: 1.00, total dup: 0, max dup: 0
      Fill distribution:
           0 - 19% = 0
          20 - 39% = 0
          40 - 59% = 0
          60 - 79% = 1
          80 - 99% = 1462

    Index TABLEIND1_IDX1 (1)
      Depth: 3, leaf buckets: 2873, nodes: 2000000
      Average data length: 0.00, total dup: 1999997, max dup: 999999
      Fill distribution:
           0 - 19% = 0
          20 - 39% = 1
          40 - 59% = 1056
          60 - 79% = 0
          80 - 99% = 1816

Abychom pochopili význam zobrazených čísel a procentuálních hodnot, můžeme použít HQbird Database Analyst, který nabízí vizuální interpretaci statistik databáze:

Kliknutím na Reports/View recommendations najdeme příslušné vysvětlení pro tento index:

Bad indices count: 1. By `bad` we name indices with many duplicate keys (90% of all keys) and big groups of equal keys (30% of all keys). Big groups of equal keys slowdown garbage collection - MaxEquals here is % of max groups of keys having equal values. Index search for such an index is not efficient. You can drop such indices (if they are not on FK constraints).

Index ( Relation) Duplicates MaxEquals TABLEIND1.IDX1 (TABLEIND1) : 100%, 50%

V produkčních databázích často vidíme mnoho špatných indexů, které mohou výrazně ovlivnit výkon databáze. V tomto příkladu vidíme tabulku se 13 miliony záznamů, která má 7 špatných indexů, které jsou (s největší pravděpodobností) nepoužitelné a výrazně snižují výkon Firebirdu:

Pokud HQbird Database Analyst zvýrazní indexy jako «Useless» (tj. mají pouze 1 hodnotu), doporučuje se takové indexy okamžitě odstranit, pokud je to možné.

Pokud je index zvýrazněn jako Bad (několik hodnot), mělo by se zvážit i jeho odstranění, ale s opatrností: je možné, že špatný index je použit v nějakém konkrétním SQL dotazu, který vyžaduje specifickou kombinaci indexů (včetně toho špatného), aby běžel rychle.

Tyto SQL dotazy by měly být rozhodně přepsány tak, aby používaly optimálnější plán provádění bez špatného indexu. Pro nalezení takových dotazů by měl být proveden audit SQL plánů: nejjednodušší způsob je spustit trace session s HQbird PerfMon a povolit logování SQL plánů, a poté prohledat SQL plány na konkrétní špatné indexy.