用于连接两个按连接逻辑排序的表的最佳 SQL 查询

Posted

技术标签:

【中文标题】用于连接两个按连接逻辑排序的表的最佳 SQL 查询【英文标题】:Optimal SQL query for joining two tables ordered with regards to join logic 【发布时间】:2019-07-23 18:32:06 【问题描述】:

我正在寻求有关优化我的 BigQuery 查询的帮助。

我有 2 张桌子:

1) ips 一列:ip 这是类似 IP 的字符串,例如192.168.4.6。它有 m 行

----------------
|   ip         |
----------------
| 73.14.170.37 |
----------------
| 5.14.121.34  |
----------------
| 61.22.122.67 |
---------------

2) ranges 一列:range 这是类似 CIDR 的字符串,例如192.168.128/28 有 n 行。范围从不重叠。

-------------------
|      range      |
-------------------
| 42.126.124.0/24 |
-------------------
| 2.36.127.0/24   |
------------------
| 12.59.78.0/23   |
-------------------

m = 大约 100M

n = 大约 65K

所以两者都很大而且 m >> n

我的目标是从ips 表中找到属于range 表中任何范围的所有IP。

我的做法:

我创建了 2 个中间表:

1) ip_num 具有不同的数字 (INT64) ip 从 ips 表计算的列,按 ip 排序。

------------
|  ip_num  |
-----------
| 16753467 |
------------
| 16753469 |
------------
| 16753474 |
------------

2) ranges_num 有 2 列:start_ipend_ip(均为 INT64),它们是根据 CIDR 范围计算的。本栏目按start_ip排序。

-----------------------
| start_ip |  end_ip  |
-----------------------
| 16753312 | 16753316 |
-----------------------
| 16753569 | 16753678 | 
-----------------------
| 16763674 | 16763688 |
-----------------------

两个表都使用数字格式,因为我希望比较数字有更好的性能。使用 NET.IPV4_TO_INT64 和 NET.IP_FROM_STRING 完成转换。

生成这两张表相当快。

现在我的最后一步是加入这两个表:

select ip from ip_num JOIN ranges_num ON ip BETWEEN start_ip AND end_ip;

最后一个查询需要很长时间(大约 30 分钟),然后没有产生任何结果,但我收到 Query exceeded resource limits 错误,可能是因为它花费的时间太长。

所以我的问题是:

我可以让它更快吗? 我的直觉是正确的,即最后一个查询生成 n * m 连接并且查询优化器未能利用 range_num 的排序,从而有效地产生 O(n*m) 复杂度? 如果我在程序的内存中而不是在关系数据库中拥有这两种结构,则使用 2 个迭代器编写 O(m+n) 算法相对简单,每个表上都有一个。 但我不知道如何用标准 SQL 表达它(如果可能的话),也许内置的查询优化器应该能够自动推导出这个算法。 是否有任何适用于 BigQuery(或任何其他)的工具可以帮助我理解查询优化器?

我不是 SQL 专家,而且我是 BigQuery 的新手,所以我将不胜感激。

【问题讨论】:

你能提供更多关于你的源表的信息吗?您的描述中有几项不清楚。也许在 select 语句中创建一些虚拟数据()或格式化表格。 @rtenha 我添加了带有示例数据的列。我希望它有帮助您的问题似乎之前被问过...***.com/questions/43992006/…
【参考方案1】:

感谢@rtenha,我通过链接找到了几乎相同问题的解决方案:Efficiently joining IP ranges in BigQuery 我附上了一个更完整的查询,我从那个查询中得出,它在 10 秒内为我工作:

(SELECT ip FROM `ips` i
JOIN `ranges` a
ON NET.IP_TRUNC(a.start_ip, 16) = NET.IP_TRUNC(NET.SAFE_IP_FROM_STRING(i.ip), 16)
WHERE NET.SAFE_IP_FROM_STRING(i.ip) BETWEEN a.start_ip AND a.end_ip 
AND mask >= 16)
UNION ALL
(
SELECT ip FROM `ips` i
JOIN `ranges`  a
ON NET.IP_TRUNC(a.start_ip, 8) = NET.IP_TRUNC(NET.SAFE_IP_FROM_STRING(i.ip), 8)
WHERE NET.SAFE_IP_FROM_STRING(i.ip) BETWEEN a.start_ip AND a.end_ip
AND mask BETWEEN 8 AND 15)
UNION ALL
(
SELECT ip FROM `ips` i
JOIN `ranges`  a
ON NET.SAFE_IP_FROM_STRING(i.ip) BETWEEN a.start_ip AND a.end_ip
AND mask < 8)

在这里,我将ranges 分为 3 个部分:具有 16 位网络前缀或更长、介于 8 和 15 之间以及低于 8 的部分。 对于每个部分,我都应用了额外的前缀比较,它具有更好的性能,它可以有效地过滤数据,然后在更小的集合上执行第二次比较(BETWEEN),这很快。 最后一部分没有前缀匹配,因为它针对的是最短的网络前缀。 在此之后,我将所有部分与 UNION 结合起来。

它起作用的一个原因是我 99% 的网络前缀是 16 或更长。较小的网络前缀处理时间更长,但由于它们很少,并且通过将短网络前缀部分(16 或更短)分成两个较小的部分来进一步缓解这一事实,从而弥补了这一点。通过将数据分解成更细粒度的部分(例如,32 个部分,每个可能的掩码长度一个),可能会进一步优化。不过我对我的结果还是很满意的。

我没有分析 INT 还是 BYTES 是更适合处理的数据类型,但是拥有中间表并没有带来明显的性能提升。

【讨论】:

【参考方案2】:

根据this 文章,您已正确订购了这些桌子。

尽管有时我遇到过 BigQuery 低估了所需的计算能力(槽),这会导致查询速度变慢或引发超出报价的错误,就像您的情况一样。

对我来说,当我切换表格的顺序并将最小的表格放在连接的左侧时,它会有所帮助。

例如:

select ip from ranges_num JOIN ip_num ON ip BETWEEN start_ip AND end_ip;

【讨论】:

可悲的是,在这种情况下,交换表顺序没有任何改变

以上是关于用于连接两个按连接逻辑排序的表的最佳 SQL 查询的主要内容,如果未能解决你的问题,请参考以下文章

SQL连接查询

SQL怎样合并显示两个没有关联的表

sql查询:使用内连接查询两张表的时候,如果左边表的一条记录对应了右边表的两条记录,结果显示排列问题

如何在sql中连接来自不同表的两个字段

SQL中外连接与内连接之间的区别

在SQL语句中,如何把两张表的数据按时间排序查询?