【问题标题】:BigQuery: Expensive full table scans when there's nothing to JOIN toBigQuery:当没有什么可以加入时进行昂贵的全表扫描
【发布时间】:2021-10-09 10:05:10
【问题描述】:

我有两个表,表 A 是 500gb+ 的人员信息表(以 id 为中心)。表 B 是仅包含表 A 中的 ID 和公共 URL 列(Facebook、Linkedin 等)(聚集在 publicURL 和 tblA_id 上)的窄查找表。这些 URL 在表 A 中,但在记录内的数组中,因此我无法对其进行聚类。

我想我可以使用表 B 来搜索表 A,只需要一个连接和“在 (URL1、URL2、URL3、URL4) 中的 URL 位置”。这最初效果很好。它将查询量从数百 GB 减少到了几个 100 MB。

问题:不能保证查询的 URL 会在表格中。如果找到至少一个 URL,则通常花费 100mb 或多或少,但如果使用未知 URL 或括号内的 NULL 执行查询,则会执行整个表扫描并花费 500gb。

我已经用几种方法编写了这个查询,无论如何问题都存在。我试过的:

SELECT
    A.*
FROM TableB AS B
JOIN TableA AS A
    ON A.id = B.tblA_id
 WHERE B.public_url IN (
     "linkedin.com/in/bingcrosby",
     "linkedin.com/in/hillaryclinton",
     "facebook.com/billhicks"
 );

有同样问题的其他选项:

SELECT
    A.*
FROM TableA AS A
WHERE id IN (
    SELECT tblA_id
    FROM TableB
    WHERE public_url IN (
        "linkedin.com/in/bingcrosby",
        "linkedin.com/in/hillaryclinton",
        "facebook.com/billhicks"
    )
);

只要找到至少一个公共 URL,这两种方法都可以廉价而高效地工作。如果使用表 B 中的某些未知 URL,如何防止整个表扫描?即使我可以从子查询中生成错误,我也会很高兴。

感谢您的建议。这开始变得昂贵了。

【问题讨论】:

  • 一种选择是在 URL 上或按 URL 的一部分进行分区。
  • 有趣的想法,比如 ABS(MOD(FARM_FINGERPRINT(B.public_url), number_of_partitions))?这应该限制表扫描。但是,我们必须将它应用于 URL 的输入数组,这意味着动态 SQL,不是吗?我总是发现这对可读性和维护很不利。我想我可以使用 WITH 准临时表将其最小化。
  • 。 .是的,您需要将相同的函数应用于输入进行比较。如果您使用散列(如您的示例),则仍需要进行实际比较,以防万一发生冲突。
  • Welp,试过这个。同样的问题。它一直想无条件地加入所有表 A。在这一点上,这似乎是一个错误。 ``` SELECT A.full_name FROM TableB AS B JOIN TableA AS A ON A.id = B.TblA_id AND B.url_partition IN (ABS(MOD(FARM_FINGERPRINT("facebook.com/billhicks"),4000)) ) WHERE B .public_url IN(“facebook.com/billhicks”)```

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


【解决方案1】:

好的,我找到了一个我不喜欢但有效的解决方案。我最终不得不制作一个存储过程,制作一个表 B 的临时表,检查长度是否大于 0,然后将其加入表 A。

在 BigQuery 中,不会返回 SProc 的结果,因此会直接返回“查询结果”选项卡,因此有点烦人(您可以在“作业历史记录”选项卡 ->“查看结果”按钮中查看结果)。

我不确定 BigQuery 存储过程是否受益于传统 SQL 数据库的优势,例如执行缓存或预解析。在 BigQuery 规模上,我认为这并不重要。

您确实获得了数据团队保留的固有优势,例如安全性、SQL 注入预防和维护。所以这个看似简单的问题的复杂解决方案带来了好处。

我认为查询优化器不执行“哦,内部连接没有可连接的内容?那么最好停下来”,这很荒谬。任何人,这是我感兴趣的解决方案。

CREATE OR REPLACE PROCEDURE `<project-id>.dataset.profile_url_search`(urls ARRAY<STRING>)
BEGIN

    CREATE TEMP TABLE ids_found_for_urls(tblA_id STRING)
    AS
    SELECT tblA_id
    FROM `<project-id>.dataset.TableB`
        WHERE public_url IN UNNEST(urls);

    IF (SELECT CAST(COUNT(*) > 0 AS BOOL) FROM ids_found_for_urls)
    THEN
        SELECT
            A.*
        FROM `<project-id>.dataset.TableA` AS A
        JOIN ids_found_for_urls
            ON A.id = ids_found_for_urls.tblA_id;
    ELSE 
        SELECT NULL;
    END IF;
END

然后由以下方式调用:

CALL `<project-id>.dataset.profile_url_search` (ARRAY<STRING>[
    "linkedin.com/in/bingcrosby",
    "linkedin.com/in/hillaryclinton",
    "facebook.com/billhicks"
])

如果有人有性能或设计建议,我虚心欢迎。

【讨论】:

    猜你喜欢
    • 2019-12-08
    • 2022-01-16
    • 2011-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多