INNER JOIN 条件中的列顺序严重影响性能

Posted

技术标签:

【中文标题】INNER JOIN 条件中的列顺序严重影响性能【英文标题】:Order of columns in INNER JOIN condition affects the performance badly 【发布时间】:2018-12-13 02:34:06 【问题描述】:

我有两个表,它们像这样相互链接:

answered_questions 包含以下列和索引:

id:主键 taken_test_id:整数(外键) question_id:整数(外键,链接到另一个名为 questions 的表) indexes: (taken_test_id, question_id)

taken_tests

id:主键 user_id:(外键,指向表用户的链接) 索引: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 在 ActiveRecord 中使用joins 方法时自动生成的)

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 条件中的列顺序不应该影响执行时间(例如:Best practices for the order of joined columns in a sql join?)

我正在使用 Postgres 9.6。 answered_questions表超过4000万行,taken_tests表超过300万行

更新 1:

当我使用(analyze true, verbose true, buffers true) 运行 EXPLAIN 时,第二个查询得到了更好的结果(与第一个查询非常相似)

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

【问题讨论】:

您在手动输入的多个测试中始终获得这种性能差异? 是的,反复测试比较,结果还是一样 你可以使用explain(analyze true, verbose true, buffers true) 吗?并再次分享结果 @dwir182 我更新了我的问题中的输出,当我添加这些选项时它似乎运行得更快 我能想象的唯一不诉诸魔法和不明飞行物的解释是,在缓慢的情况下,必须从磁盘读取内部循环的 1717 个块中的一些(缓存效果)。但这与您关于效果持续存在的说法完全相矛盾。还能重现吗? 【参考方案1】:

正如您从最初的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.483rows=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.474rows=371 loops=1)

正如评论者已经指出的那样(请参阅 wuestion cmets 中的文档链接),无论表顺序如何,内连接的查询计划都应该是相同的。它是根据查询计划器的决定进行排序的。这意味着您应该真正查看查询执行的其他性能优化部分。其中之一是用于缓存的内存 (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),这是在吸尘期间完成的。

【讨论】:

以上是关于INNER JOIN 条件中的列顺序严重影响性能的主要内容,如果未能解决你的问题,请参考以下文章

Oracle SQL - FROM 子句中的 JOIN 顺序会影响性能优化吗?

Inner Join and Left Join 与条件的结合

Inner Join and Left Join 与条件的结合

sql中的inner join ,left join ,right join

26.MySQL中的内连接INNER JOIN

我想知道使用 INNER JOIN 和相等运算符更快,或者当我尝试通过另一个表的列过滤表中的数据时使用 IN