【问题标题】:Should I store empty tsvector values or NULL values?我应该存储空的 tsvector 值还是 NULL 值?
【发布时间】:2022-05-07 15:55:43
【问题描述】:

在列中存储tsvector 值时,对于没有搜索词的记录,我应该存储一个空的tsvector 还是NULL 值?

重要吗?

存储空向量在性能或存储开销方面有什么不同吗?

换句话说,当基于可以为空的title 列的值更新向量时,我是否需要始终将其计算为to_tsvector(coalesce(title,''))(因为to_tsvector 在给定@ 时返回NULL 987654329@ 参数)还是足够做to_tsvector(title)

【问题讨论】:

  • 我认为 NULL 存储在元组之前,无论该列是否为 tsvector,因此可能对存储空间没有影响。但是是否保存 null 很重要 - 列 NOT NULL 变化很大
  • 在涉及多列时,在to_tsvector() 内使用coalesce() 通常是最佳实践。因为当任一列是NULL 时,连接运算符(||)将产生NULL。 IE。如果您有titledescription 列,则需要使用to_tsvector(coalesce(title,'')) || to_tsvector(coalesce(description,''))。因为如果您不使用coalesce(),则具有标题但没有描述的行将“丢失”。 -- 如果你只有一个列,那真的没关系。
  • 这一点尤其重要,当您也想使用setweight() 时。
  • @pozs 为什么设置权重特别重要?我不怀疑你,我只是在添加 setweight 后遇到了这个
  • @ColinD 因为在单个列上调用 setweight() 没有任何好处。只有在涉及多个列时才有意义。它也是严格的,所以NULL 文档将产生NULL。简而言之,您可以将setweight() 用作经验法则​​:您总是希望在可空列/表达式上使用coalesce()

标签: postgresql tsvector


【解决方案1】:

您问题的逻辑方面

首先,SQL NULL 的语义是UNKNOWN 的语义,而某些数据类型也有一个“空”值。这些数据类型包括:

  • TEXT''NULL::TEXT 不同)
  • JSONJSONB[]{}NULL::JSONNULL:JSONB 不同)
  • X[]ARRAY[]::X[]NULL::X[] 不同)

还有更多,包括TSVECTOR。某物的空集合的语义总是与NULL 值的语义略有不同,后者是UNKNOWN 集合(但通常仅用作不存在的集合)。当涉及到使用运算符时,这种区别特别体现,例如

  • '' || 'abc' = 'abc'NULL || 'abc' IS NULL
  • to_tsvector('cats ate rats') @@ to_tsquery('cat & rat') = trueNULL @@ to_tsquery('cat & rat') IS NULL

从这个意义上说,决策应该首先是逻辑决策,而不是存储决策,基于以下问题:即使该记录没有任何搜索,您是否仍然使用记录的 TSVECTOR 值?条款(亲空TSVECTOR)?还是该功能根本不适用于该特定记录(pro NULL 值)?对于@@ 运算符,它可能不那么相关,但它绝对适用于|| 运算符等。

答案并不明显,通常也没有明确的正确/错误方式。

问题的表现方面

如果这是您的应用程序中对性能高度敏感的情况(例如,您有很多空的 TSVECTOR 值),那么也许这个基准可以帮助您做出决定?

我在 Docker 中对 PostgreSQL 14.1 运行了以下基准测试以获得此结果:

RUN 1, Statement 1: 2.91145
RUN 1, Statement 2: 1.00000 -- The fastest run is 1. The others are multiples of 1
RUN 2, Statement 1: 2.80509
RUN 2, Statement 2: 1.05232
RUN 3, Statement 1: 2.78001
RUN 3, Statement 2: 1.00202
RUN 4, Statement 1: 2.74319
RUN 4, Statement 2: 1.00524
RUN 5, Statement 1: 2.75808
RUN 5, Statement 2: 1.00045
  • 语句 1 是 SELECT v @@ to_tsquery('cat & rat')v tsvector = to_tsvector('');
  • 声明 2 是 SELECT NULL @@ to_tsquery('cat & rat')

涉及NULL 的事实可能会导致@@ 运算符算法的捷径,与在基准中查询空的TSVECTOR 相比,它产生了2.7 倍的性能提升。因此,就性能而言,使用NULL 似乎确实有好处。

显然,这只是一个基准,不一定反映真实世界的用例,但它应该为您提供潜在差异的提示。

基准代码

如需复制或改编,here's a benchmark, based on this technique

DO $$
DECLARE
  v_ts TIMESTAMP;
  v_repeat CONSTANT INT := 10000;
  rec RECORD;
  run INT[];
  stmt INT[];
  elapsed DECIMAL[];
  min_elapsed DECIMAL;
  i INT := 1;

  -- Store the vector in a local variable to avoid re-computing it in the benchmark
  v tsvector = to_tsvector('');
BEGIN

  -- Repeat the whole benchmark several times to avoid warmup penalty
  FOR r IN 1..5 LOOP
    v_ts := clock_timestamp();

    FOR i IN 1..v_repeat LOOP
      FOR rec IN (
        -- Statement 1
        SELECT v @@ to_tsquery('cat & rat')
      ) LOOP
        NULL;
      END LOOP;
    END LOOP;

    run[i] := r;
    stmt[i] := 1;
    elapsed[i] := (EXTRACT(EPOCH FROM CAST(clock_timestamp() AS TIMESTAMP)) 
                 - EXTRACT(EPOCH FROM v_ts));
    i := i + 1;
    v_ts := clock_timestamp();

    FOR i IN 1..v_repeat LOOP
      FOR rec IN (
        -- Statement 2
        SELECT NULL @@ to_tsquery('cat & rat') 
      ) LOOP
        NULL;
      END LOOP;
    END LOOP;

    run[i] := r;
    stmt[i] := 2;
    elapsed[i] := (EXTRACT(EPOCH FROM CAST(clock_timestamp() AS TIMESTAMP))
                 - EXTRACT(EPOCH FROM v_ts));
    i := i + 1;
  END LOOP;

  SELECT min(t.elapsed)
  INTO min_elapsed
  FROM unnest(elapsed) AS t(elapsed);

  FOR i IN 1..array_length(run, 1) LOOP
    RAISE INFO 'RUN %, Statement %: %', run[i], stmt[i], 
      CAST(elapsed[i] / min_elapsed AS DECIMAL(10, 5));
  END LOOP;
END$$;

【讨论】:

  • 哇,谢谢!我确定我五年前就需要这个。近年来我没有使用过PostgreSQL,所以我不能对这个答案说太多。但这是最好/唯一的答案,我会将其标记为已接受。 ?
  • @mindplay.dk:干杯 :) 你的问题很受欢迎,表明其他人也会从中获利。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-27
  • 1970-01-01
  • 2010-11-03
  • 1970-01-01
  • 1970-01-01
  • 2012-05-14
相关资源
最近更新 更多