Недавно у меня возникла ситуация, когда мне нужно было подумать, как я использую индексы 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 INDEXdublintech=> 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 INDEXdublintech=> 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 в Дублине . |