【问题标题】:Postgres use index with `split_part`Postgres 使用带有 `split_part` 的索引
【发布时间】:2020-01-24 17:38:14
【问题描述】:

上下文:

我有一个test 表:

=> \d+ test 
                                       Table "public.test"
Column     |          Type          | Collation | Nullable | Default | Storage  | Stats target | Description 
---------------+------------------------+-----------+----------+---------+----------+-------- 
------+-------------
 id            | character varying(255) |           |          |         | extended |              
| 
 configuration | jsonb                  |           |          |         | extended |              
| 

configuration 列包含“定义明确”的 json,其中有一个名为 source_url 的键(跳过其他不相关的键)。 configuration 列的示例值为:

{
"source_url": "https://<resource-address>?Signature=R1UzTGphWEhrTTFFZnc0Q4qkGRxkA5%2BHFZSfx3vNEvRsrlDcHdntArfHwkWiT7Qxi%2BWVJ4DbHJeFp3GpbS%2Bcb1H3r1PXPkfKB7Fjr6tFRCetDWAOtwrDrVOkR9G1m7iOePdi1RW%2Fn1LKE7MzQUImpkcZXkpHTUgzXpE3TPgoeVtVOXXt3qQBARpdSixzDU8dW%2FcftEkMDVuj4B%2Bwiecf6st21MjBPjzD4GNVA%2F6bgvKA6ExrdYmM5S6TYm1lz2e6juk81%2Fk4eDecUtjfOj9ekZiGJVMyrD5Tyw%2FTWOrfUB2VM1uw1PFT2Gqet87jNRDAtiIrJiw1lfB7Od1AwNxIk0Rqkrju8jWxmQhvb1BJLV%2BoRH56OHdm5nHXFmQdldVpyagQ8bQXoKmYmZPuxQb6t9FAyovGMav3aMsxWqIuKTxLzjB89XmgwBTxZSv5E9bkWUbom2%2BWq4O3%2BCrVxYwsqg%3D%3D&Expires-At=1569340020&Issued-At=1568293200"
    .
    .
}

URL 包含查询参数Expires-At


问题:

有一个每 24 小时运行一次的计划作业。这项工作应该找到所有已过期/即将过期的记录(然后对其进行处理)。

解决方案:

我有这个查询来完成我的工作:

select * from test where to_timestamp(split_part(split_part(configuration->>'source_url', 'Expires-At=', 2), '&', 1)::bigint) <= now() + interval '24 hours';

说明:

  • 查询首先在Expires-At= 处拆分source_url 并选择其右侧的部分,然后在&amp; 上拆分结果字符串并选择其左侧部分,从而获得准确的纪元时间需要text
  • Expires-Atsource_url 中的最后一个查询参数时,相同的查询也适用于极端情况
  • 一旦提取纪元时间为text,它首先将其转换为bigint,然后将其转换为Postgres时间戳,然后比较该时间戳是否小于或等于时间24距离now() 几小时车程
  • 所有符合上述条件的行都被选中

因此,最后,在每次运行中,调度程序都会刷新所有将在接下来的 24 小时内过期的 url(包括那些已经过期的)


问题:
  1. 虽然这解决了我的问题,但我真的不喜欢这个解决方案。这有很多我觉得不干净的字符串操作。有没有更清洁的方法来做到这一点?
  2. 如果我们“不得不”采用上述解决方案,我们甚至可以使用索引来进行这种查询吗?我知道函数lower()upper() extra 可以被索引,但我真的想不出任何可以索引这个查询的方法。

替代方案:

除非有一个真正干净的解决方案,否则我会选择这个:

  • 我会在 configuration json 中引入一个名为 expires_at 的新键,确保每次插入一行时都会填充正确的值。
  • 然后直接查询这个新增的字段(在configuration列上有索引)。

我承认这样我重复了Expires-At 的信息,但是在我能想到的所有可能的解决方案中,这是我认为最干净的一个。

还有比这更好的方法吗?



编辑:

更新了查询以使用带有正则表达式的substring() 而不是内部split_part()

select * from test where to_timestamp(split_part(substring(configuration->>'source_url' from 'Expires-At=\d+'), '=', 2)::bigint) <= now() + interval '24 hours';

【问题讨论】:

    标签: sql postgresql indexing split jsonb


    【解决方案1】:

    鉴于您当前的数据模型,我认为您的 WHERE 条件没有那么糟糕。

    您可以使用索引

    CREATE INDEX ON test ( 
       to_timestamp(
          split_part(
             split_part(
                configuration->>'source_url',
                'Expires-At=',
                2
             ),
             '&',
             1
          )::bigint
       )
    );
    

    基本上,您必须索引= 左侧的整个表达式。只有当所有涉及的函数和运算符都是IMMUTABLE 时,你才能这样做,我认为它们就是你的情况。

    不过,我会更改数据模型。首先,我看不到拥有一个包含单个值的jsonb 列的价值。为什么不将 URL 设为 text 列?

    您可以更进一步,将 URL 拆分为单独的部分,这些部分存储在列中。

    如果这一切都是一个好主意取决于您如何使用数据库中的值:通常最好将您在WHERE 条件等中使用的那些数据部分分开并保留其余部分“一团”。这在某种程度上是一个品味问题。

    【讨论】:

    • 为简洁起见,我跳过了jsonb 列中的其他键。它还包含 2 个键,这就是它的类型为 jsonb 的原因。
    • 不要。 split_part 便宜很多。
    【解决方案2】:

    您可以使用 URI 解析模块,如果那是您发现不干净的部分。您可以使用 plperl 或 plpythonu,其中包含您喜欢的任何 URI 解析器库。但是,如果您的 json 真的“定义明确”,我认为没有什么意义。除非您已经在使用 plperl 或 plpythonu,否则添加这些依赖项可能会添加比删除更多的“污垢”。

    你可以建立一个索引:

    create index on test (to_timestamp(split_part(split_part(configuration->>'source_url', 'Expires-At=', 2), '&', 1)::bigint));
    set enable_seqscan TO off;
    explain select * from test where to_timestamp(split_part(split_part(configuration->>'source_url', 'Expires-At=', 2), '&', 1)::bigint) <= now() + interval '24 hours';
    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
     Index Scan using test_to_timestamp_idx1 on test  (cost=0.13..8.15 rows=1 width=36)
       Index Cond: (to_timestamp(((split_part(split_part((configuration ->> 'source_url'::text), 'Expires-At='::text, 2), '&'::text, 1))::bigint)::double precision) <= (now() + '24:00:00'::interval))
    

    我会在配置 json 中引入一个名为 expires_at 的新键,确保每次插入一行时都会填充正确的值。

    这不就是重新整理污垢吗?它使查询看起来更好,但代价是使插入更丑陋。也许你可以把它放在一个 INSERT OR UPDATE 触发器中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-03-15
      • 2021-12-29
      • 2019-11-11
      • 2023-03-06
      • 2012-05-07
      • 2019-11-08
      • 1970-01-01
      相关资源
      最近更新 更多