您问题的逻辑方面
首先,SQL NULL 的语义是UNKNOWN 的语义,而某些数据类型也有一个“空”值。这些数据类型包括:
-
TEXT('' 与 NULL::TEXT 不同)
-
JSON 和 JSONB([] 或 {} 与 NULL::JSON 或 NULL:JSONB 不同)
-
X[](ARRAY[]::X[] 与 NULL::X[] 不同)
还有更多,包括TSVECTOR。某物的空集合的语义总是与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 似乎确实有好处。
显然,这只是一个基准,不一定反映真实世界的用例,但它应该为您提供潜在差异的提示。
基准代码
如需复制或改编,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$$;