范围联接优化

区间连接是基于一个条件连接两个表,该条件检查某个值是否落在某个区间内,或者两个区间是否重叠,而不是检查两个值是否相等。 例如,一个查询将每个网站事件与其发生时所在的用户会话相匹配,检查事件的时间戳是否位于会话的开始和结束时间之间。 用于查找安排在同一时间的会议的查询会检查两个时间范围是否重叠。 Databricks 运行时中的范围连接优化可以大幅提升这些查询的性能。

因此,连接可能会比较许多无法匹配的行对。 范围连接优化将值范围划分为 分箱,仅比较落在重叠分箱中的行。参见 分箱大小。

在 Databricks SQL 中,Azure Databricks无需任何手动配置即可自动优化范围联接。 对于所有计算类型,还可以使用联接提示或会话配置手动优化范围联接。

间隔中的点范围联接

间隔范围联接中的点是一个联接,其条件包含谓词,该谓词指定一个关系中的值位于另一个关系中的两个值之间。 例如: 。

-- using BETWEEN expressions
SELECT *
FROM points JOIN ranges ON points.p BETWEEN ranges.start and ranges.end;

-- using inequality expressions
SELECT *
FROM points JOIN ranges ON points.p >= ranges.start AND points.p < ranges.end;

-- with fixed length interval
SELECT *
FROM points JOIN ranges ON points.p >= ranges.start AND points.p < ranges.start + 100;

-- join two sets of point values within a fixed distance from each other
SELECT *
FROM points1 p1 JOIN points2 p2 ON p1.p >= p2.p - 10 AND p1.p <= p2.p + 10;

-- a range condition together with other join conditions
SELECT *
FROM points, ranges
WHERE points.symbol = ranges.symbol
  AND points.p >= ranges.start
  AND points.p < ranges.end;

间隔重叠范围联接

间隔重叠范围联接是一种联接,其中的条件包含的谓词指定每个关系中两个值之间的间隔的重叠。 例如: 。

-- overlap of [r1.start, r1.end] with [r2.start, r2.end]
SELECT *
FROM r1 JOIN r2 ON r1.start < r2.end AND r2.start < r1.end;

-- overlap of fixed length intervals
SELECT *
FROM r1 JOIN r2 ON r1.start < r2.start + 100 AND r2.start < r1.start + 100;

-- a range condition together with other join conditions
SELECT *
FROM r1 JOIN r2 ON r1.symbol = r2.symbol
  AND r1.start <= r2.end
  AND r1.end >= r2.start;

范围联接优化

距离连接优化适用于满足以下条件的连接:

  • 在间隔或间隔重叠范围联接条件中具有一个点。
  • 仅在区间连接条件中使用数值(整数、浮点或十进制)、DATE 或 TIMESTAMP 值。
  • 在区间连接条件中使用相同类型的值。 十进制值也必须具有相同的小数位数和精度。
  • 使用支持的连接类型:
    • 适用于任意范围联接的 INNER JOIN
    • 点值位于左侧的 LEFT OUTER JOIN,或点值位于右侧的 RIGHT OUTER JOIN,仅适用于间隔范围联接中的点
  • 设置一个箱大小,可以是自动生成的,也可以手动指定。

具有数字相等性和范围条件的联接

当联接条件同时包含数值列上的相等条件和范围条件时,优化器可能会对该数值相等列应用分箱,因为该列满足范围联接优化的类型要求。 这可能会导致等值列被分配到分箱中,或被排除在优化之外,从而降低性能。

若要确保范围联接优化仅适用于预期的范围条件,请将数值相等性列强制转换为 STRING。 这会将它们排除在作为范围条件列的考量之外。

SELECT /*+ RANGE_JOIN(reference, 3306084) */
    reference.*, position.*
FROM position
INNER JOIN reference
    ON CAST(position.parent_index AS STRING) = CAST(reference.parent_index AS STRING)
    AND position.child_index BETWEEN reference.min_child_index AND reference.max_child_index;

同一模式适用于用作相等键的其他数值列,例如 DATE整数标识符或聚集分区列。

箱大小

“箱大小”是一个数值优化参数,它将范围条件的值域拆分为多个大小相等的“箱”。 例如,如果箱大小为 10,则优化会将域拆分为长度为 10 的间隔的箱。 如果你在范围条件 p BETWEEN start AND end 中有一个点,并且 start 为 8,end 为 22,则此值间隔与长度为 10 的三个箱重叠 – 第一个箱从 0 到 10,第二个箱从 10 到 20,第三个箱从 20 到 30。 只有位于相同的三个箱中的点需要被视为该间隔的可能的联接匹配项。 例如,如果 p 为 32,则可以排除它位于 start 为 8 且 end 为 22 的范围中,因为它位于从 30 到 40 的箱中。

注意

  • 对于 DATE 值,箱尺寸的数值被解释为天数。 例如,箱大小值为 7 表示一周。
  • 对于 TIMESTAMP 值,箱大小的值被解释为秒数。 对于亚秒级的数值,请使用小数值。 例如,箱大小值为 60 表示一分钟,而箱大小值为 0.1 表示 100 毫秒。

可以通过在查询中使用范围联接提示或设置会话配置参数来指定箱大小。 在 Databricks SQL 中,启用自动范围连接优化时,分箱大小会自动确定。

自动范围连接优化

在 Databricks SQL 中,Azure Databricks 会自动检测符合条件的范围连接,并通过对间隔表进行采样来推导出最佳分箱大小。 这样就无需通过提示或会话配置手动指定箱大小。

默认情况下,Databricks SQL 中启用了自动范围联接优化。 要禁用它,请设置以下配置:

SET spark.databricks.optimizer.autoRangeJoin.enabled = false;

如果通过范围联接提示或会话配置指定了箱大小,该值将替代自动推导出的箱大小。

使用范围联接提示启用范围联接

若要在 SQL 查询中启用范围联接优化,请使用 范围联接提示 指定箱大小。 提示必须包含所联接的关系之一的关系名称和箱大小数值参数。 关系名称是表、视图或子查询。

SELECT /*+ RANGE_JOIN(points, 10) */ *
FROM points JOIN ranges ON points.p >= ranges.start AND points.p < ranges.end;

SELECT /*+ RANGE_JOIN(r1, 0.1) */ *
FROM (SELECT * FROM ranges WHERE ranges.amount < 100) r1, ranges r2
WHERE r1.start < r2.start + 100 AND r2.start < r1.start + 100;

SELECT /*+ RANGE_JOIN(c, 500) */ *
FROM a
  JOIN b ON (a.b_key = b.id)
  JOIN c ON (a.ts BETWEEN c.start_time AND c.end_time)

注意

在第三个示例中,你必须在c上放置提示。 这是因为联接是左结合,因此,该查询将被解释为 (a JOIN b) JOIN c,a 上的提示应用于 a 与 b 的联接,而不是与 c 的联接。

#create minute table
minutes = spark.createDataFrame(
    [(0, 60), (60, 120)],
    "minute_start: int, minute_end: int"
)

#create events table
events = spark.createDataFrame(
    [(12, 33), (0, 120), (33, 72), (65, 178)],
    "event_start: int, event_end: int"
)

#Range_Join with "hint" on the from table
(events.hint("range_join", 60)
  .join(minutes,
    on=[events.event_start < minutes.minute_end,
    minutes.minute_start < events.event_end])
  .orderBy(events.event_start,
    events.event_end,
    minutes.minute_start)
  .show()
)

#Range_Join with "hint" on the join table
(events.join(minutes.hint("range_join", 60),
  on=[events.event_start < minutes.minute_end,
    minutes.minute_start < events.event_end])
  .orderBy(events.event_start,
    events.event_end,
    minutes.minute_start)
  .show()
)

你还可以在某个已联接的数据帧上放置范围联接提示。 在这种情况下,提示仅包含箱大小数值参数。

val df1 = spark.table("ranges").as("left")
val df2 = spark.table("ranges").as("right")

val joined = df1.hint("range_join", 10)
  .join(df2, $"left.type" === $"right.type" &&
     $"left.end" > $"right.start" &&
     $"left.start" < $"right.end")

val joined2 = df1
  .join(df2.hint("range_join", 0.5), $"left.type" === $"right.type" &&
     $"left.end" > $"right.start" &&
     $"left.start" < $"right.end")

使用会话配置启用范围联接

如果不想修改查询,请将 bin 大小指定为配置参数。

SET spark.databricks.optimizer.rangeJoin.binSize=5

此配置参数适用于具有范围条件的任何联接。 但是,通过范围联接提示设置的不同箱大小始终会覆盖通过参数设置的大小。

选择箱大小

范围联接优化的有效性取决于选择的箱大小是否合适。

箱大小越小,箱数量越多,这有助于筛选潜在的匹配项。 如果值区间远大于分箱大小,那么较小的分箱大小会变得低效,导致区间与许多分箱重叠。例如,在条件 p BETWEEN start AND end 下,start 为 1,000,000,end 为 1,999,999,且分箱大小为 10 时,该区间会与 100,000 个分箱重叠。

如果区间长度较为均匀且已知,则将区间大小设置为典型的区间长度。 对于变化且偏斜的间隔,请选择一个能够高效筛选短间隔、防止长间隔与过多箱重叠的箱大小。对于间隔位于 start 和 end 列之间的表 ranges,请使用此查询确定偏斜间隔长度的百分位数:

SELECT
  map_from_arrays(
    ARRAY(0.5, 0.9, 0.99, 0.999, 0.9999),
    APPROX_PERCENTILE(
      end::DOUBLE - start::DOUBLE,
      ARRAY(0.5, 0.9, 0.99, 0.999, 0.9999)
    )
  ) AS bin_sizes
FROM
  ranges;

在减去之前将每个列强制转换为 DOUBLE 可确保查询正常工作,无论列是数字、DATE、还是 TIMESTAMP 值都不例外。

将箱大小设置为这些值中最大的:

  • 间隔长度的第90百分位。 在这个规模下,最多只有10% 的区间跨越超过2个分箱。
  • 第99百分位数除以10。 在此大小下,最多 1% 的间隔跨越的箱超过了 11 个。
  • 第 99.9 百分位除以 100。 在这个尺寸下,最多只有0.1%的间隔跨越超过101个分箱。

要限制更长的间隔,请使用第 99.99 个和第 99.999 个百分位数继续此模式。 使用结果作为起点,然后为你的工作负载优化箱大小。