postgres Mau desempenho em ORDER BY “id” DESC LIMIT 1
-
29-09-2020 - |
Pergunta
eu tenho mesa items
com o seguinte esquema (no postgres v9.3.5):
Column | Type | Modifiers | Storage
-----------+--------+----------------------------------------------------+----------
id | bigint | not null default nextval('items_id_seq'::regclass) | plain
data | text | not null | extended
object_id | bigint | not null | plain
Indexes:
"items_pkey" PRIMARY KEY, btree (id)
"items_object_id_idx" btree (object_id)
Has OIDs: no
Quando executo a consulta, ela trava por muito tempo:
SELECT * FROM "items" WHERE "object_id" = '123' ORDER BY "id" DESC LIMIT 1;
Após VACUUM ANALYZE a execução da consulta melhorou bastante, mas ainda não é perfeita.
# EXPLAIN ANALYZE SELECT * FROM "items" WHERE "object_id" = '123' ORDER BY "id" DESC LIMIT 1;
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=0.44..1269.14 rows=1 width=63) (actual time=873796.061..873796.061 rows=0 loops=1)
-> Index Scan Backward using items_pkey on items (cost=0.44..1164670.11 rows=918 width=63) (actual time=873796.059..873796.059 rows=0 loops=1)
Filter: (object_id = 123::bigint)
Rows Removed by Filter: 27942522
Total runtime: 873796.113 ms
(5 rows)
O estranho é que quando executo
SELECT * FROM "items" WHERE "object_id" = '123' LIMIT 1;
ele retorna 0 linhas e posso fazer isso em meu código para otimizar o desempenho do meu aplicativo web, mas por que isso pode ser feito pelo próprio Postgres?Vim do MySQL para o Postgres e nunca vi coisas tão estranhas lá.
=====
Descobri que ele usa planos de consulta diferentes, índices diferentes, mas por quê?
# EXPLAIN ANALYZE SELECT * FROM "items" WHERE "object_id" = '123' LIMIT 1;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=0.56..3.34 rows=1 width=63) (actual time=0.014..0.014 rows=0 loops=1)
-> Index Scan using items_object_id_operation_idx on items (cost=0.56..2579.16 rows=929 width=63) (actual time=0.013..0.013 rows=0 loops=1)
Index Cond: (object_id = 123::bigint)
Total runtime: 0.029 ms
(4 rows)
Solução 2
Para otimizar a consulta
SELECT * FROM "items" WHERE "object_id" = '123' ORDER BY "id" DESC LIMIT 1;
Eu fiz o seguinte
SELECT * FROM
(SELECT * FROM "items"
WHERE "object_id" = '123'
ORDER BY "id" DESC) AS "items"
ORDER BY "id" DESC LIMIT 1;
Ajudou sem adicionar índice (object_id asc, id desc)
que foi sugerido por @mustaccio.
# EXPLAIN SELECT * FROM
(SELECT * FROM "items"
WHERE "object_id" = '123'
ORDER BY "id" DESC) AS "items"
ORDER BY "id" DESC LIMIT 1;
QUERY PLAN
--------------------------------------------------------------------------------------------------------
Limit (cost=16629.84..16629.86 rows=1 width=59)
-> Sort (cost=16629.84..16640.44 rows=4239 width=59)
Sort Key: items.id
-> Bitmap Heap Scan on items (cost=125.42..16374.45 rows=4239 width=59)
Recheck Cond: (object_id = 123::bigint)
-> Bitmap Index Scan on items_object_id_idx (cost=0.00..124.36 rows=4239 width=0)
Index Cond: (object_id = 123::bigint)
(7 rows)
Outras dicas
Tentando explicar porque há diferença de desempenho entre as duas consultas.
Este: SELECT * FROM "items" WHERE "object_id" = '123' LIMIT 1
está satisfeito por qualquer uma linha com a correspondência object_id
, então o índice em object_id
é uma escolha natural.A consulta requer E/S mínima:varredura de índice para encontrar o primeiro valor correspondente mais uma leitura de heap para buscar a linha inteira.
A alternativa: SELECT * FROM "items" WHERE "object_id" = '123' ORDER BY "id" DESC LIMIT 1
requer todos linhas com a correspondência object_id
ser classificado por outra coluna, id
, então a linha com o valor máximo de id
Ser devolvido.Se você usasse o índice em object_id
você precisaria realizar as seguintes operações:escaneie o índice para encontrar todo Coincidindo object_id
;para cada partida, busque a linha real;em seguida, classifique todas as linhas buscadas por id
e devolva aquele com o maior id
.
A alternativa escolhida pelo otimizador, presumivelmente baseada na object_id
histograma, é:escaneie o índice em id
para trás, na sua totalidade;para cada valor vá buscar a linha e verifique se o valor de object_id
partidas;retornar a primeira linha correspondente, que terá o máximo possível id
valor.Esta alternativa evita a classificação das linhas, então acho que o otimizador prefere usar o índice em object_id
.
A presença de um índice (object_id asc, id desc)
permite ainda outra alternativa:digitalize este novo índice para a primeira entrada que corresponda ao fornecido object_id
valor, que por definição terá o maior id
valor;vá buscar uma linha correspondente e retorne.Obviamente, esta é a abordagem mais eficiente.