Тестирование PostgreSQL 8.1 на больших таблицах
В очередной раз "ввязался" в дискуссию о разработке больших баз данных на СУБД PostgreSQL. В ходе обсуждения появилась необходимость провести численные измерения скорости выполнения запросов на базах разного масштаба. Так же обнаружилось, что в PostgreSQL 8.3 планировщик работает получше, но принципиальной разницы нет, т.к. того же самого поведения можно добиться и в 8.1.
Выполнено:
Проверил 1 М, 10 М и 100 М записей с тремя индексами (запрос по 4-м полям, но каждый раз использовалось только 3 индекса). Размер выборки во всех случаях был практически идентичный. Для того, чтобы заставить постгрес пользоваться индексами, были подобраны параметры тестового распределения. При 100 М записей постгрес не смог создать индекс после вставки всех записей, требуя больше памяти, потому индексы создавались до вставки. Время измерено в pgadmin, все запросы работали по "горячему кэшу", т.е. запускались несколько раз подряд до стабилизации времени выборки (для 100 М таблицы первые 3 запуска время выполнения было около 40-ка секунд, и только на 4-м запуске кэш "разогрелся").
Параметры сервера:
Параметры сервера PostgrSQL:
Версия сервера PostgrSQL:
Размеры тестовых таблиц и индексов (кол-во 8к блоков):
С индексами по каждому из столбцов:
С индексами по каждому из столбцов и с композитным индексом по всем столбцам:
Для 100 М не удалось провести тест с композитным индексом, см. примечания ниже.
Тест базы 1 М записей.
С композитным индексом:
Тест базы 10 М записей.
С композитным индексом:
Тест базы 100 М записей.
Обсуждение:
Композитный индекс для таблицы 100 М записей протестировать не удалось - создать индекс по заполненной таблицы постгрес не смог (говорит, памяти не хватает), а заполнение индексированной таблицы производится очень медленно, притом скорость заполнения падает; 10 М записей были вставлены за время порядка часа, а вставки следущего набора из 1 М мне уже не удалось дождаться.
Заметно, что планировщик с подзапросами работает плохо, в разы замедляя даже простейший запрос. На мелких базах это не так существенно, но на больших создает определенные проблемы. Следовательно, нужно избегать подзапросов, когда это возможно. При этом и JOIN-ов следует избегать, т.к. с ними возникают точно такие же проблемы. Ну, кому-то достаточно и выборок из одной таблицы, что чаще всего и делают на больших БД - для OLTP и этого бывает достаточно. Только сомнительна необходимость в PostgreSQL для подобных задач, когда есть SQLite, BerkeleyDB (чтобы делать выборку из одной таблицы поддержка SQL не сильно и нужна) и другие намного более эффективные решения.
Грубая прикидка показывает, что деградация производительности при росте размера базы примерно пропорциональна корню квадратному из отношения размеров баз до тех пор, пока база помещается в ОЗУ. После этого деградация становится линейно зависящей от роста размера базы. Как очевидное следствие, работа с базами, не помещающимися в shared_memory, проблематична.
Выполнено:
Проверил 1 М, 10 М и 100 М записей с тремя индексами (запрос по 4-м полям, но каждый раз использовалось только 3 индекса). Размер выборки во всех случаях был практически идентичный. Для того, чтобы заставить постгрес пользоваться индексами, были подобраны параметры тестового распределения. При 100 М записей постгрес не смог создать индекс после вставки всех записей, требуя больше памяти, потому индексы создавались до вставки. Время измерено в pgadmin, все запросы работали по "горячему кэшу", т.е. запускались несколько раз подряд до стабилизации времени выборки (для 100 М таблицы первые 3 запуска время выполнения было около 40-ка секунд, и только на 4-м запуске кэш "разогрелся").
Параметры сервера:
Intel(R) Core(TM)2 Quad CPU Q6700 @ 2.66GHz
MemTotal: 8310900 kB
Параметры сервера PostgrSQL:
work_mem = 100000 # min 64, size in KB
maintenance_work_mem = 2000000 # min 1024, size in KB
shared_buffers = 200000 # min 16 or max_connections*2, 8KB each
Версия сервера PostgrSQL:
select version();
version
----------------------------------------------------------------------------------------------------------------
PostgreSQL 8.1.15 on i486-pc-linux-gnu, compiled by GCC cc (GCC) 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)
(1 row)
Размеры тестовых таблиц и индексов (кол-во 8к блоков):
С индексами по каждому из столбцов:
SELECT relname, relpages, reltuples FROM pg_class
WHERE NOT relname LIKE 'pg_%' ORDER BY relpages DESC;
relname | relpages | reltuples
------------------------------------+----------+-------------
tmp_facts8 | 575323 | 9.00079e+07
tmp_facts8_a2_idx | 279976 | 9.00079e+07
tmp_facts8_a4_idx | 279811 | 9.00079e+07
tmp_facts8_a1_idx | 279740 | 9.00079e+07
tmp_facts8_a3_idx | 279652 | 9.00079e+07
tmp_facts7 | 58824 | 1e+07
tmp_facts7_a1_idx | 21899 | 1e+07
tmp_facts7_a2_idx | 21899 | 1e+07
tmp_facts7_a3_idx | 21899 | 1e+07
tmp_facts7_a4_idx | 21899 | 1e+07
tmp_facts6 | 5883 | 1e+06
tmp_facts6_a1_idx | 2193 | 1e+06
tmp_facts6_a2_idx | 2193 | 1e+06
tmp_facts6_a3_idx | 2193 | 1e+06
tmp_facts6_a4_idx | 2193 | 1e+06
С индексами по каждому из столбцов и с композитным индексом по всем столбцам:
SELECT relname, relpages, reltuples FROM pg_class
WHERE NOT relname LIKE 'pg_%' ORDER BY relpages DESC;
relname | relpages | reltuples
------------------------------------+----------+-----------
tmp_facts7 | 58824 | 1e+07
tmp_facts7_complex_idx | 38506 | 1e+07
tmp_facts7_a1_idx | 21899 | 1e+07
tmp_facts7_a2_idx | 21899 | 1e+07
tmp_facts7_a3_idx | 21899 | 1e+07
tmp_facts7_a4_idx | 21899 | 1e+07
tmp_facts6 | 5883 | 1e+06
tmp_facts6_complex_idx | 3853 | 1e+06
tmp_facts6_a1_idx | 2193 | 1e+06
tmp_facts6_a2_idx | 2193 | 1e+06
tmp_facts6_a3_idx | 2193 | 1e+06
tmp_facts6_a4_idx | 2193 | 1e+06
Для 100 М не удалось провести тест с композитным индексом, см. примечания ниже.
Тест базы 1 М записей.
create table tmp_facts6(a1 int,a2 int,a3 int,a4 int);
insert into tmp_facts6(a1,a2,a3,a4) select (10*random())::int,(10*random())::int,
(10*random())::int,(10*random())::int from generate_series(1,1000000);
create index tmp_facts6_a1_idx on tmp_facts6(a1);
create index tmp_facts6_a2_idx on tmp_facts6(a2);
create index tmp_facts6_a3_idx on tmp_facts6(a3);
create index tmp_facts6_a4_idx on tmp_facts6(a4);
select count(*) from tmp_facts6 where a1=2 and a2=2 and a3=2 and a4=2;
90
140 ms
explain select * from tmp_facts6 where a1=2 and a2=2 and a3=2 and a4=2;
QUERY PLAN
------------------------------------------------------------------------------------------
Bitmap Heap Scan on tmp_facts6 (cost=89.00..93.02 rows=1 width=16)
Recheck Cond: ((a4 = 2) AND (a3 = 2) AND (a2 = 2))
Filter: (a1 = 2)
-> BitmapAnd (cost=89.00..89.00 rows=1 width=0)
-> Bitmap Index Scan on tmp_facts6_a4_idx (cost=0.00..29.50 rows=5000 width=0)
Index Cond: (a4 = 2)
-> Bitmap Index Scan on tmp_facts6_a3_idx (cost=0.00..29.50 rows=5000 width=0)
Index Cond: (a3 = 2)
-> Bitmap Index Scan on tmp_facts6_a2_idx (cost=0.00..29.50 rows=5000 width=0)
Index Cond: (a2 = 2)
(10 rows)
select count(*) from tmp_facts6 where a1 in (select 2) and a2 in (select 2)
and a3 in (select 2) and a4 in (select 2);
90
233 ms
explain select * from tmp_facts6 where a1 in (select 2) and a2 in (select 2)
and a3 in (select 2) and a4 in (select 2);
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------
Hash IN Join (cost=0.11..40561.47 rows=68 width=16)
Hash Cond: ("outer".a1 = "inner"."?column?")
-> Hash IN Join (cost=0.09..40557.01 rows=751 width=16)
Hash Cond: ("outer".a3 = "inner"."?column?")
-> Hash IN Join (cost=0.07..40508.16 rows=8264 width=16)
Hash Cond: ("outer".a4 = "inner"."?column?")
-> Merge Join (cost=0.04..39970.95 rows=90909 width=16)
Merge Cond: ("outer"."?column?" = "inner".a2)
-> Sort (cost=0.04..0.05 rows=1 width=4)
Sort Key: "IN_subquery"."?column?"
-> HashAggregate (cost=0.02..0.03 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Index Scan using tmp_facts6_a2_idx on tmp_facts6 (cost=0.00..36561.81 rows=1000000 width=16)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
(19 rows)
select count(*)
from tmp_facts6
join (select 2 as a1) as x1 on x1.a1=tmp_facts6.a1
join (select 2 as a2) as x2 on x2.a2=tmp_facts6.a2
join (select 2 as a3) as x3 on x3.a3=tmp_facts6.a3
join (select 2 as a4) as x4 on x4.a4=tmp_facts6.a4;
90
205 ms
explain select count(*)
from tmp_facts6
join (select 2 as a1) as x1 on x1.a1=tmp_facts6.a1
join (select 2 as a2) as x2 on x2.a2=tmp_facts6.a2
join (select 2 as a3) as x3 on x3.a3=tmp_facts6.a3
join (select 2 as a4) as x4 on x4.a4=tmp_facts6.a4;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=40561.63..40561.64 rows=1 width=0)
-> Hash Join (cost=0.10..40561.46 rows=68 width=0)
Hash Cond: ("outer".a1 = "inner".a1)
-> Hash Join (cost=0.08..40557.00 rows=751 width=4)
Hash Cond: ("outer".a3 = "inner".a3)
-> Hash Join (cost=0.05..40508.15 rows=8264 width=8)
Hash Cond: ("outer".a4 = "inner".a4)
-> Merge Join (cost=0.03..39970.94 rows=90909 width=12)
Merge Cond: ("outer".a2 = "inner".a2)
-> Sort (cost=0.03..0.04 rows=1 width=4)
Sort Key: x2.a2
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Index Scan using tmp_facts6_a2_idx on tmp_facts6 (cost=0.00..36561.81 rows=1000000 width=16)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
(19 rows)
С композитным индексом:
create index tmp_facts6_complex_idx on tmp_facts6(a1,a2,a3,a4);
select count(*) from tmp_facts6 where a1=2 and a2=2 and a3=2 and a4=2;
90
35 ms
explain select count(*) from tmp_facts6 where a1=2 and a2=2 and a3=2 and a4=2;
QUERY PLAN
---------------------------------------------------------------------------------------------
Aggregate (cost=370.14..370.15 rows=1 width=0)
-> Bitmap Heap Scan on tmp_facts6 (cost=3.12..369.88 rows=102 width=0)
Recheck Cond: ((a1 = 2) AND (a2 = 2) AND (a3 = 2) AND (a4 = 2))
-> Bitmap Index Scan on tmp_facts6_complex_idx (cost=0.00..3.12 rows=102 width=0)
Index Cond: ((a1 = 2) AND (a2 = 2) AND (a3 = 2) AND (a4 = 2))
(5 rows)
select count(*)
from tmp_facts6
join (select 2 as a1) as x1 on x1.a1=tmp_facts6.a1
join (select 2 as a2) as x2 on x2.a2=tmp_facts6.a2
join (select 2 as a3) as x3 on x3.a3=tmp_facts6.a3
join (select 2 as a4) as x4 on x4.a4=tmp_facts6.a4;
90
220 ms
explain select count(*)
from tmp_facts6
join (select 2 as a1) as x1 on x1.a1=tmp_facts6.a1
join (select 2 as a2) as x2 on x2.a2=tmp_facts6.a2
join (select 2 as a3) as x3 on x3.a3=tmp_facts6.a3
join (select 2 as a4) as x4 on x4.a4=tmp_facts6.a4;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=40561.63..40561.64 rows=1 width=0)
-> Hash Join (cost=0.10..40561.46 rows=68 width=0)
Hash Cond: ("outer".a1 = "inner".a1)
-> Hash Join (cost=0.08..40557.00 rows=751 width=4)
Hash Cond: ("outer".a3 = "inner".a3)
-> Hash Join (cost=0.05..40508.15 rows=8264 width=8)
Hash Cond: ("outer".a4 = "inner".a4)
-> Merge Join (cost=0.03..39970.94 rows=90909 width=12)
Merge Cond: ("outer".a2 = "inner".a2)
-> Sort (cost=0.03..0.04 rows=1 width=4)
Sort Key: x2.a2
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Index Scan using tmp_facts6_a2_idx on tmp_facts6 (cost=0.00..36561.81 rows=1000000 width=16)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
(19 rows)
Тест базы 10 М записей.
create table tmp_facts7(a1 int,a2 int,a3 int,a4 int);
insert into tmp_facts7(a1,a2,a3,a4) select (18*random())::int,(18*random())::int,
(18*random())::int,(18*random())::int from generate_series(1,10000000);
create index tmp_facts7_a1_idx on tmp_facts7(a1);
create index tmp_facts7_a2_idx on tmp_facts7(a2);
create index tmp_facts7_a3_idx on tmp_facts7(a3);
create index tmp_facts7_a4_idx on tmp_facts7(a4);
select count(*) from tmp_facts7 where a1=2 and a2=2 and a3=2 and a4=2;
94
370 ms
explain select * from tmp_facts7 where a1=2 and a2=2 and a3=2 and a4=2;
QUERY PLAN
--------------------------------------------------------------------------------------------
Bitmap Heap Scan on tmp_facts7 (cost=858.50..862.52 rows=1 width=16)
Recheck Cond: ((a4 = 2) AND (a3 = 2) AND (a2 = 2))
Filter: (a1 = 2)
-> BitmapAnd (cost=858.50..858.50 rows=1 width=0)
-> Bitmap Index Scan on tmp_facts7_a4_idx (cost=0.00..286.00 rows=50000 width=0)
Index Cond: (a4 = 2)
-> Bitmap Index Scan on tmp_facts7_a3_idx (cost=0.00..286.00 rows=50000 width=0)
Index Cond: (a3 = 2)
-> Bitmap Index Scan on tmp_facts7_a2_idx (cost=0.00..286.00 rows=50000 width=0)
Index Cond: (a2 = 2)
(10 rows)
select count(*) from tmp_facts7 where a1 in (select 2) and a2 in (select 2)
and a3 in (select 2) and a4 in (select 2);
94
1140 ms
explain select * from tmp_facts7 where a1 in (select 2) and a2 in (select 2)
and a3 in (select 2) and a4 in (select 2);
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
Hash IN Join (cost=0.10..403957.90 rows=77 width=16)
Hash Cond: ("outer".a1 = "inner"."?column?")
-> Hash IN Join (cost=0.08..403949.81 rows=1458 width=16)
Hash Cond: ("outer".a2 = "inner"."?column?")
-> Hash IN Join (cost=0.05..403796.71 rows=27701 width=16)
Hash Cond: ("outer".a3 = "inner"."?column?")
-> Merge IN Join (cost=0.03..400888.09 rows=526316 width=16)
Merge Cond: ("outer".a4 = "inner"."?column?")
-> Index Scan using tmp_facts7_a4_idx on tmp_facts7 (cost=0.00..366677.53 rows=10000000 width=16)
-> Sort (cost=0.03..0.04 rows=1 width=4)
Sort Key: "IN_subquery"."?column?"
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
(18 rows)
select count(*)
from tmp_facts7
join (select 2 as a1) as x1 on x1.a1=tmp_facts7.a1
join (select 2 as a2) as x2 on x2.a2=tmp_facts7.a2
join (select 2 as a3) as x3 on x3.a3=tmp_facts7.a3
join (select 2 as a4) as x4 on x4.a4=tmp_facts7.a4;
94
1106 ms
explain select count(*)
from tmp_facts7
join (select 2 as a1) as x1 on x1.a1=tmp_facts7.a1
join (select 2 as a2) as x2 on x2.a2=tmp_facts7.a2
join (select 2 as a3) as x3 on x3.a3=tmp_facts7.a3
join (select 2 as a4) as x4 on x4.a4=tmp_facts7.a4;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=400010.73..400010.74 rows=1 width=0)
-> Hash Join (cost=0.10..400010.53 rows=77 width=0)
Hash Cond: ("outer".a1 = "inner".a1)
-> Hash Join (cost=0.08..400002.45 rows=1458 width=4)
Hash Cond: ("outer".a2 = "inner".a2)
-> Hash Join (cost=0.05..399849.34 rows=27701 width=8)
Hash Cond: ("outer".a3 = "inner".a3)
-> Merge Join (cost=0.03..396940.73 rows=526316 width=12)
Merge Cond: ("outer".a4 = "inner".a4)
-> Sort (cost=0.03..0.04 rows=1 width=4)
Sort Key: x4.a4
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Index Scan using tmp_facts7_a4_idx on tmp_facts7 (cost=0.00..366677.53 rows=10000000 width=16)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
(19 rows)
С композитным индексом:
create index tmp_facts7_complex_idx on tmp_facts7(a1,a2,a3,a4);
select count(*) from tmp_facts7 where a1=2 and a2=2 and a3=2 and a4=2;
94
54 ms
explain select count(*) from tmp_facts7 where a1=2 and a2=2 and a3=2 and a4=2;
QUERY PLAN
--------------------------------------------------------------------------------------------
Aggregate (cost=373.44..373.45 rows=1 width=0)
-> Bitmap Heap Scan on tmp_facts7 (cost=3.04..373.20 rows=95 width=0)
Recheck Cond: ((a1 = 2) AND (a2 = 2) AND (a3 = 2) AND (a4 = 2))
-> Bitmap Index Scan on tmp_facts7_complex_idx (cost=0.00..3.04 rows=95 width=0)
Index Cond: ((a1 = 2) AND (a2 = 2) AND (a3 = 2) AND (a4 = 2))
(5 rows)
select count(*)
from tmp_facts7
join (select 2 as a1) as x1 on x1.a1=tmp_facts7.a1
join (select 2 as a2) as x2 on x2.a2=tmp_facts7.a2
join (select 2 as a3) as x3 on x3.a3=tmp_facts7.a3
join (select 2 as a4) as x4 on x4.a4=tmp_facts7.a4;
94
1150 ms
explain select count(*)
from tmp_facts7
join (select 2 as a1) as x1 on x1.a1=tmp_facts7.a1
join (select 2 as a2) as x2 on x2.a2=tmp_facts7.a2
join (select 2 as a3) as x3 on x3.a3=tmp_facts7.a3
join (select 2 as a4) as x4 on x4.a4=tmp_facts7.a4;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------
Aggregate (cost=400010.73..400010.74 rows=1 width=0)
-> Hash Join (cost=0.10..400010.53 rows=77 width=0)
Hash Cond: ("outer".a1 = "inner".a1)
-> Hash Join (cost=0.08..400002.45 rows=1458 width=4)
Hash Cond: ("outer".a2 = "inner".a2)
-> Hash Join (cost=0.05..399849.34 rows=27701 width=8)
Hash Cond: ("outer".a3 = "inner".a3)
-> Merge Join (cost=0.03..396940.73 rows=526316 width=12)
Merge Cond: ("outer".a4 = "inner".a4)
-> Sort (cost=0.03..0.04 rows=1 width=4)
Sort Key: x4.a4
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Index Scan using tmp_facts7_a4_idx on tmp_facts7 (cost=0.00..366677.53 rows=10000000 width=16)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
(19 rows)
Тест базы 100 М записей.
drop table tmp_facts8;
create table tmp_facts8(a1 int,a2 int,a3 int,a4 int);
create index tmp_facts8_a1_idx on tmp_facts8(a1);
create index tmp_facts8_a2_idx on tmp_facts8(a2);
create index tmp_facts8_a3_idx on tmp_facts8(a3);
create index tmp_facts8_a4_idx on tmp_facts8(a4);
insert into tmp_facts8(a1,a2,a3,a4) select (33*random())::int,(33*random())::int,
(33*random())::int,(33*random())::int from generate_series(1,10000000);
... выполняем 10 раз для вставки 100 М записей
select count(*) from tmp_facts8 where a1=2 and a2=2 and a3=2 and a4=2;
80
2260 ms
explain select * from tmp_facts8 where a1=2 and a2=2 and a3=2 and a4=2;
QUERY PLAN
------------------------------------------------------------------------------------------------
Bitmap Heap Scan on tmp_facts8 (cost=55213.58..64940.47 rows=78 width=16)
Recheck Cond: ((a1 = 2) AND (a3 = 2) AND (a2 = 2))
Filter: (a4 = 2)
-> BitmapAnd (cost=55213.58..55213.58 rows=2550 width=0)
-> Bitmap Index Scan on tmp_facts8_a1_idx (cost=0.00..18275.05 rows=2765444 width=0)
Index Cond: (a1 = 2)
-> Bitmap Index Scan on tmp_facts8_a3_idx (cost=0.00..18432.87 rows=2789678 width=0)
Index Cond: (a3 = 2)
-> Bitmap Index Scan on tmp_facts8_a2_idx (cost=0.00..18505.16 rows=2799187 width=0)
Index Cond: (a2 = 2)
(10 rows)
select count(*) from tmp_facts8 where a1 in (select 2) and a2 in (select 2)
and a3 in (select 2) and a4 in (select 2);
80
7419 ms
explain select * from tmp_facts8 where a1 in (select 2) and a2 in (select 2)
and a3 in (select 2) and a4 in (select 2);
QUERY PLAN
------------------------------------------------------------------------------------------------------------------
Hash IN Join (cost=100017884.57..100681784.99 rows=69 width=16)
Hash Cond: ("outer".a1 = "inner"."?column?")
-> Hash IN Join (cost=100017884.55..100681772.57 rows=2341 width=16)
Hash Cond: ("outer".a2 = "inner"."?column?")
-> Hash IN Join (cost=100017884.52..100681351.09 rows=79609 width=16)
Hash Cond: ("outer".a4 = "inner"."?column?")
-> Nested Loop (cost=100017884.50..100667021.44 rows=2706708 width=16)
-> HashAggregate (cost=0.02..0.03 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Bitmap Heap Scan on tmp_facts8 (cost=17884.48..633187.56 rows=2706708 width=16)
Recheck Cond: (tmp_facts8.a3 = "outer"."?column?")
-> Bitmap Index Scan on tmp_facts8_a3_idx (cost=0.00..17884.48 rows=2706708 width=0)
Index Cond: (tmp_facts8.a3 = "outer"."?column?")
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Hash (cost=0.02..0.02 rows=1 width=4)
-> Result (cost=0.00..0.01 rows=1 width=0)
(19 rows)
Обсуждение:
Композитный индекс для таблицы 100 М записей протестировать не удалось - создать индекс по заполненной таблицы постгрес не смог (говорит, памяти не хватает), а заполнение индексированной таблицы производится очень медленно, притом скорость заполнения падает; 10 М записей были вставлены за время порядка часа, а вставки следущего набора из 1 М мне уже не удалось дождаться.
Заметно, что планировщик с подзапросами работает плохо, в разы замедляя даже простейший запрос. На мелких базах это не так существенно, но на больших создает определенные проблемы. Следовательно, нужно избегать подзапросов, когда это возможно. При этом и JOIN-ов следует избегать, т.к. с ними возникают точно такие же проблемы. Ну, кому-то достаточно и выборок из одной таблицы, что чаще всего и делают на больших БД - для OLTP и этого бывает достаточно. Только сомнительна необходимость в PostgreSQL для подобных задач, когда есть SQLite, BerkeleyDB (чтобы делать выборку из одной таблицы поддержка SQL не сильно и нужна) и другие намного более эффективные решения.
Грубая прикидка показывает, что деградация производительности при росте размера базы примерно пропорциональна корню квадратному из отношения размеров баз до тех пор, пока база помещается в ОЗУ. После этого деградация становится линейно зависящей от роста размера базы. Как очевидное следствие, работа с базами, не помещающимися в shared_memory, проблематична.
Comments
Без этого, конечно интересно, но ИМХО недостаточно показательно.