Должен ли я хранить пустые значения 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[])

Есть еще много, в том числе. Семантика пустой коллекции чего-либо всегда слегка отличается от семантики значения, которое представляет собой UNKNOWNcollection (часто используется просто как отсутствующая коллекция). Различие особенно проявляется, когда речь идет об использовании операторов, например

  • '' || '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$$;
Другие вопросы по тегам