我应该存储空的 tsvector 值还是 NULL 值?
Should I store empty tsvector values or NULL values?
在列中存储 tsvector
值时,对于没有搜索词的记录,我应该存储空 tsvector
还是 NULL
值?
重要吗?
在性能或存储空向量的存储开销方面是否存在差异?
换句话说,当基于可为 null 的 title
列的值更新向量时,我是否需要始终将其计算为 to_tsvector(coalesce(title,''))
(因为 to_tsvector
returns NULL
当给定一个 NULL
参数时)还是足以做 to_tsvector(title)
?
你问题的逻辑方面
首先,SQL NULL
的语义是 UNKNOWN
的语义,而某些数据类型也有一个“空”值。这些数据类型包括:
TEXT
(''
与 NULL::TEXT
不同)
JSON
和 JSONB
([]
或 {}
与 NULL::JSON
或 NULL:JSONB
不同)
X[]
(ARRAY[]::X[]
与 NULL::X[]
不同)
还有很多,包括 TSVECTOR
。空集合的语义总是与 NULL
值的语义略有不同,NULL
值是 UNKNOWN
集合(尽管通常只是用作缺席集合)。当涉及到使用运算符时,这种区别特别明显,例如
'' || 'abc' = 'abc'
但 NULL || 'abc' IS NULL
to_tsvector('cats ate rats') @@ to_tsquery('cat & rat') = true
但 NULL @@ 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
似乎确实有好处。
显然,这只是一个基准,不一定反映 real-world use-cases,但它应该会给您一些潜在差异的提示。
基准代码
对于复制或改编,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$$;
在列中存储 tsvector
值时,对于没有搜索词的记录,我应该存储空 tsvector
还是 NULL
值?
重要吗?
在性能或存储空向量的存储开销方面是否存在差异?
换句话说,当基于可为 null 的 title
列的值更新向量时,我是否需要始终将其计算为 to_tsvector(coalesce(title,''))
(因为 to_tsvector
returns NULL
当给定一个 NULL
参数时)还是足以做 to_tsvector(title)
?
你问题的逻辑方面
首先,SQL NULL
的语义是 UNKNOWN
的语义,而某些数据类型也有一个“空”值。这些数据类型包括:
TEXT
(''
与NULL::TEXT
不同)JSON
和JSONB
([]
或{}
与NULL::JSON
或NULL:JSONB
不同)X[]
(ARRAY[]::X[]
与NULL::X[]
不同)
还有很多,包括 TSVECTOR
。空集合的语义总是与 NULL
值的语义略有不同,NULL
值是 UNKNOWN
集合(尽管通常只是用作缺席集合)。当涉及到使用运算符时,这种区别特别明显,例如
'' || 'abc' = 'abc'
但NULL || 'abc' IS NULL
to_tsvector('cats ate rats') @@ to_tsquery('cat & rat') = true
但NULL @@ 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
似乎确实有好处。
显然,这只是一个基准,不一定反映 real-world use-cases,但它应该会给您一些潜在差异的提示。
基准代码
对于复制或改编,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$$;