Порядок столбцов в состоянии INNER JOIN плохо влияет на производительность - PullRequest
0 голосов
/ 13 декабря 2018

У меня есть две таблицы, которые связаны друг с другом следующим образом:

Таблица answered_questions со следующими столбцами и индексами:

  • id: первичный ключ
  • taken_test_id: целое число (внешний ключ)
  • question_id: целое число (внешний ключ, ссылки на другую таблицу с именем questions)
  • indexes: (take_test_id, question_id)

Таблица taken_tests

  • id: первичный ключ
  • user_id: (внешний ключ, ссылки на таблицу Users)
  • индексы: user_id столбец

Первый запрос (с выводом EXPLAIN ANALYZE):

EXPLAIN ANALYZE 
SELECT 
  "answered_questions".* 
FROM 
  "answered_questions" 
  INNER JOIN "taken_tests" ON "answered_questions"."taken_test_id" = "taken_tests"."id" 
WHERE 
  "taken_tests"."user_id" = 1;

Вывод:

Nested Loop  (cost=0.99..116504.61 rows=1472 width=61) (actual time=0.025..2.208 rows=653 loops=1)
   ->  Index Scan using index_taken_tests_on_user_id on taken_tests  (cost=0.43..274.18 rows=91 width=4) (actual time=0.014..0.483 rows=371 loops=1)
         Index Cond: (user_id = 1)
   ->  Index Scan using index_answered_questions_on_taken_test_id_and_question_id on answered_questions  (cost=0.56..1273.61 rows=365 width=61) (actual time=0.00
2..0.003 rows=2 loops=371)
         Index Cond: (taken_test_id = taken_tests.id)
 Planning time: 0.276 ms
 Execution time: 2.365 ms
(7 rows)

Другой запрос (он генерируется Rails автоматически при использовании метода joins в ActiveRecord)

EXPLAIN ANALYZE 
SELECT 
  "answered_questions".* 
FROM 
  "answered_questions" 
  INNER JOIN "taken_tests" ON "taken_tests"."id" = "answered_questions"."taken_test_id" 
WHERE 
  "taken_tests"."user_id" = 1;

А вот вывод

Nested Loop  (cost=0.99..116504.61 rows=1472 width=61) (actual time=23.611..1257.807 rows=653 loops=1)
   ->  Index Scan using index_taken_tests_on_user_id on taken_tests  (cost=0.43..274.18 rows=91 width=4) (actual time=10.451..71.474 rows=371 loops=1)
         Index Cond: (user_id = 1)
   ->  Index Scan using index_answered_questions_on_taken_test_id_and_question_id on answered_questions  (cost=0.56..1273.61 rows=365 width=61) (actual time=2.07
1..3.195 rows=2 loops=371)
         Index Cond: (taken_test_id = taken_tests.id)
 Planning time: 0.302 ms
 Execution time: 1258.035 ms
(7 rows)

Единственным отличием является порядок столбцов в условии INNER JOIN .В первом запросе это ON "answered_questions"."taken_test_id" = "taken_tests"."id", а во втором запросе это ON "taken_tests"."id" = "answered_questions"."taken_test_id".Но время запроса сильно отличается.

У вас есть идеи, почему это происходит?Я прочитал некоторые статьи, и там говорится, что порядок столбцов в состоянии JOIN не должен влиять на время выполнения (например: Рекомендации по порядку столбцов, объединяемых в соединении SQL? )

Я использую Postgres 9.6.В таблице answered_questions более 40 миллионов строк и в таблице taken_tests более 3 миллионов строк

Обновление 1:

Когда я запустил EXPLAIN с помощью (analyze true, verbose true, buffers true), я получилгораздо лучший результат для второго запроса (очень похожий на первый запрос)

EXPLAIN (ANALYZE TRUE, VERBOSE TRUE, BUFFERS TRUE) 
SELECT
  "answered_questions".* 
FROM
  "answered_questions"
  INNER JOIN "taken_tests" ON "taken_tests"."id" = "answered_questions"."taken_test_id" 
WHERE
  "taken_tests"."user_id" = 1;

Вывод

Nested Loop  (cost=0.99..116504.61 rows=1472 width=61) (actual time=0.030..2.192 rows=653 loops=1)
   Output: answered_questions.id, answered_questions.question_id, answered_questions.answer_text, answered_questions.created_at, answered_questions.updated_at, a
nswered_questions.taken_test_id, answered_questions.correct, answered_questions.answer
   Buffers: shared hit=1986
   ->  Index Scan using index_taken_tests_on_user_id on public.taken_tests  (cost=0.43..274.18 rows=91 width=4) (actual time=0.014..0.441 rows=371 loops=1)
         Output: taken_tests.id
         Index Cond: (taken_tests.user_id = 1)
         Buffers: shared hit=269
   ->  Index Scan using index_answered_questions_on_taken_test_id_and_question_id on public.answered_questions  (cost=0.56..1273.61 rows=365 width=61) (actual ti
me=0.002..0.003 rows=2 loops=371)
         Output: answered_questions.id, answered_questions.question_id, answered_questions.answer_text, answered_questions.created_at, answered_questions.updated
_at, answered_questions.taken_test_id, answered_questions.correct, answered_questions.answer
         Index Cond: (answered_questions.taken_test_id = taken_tests.id)
         Buffers: shared hit=1717
 Planning time: 0.238 ms
 Execution time: 2.335 ms

1 Ответ

0 голосов
/ 27 марта 2019

Как видно из исходных результатов оператора EXPLAIN ANALYZE - запросы приводят к эквивалентному плану запросов и выполняются точно так же.

Разница заключается в времени выполнения одного и того же блока:

-> Index Scan using index_taken_tests_on_user_id on taken_tests (cost=0.43..274.18 rows=91 width=4) ( фактическое время = 0,014..0.483 rows=371 loops=1)

и

-> Index Scan using index_taken_tests_on_user_id on taken_tests (cost=0.43..274.18 rows=91 width=4) ( фактическое время = 10.451 ..71.474 rows=371 loops=1)

Как уже отмечали комментаторы (см. Ссылки на документацию в комментариях к wuestion), ожидается, что план запроса для внутреннего объединения будет одинаковым независимо от порядка таблиц.Заказывается на основе решений планировщика запросов.Это означает, что вам следует обратить внимание на другие части оптимизации производительности при выполнении запроса.Одним из них будет память, используемая для кэширования (SHARED BUFFER).Похоже, что результаты запроса во многом будут зависеть от того, были ли эти данные уже загружены в память.Как вы заметили, время выполнения запроса увеличивается после того, как вы подождали некоторое время.Это ясно указывает на проблему с истечением срока действия кэша, чем на план.Увеличение размера общих буферов может помочь решить его, но первоначальное выполнение запроса всегда будет занимать больше времени - это всего лишь скорость доступа к диску.

Дополнительные советы по настройке памяти базы данных Pg см. Здесь: https://wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server

Примечание : команды VACUUM или ANALYZE вряд ли помогут здесь.Оба запроса уже используют один и тот же план.Имейте в виду, однако, что из-за механизма изоляции транзакций PostgreSQL (MVCC) может потребоваться прочитать строки базовой таблицы, чтобы проверить, что они все еще видны текущей транзакции после получения результатов из индекса.Это можно улучшить, обновив карту видимости (см. https://www.postgresql.org/docs/10/storage-vm.html),, которая выполняется во время очистки.

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