Цю сторінку перекладено машинним перекладом. Читайте англійський оригінал. English

Бібліотека IBSurgeon

Негативний вплив індексів на продуктивність INSERT, UPDATE та DELETE у Firebird SQL

Загалом, індекси необхідні для будь-якої серйозної бази даних, оскільки вони критично важливі для прискорення запитів із реченням WHERE (SELECT, UPDATE, MERGE, DELETE тощо). Однак кожен індекс має свою ціну, і ця ціна стає очевидною, коли ми порівнюємо швидкість операцій INSERT/UPDATE/DELETE для індексованих та неіндексованих полів.

У цій статті ми продемонструємо вплив індексу з кількома унікальними ключами на швидкість операцій.

Тестова база даних

Створімо просту базу даних 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;

Щоб продемонструвати проблему з поганими індексами, виконаймо такі операції:

  • Спочатку вставимо 1 мільйон записів у таблицю
  • Потім оновимо ці записи
  • Після цього видалимо всі записи
  • І, нарешті, виконаємо SELECT count(*) з таблиці

Наведені нижче команди виконують описані операції:

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;

Запустімо їх і збережемо результати для подальшого аналізу.

Після цього створімо іншу таблицю з такою самою структурою - і додамо індекс для стовпця MANORWOMAN

Code
CREATE INDEX TABLEIND1_IDX1 ON TABLEIND1 (MANORWOMAN);

Як видно вище у скрипті, ми вставляємо в цей стовпець лише цілі значення 0 або 1. Такий індекс є марним для запитів SELECT, і теоретично всі розробники баз даних мали б уникати таких поганих індексів (за винятком дуже особливого випадку з незбалансованим розподілом значень у таблиці), але на практиці існує багато індексів із 2 унікальними значеннями або навіть з одним із них.

Потім повторімо скрипт із цим індексом і порівняймо результати - див. наступну таблицю:

Без індексу для MANORWOMAN З індексом для 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

Отже, поганий індекс знижує продуктивність приблизно вдвічі під час вставлення або оновлення. Також ми бачимо, що неоптимальний індекс значно збільшує кількість записів і вибірок записів.

Отримаймо статистику для цієї прикладної бази даних (з поганим індексом для MANORWOMAN) і спробуймо знайти деякі деталі. Щоб зібрати статистику, виконаймо таку команду:

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

Розділ статистики таблиці TABLEIND1 та індексів виглядає інтригуюче, але яку корисну інформацію він нам дає?

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

Щоб зрозуміти значення показаних чисел і відсоткових значень, ми можемо скористатися HQbird Database Analyst, який пропонує візуальну інтерпретацію статистики бази даних:

Натиснувши Reports/View recommendations, ми можемо знайти відповідне пояснення для цього індексу:

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%

У виробничих базах даних часто можна побачити багато поганих індексів, які можуть значно вплинути на продуктивність бази даних. У цьому прикладі ми бачимо таблицю з 13 мільйонами записів, яка має 7 поганих індексів, які (найімовірніше) є марними та значно знижують продуктивність Firebird:

Якщо HQbird Database Analyst виділяє індекси як «Useless» (тобто вони мають лише 1 значення), такі індекси рекомендується негайно видалити, якщо це можливо.

Якщо індекс виділено як Bad (там є кілька значень), його також слід розглянути для видалення, але з обережністю: можливо, поганий індекс використовується в якомусь конкретному SQL-запиті, який потребує певної комбінації індексів (включно з поганим), щоб працювати швидко.

Звісно, ці SQL-запити слід переписати, щоб використовувати більш оптимальний план виконання без поганого індексу. Щоб знайти такі запити, слід провести аудит SQL-планів: найпростіший спосіб - запустити сеанс трасування з HQbird PerfMon і ввімкнути журналювання SQL-планів, а потім шукати SQL-плани для конкретних поганих індексів.