Статьи

Индексы Postgres

Недавно у меня возникла ситуация, когда мне нужно было подумать, как я использую индексы Postgres. У меня была простая таблица Book со следующей схемой…

01
02
03
04
05
06
07
08
09
10
11
>\d book
 
                  Table "shopping.book"
       Column        |          Type          | Modifiers
---------------------+------------------------+-----------
 id                  | uuid                   | not null
 version             | bigint                 | not null
 amount_minor_units  | integer                | not null
 currency            | character varying(255) | not null
 author       | character varying(255) | not null
 publisher           | character varying(255) |

Столбцы автора и издателя были просто указателями String на фактические ссылки на авторов и издателей, которые были в другой системе, что означало, что классические внешние ключи нельзя использовать и что эти парни были просто обычными столбцами.

Мне нужно было понять, как таблица будет работать с большим количеством данных, поэтому сначала немного простого SQL, чтобы добавить множество тестовых данных:

1
2
3
> CREATE EXTENSION "uuid-ossp";
> insert into book (id, version, amount_minor_units, currency, author, publisher)
select uuid_generate_v4(), 2, 22, 'USD', 'author' || x.id, 'publisher' || x.id from generate_series(1,1000000) AS x(id);

Эта таблица будет поражена много раз этим простым запросом:

1
select * from book where author = 'Tony Biggins' and publisher='Books unlimited';

Чтобы получить объяснение, я сделал:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
dublintech=> EXPLAIN (FORMAT JSON) select * from book where author = 'Tony Biggins' and publisher = 'Books unlimited';
                                                            QUERY PLAN                                                            
-----------------------------------------------------------------------------------------------------------------------------------
 [                                                                                                                                +
   {                                                                                                                              +
     "Plan": {                                                                                                                    +
       "Node Type": "Seq Scan",                                                                                                   +
       "Relation Name": "book",                                                                                                   +
       "Alias": "book",                                                                                                           +
       "Startup Cost": 0.00,                                                                                                      +
       "Total Cost": 123424.88,                                                                                                   +
       "Plan Rows": 1,                                                                                                            +
       "Plan Width": 127,                                                                                                         +
       "Filter": "(((author)::text = 'Tony Biggins'::text) AND ((publisher)::text = 'Books unlimited'::text))"+
     }                                                                                                                            +
   }                                                                                                                              +
 ]
(1 row)

Как видно, Postgres выполняет сканирование Seq , то есть сканирование таблицы . Я хотел ускорить процесс. В таблице был только один индекс для идентификатора . Это был просто обычный индекс B-Tree, который был бы бесполезен в этом запросе, поскольку его даже не было в предложении where . Некоторые из вариантов, о которых я думал:

  • Создать индекс по автору или издателю
  • Создать индекс по автору и создать индекс по издателю
  • Создайте индекс комбинации как для индекса, так и для издателя.

Хммм … пусть начнутся расследования. Начните с индексации только автора .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
dublintech=> create index author_idx on book(author);
dublintech=> EXPLAIN (FORMAT JSON) select * from book where publisher = 'publisher3' and author='author3';
                                  QUERY PLAN                                  
-------------------------------------------------------------------------------
 [                                                                            +
   {                                                                          +
     "Plan": {                                                                +
       "Node Type": "Index Scan",                                             +
       "Scan Direction": "Forward",                                           +
       "Index Name": "author_idx",                                  +
       "Relation Name": "book",                                               +
       "Alias": "book",                                                       +
       "Startup Cost": 0.42,                                                  +
       "Total Cost": 8.45,                                                    +
       "Plan Rows": 1,                                                        +
       "Plan Width": 127,                                                     +
       "Index Cond": "((author)::text = 'author3'::text)",+
       "Filter": "((publisher)::text = 'publisher3'::text)"                     +
     }                                                                        +
   }                                                                          +
 ]
(1 row)

Как видно, Postgres выполняет сканирование индекса, и общая стоимость значительно ниже, чем тот же запрос, который использует сканирование таблицы. А как насчет подхода с несколькими столбцами? Конечно, поскольку оба используются в запросе, он должен быть быстрее снова, верно?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
dublintech=> create index author_publisher_idx on book(author, publisher);
CREATE INDEX
dublintech=> EXPLAIN (FORMAT JSON) select * from book where publisher = 'publisher3' and author='author3';
                                                        QUERY PLAN                                                        
---------------------------------------------------------------------------------------------------------------------------
 [                                                                                                                        +
   {                                                                                                                      +
     "Plan": {                                                                                                            +
       "Node Type": "Index Scan",                                                                                         +
       "Scan Direction": "Forward",                                                                                       +
       "Index Name": "author_publisher_idx",                                                                     +
       "Relation Name": "book",                                                                                           +
       "Alias": "book",                                                                                                   +
       "Startup Cost": 0.42,                                                                                              +
       "Total Cost": 8.45,                                                                                                +
       "Plan Rows": 1,                                                                                                    +
       "Plan Width": 127,                                                                                                 +
       "Index Cond": "(((author)::text = 'author3'::text) AND ((publisher)::text = 'publisher3'::text))"+
     }                                                                                                                    +
   }                                                                                                                      +
 ]
(1 row)

На этот раз Postgres использует мультииндекс, но запрос не идет быстрее. Май, залить ? Напомним, как мы заселили стол.

1
2
insert into book (id, version, amount_minor_units, currency, author, publisher)
select uuid_generate_v4(), 2, 22, 'USD', 'author' || x.id, 'publisher' || x.id from generate_series(1,1000000) AS x(id);

Есть много строк, но каждая строка имеет уникальное авторское значение и уникальное издательское значение. Это будет означать, что авторский индекс для этого запроса должен работать точно так же. Аналогия может быть, вы идете в музыкальный магазин в поисках нового набора громкоговорителей, которые кто-то сказал вам купить, которые имеют определенную стоимость и определенную выходную мощность (количество ватт). Когда вы входите в магазин, вы видите, что колонки красиво упорядочены по цене, и знаете что? Нет двух комплектов колонок одинаковой стоимости. Думаю об этом. Собираетесь ли вы найти динамики быстрее, если вы используете только стоимость или вы используете стоимость и громкоговоритель?

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

Теперь давайте возьмем эту точку до крайности в наших тестовых данных. Предположим, что все авторы были одинаковыми. Индекс автора становится бесполезным, и если у нас нет индекса комбинации автор / издатель, мы вернемся к просмотру таблицы.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
// drop combination index and just leave author index on table
dublintech=> drop index author_uesr_ref_idx;
DROP INDEX
dublintech=> update book set author='author3';
dublintech=> EXPLAIN (FORMAT JSON) select * from book where publisher = 'publisher3' and author='author3';
                                                      QUERY PLAN                                                      
-----------------------------------------------------------------------------------------------------------------------
 [                                                                                                                    +
   {                                                                                                                  +
     "Plan": {                                                                                                        +
       "Node Type": "Seq Scan",                                                                                       +
       "Relation Name": "book",                                                                                       +
       "Alias": "book",                                                                                               +
       "Startup Cost": 0.00,                                                                                          +
       "Total Cost": 153088.88,                                                                                       +
       "Plan Rows": 1,                                                                                                +
       "Plan Width": 123,                                                                                             +
       "Filter": "(((publisher)::text = 'publisher3'::text) AND ((author)::text = 'author3'::text))"+
     }                                                                                                                +
   }                                                                                                                  +
 ]
(1 row)

Таким образом, из этого можно сделать вывод, что индексы с одним столбцом для комбинированного поиска могут работать так же хорошо, как и комбинационные индексы, когда в данных этого отдельного столбца существует огромная разница. Однако, когда их нет, они не будут работать так же хорошо, и следует использовать комбинационный индекс. Да, я проверил крайности, но это лучший способ прояснить принципы. И обратите внимание: для случая, когда существует максимальная разница в данных, добавление другого индекса в другой столбец в предложении where, издатель не имеет значения. Это как и ожидалось.

Хорошо, давайте остановимся на случае, когда в столбце есть значительная разница в значениях данных. Рассмотрим случай максимальной дисперсии, и в запросе всегда используется только точное соответствие . В этом случае все значения авторов гарантированно будут уникальными, и вы никогда не будете заинтересованы в том, чтобы делать что-то вроде меньше или больше чем . Так почему бы не использовать хеш-индекс вместо индекса B-Tree?

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
dublintech=> create index author_hash on book using hash (author);
dublintech=> EXPLAIN (FORMAT JSON) select * from book where publisher = 'publisher3' and author='author3';
                                  QUERY PLAN                                  
-------------------------------------------------------------------------------
 [                                                                            +
   {                                                                          +
     "Plan": {                                                                +
       "Node Type": "Index Scan",                                             +
       "Scan Direction": "NoMovement",                                        +
       "Index Name": "author_hash",                                 +
       "Relation Name": "book",                                               +
       "Alias": "book",                                                       +
       "Startup Cost": 0.00,                                                  +
       "Total Cost": 8.02,                                                    +
       "Plan Rows": 1,                                                        +
       "Plan Width": 127,                                                     +
       "Index Cond": "((author)::text = 'author3'::text)",+
       "Filter": "((publisher)::text = 'publisher3'::text)"                     +
     }                                                                        +
   }                                                                          +
 ]
(1 row)

Интересно, мы снова пошли быстрее. На этот раз не большая разница, но, тем не менее, это улучшение, которое может быть более актуальным при увеличении объема данных и / или когда требуется более сложный запрос с большим количеством вычислений. Из этой части можно смело сделать вывод, что, если вас интересуют только точные совпадения, тогда хеш-индекс превосходит индекс b-дерева. До следующего раза береги себя. Рекомендации:

  • http://www.postgresql.org/docs/9.1/static/using-explain.html
  • http://www.postgresql.org/docs/9.3/static/indexes-bitmap-scans.html
Ссылка: Индексы Postgres от нашего партнера JCG Алекса Стейвли в блоге Tech Blog в Дублине .