Чому CTE набагато гірший, ніж вбудовані підзапити


11

Я намагаюся краще зрозуміти, як працює планувальник запитів у postgresql.

У мене є цей запит:

select id from users 
    where id <> 2
    and gender = (select gender from users where id = 2)
    order by latest_location::geometry <-> (select latest_location from users where id = 2) ASC
    limit 50

У моїй базі даних він працює менше ніж 10 мс, в таблиці користувачів близько 500 тис. Записів.

Тоді я подумав, що для уникнення дублікатів підселектів я можу переписати запит як CTE, наприклад:

with me as (
    select * from users where id = 2
)
select u.id, u.popularity from users u, me 
    where u.gender = me.gender
    order by  u.latest_location::geometry <-> me.latest_location::geometry ASC
    limit 50;

Однак цей переписаний запит працює приблизно за 1 секунду! Чому це відбувається? Я бачу в поясненнях, що він не використовує індекс геометрії, але чи можна щось зробити для цього? Дякую!

Ще один спосіб написати запит:

select u.id, u.popularity from users u, (select gender, latest_location from users where id = 2) as me 
    where u.gender = me.gender
    order by  u.latest_location::geometry <-> me.latest_location::geometry ASC
    limit 50;

Однак це також буде настільки ж повільним, як і CTE.

Якщо з іншого боку я витягаю параметри мені і статично вставляю їх, запит знову швидкий:

select u.id, u.popularity from users u
    where u.gender = 'male'
    order by  u.latest_location::geometry <-> '0101000000A49DE61DA71C5A403D0AD7A370F54340'::geometry ASC
    limit 50;

Поясніть перший (швидкий) запит

 Limit  (cost=5.69..20.11 rows=50 width=36) (actual time=0.512..8.114 rows=50 loops=1)
   InitPlan 1 (returns $0)
     ->  Index Scan using users_pkey on users users_1  (cost=0.42..2.64 rows=1 width=32) (actual time=0.032..0.033 rows=1 loops=1)
           Index Cond: (id = 2)
   InitPlan 2 (returns $1)
     ->  Index Scan using users_pkey on users users_2  (cost=0.42..2.64 rows=1 width=4) (actual time=0.009..0.010 rows=1 loops=1)
           Index Cond: (id = 2)
   ->  Index Scan using users_latest_location_gix on users  (cost=0.41..70796.51 rows=245470 width=36) (actual time=0.509..8.100 rows=50 loops=1)
         Order By: (latest_location <-> $0)
         Filter: (gender = $1)
         Rows Removed by Filter: 20
 Total runtime: 8.211 ms
(12 rows)

Поясніть другий (повільний) запит

Limit  (cost=62419.82..62419.95 rows=50 width=76) (actual time=1024.963..1024.970 rows=50 loops=1)
   CTE me
     ->  Index Scan using users_pkey on users  (cost=0.42..2.64 rows=1 width=221) (actual time=0.037..0.038 rows=1 loops=1)
           Index Cond: (id = 2)
   ->  Sort  (cost=62417.18..63030.86 rows=245470 width=76) (actual time=1024.959..1024.963 rows=50 loops=1)
         Sort Key: ((u.latest_location <-> me.latest_location))
         Sort Method: top-N heapsort  Memory: 28kB
         ->  Hash Join  (cost=0.03..54262.85 rows=245470 width=76) (actual time=0.122..938.131 rows=288646 loops=1)
               Hash Cond: (u.gender = me.gender)
               ->  Seq Scan on users u  (cost=0.00..49353.41 rows=490941 width=48) (actual time=0.021..465.025 rows=490994 loops=1)
               ->  Hash  (cost=0.02..0.02 rows=1 width=36) (actual time=0.054..0.054 rows=1 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 1kB
                     ->  CTE Scan on me  (cost=0.00..0.02 rows=1 width=36) (actual time=0.047..0.049 rows=1 loops=1)
 Total runtime: 1025.096 ms

3
Я писав про це нещодавно; див. blog.2ndquadrant.com/postgresql-ctes-are-optimization-fences . Хоча наразі є деякі проблеми з DNS, які можуть обмежувати доступність цього сайту. FROMДля кращих результатів спробуйте запит замість терміна CTE.
Крейг Рінгер

що робити, якщо ви використовуєте (select id, latest_location from users where id = 2)як cte? Може бути , це * , що викликає цю проблему
тя

Я б подумав , що ви б шукати найближчі користувач протилежної статі :)
тя

@cha Не має різниці в швидкості, просто вибираючи стать та місце розташування в cte. (У моєму випадку я хочу взяти в середньому подібних користувачів, тільки що я спростив запит на запитання)
viblo

@CraigRinger Я не думаю, що його паркан оптимізації. Я також спробував вашу пропозицію, і це було також повільно. З іншого боку, якщо я витягаю параметри вручну, це швидко (і в моєму випадку це реальна опція, у будь-якому випадку кінцевий результат - це функція).
viblo

Відповіді:


11

Спробуйте це:

with me as (
    select * from users where id = 2
)
select u.id, u.popularity from users u, me 
    where u.gender = (select gender from me)
    order by  u.latest_location::geometry <-> (select latest_location from me)::geometry ASC
    limit 50;

Коли я дивлюсь на швидкий план, ось що вискакує на мене (жирним шрифтом):

 Обмеження (вартість = 5,69..20,11 рядів = 50 ширина = 36) (фактичний час = 0,512..8,114 рядків = 50 петель = 1)
   InitPlan 1 ( повертає $ 0 )
     -> Індексування сканування за допомогою_користувача на користувачів users_1 (вартість = 0,42..2,64 рядка = 1 ширина = 32) (фактичний час = 0,032..0,033 рядків = 1 петля = 1)
           Індекс Cond: (id = 2)
   InitPlan 2 ( повертає $ 1 )
     -> Індексування сканування за допомогою_користувача на користувача users_2 (вартість = 0,42..2,64 рядка = 1 ширина = 4) (фактичний час = 0,009..0,010 рядків = 1 петля = 1)
           Індекс Cond: (id = 2)
   -> Індексування сканування за допомогою користувачів_latest_location_gix для користувачів (вартість = 0,41..70796,51 рядків = 245470 ширина = 36) (фактичний час = 0,550..8,1100 рядків = 50 циклів = 1)
         Замовити за: (остання_розміщення   $ 0 )
         Фільтр: (стать = 1 долар )
         Рядки видалені фільтром: 20
 Загальний час виконання: 8,211 мс
(12 рядів)

У повільній версії планувальник запитів оцінює оператора рівності genderта оператора геометрії на latest_locationв контексті з'єднання , де значення meможе змінюватись у кожному рядку (навіть якщо він правильно оцінив лише 1 рядок). У швидкій версії значення genderі latest_locationрозглядаються як скаляри, оскільки вони випромінюються вбудованими підзапитими, що повідомляє планувальнику запитів, що він має лише одне значення кожного, з яким потрібно мати справу. Це та сама причина, чому ви отримуєте швидкий план, коли вставляєте буквальні значення.


Я думаю, ви можете вилучити meцю fromпропозицію вже зараз.
Jarius Hebzo
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.