Isenção de responsabilidade : sou relativamente novo no PostgreSQL.
Eu estou querendo saber como otimizar uma consulta que faz 2 INNER JOIN
s. Meu cenário é bastante simples:
Selecione Postagens com uma foto ( Posts.photo IS NOT NULL
) e uma Hashtag com o nome 'morto' ( Hashtags.name = 'dead'
).
As associações são as seguintes:
Posts <- PostHashtags -> Hashtags
Posts.id = PostHashtags.postId (FK)
Hashtags.id = PostHashtags.hashtagId (FK)
Aqui está a consulta:
SELECT
"Posts".*,
"hashtags"."id" AS "hashtags.id",
"hashtags"."count" AS "hashtags.count",
"hashtags"."name" AS "hashtags.name",
"hashtags"."createdAt" AS "hashtags.createdAt",
"hashtags"."updatedAt" AS "hashtags.updatedAt",
"hashtags"."objectId" AS "hashtags.objectId",
"hashtags"."_etl" AS "hashtags._etl",
"hashtags.PostHashtag"."id" AS "hashtags.PostHashtag.id",
"hashtags.PostHashtag"."createdAt" AS "hashtags.PostHashtag.createdAt",
"hashtags.PostHashtag"."updatedAt" AS "hashtags.PostHashtag.updatedAt",
"hashtags.PostHashtag"."postId" AS "hashtags.PostHashtag.postId",
"hashtags.PostHashtag"."hashtagId" AS "hashtags.PostHashtag.hashtagId",
"hashtags.PostHashtag"."objectId" AS "hashtags.PostHashtag.objectId",
"hashtags.PostHashtag"."_etl" AS "hashtags.PostHashtag._etl"
FROM (
SELECT
"Posts"."id",
"Posts"."note",
"Posts"."photo",
"Posts"."createdAt",
"user"."id" AS "user.id",
"user"."name" AS "user.name"
FROM "Posts" AS "Posts"
INNER JOIN "Users" AS "user" ON "Posts"."userId" = "user"."id"
WHERE "Posts"."photo" IS NOT NULL
AND (
SELECT "PostHashtags"."id" FROM "PostHashtags" AS "PostHashtags"
INNER JOIN "Hashtags" AS "Hashtag" ON "PostHashtags"."hashtagId" = "Hashtag"."id"
WHERE "Posts"."id" = "PostHashtags"."postId"
LIMIT 1
) IS NOT NULL
ORDER BY "Posts"."createdAt" DESC LIMIT 10
) AS "Posts"
INNER JOIN (
"PostHashtags" AS "hashtags.PostHashtag"
INNER JOIN "Hashtags" AS "hashtags" ON "hashtags"."id" = "hashtags.PostHashtag"."hashtagId"
)
ON "Posts"."id" = "hashtags.PostHashtag"."postId"
AND "hashtags"."name" = 'dead'
ORDER BY "Posts"."createdAt" DESC;
EXPLICAR os resultados:
Nested Loop (cost=886222912.89..886223769.55 rows=1 width=277)
Join Filter: ("hashtags.PostHashtag"."postId" = "Posts".id)
-> Limit (cost=886220835.39..886220835.42 rows=10 width=189)
-> Sort (cost=886220835.39..886220988.88 rows=61394 width=189)
Sort Key: "Posts"."createdAt"
-> Nested Loop (cost=0.42..886219508.69 rows=61394 width=189)
-> Seq Scan on "Posts" (cost=0.00..885867917.51 rows=78196 width=177)
Filter: ((photo IS NOT NULL) AND ((SubPlan 1) IS NOT NULL))
SubPlan 1
-> Limit (cost=0.42..815.70 rows=1 width=4)
-> Nested Loop (cost=0.42..815.70 rows=1 width=4)
-> Seq Scan on "PostHashtags" (cost=0.00..811.25 rows=1 width=8)
Filter: ("Posts".id = "postId")
-> Index Only Scan using "Hashtags_pkey" on "Hashtags" "Hashtag" (cost=0.42..4.44 rows=1 width=4)
Index Cond: (id = "PostHashtags"."hashtagId")
-> Index Scan using "Users_pkey" on "Users" "user" (cost=0.42..4.49 rows=1 width=16)
Index Cond: (id = "Posts"."userId")
-> Materialize (cost=2077.50..2933.89 rows=1 width=88)
-> Hash Join (cost=2077.50..2933.89 rows=1 width=88)
Hash Cond: ("hashtags.PostHashtag"."hashtagId" = hashtags.id)
-> Seq Scan on "PostHashtags" "hashtags.PostHashtag" (cost=0.00..721.00 rows=36100 width=40)
-> Hash (cost=2077.49..2077.49 rows=1 width=48)
-> Seq Scan on "Hashtags" hashtags (cost=0.00..2077.49 rows=1 width=48)
Filter: ((name)::text = 'dead'::text)
Esta consulta foi ligeiramente simplificada. Ele também executa OUTER JOINS
em outros dados relacionados a Posts
, e é por isso que SELECT
deve ser executado em Posts
vez de, digamos, PostHashtags
.
Qualquer ajuda na tradução EXPLAIN
para um índice útil seria muito apreciada.
Minhas ideias:
- Construa um índice em
Posts.photo
, mas deve ser um índice parcialWHERE "photo" IS NOT NULL
? - Crie um
UNIQUE
índice emHashtags.name
.
Não tenho certeza se esses são necessariamente os gargalos.
Considere também a primeira resposta .
Consulta
Isso faz o que sua consulta atual faz atualmente, apenas mais simples e rápido:
A
EXISTS
semijunção deve ser mais rápida que a construção da subconsulta. Estou assumindo que a coluna"PostHashtags".id
é o PK e não pode ser NULL por si só. Além disso, se a integridade referencial for imposta por uma restrição FK, não há necessidade de ingressar"Hashtags"
neste teste.Índices
índice parcial em
Posts
Observe as colunas:
("createdAt", id)
. O Postgres fará as postagens mais recentes, espero uma varredura de índice aposts_foo_idx
partir do topo, seguida de um teste para correspondência de entradasPostHashtags
usandoid
o próximo índice.índice ÚNICO em
PostHashtags
Desta vez, precisamos do índice com
"postId"
primeiro.O resto é principalmente como na primeira resposta .
Considere também a resposta alternativa .
Consulta
Isso faz o que sua descrição diz, não o que sua consulta faz atualmente ( veja o comentário ):
.. limitado aos últimos 10 postos de qualificação:
Se você impor a integridade referencial entre
Posts
eUsers
com uma restrição FK, faça isso Dessa forma, o Postgres pode escolher as últimas dez linhas antes de considerar os usuários.LEFT
JOIN "Users"
As várias subconsultas e parênteses em suas junções não foram úteis.
Índices
Suas duas ideias são boas. Mais pelo menos mais um.
índice parcial em
Posts
Observe as colunas:
(id, "createdAt")
. Minha expectativa é que o Postgres comece com o predicado mais seletivoHashtags.name
e trabalhePostHashtags
atéPosts
. Então o índice usadoposts_foo_idx
já fornececreatedAt
, que é necessário paraORDER BY
/LIMIT
.O índice parcial só faz sentido se uma grande porcentagem da tabela tiver
photo IS NOT NULL
. Caso contrário, torne-o um índice completo.Índice ÚNICO em
Hashtags.name
.índice ÚNICO em
PostHashtags
Assumindo que cada postagem só pode ser associada a cada hashtag uma vez .
Este poderia ser o seu PK. Talvez já seja o seu PK, mas com a ordem das colunas invertida: ("postId", "hashtagId"). Você provavelmente deve ter ambas as variantes. Para minha consulta, você precisa primeiro deste com "hashtagId". Para sua consulta, você precisaria do contrário. Considerar:
Aparte
Eu sugiro que você reconsidere sua convenção de nomenclatura. Use identificadores legais em minúsculas para evitar a confusão de citações e muito potencial para erros.