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

10

J'utilise postgres 9.4.

Le messagesa 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 chacun parent_id, je souhaite renvoyer un seul message. c'est-à-dire, si plusieurs messages ont le même parent_id, alors seul le dernier ( posted_at) est retourné. Le parent_idpeut être nul, les messages avec null parent_iddoivent tous retourner.

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/0 , 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?

Zhaohan Weng
la source
À première vue, le ORDER BYdans la sous-requête est totalement inutile. De plus, le plan lié ne peut pas être le résultat de la requête publiée - il n'est pas fait mention metadata, par exemple.
dezso
Votre description ne couvre pas le rôle de feed_idet posted_atet vous ne l'avez pas mentionné metadatadu tout, qui semble être un type JSON? Veuillez réparer votre question pour la rendre cohérente. Vous sélectionnez> 500k lignes dans le CTE ... Combien de lignes sont dans le tableau? Quel pourcentage de lignes sélectionnez-vous généralement dans le CTE? Quel est le pourcentage de lignes parent_id IS NULL? Considérez les informations dans la balise [postgresql-performance] pour les questions de performances.
Erwin Brandstetter
Aussi important: combien de lignes pour chacune parent_id? (min / moy / max)
Erwin Brandstetter
désolé, j'essayais de rendre la question plus claire en réduisant certaines des colonnes, share_count était en fait dans hstore metadata. Actuellement, le tableau des messages contient des données de 10 mil, mais augmente rapidement. Je pense me séparer en tables de partition pour chaque feed_id. Étant donné que je ne récupère que par identifiant de flux. le pourcentage de parent_id nul vs non nul est d'environ 60% / 40%. une extraction typique représente environ 1 à 2% du tableau. (environ 100K messages) Les performances pour 100K sont d'environ 1s, mais une fois à 500K +, il utilise un index bitmap et prend normalement 10s.
Zhaohan Weng

Réponses:

9

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.

  • Le fait de diviser le cas simple parent_id IS NULLen un autre SELECTpeut 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_idn'ont de sens que si vous en obtenez des analyses d'index uniquement . Sinon, supprimez-les des deux indices.

SQL Fiddle.

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

Et des alternatives peut-être plus rapides ici:

Erwin Brandstetter
la source