PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Преглед на промените в индекса в PostgreSQL 11

Правилното прилагане на индекси може да направи заявките изключително бързи.

Индексите използват указатели за бърз достъп до страници с данни.

Настъпиха големи промени в индексите в PostgreSQL 11, бяха пуснати много дългоочаквани пачове.

Нека да разгледаме някои от страхотните функции на тази версия.

Изграждане на паралелни B-TREE индекси

PostgreSQL 11 въведе инфраструктурна корекция, за да позволи създаването на паралелен индекс.

Може да се използва само с B-Tree индекс, както засега.

Създаването на паралелен B-Tree индекс е два до три пъти по-бързо от това да правите едно и също нещо без паралелна работа (или серийно изграждане).

В PostgreSQL 11 паралелното създаване на индекс е включено по подразбиране.

Има два важни параметъра:

  • max_parallel_workers – Задава максималния брой работници, които системата може да поддържа за паралелни заявки.
  • max_parallel_maintenance_workers – Контролира максималния брой работни процеси, които могат да се използват за СЪЗДАВАНЕ НА ИНДЕКС.

Нека го проверим с пример:

severalnines=# CREATE TABLE test_btree AS SELECT generate_series(1,100000000) AS id;
SELECT 100000000
severalnines=#  SET maintenance_work_mem = '1GB';
severalnines=# \timing
severalnines=#  CREATE INDEX q ON test_btree (id);
TIME: 25294.185 ms (00:25.294)

Нека опитаме с 8-посочна паралелна работа:

severalnines=# SET maintenance_work_mem = '2GB';
severalnines=# SET max_parallel_workers = 16;
severalnines=# SET max_parallel_maintenance_workers = 8;
severalnines=# \timing
severalnines=# CREATE INDEX q1 ON test_btree (id);
TIME: 11001.240 ms (00:11.001)

Можем да видим разликата в производителността при паралелния работник, над 60% производителност само с малка промяна. Maintenance_work_mem също може да бъде увеличен, за да получите повече производителност.

Таблицата ALTER също помага за увеличаване на паралелните работници. Синтаксисът по-долу може да се използва за увеличаване на паралелните работници заедно с max_parallel_maintenance_workers. Това напълно заобикаля модела на разходите.

ALTER TABLE test_btree SET (parallel_workers = 24);

Съвет:НУЛИРАЙТЕ по подразбиране, след като изграждането на индекса приключи, за да предотвратите неблагоприятния план за заявки.

CREATE INDEX с опцията CONCURRENTLY поддържа паралелни компилации без специални ограничения, само първото сканиране на таблицата всъщност се извършва паралелно.

По-задълбочени тестове за производителност можете да намерите тук.

Добавяне на заключване на предикатите за хеш, същност и индекси на Джин

PostgreSQL 11 се доставя с поддръжка на предикатно заключване за хеш индекси, gin индекси и gist индекси. Това ще направи изолацията на транзакциите SERIALIZABLE много по-ефективна с тези индекси.

Предимство:заключването на предикатите може да осигури по-добра производителност при ниво на изолация, което може да се сериализира, като намали броя на фалшивите положителни резултати, което води до ненужен отказ на сериализацията.

В PostgreSQL 10 диапазонът на заключване е релацията, но в PostgreSQL 11 заключването е само страница.

Нека го тестваме.

severalnines=# CREATE TABLE sv_predicate_lock1(c1 INT, c2 VARCHAR(10)) ;
CREATE TABLE
severalnines=# CREATE INDEX idx1_sv_predicate_lock1 ON sv_predicate_lock1 USING 'hash(c1) ;
CREATE INDEX
severalnines=# INSERT INTO sv_predicate_lock1 VALUES (generate_series(1, 100000),  'puja') ;
INSERT 0 100000
severalnines=#  BEGIN ISOLATION LEVEL SERIALIZABLE ;
BEGIN
severalnines=# SELECT * FROM sv_predicate_lock1 WHERE c1=10000 FOR UPDATE ;
  c1   |  c2
-------+-------
 10000 | puja
(1 row)

Както можем да видим по-долу, заключването е на ниво страница вместо на релация. В PostgreSQL 10 беше на ниво релация, така че това е ГОЛЯМА ПЕЧАЛБА за едновременни транзакции в PostgreSQL 11.

severalnines=# SELECT locktype, relation::regclass, mode FROM pg_locks ;
   locktype    |        relation         |      mode
---------------+-------------------------+-----------------
 relation      | pg_locks                | AccessShareLock
 relation      | idx1_sv_predicate_lock1 | AccessShareLock
 relation      | sv_predicate_lock1      | RowShareLock
 virtualxid    |                         | ExclusiveLock
 transactionid |                         | ExclusiveLock
 page          | idx1_sv_predicate_lock1 | SIReadLock
 tuple         | sv_predicate_lock1      | SIReadLock
(7 rows)

Съвет:Последователното сканиране винаги ще се нуждае от предикатно заключване на ниво релация. Това може да доведе до повишен процент на откази на сериализация. Може да е полезно да се насърчи използването на индексни сканирания чрез намаляване на random_page_cost и/или увеличаване на cpu_tuple_cost.

Разрешаване на ГОРЕЩИ актуализации за някои изразни индекси

Функцията Heap Only Tuple (HOT) елиминира излишните записи в индекса и позволява повторното използване на пространството, заето от изтрити или остарели АКТУАЛИЗИРАНИ кортежи без извършване на вакуум в цялата таблица. Той намалява размера на индекса, като избягва създаването на записи с идентичен ключ.

Ако стойността на индексния израз е непроменена след АКТУАЛИЗИРАНЕ, разрешете HOT актуализации там, където преди това PostgreSQL ги е забранил, давайки значително увеличение на производителността в тези случаи.

Това е особено полезно за индекси като JSON->>поле, където стойността на JSON се променя, но индексираната стойност не.

Тази функция беше върната в 11.1 поради влошаване на производителността (AT Free BSD само според Simon), повече подробности/бенчмарк можете да намерите тук. Това трябва да бъде коригирано в бъдеща версия.

Разрешаване на сканиране на цели страници с хеш индекс

Хеш индекс:Планирането на заявки ще обмисли използването на хеш индекс всеки път, когато индексирана колона участва в сравнение с помощта на оператора =. Освен това не беше безопасен при срив (не е влязъл в WAL), така че трябва да се изгради отново след сривове на DB и промените в хеша не са записани чрез поточно репликация.

В PostgreSQL 10 хеш индексът е регистриран в WAL, което означава, че е безопасен от CRASH и може да бъде репликиран. Хеш индексите използват много по-малко място в сравнение с B-Tree, за да могат да се поберат по-добре в паметта.

В PostgreSQL 11 индексите на Btree имат оптимизация, наречена „вакуум на една страница“, която опортюнистично премахва мъртвите индексни указатели от индексните страници, предотвратявайки огромно количество раздуване на индекса, което иначе би настъпило. Същата логика е пренесена към хеш индексите. Ускорява рециклирането на пространството, намалява подуването.

СТАТИСТИКА на индекса на функции

Вече е възможно да се посочи стойност STATISTICS за колона с индекс на функция. Той е много ценен за ефективността на специализирано приложение. Вече можем да събираме статистически данни за колоните с изрази, които ще помогнат на плановицата да вземе по-точно решение.

severalnines=# CREATE INDEX idx1_stats ON stat ((s1 + s2)) ;
CREATE INDEX
severalnines=# ALTER INDEX idx1_stats ALTER COLUMN 1 SET STATISTICS 1000 ;
ALTER INDEX
severalnines=# \d+ idx1_stats
 Index "public.idx1_stats"
Column | Type | Definition | Storage | Stats target
--------+---------+------------+---------+--------------
expr | numeric | (c1 + c2) | main | 1000
btree, for table "public.stat1"

amcheck

Добавена е нова проверка на модула Contrib. Могат да се проверяват само индексите на B-Tree.

Нека го тестваме!

severalnines=# CREATE EXTENSION amcheck ;
CREATE EXTENSION
severalnines=# SELECT bt_index_check('idx1_stats') ;
ERROR: invalid page in block 0 of relation base/16385/16580
severalnines=#CREATE INDEX idx1_hash_data1 ON data1 USING hash (c1) ;
CREATE INDEX
severalnines=# SELECT bt_index_check('idx1_hash_data1') ;
ERROR: only B-Tree indexes are supported as targets for verification
DETAIL: Relation "idx1_hash_data1" is not a B-Tree index.

Възможен е локален разделен индекс

Преди PostgreSQL11 не беше възможно да се създаде индекс на дъщерна таблица или разделена таблица.

В PostgreSQL 11, когато CREATE INDEX се изпълнява върху разделена таблица/родителска таблица, той създава записи в каталог за индекс на разделената таблица и каскади, за да създаде действителни индекси на съществуващите дялове. Ще ги създаде и в бъдещи дялове.

Нека се опитаме да създадем родителска таблица и да я разделим:

severalnines=# create table test_part ( a int, list varchar(5) ) partition by list (list);
CREATE TABLE
severalnines=# create table part_1 partition of test_part for values in ('India');
CREATE TABLE
severalnines=# create table part_2 partition of test_part for values in ('USA');
CREATE TABLE
severalnines=#
severalnines=# \d+ test_part
                                        Table "public.test_part"
 Column |         Type         | Collation | Nullable | Default | Storage  | Stats target | Description
--------+----------------------+-----------+----------+---------+----------+--------------+-------------
 a      | integer              |           |          |         | plain    |              |
 list   | character varying(5) |           |          |         | extended |              |
Partition key: LIST (list)
Partitions: part_1 FOR VALUES IN ('India'),
            part_2 FOR VALUES IN ('USA')

Нека се опитаме да създадем индекс на родителската таблица:

severalnines=# create index i_test on test_part (a);
CREATE INDEX
severalnines=# \d part_2
                     Table "public.part_2"
 Column |         Type         | Collation | Nullable | Default
--------+----------------------+-----------+----------+---------
 a      | integer              |           |          |
 list   | character varying(5) |           |          |
Partition of: test_part FOR VALUES IN ('USA')
Indexes:
    "part_2_a_idx" btree (a)

severalnines=# \d part_1
                     Table "public.part_1"
 Column |         Type         | Collation | Nullable | Default
--------+----------------------+-----------+----------+---------
 a      | integer              |           |          |
 list   | character varying(5) |           |          |
Partition of: test_part FOR VALUES IN ('India')
Indexes:
    "part_1_a_idx" btree (a)

Индексът е каскаден надолу към всички дялове в PostgreSQL 11, което е наистина страхотна функция.

Покриващ индекс (включва КЛАУЗА за индекси)

Може да се посочи клауза INCLUDE за добавяне на колони към индекса. Това е ефективно при добавяне на колони, които не са свързани с уникално ограничение на уникален индекс. Колоните INCLUDE съществуват единствено, за да позволят на повече заявки да се възползват от сканирането само с индекс. Само индексите на B-дърво поддържат клауза INCLUDE както засега.

Нека проверим поведението без INNCLUDE. Няма да използва само индексно сканиране, ако в SELECT се появят допълнителни колони. Това може да се постигне чрез използване на клауза INCLUDE.

severalnines=# CREATE TABLE no_include (a int, b int, c int);
CREATE TABLE
severalnines=# INSERT INTO no_include SELECT 3 * val, 3 * val + 1, 3 * val + 2 FROM generate_series(0, 1000000) as val;
INSERT 0 1000001
severalnines=# CREATE UNIQUE INDEX old_unique_idx ON no_include(a, b);
CREATE INDEX
severalnines=# VACUUM ANALYZE;
VACUUM
EXPLAIN ANALYZE SELECT a, b FROM no_include WHERE a < 1000;  - It will do index only scan 
EXPLAIN ANALYZE SELECT a, b, c FROM no_include WHERE a < 1000; - It will not do index only scan as we have extra column in select. 
severalnines=# CREATE INDEX old_idx ON no_include (a, b, c);
CREATE INDEX
severalnines=# VACUUM ANALYZE;
VACUUM
severalnines=# EXPLAIN ANALYZE SELECT a, b, c FROM no_include WHERE a < 1000;   - It did index only scan as index on all three columns. 
                     QUERY PLAN
-------------------------------------------------
 Index Only Scan using old_idx on no_include
     (cost=0.42..14.92 rows=371 width=12)
     (actual time=0.086..0.291 rows=334 loops=1)
   Index Cond: (a < 1000)
   Heap Fetches: 0
 Planning Time: 2.108 ms
 Execution Time: 0.396 ms
(5 rows)

Нека опитаме с клауза за включване. В примера по-долу УНИКАЛНОТО ОГРАНИЧЕНИЕ е създадено в колони a и b, но индексът включва колона c.

severalnines=# CREATE TABLE with_include (a int, b int, c int);
CREATE TABLE
severalnines=# INSERT INTO with_include SELECT 3 * val, 3 * val + 1, 3 * val + 2 FROM generate_series(0, 1000000) as val;
INSERT 0 1000001
severalnines=# CREATE UNIQUE INDEX new_unique_idx ON with_include(a, b) INCLUDE (c);
CREATE INDEX
severalnines=# VACUUM ANALYZE;
VACUUM
severalnines=#  EXPLAIN ANALYZE SELECT a, b, c FROM with_include WHERE a < 10000;
                       QUERY PLAN
-----------------------------------------------------
 Index Only Scan using new_unique_idx on with_include
     (cost=0.42..116.06 rows=3408 width=12)
     (actual time=0.085..2.348 rows=3334 loops=1)
   Index Cond: (a < 10000)
   Heap Fetches: 0
 Planning Time: 1.851 ms
 Execution Time: 2.840 ms
(5 rows)

Не може да има припокриване между колоните в главния списък с колони и тези от списъка за включване

severalnines=# CREATE UNIQUE INDEX new_unique_idx ON with_include(a, b) INCLUDE (a);
ERROR:  42P17: included columns must not intersect with key columns
LOCATION:  DefineIndex, indexcmds.c:373

Колона, използвана с израз в основния списък, работи:

severalnines=# CREATE UNIQUE INDEX new_unique_idx_2 ON with_include(round(a), b) INCLUDE (a);
CREATE INDEX

Изразите не могат да се използват в списък за включване, защото не могат да се използват при сканиране само с индекс:

severalnines=# CREATE UNIQUE INDEX new_unique_idx_2 ON with_include(a, b) INCLUDE (round(c));
ERROR:  0A000: expressions are not supported in included columns
LOCATION:  ComputeIndexAttrs, indexcmds.c:1446

Заключение

Новите функции на PostgreSQL със сигурност ще подобрят живота на DBA, така че се насочва да се превърне в силен алтернативен избор в DB с отворен код. Разбирам, че някои функции на индексите в момента са ограничени до B-Tree, това все още е чудесно начало на ерата на паралелно изпълнение за PostgreSQL и се насочва към приятен инструмент, който да разгледате отблизо. Благодаря!


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Генериране на последователност в ред

  2. Многоредова вложка с pg-promise

  3. Рекурсивна CTE конкатенация на полета с родители от произволна точка

  4. 3 начина за изброяване на всички съхранени процедури, които препращат към таблица в PostgreSQL

  5. Как да инсталирам libpq-fe.h?