Det vigtigste punkt er højst sandsynligt, at du JOIN
og GROUP
over alt bare for at få max(created)
. Få denne værdi separat.
Du nævnte alle de indekser, der er nødvendige her:på report_rank.created
og på fremmednøglerne. Du har det godt der. (Hvis du er interesseret i bedre end "okay", fortsæt med at læse !)
LEFT JOIN report_site
vil blive tvunget til en almindelig JOIN
ved WHERE
klausul. Jeg erstattede en almindelig JOIN
. Jeg har også forenklet din syntaks meget.
Opdateret juli 2015 med enklere, hurtigere forespørgsler og smartere funktioner.
Løsning til flere rækker
report_rank.created
er ikke unik og du vil have alle de seneste rækker.
Brug af vinduesfunktionen rank()
i en underforespørgsel.
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;
Hvorfor DESC NULLS LAST
?
Løsning for én række
Hvis report_rank.created
er unik eller du er tilfreds med en hvilken som helst række 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;
Det burde stadig være hurtigere. Flere muligheder:
Ultimativ hastighed med dynamisk justeret delvist indeks
Du har muligvis bemærket den kommenterede del i den sidste forespørgsel:
AND r.created > f_report_rank_cap()
Du nævnte 50 mio. rækker, det er meget. Her er en måde at fremskynde tingene på:
- Opret en simpel
IMMUTABLE
funktion, der returnerer et tidsstempel, der garanteret er ældre end rækker af interesse, mens det er så ungt som muligt. - Opret et delvist indeks kun på yngre rækker - baseret på denne funktion.
- Brug en
WHERE
betingelse i forespørgsler, der matcher indeksbetingelsen. - Opret en anden funktion, der opdaterer disse objekter til den seneste række med dynamisk DDL. (Minus en sikker margen i tilfælde af at den eller de nyeste rækker bliver slettet / deaktiveret - hvis det kan ske)
- Start denne sekundære funktion på pauser med et minimum af samtidig aktivitet pr. cronjob eller efter behov. Så ofte du vil, kan ikke gøre skade, den skal bare have en kort eksklusiv lås på bordet.
Her er en komplet fungerende demo .
@erikcw, du bliver nødt til at aktivere den kommenterede del som beskrevet nedenfor.
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 til:
SELECT f_report_rank_set_cap();
Se:
SELECT f_report_rank_cap();
Fjern kommentarsætningen AND r.created > f_report_rank_cap()
i forespørgslen ovenfor og observer forskellen. Bekræft, at indekset bliver brugt med EXPLAIN ANALYZE
.
Manualen om samtidighed og REINDEX
: