Най-важното най-вероятно е да 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
: