Huvudsaken är troligen att du JOIN
och GROUP
över allt bara för att få max(created)
. Få detta värde separat.
Du nämnde alla index som behövs här:på report_rank.created
och på de främmande nycklarna. Du mår bra där. (Om du är intresserad av bättre än "okej", fortsätt läsa !)
LEFT JOIN report_site
kommer att tvingas till en vanlig JOIN
med WHERE
klausul. Jag bytte ut en vanlig JOIN
. Jag har också förenklat din syntax mycket.
Uppdaterad juli 2015 med enklare, snabbare frågor och smartare funktioner.
Lösning för flera rader
report_rank.created
är inte unik och du vill ha alla de senaste raderna.
Med fönsterfunktionen rank()
i en underfråga.
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;
Varför DESC NULLS LAST
?
Lösning för en rad
Om report_rank.created
är unikt eller så är du nöjd med valfri rad med 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;
Borde vara snabbare, ändå. Fler alternativ:
Ultimat hastighet med dynamiskt justerat partiellt index
Du kanske har märkt den kommenterade delen i den senaste frågan:
AND r.created > f_report_rank_cap()
Du nämnde 50 miljoner. rader, det är mycket. Här är ett sätt att snabba upp saker och ting:
- Skapa en enkel
IMMUTABLE
funktion som returnerar en tidsstämpel som garanterat är äldre än rader av intresse samtidigt som den är så ung som möjligt. - Skapa ett partiellt index endast på yngre rader - baserat på denna funktion.
- Använd en
WHERE
villkor i frågor som matchar indexvillkoret. - Skapa en annan funktion som uppdaterar dessa objekt till den senaste raden med dynamisk DDL. (Minus en säker marginal ifall de senaste raden(erna) tas bort/avaktiveras - om det kan hända)
- Anropa denna sekundära funktion vid avbrottstider med ett minimum av samtidig aktivitet per cronjob eller på begäran. Så ofta du vill, kan inte göra skada, den behöver bara ett kort exklusivt lås på bordet.
Här är en komplett fungerande demo .
@erikcw, du måste aktivera den kommenterade delen enligt instruktionerna nedan.
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.';
Ring:
SELECT f_report_rank_set_cap();
Se:
SELECT f_report_rank_cap();
Avkommentera klausulen AND r.created > f_report_rank_cap()
i frågan ovan och observera skillnaden. Verifiera att indexet används med EXPLAIN ANALYZE
.
Handboken om samtidighet och REINDEX
: