Основной момент, скорее всего, что вы JOIN
и GROUP
за все, чтобы получить max(created)
.Получите это значение отдельно.
Вы упомянули все необходимые здесь индексы: на report_rank.created
и на внешних ключах.Там у тебя все хорошо.(Если вас интересует лучше, чем "хорошо", продолжайте читать !)
LEFT JOIN report_site
будет принудительно переведен в простой JOIN
по предложению WHERE
.Я подставил равнину JOIN
.Я также значительно упростил ваш синтаксис.
Обновлен в июле 2015 года с более простыми, быстрыми запросами и более умными функциями.
Решение для нескольких строк
report_rank.created
это не уникально и вы хотите все самые последние строки.
Использование оконной функции rank()
в подзапросе.
SELECT r.id, r.keyword_id, r.site_id
, r.rank, r.url, r.competition
, r.source, r.country, r.created -- same as "max"
FROM (
SELECT *, rank() OVER (ORDER BY created DESC NULLS LAST) AS rnk
FROM report_rank r
WHERE EXISTS (
SELECT *
FROM report_site s
JOIN report_profile p ON p.site_id = s.id
JOIN crm_client c ON c.id = p.client_id
JOIN auth_user u ON u.id = c.user_id
WHERE s.id = r.site_id
AND u.is_active
AND c.is_deleted = FALSE
)
) sub
WHERE rnk = 1;
Почему DESC NULLS LAST
?
Решение для одной строки
Если report_rank.created
является уникальным или вас устраивает любой 1 ряд с max(created)
:
SELECT id, keyword_id, site_id
, rank, url, competition
, source, country, created -- same as "max"
FROM report_rank r
WHERE EXISTS (
SELECT 1
FROM report_site s
JOIN report_profile p ON p.site_id = s.id
JOIN crm_client c ON c.id = p.client_id
JOIN auth_user u ON u.id = c.user_id
WHERE s.id = r.site_id
AND u.is_active
AND c.is_deleted = FALSE
)
-- AND r.created > f_report_rank_cap()
ORDER BY r.created DESC NULLS LAST
LIMIT 1;
Должно быть быстрее,еще.Дополнительные параметры:
Максимальная скорость с динамически настраиваемым частичным индексом
Возможно, вы заметили прокомментированную часть в последнем запросе:
AND r.created > f_report_rank_cap()
Вы упомянули 50 млн.строк, это много.Вот способ ускорить процесс:
- Создайте простую
IMMUTABLE
функцию, возвращающую метку времени, которая гарантированно будет старше строк, представляющих интерес, и при этом будет максимально молодой. - Создать частичный индекс только для младших строк - на основе этой функции.
- Использовать условие
WHERE
в запросах, которое соответствует условию индекса. - Создайте еще одну функцию, которая обновляет эти объекты до последней строки с динамическим DDL.(Минус безопасное поле в случае, если самые новые строки будут удалены / деактивированы - если это может произойти)
- Вызовите эту вторичную функцию в нерабочее время с минимумом одновременной активностиCronjob или по требованию.Как часто, как вы хотите, не можете причинить вреда, ему просто нужна короткая эксклюзивная блокировка на столе.
Вот полная рабочая демонстрация .
@erikcwвам нужно будет активировать закомментированную часть, как указано ниже.
CREATE TABLE report_rank(created timestamp);
INSERT INTO report_rank VALUES ('2011-11-11 11:11'),(now());
-- initial function
CREATE OR REPLACE FUNCTION f_report_rank_cap()
RETURNS timestamp LANGUAGE sql COST 1 IMMUTABLE AS
$y$SELECT timestamp '-infinity'$y$; -- or as high as you can safely bet.
-- initial index; 1st run indexes whole tbl if starting with '-infinity'
CREATE INDEX report_rank_recent_idx ON report_rank (created DESC NULLS LAST)
WHERE created > f_report_rank_cap();
-- function to update function & reindex
CREATE OR REPLACE FUNCTION f_report_rank_set_cap()
RETURNS void AS
$func$
DECLARE
_secure_margin CONSTANT interval := interval '1 day'; -- adjust to your case
_cap timestamp; -- exclude older rows than this from partial index
BEGIN
SELECT max(created) - _secure_margin
FROM report_rank
WHERE created > f_report_rank_cap() + _secure_margin
/* not needed for the demo; @erikcw needs to activate this
AND EXISTS (
SELECT *
FROM report_site s
JOIN report_profile p ON p.site_id = s.id
JOIN crm_client c ON c.id = p.client_id
JOIN auth_user u ON u.id = c.user_id
WHERE s.id = r.site_id
AND u.is_active
AND c.is_deleted = FALSE)
*/
INTO _cap;
IF FOUND THEN
-- recreate function
EXECUTE format('
CREATE OR REPLACE FUNCTION f_report_rank_cap()
RETURNS timestamp LANGUAGE sql IMMUTABLE AS
$y$SELECT %L::timestamp$y$', _cap);
-- reindex
REINDEX INDEX report_rank_recent_idx;
END IF;
END
$func$ LANGUAGE plpgsql;
COMMENT ON FUNCTION f_report_rank_set_cap()
IS 'Dynamically recreate function f_report_rank_cap()
and reindex partial index on report_rank.';
Звоните:
SELECT f_report_rank_set_cap();
См .:
SELECT f_report_rank_cap();
Раскомментируйте пункт AND r.created > f_report_rank_cap()
в запросе выше и обратите внимание на разницу.Убедитесь, что индекс используется с EXPLAIN ANALYZE
.
Руководством по параллелизму и REINDEX
:
Для создания индекса без вмешательства в производство выследует удалить индекс и еще раз ввести команду CREATE INDEX CONCURRENTLY
.