Почему один запрос с дополнительным соединением выполняется быстрее, чем тот же запрос по нормализованным данным (на одно соединение меньше)? - PullRequest
0 голосов
/ 30 мая 2020

У меня две базы данных, каждая из которых содержит данные и «метки» (обозначены обозначением * _p). В одной базе данных метки встроены в данные (хранятся в таблице), в другой метки хранятся в другой таблице, и поэтому для доступа к меткам требуется соединение. Для большинства запросов вариант, использующий встроенные метки, работает быстрее, за исключением одного запроса. Мне было интересно, может ли кто-нибудь дать мне некоторое представление о том, почему это может быть так, я сам не очень знаком с деталями postgres, sql или базами данных в целом? Ниже я привожу вывод EXPLAIN ANALYZE. Заранее благодарим.

База данных состоит из местоположений и пользователей, связанных с этими точками местоположения, данные помечены полями * _p. Разница между двумя базами данных состоит в том, что в одной метки встроены в ту же таблицу, что и данные, а в другой они хранятся в другой таблице, что требует дополнительного соединения. Помимо этой разницы, оба запроса делают одно и то же. Для большинства запросов мы видим, что метод со встроенными метками работает быстрее, но для этого конкретного запроса он медленнее, и я хотел бы знать, почему, если это возможно. На самом деле я не ищу подробного ответа. Мне просто интересно, было ли что-то очевидное, из-за чего один план запроса работал медленнее, чем другой.

Запрос внешних меток:

SELECT firstname, lastname, latitude, longitude 
FROM locations INNER 
   JOIN users ON locations.userid = users.id 
   JOIN users_p users0x ON users.id = users0x.users_id 
   JOIN locations_p locations0x ON locations.id = locations0x.locations_id 
WHERE country = ? AND date_part('year', age(birthdate)) > 18 
    AND (date_part('year', to_date(timestamp, 'YYYY-MM-DD')) BETWEEN 2010 AND 2019) 
    AND (locations0x.longitude_p & '2') != 0 AND (users0x.lastname_p & '2') != 0 AND (users0x.firstname_p & '2') != 0 AND (users0x.birthdate_p & '2') != 0 AND (locations0x.timestamp_p & '2') != 0 
    AND (users0x.country_p & '2') != 0 AND (locations0x.latitude_p & '2') != 0

Объяснение результатов анализа:

 Nested Loop  (cost=0.42..10116.71 rows=12 width=22) (actual time=317.811..331.306 rows=954 loops=1)
 ->  Nested Loop  (cost=0.00..10051.48 rows=12 width=26) (actual time=317.793..328.701 rows=954 loops=1)
     Join Filter: (locations.userid = users0x.users_id)
     Rows Removed by Join Filter: 94446
     ->  Seq Scan on users_p users0x  (cost=0.00..4.00 rows=96 width=4) (actual time=0.007..0.051 rows=100 loops=1)
           Filter: (((country_p & 2) <> 0) AND ((birthdate_p & 2) <> 0) AND ((lastname_p & 2) <> 0) AND ((firstname_p & 2) <> 0))
     ->  Materialize  (cost=0.00..10030.23 rows=12 width=34) (actual time=0.000..3.209 rows=954 loops=100)
           ->  Nested Loop  (cost=0.00..10030.17 rows=12 width=34) (actual time=0.034..317.266 rows=954 loops=1)
                 Join Filter: (locations.userid = users.id)
                 Rows Removed by Join Filter: 98382
                 ->  Seq Scan on users  (cost=0.00..6.25 rows=1 width=18) (actual time=0.019..0.043 rows=2 loops=1)
                       Filter: ((country = 'Colombia'::text) AND (date_part('year'::text, age((('now'::cstring)::date)::timestamp with time zone, (to_date(birthdate, 'YYYY-MM-DD'::text))::timestamp with time zone)) > '18'::double precision))
                       Rows Removed by Filter: 98
                 ->  Seq Scan on locations  (cost=0.00..10008.30 rows=1250 width=16) (actual time=0.007..155.286 rows=49668 loops=2)
                       Filter: ((date_part('year'::text, (to_date("timestamp", 'YYYY-MM-DD'::text))::timestamp without time zone) >= '2010'::double precision) AND (date_part('year'::text, (to_date("timestamp", 'YYYY-MM-DD'::text))::timestamp without time zone) <= '2019'::double precision))
                       Rows Removed by Filter: 200332
 ->  Index Scan using locations_purpose_index on locations_p locations0x  (cost=0.42..5.43 rows=1 width=4) (actual time=0.002..0.002 rows=1 loops=954)
     Index Cond: (locations_id = locations.id)
     Filter: (((latitude_p & 2) <> 0) AND ((longitude_p & 2) <> 0) AND ((timestamp_p & 2) <> 0))
Planning time: 0.555 ms
Execution time: 331.449 ms

Запрос встроенных меток:

 SELECT firstname, lastname, latitude, longitude 
 FROM locations INNER JOIN users ON locations.userid = users.id 
 WHERE country = ? AND date_part('year', age(birthdate)) > 18 
    AND (date_part('year', to_date(timestamp, 'YYYY-MM-DD')) BETWEEN 2010 AND 2019) 
    AND (users.firstname_p & '2') != 0 
    AND (locations.timestamp_p & '2') != 0 
    AND (users.country_p & '2') != 0 AND (locations.longitude_p & '2') != 0 
    AND (locations.latitude_p & '2') != 0 
    AND (users.lastname_p & '2') != 0 AND (users.birthdate_p & '2') != 0

Объяснение результатов анализа:

 Nested Loop  (cost=0.00..13782.09 rows=12 width=22) (actual time=0.113..421.690 rows=954 loops=1)
 Join Filter: (locations.userid = users.id)
 Rows Removed by Join Filter: 98382
 ->  Seq Scan on users  (cost=0.00..8.25 rows=1 width=18) (actual time=0.062..0.087 rows=2 loops=1)
     Filter: ((country = 'Colombia'::text) AND ((country_p & 2) <> 0) AND ((birthdate_p & 2) <> 0) AND ((lastname_p & 2) <> 0) AND ((firstname_p & 2) <> 0) AND (date_part('year'::text, age((('now'::cstring)::date)::timestamp with time zone, (to_date(birthdate, 'YYYY-MM-DD'::text))::timestamp with time zone)) > '18'::double precision))
     Rows Removed by Filter: 98
 ->  Seq Scan on locations  (cost=0.00..13758.45 rows=1231 width=12) (actual time=0.018..207.065 rows=49668 loops=2)
     Filter: (((latitude_p & 2) <> 0) AND ((longitude_p & 2) <> 0) AND ((timestamp_p & 2) <> 0) AND (date_part('year'::text, (to_date("timestamp", 'YYYY-MM-DD'::text))::timestamp without time zone) >= '2010'::double precision) AND (date_part('year'::text, (to_date("timestamp", 'YYYY-MM-DD'::text))::timestamp without time zone) <= '2019'::double precision))
     Rows Removed by Filter: 200332
 Planning time: 0.811 ms
 Execution time: 421.820 ms

1 Ответ

0 голосов
/ 11 июня 2020
date_part('year', age(birthdate)) > 18
(date_part('year', to_date(timestamp, 'YYYY-MM-DD')) BETWEEN 2010 AND 2019)

Первый даже «правильный»? Вроде игнорирует месяц и день. Возьмите творог, вычтите 18 лет и сравните с birthdate. См. https://en.wikipedia.org/wiki/Sargable

Второй не должен преобразовывать временную метку вообще. Вместо этого просто имейте литералы диапазона дат, которые совместимы с ним.

Что с & 2? Это пахнет убийцей производительности.

Затем рассмотрим эти составные индексы:

(country, birthdate)
(country, timestamp)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...