用于连接两个按连接逻辑排序的表的最佳 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_ip
和 end_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,我通过链接找到了几乎相同问题的解决方案: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 查询的主要内容,如果未能解决你的问题,请参考以下文章