Вопрос
Как сделать запрос, описанный в этом посте, быстрее, в частности, сделав PostgreSQL с использованием доступной оперативной памяти? - Обратите внимание, что я попытался соответствующим образом настроить effective_cache_size и shared_buffers. Увидеть ниже.
Задний план
Я должен регулярно присоединяться к таблице с ~ 260 миллионами строк (coreg_master) с новыми входящими данными. Я разбил таблицу на разделы, чтобы каждый раздел поместился в ОЗУ. Конечно, у меня тоже есть соответствующие индексы. Однако при объединении секционированных таблиц отдельно с другими (гораздо меньшими) таблицами он выполняет полностью случайный ввод-вывод на диске. Это происходит из-за сканирования индекса вложенного цикла в большой таблице, что очень медленно, поскольку у нас нет хорошей настройки диска.
Я ожидал, что вместо этого он будет использовать всю доступную оперативную память для кеширования большой многораздельной таблицы, что, как я понимаю, должно выполняться самим ядром / файловой системой Linux. Но он все равно не загружает таблицу в оперативную память, хотя и поместится. Я думаю, это потому, что шаблон доступа не является последовательным и, следовательно, не запускает кеширование? Я понятия не имею. План запроса и параметры конфигурации ниже.
Структура таблицы
Это один из разделов coreg_master, моей большой таблицы. Секционированные таблицы называются coreg_a, coreg_b и т. Д.
\d coreg_a
Table "public.coreg_a"
Column | Type | Modifiers
-------------+-------------------+-----------------------------------------------------------
id | integer | not null default nextval('coreg_master_id_seq'::regclass)
first_name | character varying |
last_name | character varying |
phone | character varying |
city | character varying |
zip | integer |
address | character varying |
dob | date |
ip | character varying |
source | character varying |
gender | character varying |
state | character varying |
record_date | date |
email | character varying |
Indexes:
"coreg_a_name" btree (lower(first_name::text), lower(last_name::text))
Check constraints:
"coreg_a_first_name_check" CHECK (first_name::text >= 'a'::text AND first_name::text < 'b'::text)
Inherits: coreg_master
Ниже приведен раздел таблицы appendable_24, пример таблицы, соединенной с coreg_master. Он также разбит на разделы так же, как coreg_master, поэтому на самом деле coreg_a объединяется с appendable_24_a и т. Д. По одному.
\d appendable_24_a
Table "public.appendable_24_a"
Column | Type | Modifiers
------------+-------------------+-----------
line_num | integer | not null
first_name | character varying |
last_name | character varying |
address | character varying |
state | character varying |
zip | integer |
Indexes:
"appendable_24_a_name_index" btree (lower(first_name::text), lower(last_name::text))
Check constraints:
"appendable_24_a_first_name_check" CHECK (first_name::text >= 'a'::text AND first_name::text < 'b'::text)
Inherits: appendable_24
Запрос и EXPLAIN ANALYZE
Ниже приведен результат анализа объяснения для наименьшего из объединений (таблицы разбиваются на разделы в соответствии с первой буквой столбца first_name), так как это не требует времени. Однако план запроса один и тот же для всех объединений в каждом разделе, поэтому он также должен быть репрезентативным для более крупных объединений (обратите внимание, я сделал ANALYZE
таблицы, а общее время фактически составило 20 секунд, но здесь быстрее, так как результат был кэширован):
explain analyze SELECT
coreg_x.phone,
coreg_x.email,
coreg_x.record_date,
appendable_24_x.line_num
FROM appendable_24_x INNER JOIN coreg_x ON
lower(appendable_24_x.first_name) = lower(coreg_x.first_name) AND
lower(appendable_24_x.last_name) = lower(coreg_x.last_name) AND
(coreg_x.phone IS NOT NULL OR coreg_x.email IS NOT NULL) AND
similarity(lower(appendable_24_x.address), lower(coreg_x.address)) > 0.7
;
QUE
RY PLAN
------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------
-----
Nested Loop (cost=0.01..640.49 rows=875 width=39) (actual time=9.990..53.839 rows=29 loo
ps=1)
Join Filter: (similarity(lower((appendable_24_x.address)::text), lower((coreg_x.address
)::text)) > 0.7::double precision)
-> Seq Scan on appendable_24_x (cost=0.00..1.80 rows=80 width=34) (actual time=0.009.
.0.111 rows=80 loops=1)
-> Index Scan using coreg_x_name on coreg_x (cost=0.01..7.95 rows=1 width=64) (actual
time=0.024..0.137 rows=44 loops=80)
Index Cond: ((lower((coreg_x.first_name)::text) = lower((appendable_24_x.first_na
me)::text)) AND (lower((coreg_x.last_name)::text) = lower((appendable_24_x.last_name)::tex
t)))
Filter: ((coreg_x.phone IS NOT NULL) OR (coreg_x.email IS NOT NULL))
Total runtime: 53.950 ms
(7 rows)
Некоторая статистика, параметры конфигурации и другие данные
show all
: http://pastie.org/1159746ОЗУ достаточно, чтобы кэшировать часть вашей базы данных, и, как показывает опубликованный вами план запроса, ранее использованные данные / части рассматриваемого индекса действительно кэшируются. (Postgres не кэширует результаты запросов).
Время выполнения 53 мс не так уж и плохо, и я не уверен, что 20 секунд на некэшированных данных означают, что PG также выбрала плохой план запроса. В конце концов, размер рассматриваемого индекса составляет 1 ГБ, но было бы интересно увидеть результаты анализа для медленного запроса.
Вы можете попробовать скорректировать затраты на планировщик, чтобы увидеть, не повлияет ли это на производительность в худшем случае, если это ваша проблема.
Вы также можете немного увеличить свой maintentance_work_mem, даже если это никоим образом не связано.