【问题标题】:Avoiding hash-join with IN clause and sub-query in Spanner在 Spanner 中避免使用 IN 子句和子查询进行哈希连接
【发布时间】:2017-11-10 15:45:26
【问题描述】:

我在 Spanner 中遇到了以下查询优化问题,希望我缺少一个技巧,可以帮助我根据自己的意愿调整查询规划器。

这是简化的架构:

create table T0 (
  key0  int64 not null,
  value int64,
  other int64 not null,
) primary key (key0);

create table T1 {
  key1  int64 not null,
  other int64 not null
} primary key (key1);

IN 子句中带有子查询的查询:

select value from T0 t0
where t0.other in (
  select t1.other from T1 t1 where t1.key1 in (42, 43, 44)  -- note: this subquery is a good deal more complex than this
)

通过 T0 的哈希连接与子查询的输出产生一个 10 元素集:

Operator                     Rows  Executions
-----------------------      ----- ----------
Serialize Result               10          1
Hash Join                      10          1
  Distributed union         10000          1
    Local distributed union 10000          1
    Table Scan: T0          10000          1
  Distributed cross apply:      5          1
   ...lots moar T1 subquery stuff...

请注意,虽然子查询很复杂,但它实际上会生成一个非常小的集合。不幸的是,它还会扫描 T1 的 整个 以馈送到散列连接,这非常慢。

但是,如果我在 T1 上获取子查询的输出并手动将其推入 IN 子句:

select value from T0
where other in (5, 6, 7, 8, 9)  -- presume this `IN` clause to be the output of the above subquery

它的速度要快得多,大概是因为它每个条目只命中 T0 的索引一次,而不是对全部内容使用散列连接:

Operator                Rows Executions
----------------------- ---- ----------
Distributed union         10          1
Local distributed union   10          1
Serialize Result          10          1
Filter                    10          1
Index Scan:               10          1

我可以简单地运行两个查询,这是我迄今为止最好的计划。但我希望我能找到某种方法来哄骗 Spanner 决定这是它应该对第一个示例中的子查询的输出执行的操作。我已经尝试了所有我能想到的方法,但这可能根本无法用 SQL 来表达。

另外:我还没有完全证明这一点,但在某些情况下,我担心 10 个元素的子查询输出可能会爆炸到几千个元素(T1 或多或少会无限制地增长,很容易达到数百万)。我已经在 splatted-out IN 子句中手动测试了几百个元素,它的性能似乎可以接受,但我有点担心它可能会失控。

请注意,我还尝试了子查询的连接,如下所示:

select t0.other from T0 t0
join (
  -- Yes, this could be a simple join rather than a subquery, but in practice it's complex
  -- enough that it can't be expressed that way.
  select t1.other from T1 t1 where t1.key = 42
) sub on sub.other = t0.other

但它在查询计划器中做了一些真正可怕的事情,我什至不会在这里解释。

【问题讨论】:

  • 所写的子查询有点令人困惑:您的意思是说key1 而不是key?另外:正如所写,子查询只能返回一个结果,因为 key1 是完整的主键;也许你应该有两个 T1 的主键,或者你可以说 t1.key1 IN (42, 43, 44)
  • 哎呀,抱歉——刚刚注意到这条评论。是的,这是我在尝试抽象问题时犯的一个错误。它应该基本上按照您的建议阅读。我将对其进行编辑以反映这一点,以避免将来出现混淆。

标签: sql google-cloud-platform google-cloud-spanner


【解决方案1】:

IN 子句中的实际子查询是否使用来自T0 的任何变量? 如果不是,如果您尝试使用重新排序的表进行连接查询(并为正确性添加一个 distinct,除非您知道这些值将是不同的),会发生什么?

SELECT t0.other FROM  (
      -- Yes, this could be a simple join rather than a subquery, but in practice it's complex
      -- enough that it can't be expressed that way.
      SELECT DISTINCT t1.other FROM T1 t1 WHERE t1.key = 42
    ) sub 
JOIN T0 t0
ON sub.other = t0.other

【讨论】:

  • 谢谢,迈克,这太完美了!我们刚刚在周末提出了大致相同的答案(减去distinct 部分,我认为这可能也是必要的)。大致扫描行 O(sub-query),这正是我所希望的。
  • 不客气。实际上,我只是传递了我们团队中没有 SO 帐户的其他人的答案,但我们很高兴它成功了!
猜你喜欢
  • 1970-01-01
  • 2011-07-25
  • 2015-02-25
  • 2011-12-23
  • 1970-01-01
  • 2019-12-15
  • 2011-05-14
  • 2013-01-31
  • 1970-01-01
相关资源
最近更新 更多