【问题标题】:Redshift - Simplify Query PlanRedshift - 简化查询计划
【发布时间】:2016-09-26 23:25:47
【问题描述】:

我在 Redshift 中有两个表,我正在尝试根据用户规范化的 IP 地址进行连接以获取邮政编码人口统计数据。通过规范化地址,我的意思是它与一个统一长度的字符串一致,该字符串去除了句点并且可以直接相互比较。例如,这在任何连接完成之前应用于所有 ips 并存储在表中:

lpad(split_part(ip, '.', 1), 3, '0') ||
lpad(split_part(ip, '.', 2), 3, '0') ||
lpad(split_part(ip, '.', 3), 3, '0') ||
lpad(split_part(ip, '.', 4), 3, '0')

所以209.170.151.71 将被转换为209170151071

我有两张桌子。首先是 visitor_details,其中包含以下内容:

-----------------------------
| visitor_id |      ip      |
-----------------------------
|      1     | 209170151071 |
|      2     | 123170167071 |
      ...           ...
| 50000000   | 001213020341 |
-----------------------------

我有一个名为 geo_ip 的表,其结构如下:

----------------------------------------
|    start_ip |    end_ip      |  zip  |
----------------------------------------
|209170151071 | 209170151071   | 11101 |
|309170151071 | 409170151071   | 11102 |
      ...           ...           ...
|509170151071 | 609170151071   | 11103 |
----------------------------------------

我正在尝试运行以下查询:

WITH vd AS (
  SELECT visitor_id,
         ip_address as c_ip
  FROM dev.visitor_details
)
SELECT
  visitor_id,
  c_ip,
  g.*
FROM
  vd
JOIN
  dev.geo_ip g
  ON vd.c_ip BETWEEN g.startip and g.endip
LIMIT 500;

geo ip 上的排序键是使用 startip 和 endip 的交错排序键。桌子似乎也没有歪斜。但是,运行查询会导致执行时间很长(从未完成)。查看说明,我看到以下内容:

XN Limit  (cost=0.00..245.17 rows=500 width=238)
   ->  XN Nested Loop DS_BCAST_INNER  (cost=0.00..18442148764959.20 rows=37610983146614 width=238)
         Join Filter: ((("inner".startip)::text <= ("outer".ip_address)::text) AND (("inner".endip)::text >= ("outer".ip_address)::text))
         ->  XN Seq Scan on visitor_details  (cost=0.00..596971.20 rows=59697120 width=72)
         ->  XN Seq Scan on geo_ip g  (cost=0.00..56702.71 rows=5670271 width=166)
 ----- Nested Loop Join in the query plan - review the join predicates to avoid Cartesian products -----

更奇怪的是,如果我为连接硬编码一个 IP 地址,查询计划看起来很正常。

任何人都可以就如何优化表设置的查询以使其高效运行提出任何建议吗?

更新

我按照第一个响应的建议进行了更改,但我仍然看到嵌套循环。所有 IP 现在都是 bigint,并且删除了 with 语句。

explain SELECT 
    vd.visitor_id,
    vd.ip_address,
    gi.zip
FROM
dev.visitor_details2 vd
JOIN dev.geo_ip3 gi ON vd.ip BETWEEN gi.startip and gi.endip
LIMIT 500;


                                               QUERY PLAN                                                
---------------------------------------------------------------------------------------------------------
 XN Limit  (cost=0.00..136.62 rows=500 width=51)
   ->  XN Nested Loop DS_BCAST_INNER  (cost=0.00..10276958524959.20 rows=37610983146614 width=51)
         Join Filter: (("inner".startip <= "outer".ip) AND ("inner".endip >= "outer".ip))
         ->  XN Seq Scan on visitor_details2 vd  (cost=0.00..596971.20 rows=59697120 width=52)
         ->  XN Seq Scan on geo_ip3 gi  (cost=0.00..56702.71 rows=5670271 width=23)
 ----- Nested Loop Join in the query plan - review the join predicates to avoid Cartesian products -----
(6 rows)

更新 2 以下是确认它们都是 bigint 的表定义:

master=# \d dev.visitor_details2;
          Table "dev.visitor_details2"
   Column   |          Type          | Modifiers 
------------+------------------------+-----------
 id         | integer                | not null
 visitor_id | character varying(108) | 
 ip         | bigint                 | 
 ip_address | character varying(192) | 
 domain     | integer                | 
Indexes:
    "visitor_details2_pkey" PRIMARY KEY, btree (id)

master=# \d dev.geo_ip3;
                Table "dev.geo_ip3"
    Column    |          Type          | Modifiers 
--------------+------------------------+-----------
 startip      | bigint                 | 
 endip        | bigint                 | 
 country      | character varying(16)  | 
 region       | character varying(32)  | 
 city         | character varying(32)  | 
 zip          | character varying(16)  | 
 latitude     | double precision       | 
 longitude    | double precision       | 
 areacode     | integer                | 
 metrocode    | integer                | 
 timezone     | character varying(32)  | 
 isp          | character varying(128) | 
 organization | character varying(128) | 
 netspeed     | character varying(32)  | 
 domain       | character varying(128) | 

【问题讨论】:

  • 您的统计数据是最新的吗?每个表的行数是多少?你的桌子是如何分布的?
  • 是的,我在解释之前对表运行了分析,将 analyze_threshold_percent 设置为 0 以强制分析运行。 visitor_details2 有 59697122 行,geo_ip3 有 5670271 行。 geo_ip3 表通过 startip 分发,visitor_details 表通过 visitor_id 分发。 RedShift 报告 geo_ip3 的偏差为 1.0000,visitor_details2 的偏差为 1.0064。两者的 pct_stats_off 都是 0.00,两者的 pct_unsorted 都是 0.00。
  • \d 本身并不能提供完整的 DDL。您没有显示排序键、dist 键等。请问我们可以看到实际的创建表语句吗?

标签: amazon-redshift


【解决方案1】:

我不确定您为什么在此处使用 with 语句。你读过文档吗? 我猜发生了什么,它正在 with 块中为 visitor_detail 表中的每个条目执行查询,然后必须将其广播到另一个节点XN Nested Loop DS_BCAST_INNER。 您还可以看到它正在加入文本Join Filter: ((("inner".startip)::text。 您应该考虑将 ip_address 的数据类型更改为BIGINT

我会这样写查询:

SELECT 
    vd.visitor_id,
    vd.ip_address,
    gi.zip
FROM
dev.visitor_details vd
JOIN geo_ip gi ON vd.ip_address BETWEEN gi.start_ip and gi.end_ip
LIMIT 500;

更新

看起来 Redshift Handels 加入“between”的方式非常昂贵。您是否考虑过在这些范围内显式添加所有 IP 地址并使用 ip_address 作为排序键?我知道这张表的行数可能会变得非常大,但是如果您使用适当的压缩(IP 为 DELTA32K,zip 为运行长度)并分发到所有节点,这可能是一个解决方案。

【讨论】:

  • 我做了一个变体,所有的东西都被预制为一个 bigint。即使没有 with 语句,我仍然看到嵌套循环。有关更多详细信息,请参阅更新的问题。谢谢。
  • @user2694306 您是否确保两个表中的数据类型都是 BIGINT?我建议为两个表提供完整的 DDL,以便我们可以查看数据类型、排序键、dist 键等。
  • 它们都是 bigint,我已经为两个表添加了完整的 ddls。请注意,第二个说明我使用的是 visitor_details2 表中的已用 'ip'。
  • 我在想类似的东西。这将是大约 43 亿行,但可能是一个可行的选择。
  • 当您找到适合您的解决方案时告诉我。很有趣的话题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-09-30
  • 1970-01-01
  • 2016-06-08
  • 2019-10-20
  • 2018-05-10
  • 1970-01-01
相关资源
最近更新 更多