Le point principal est très probablement que vous JOIN
et GROUP
sur tout juste pour obtenir max(created)
. Obtenez cette valeur séparément.
Vous avez mentionné tous les index nécessaires ici :sur report_rank.created
et sur les clés étrangères. Tu vas bien là. (Si vous êtes intéressé par mieux que "bien", continuez à lire !)
Le LEFT JOIN report_site
sera forcé à un simple JOIN
par le WHERE
clause. J'ai remplacé un simple JOIN
. J'ai aussi beaucoup simplifié votre syntaxe.
Mise à jour en juillet 2015 avec des requêtes plus simples et plus rapides et des fonctions plus intelligentes.
Solution pour plusieurs lignes
report_rank.created
n'est pas unique et vous voulez tout les dernières lignes.
Utilisation de la fonction window rank()
dans une sous-requête.
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;
Pourquoi DESC NULLS LAST
?
Solution pour une ligne
Si report_rank.created
est unique ou vous êtes satisfait de n'importe quelle ligne avec 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;
Devrait être plus rapide, encore. Plus d'options :
-
Sélectionner la première ligne de chaque GROUPER PAR groupe ?
-
Optimiser Requête GROUP BY pour récupérer le dernier enregistrement par utilisateur
Vitesse ultime avec index partiel ajusté dynamiquement
Vous avez peut-être remarqué la partie commentée dans la dernière requête :
AND r.created > f_report_rank_cap()
Vous avez parlé de 50 millions. lignes, c'est beaucoup. Voici un moyen d'accélérer les choses :
- Créer un simple
IMMUTABLE
fonction renvoyant un horodatage qui est garanti plus ancien que les lignes d'intérêt tout en étant aussi jeune que possible. - Créer un index partiel sur les lignes plus jeunes uniquement - basé sur cette fonction.
- Utilisez un
WHERE
condition dans les requêtes qui correspond à la condition d'index. - Créez une autre fonction qui met à jour ces objets à la dernière ligne avec DDL dynamique. (Moins une marge sécurisée au cas où la ou les lignes les plus récentes seraient supprimées/désactivées - si cela peut arriver)
- Invoquez cette fonction secondaire à des heures creuses avec un minimum d'activité simultanée par cronjob ou à la demande. Aussi souvent que vous le souhaitez, ne peut pas faire de mal, il suffit d'un court verrouillage exclusif sur la table.
Voici une démo de travail complète .
@erikcw, vous devrez activer la partie commentée comme indiqué ci-dessous.
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.';
Appel :
SELECT f_report_rank_set_cap();
Voir :
SELECT f_report_rank_cap();
Décommentez la clause AND r.created > f_report_rank_cap()
dans la requête ci-dessus et observez la différence. Vérifiez que l'index est utilisé avec EXPLAIN ANALYZE
.
Le manuel sur la concurrence et REINDEX
: