Должен ли я хранить пустые значения tsvector или значения NULL?
При хранении tsvector
значение в столбце, для записей без условий поиска, следует ли хранить пустое tsvector
или NULL
значение?
Это имеет значение?
Есть ли разница с точки зрения производительности или затрат на хранение от хранения пустых векторов?
Другими словами, при обновлении вектора на основе значения, скажем, обнуляемого title
колонка, мне нужно всегда вычислять это как to_tsvector(coalesce(title,''))
(поскольку to_tsvector
возвращается NULL
когда дали NULL
аргумент) или достаточно сделать to_tsvector(title)
?
1 ответ
Логические аспекты вашего вопроса
Во-первых, семантика SQL такая же, как у , тогда как некоторые типы данных также имеют «пустое» значение. Эти типы данных включают:
-
TEXT
(''
не то же самое, чтоNULL::TEXT
) -
JSON
а такжеJSONB
([]
или же{}
не то же самое, чтоNULL::JSON
или жеNULL:JSONB
) -
X[]
(ARRAY[]::X[]
не то же самое, чтоNULL::X[]
)
Есть еще много, в том числе. Семантика пустой коллекции чего-либо всегда слегка отличается от семантики значения, которое представляет собой
UNKNOWN
collection (часто используется просто как отсутствующая коллекция). Различие особенно проявляется, когда речь идет об использовании операторов, например
-
'' || 'abc' = 'abc'
ноNULL || 'abc' IS NULL
-
to_tsvector('cats ate rats') @@ to_tsquery('cat & rat') = true
ноNULL @@ to_tsquery('cat & rat') IS NULL
В этом смысле решение должно быть в первую очередь логическим, а не хранилищем, основанным на следующем вопросе: будете ли вы по-прежнему работать со значением записи, даже если в записи нет условий поиска (за пустой)? Или эта функция вообще не распространяется на эту конкретную запись (про ценность)? Для оператора это может быть не так актуально, но для
||
оператор и другие.
Ответ не очевиден, и в целом нет четкого правильного/неправильного пути.
Аспекты производительности вашего вопроса
Если это очень чувствительная к производительности ситуация в вашем приложении (например, у вас много пустых значений), то, возможно, этот тест может помочь вам с решением?
Я выполнил приведенный ниже тест на PostgreSQL 14.1 в Docker, чтобы получить следующий результат:
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')
Тот факт, что это задействовано, вероятно, приводит к сокращению пути в
@@
алгоритм оператора, который обеспечивает повышение производительности в 2,7 раза по сравнению с запросом пустого
TSVECTOR
в эталоне. Таким образом, кажется, есть преимущества использования
NULL
с точки зрения производительности.
Очевидно, что это всего лишь тест, который не обязательно отражает реальные варианты использования, но он должен дать вам намек на потенциальную разницу.
Эталонный код
Для воспроизведения или адаптации есть эталон, основанный на этой технике .
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$$;