Ta strona została przetłumaczona maszynowo. Przeczytaj oryginał angielski. English

Biblioteka IBSurgeon

Negatywny wpływ indeksów na wydajność operacji INSERT, UPDATE i DELETE w Firebird SQL

Ogólnie rzecz biorąc, indeksy są niezbędne w każdej poważnej bazie danych, ponieważ są kluczowe dla przyspieszenia zapytań z klauzulą WHERE (SELECT, UPDATE, MERGE, DELETE itp.). Jednak każdy indeks ma swój koszt, a koszt ten jest widoczny, gdy porównamy szybkość operacji INSERT/UPDATE/DELETE na polach indeksowanych i nieindeksowanych.

W tym artykule zademonstrujemy wpływ indeksu z kilkoma unikalnymi kluczami na szybkość operacji.

Baza testowa

Stwórzmy prostą bazę danych Firebird:

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;

Aby zademonstrować problem ze złymi indeksami, wykonajmy następujące operacje:

  • Najpierw wstawiamy 1 milion rekordów do tabeli
  • Następnie aktualizujemy te rekordy
  • Potem usuwamy wszystkie rekordy
  • I na koniec uruchamiamy SELECT count(*) z tabeli

Poniższe polecenia wykonują opisane operacje:

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;

Uruchommy to i zachowajmy wyniki do dalszej analizy.

Następnie utwórzmy kolejną tabelę o tej samej strukturze - i dodajmy tam indeks dla kolumny MANORWOMAN

Code
CREATE INDEX TABLEIND1_IDX1 ON TABLEIND1 (MANORWOMAN);

Jak widać powyżej w skrypcie, wstawiamy do tej kolumny tylko wartości całkowite 0 lub 1. Taki indeks jest bezużyteczny dla zapytań SELECT i teoretycznie wszyscy programiści baz danych powinni unikać takich złych indeksów (z wyjątkiem bardzo szczególnych przypadków z niezrównoważonym rozkładem wartości w tabeli), ale w praktyce istnieje wiele indeksów z 2 unikalnymi wartościami, a nawet z 1 z nich.

Następnie powtórzmy skrypt z tym indeksem i porównajmy wyniki - zobacz poniższą tabelę:

Bez indeksu dla MANORWOMAN Z indeksem dla 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 więc zły indeks zmniejsza wydajność około 2 razy podczas wstawiania lub aktualizacji. Widzimy również, że nieoptymalny indeks znacznie zwiększa liczbę zapisów i pobrań rekordów.

Pobierzmy statystyki dla tej przykładowej bazy danych (ze złym indeksem dla MANORWOMAN) i spróbujmy znaleźć szczegóły. Aby zebrać statystyki, uruchamiamy następujące polecenie:

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

Sekcja statystyk tabeli TABLEIND1 i indeksów wygląda intrygująco, ale jakie przydatne informacje nam daje?

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

Aby zrozumieć znaczenie pokazanych liczb i wartości procentowych, możemy użyć HQbird Database Analyst, który oferuje wizualną interpretację statystyk bazy danych:

Klikając Reports/View recommendations możemy znaleźć odpowiednie wyjaśnienie dla tego indeksu:

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%

W produkcyjnych bazach danych często możemy zobaczyć wiele złych indeksów, które mogą znacznie wpłynąć na wydajność bazy danych. W tym przykładzie widzimy tabelę z 13 milionami rekordów, która ma 7 złych indeksów, które są (najprawdopodobniej) bezużyteczne i znacznie zmniejszają wydajność Firebird:

Jeśli HQbird Database Analyst oznacza indeksy jako «Useless» (tj. mają tylko 1 wartość), takie indeksy zaleca się natychmiast usunąć, jeśli to możliwe.

Jeśli indeks jest oznaczony jako Bad (kilka wartości), należy również rozważyć jego usunięcie, ale ostrożnie: możliwe, że zły indeks jest używany w jakimś konkretnym zapytaniu SQL, które wymaga określonej kombinacji indeksów (w tym złego), aby działać szybko.

Oczywiście te zapytania SQL powinny zostać przepisane, aby używać bardziej optymalnego planu wykonania bez złego indeksu. Aby znaleźć takie zapytania, należy przeprowadzić audyt planów SQL: najłatwiejszym sposobem jest uruchomienie sesji śledzenia z HQbird PerfMon i włączenie rejestrowania planów SQL, a następnie przeszukanie planów SQL pod kątem konkretnych złych indeksów.