Najważniejszym punktem jest najprawdopodobniej to, że JOIN
i GROUP
nad wszystkim, aby uzyskać max(created)
. Pobierz tę wartość osobno.
Wspomniałeś o wszystkich indeksach, które są potrzebne tutaj:na report_rank.created
i na kluczach obcych. Dobrze sobie tam radzisz. (Jeśli interesuje Cię coś lepszego niż „w porządku”, czytaj dalej !)
LEFT JOIN report_site
zostanie zmuszony do zwykłego JOIN
przez WHERE
klauzula. Zastąpiłem zwykły JOIN
. Bardzo uprościłem też twoją składnię.
Zaktualizowano w lipcu 2015 r. z prostszymi, szybszymi zapytaniami i inteligentniejszymi funkcjami.
Rozwiązanie dla wielu rzędów
report_rank.created
jest nie niepowtarzalny i chcesz wszystkie najnowsze wiersze.
Korzystanie z funkcji okna rank()
w podzapytaniu.
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;
Dlaczego DESC NULLS LAST
?
Rozwiązanie dla jednego rzędu
Jeśli report_rank.created
jest wyjątkowy lub jesteś zadowolony z dowolnego 1 wiersza z 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;
Powinno być jeszcze szybciej. Więcej opcji:
Najwyższa prędkość z dynamicznie dostosowywanym indeksem częściowym
Być może zauważyłeś skomentowaną część ostatniego zapytania:
AND r.created > f_report_rank_cap()
Wspomniałeś o 50 mln. rzędy, to dużo. Oto sposób na przyspieszenie:
- Utwórz prosty
IMMUTABLE
funkcja zwracająca znacznik czasu, który na pewno jest starszy niż wiersze zainteresowania, a jednocześnie jest tak młody, jak to możliwe. - Utwórz indeks częściowy tylko w młodszych wierszach - na podstawie tej funkcji.
- Użyj
WHERE
warunek w zapytaniach, które pasują do warunku indeksu. - Utwórz inną funkcję, która zaktualizuje te obiekty do najnowszego wiersza za pomocą dynamicznego kodu DDL. (Musisz bezpieczny margines w przypadku, gdy najnowsze wiersze zostaną usunięte / dezaktywowane - jeśli to może się zdarzyć)
- Wywołaj tę drugorzędną funkcję w czasie przerwy z minimalną równoczesną aktywnością na zadanie lub na żądanie. Tak często, jak chcesz, nie może zaszkodzić, wystarczy krótka, ekskluzywna blokada na stole.
Oto kompletne działające demo .
@erikcw, musisz aktywować komentowaną część zgodnie z poniższymi instrukcjami.
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.';
Zadzwoń:
SELECT f_report_rank_set_cap();
Zobacz:
SELECT f_report_rank_cap();
Odkomentuj klauzulę AND r.created > f_report_rank_cap()
w powyższym zapytaniu i obserwuj różnicę. Sprawdź, czy indeks jest używany z EXPLAIN ANALYZE
.
Podręcznik dotyczący współbieżności i REINDEX
: