web-dev-qa-db-fra.com

comment utiliser l'index pour accélérer le tri dans les postgres

J'utilise postgres 9.4.

messages a le schéma suivant: les messages appartiennent à feed_id, et a posted_at, les messages peuvent également avoir un message parent (en cas de réponses).

                    Table "public.messages"
            Column            |            Type             | Modifiers
------------------------------+-----------------------------+-----------
 message_id                   | character varying(255)      | not null
 feed_id                      | integer                     |
 parent_id                    | character varying(255)      |
 posted_at                    | timestamp without time zone |
 share_count                  | integer                     |
Indexes:
    "messages_pkey" PRIMARY KEY, btree (message_id)
    "index_messages_on_feed_id_posted_at" btree (feed_id, posted_at DESC NULLS LAST)

Je souhaite renvoyer tous les messages commandés par share_count, mais pour chaque parent_id, Je souhaite renvoyer un seul message. c'est-à-dire, si plusieurs messages ont le même parent_id, puis uniquement le dernier (posted_at) est retourné. Le parent_id peut être nul, les messages avec null parent_id devrait tous revenir.

La requête que j'ai utilisée est:

WITH filtered_messages AS (SELECT * 
                           FROM messages
                           WHERE feed_id IN (7) 
                           AND (posted_at >= '2015-01-01 04:00:00.000000') 
                           AND (posted_at < '2015-04-28 04:00:00.000000'))
    SELECT *
    FROM (SELECT DISTINCT ON(COALESCE(parent_id, message_id)) parent_id,
                          message_id, 
                          posted_at, 
                          share_count
          FROM filtered_messages
          ORDER BY COALESCE(parent_id, message_id), posted_at DESC NULLS LAST
         ) messages
    ORDER BY share_count DESC NULLS LAST, posted_at DESC NULLS LAST;

Voici le http://sqlfiddle.com/#!15/588e5/1/ , dans le SQL Fiddle, j'ai défini le schéma, la requête exacte et le résultat attendu.

Mais les performances de la requête sont lentes une fois que la table des messages devient volumineuse. J'ai essayé d'ajouter plusieurs index de tri, mais il ne semble pas utiliser l'index. Voici l'explication: http://explain.depesz.com/s/Sv2

Comment puis-je créer un index correct?

10
Zhaohan Weng

Requete

Cette requête devrait être sensiblement plus rapide dans tous les cas:

SELECT parent_id, message_id, posted_at, share_count
FROM   messages
WHERE  feed_id = 7
AND    posted_at >= '2015-01-01 4:0:0'
AND    posted_at <  '2015-04-28 4:0:0'
AND    parent_id IS NULL  -- match index condition
UNION ALL
(
SELECT DISTINCT ON(parent_id)
       parent_id, message_id, posted_at, share_count
FROM   messages
WHERE  feed_id = 7
AND    posted_at >= '2015-01-01 4:0:0'
AND    posted_at <  '2015-04-28 4:0:0'
AND    parent_id IS NOT NULL  -- match index condition
ORDER  BY parent_id, posted_at DESC NULLS LAST
)
ORDER  BY share_count DESC NULLS LAST, posted_at DESC NULLS LAST;
  • Le CTE ne fait rien ici qu'une sous-requête simple ne pourrait pas fournir également. Et un CTE introduit une barrière d'optimisation car il est exécuté séparément et son résultat est matérialisé.

  • Vous disposez d'un niveau de sous-requête de plus que ce dont vous avez réellement besoin.

  • L'expression (COALESCE(parent_id, message_id) n'est pas compatible avec un index simple, vous auriez besoin d'un index sur cette expression. Mais cela peut ne pas être très utile non plus, selon la distribution des données. Suivez mes liens ci-dessous pour des informations détaillées.

  • Fractionner le cas simple de parent_id IS NULL dans un SELECT séparé peut ou non fournir l'optimum. Surtout pas, si c'est un cas rare de toute façon, auquel cas une requête combinée avec un index sur (COALESCE(parent_id, message_id) peut mieux fonctionner. D'autres considérations s'appliquent ...

Indices

Surtout lorsqu'il est pris en charge avec ces indices:

CREATE INDEX messages_idx_null ON messages (
  feed_id
, posted_at DESC NULLS LAST
, share_count DESC NULLS LAST
, parent_id, message_id
)
WHERE parent_id IS NULL;

CREATE INDEX messages_idx_notnull ON messages (
  feed_id
, posted_at DESC NULLS LAST
, share_count DESC NULLS LAST
, parent_id, message_id
)
WHERE parent_id IS NOT NULL;

Les deux indices partiels couvrent l'ensemble du tableau - ensemble et ont à peu près la même taille qu'un seul indice total.

Les deux dernières colonnes parent_id, message_id n'a de sens que si vous en retirez analyses d'index uniquement . Sinon, supprimez-les des deux indices.

SQL Fiddle.

Selon les détails manquants, DISTINCT ON peut ou non être la meilleure technique de requête à cet effet. Lisez l'explication détaillée ici:

Et des alternatives peut-être plus rapides ici:

9
Erwin Brandstetter