postgres Плохая производительность при ORDER BY «id» DESC LIMIT 1
-
29-09-2020 - |
Вопрос
у меня есть стол items
со следующей схемой (в 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
Когда я выполняю запрос, он очень долго зависает:
SELECT * FROM "items" WHERE "object_id" = '123' ORDER BY "id" DESC LIMIT 1;
После VACUUM ANALYZE выполнение запросов значительно улучшилось, но все еще не идеально.
# 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)
Странно то, что когда я выполняю
SELECT * FROM "items" WHERE "object_id" = '123' LIMIT 1;
он возвращает 0 строк, и я могу сделать это в своем коде, чтобы оптимизировать производительность моего веб-приложения, но почему это может сделать сам Postgres?Я пришел в Postgres из MySQL и никогда не видел там таких странных вещей.
=====
Я обнаружил, что он использует другой план запроса, другой индекс, но почему?
# 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)
Решение 2
Чтобы оптимизировать запрос
SELECT * FROM "items" WHERE "object_id" = '123' ORDER BY "id" DESC LIMIT 1;
Я сделал следующее
SELECT * FROM
(SELECT * FROM "items"
WHERE "object_id" = '123'
ORDER BY "id" DESC) AS "items"
ORDER BY "id" DESC LIMIT 1;
Помогло без добавления индекса (object_id asc, id desc)
который предложил @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)
Другие советы
Попытка объяснить, почему существует разница в производительности между двумя запросами.
Вот этот: SELECT * FROM "items" WHERE "object_id" = '123' LIMIT 1
удовлетворен любой одна строка с соответствием object_id
, поэтому индекс на object_id
это естественный выбор.Запрос требует минимального ввода-вывода:сканирование индекса для поиска первого совпадающего значения плюс одно чтение кучи для извлечения всей строки.
Альтернатива: SELECT * FROM "items" WHERE "object_id" = '123' ORDER BY "id" DESC LIMIT 1
требует все строки с соответствием object_id
сортироваться по другому столбцу, id
, то строка с максимальным значением id
быть возвращено.Если бы вы использовали индекс на object_id
вам потребуется выполнить следующие операции:просканируйте индекс, чтобы найти каждый соответствие object_id
;для каждого совпадения выберите фактическую строку;затем отсортируйте все выбранные строки по id
и верните тот, у которого самый большой id
.
Альтернатива, выбранная оптимизатором, предположительно основанная на object_id
гистограмма, это:сканировать индекс на id
назад, целиком;для каждого значения выберите строку и проверьте, соответствует ли значение object_id
Матчи;вернуть первую совпадающую строку, которая будет иметь максимально возможное значение id
ценить.Эта альтернатива позволяет избежать сортировки строк, поэтому я думаю, что оптимизатор предпочитает использовать индекс в object_id
.
Наличие индекса на (object_id asc, id desc)
допускает еще одну альтернативу:просканируйте этот новый индекс на предмет первой записи, соответствующей предоставленному object_id
значение, которое по определению будет иметь наибольшую id
ценить;возьмите одну подходящую строку и вернитесь.Очевидно, что это наиболее эффективный подход.