Эффективная проверка на наличие текста в текстовом столбце - PullRequest
2 голосов
/ 29 января 2009

У меня есть таблица с около 2 000 000 строк. Мне нужно запросить один из столбцов, чтобы получить строки, в которых есть строка как часть значения.

Когда я выполню запрос, я узнаю положение строки, но не перед рукой. Поэтому представление, которое принимает подстроку, не является вариантом.

Насколько я вижу, у меня есть три варианта

  1. используя как «%%»
  2. используя instr
  3. с использованием подстроки

У меня есть возможность создания индекса на основе функций, если я хорошо отношусь к dba.

На данный момент все запросы занимают около двух секунд. У кого-нибудь есть опыт, какой из этих вариантов будет работать лучше, или есть другой вариант? Выбор будет использоваться для удаления каждые несколько секунд, обычно он выбирает 10 строк.

редактировать с дополнительной информацией

Проблема возникает, когда мы используем таблицу для хранения объектов с произвольными ключами и значениями. Объекты поступают из-за пределов нашей системы, поэтому у нас ограниченная область управления ими, поэтому текстовый столбец выглядит примерно так: «key1 = abc, key2 = def, keyn = ghi». Я знаю, что это ужасно денормализовано, но поскольку мы не знаем, что ключи будут (в некоторой степени) это надежный способ хранения и извлечения значений. Получить строку довольно быстро, так как мы ищем весь столбец, который проиндексирован. Но производительность не очень хорошая, если мы хотим получить строки с ключом key2 = def.

Возможно, нам удастся создать таблицу со столбцами для наиболее распространенных ключей, но мне было интересно, есть ли способ повысить производительность с существующей настройкой.

Ответы [ 8 ]

2 голосов
/ 29 января 2009

В Oracle 10:

CREATE TABLE test (tst_test VARCHAR2(200));

CREATE INDEX ix_re_1 ON test(REGEXP_REPLACE(REGEXP_SUBSTR(tst_test, 'KEY1=[^,]*'), 'KEY1=([^,]*)', '\1'))

SELECT  *
FROM    TEST
WHERE   REGEXP_REPLACE(REGEXP_SUBSTR(TST_TEST, 'KEY1=[^,]*'), 'KEY1=([^,]*)', '\1') = 'TEST'

При этом будет использоваться вновь выбранный индекс.

Вам потребуется столько индексов, сколько KEY s в ваших данных.

Наличие INDEX, конечно, влияет на производительность, но это очень мало зависит от присутствия REGEXP:

SQL> CREATE INDEX ix_test ON test (tst_test)
  2  /
Index created
Executed in 0,016 seconds

SQL> INSERT
  2  INTO   test (tst_test)
  3  SELECT 'KEY1=' || level || ';KEY2=' || (level + 10000)
  4  FROM   dual
  5  CONNECT BY
  6     LEVEL <= 1000000
  7  /
1000000 rows inserted
Executed in 47,781 seconds

SQL> TRUNCATE TABLE test
  2  /
Table truncated
Executed in 2,546 seconds

SQL> DROP INDEX ix_test
  2  /
Index dropped
Executed in 0 seconds

SQL> CREATE INDEX ix_re_1 ON test(REGEXP_REPLACE(REGEXP_SUBSTR(tst_test, 'KEY1=[^,]*'), 'KEY1=([^,]*)', '\1'))
  2  /
Index created
Executed in 0,015 seconds

 SQL> INSERT
      2  INTO   test (tst_test)
      3  SELECT 'KEY1=' || level || ';KEY2=' || (level + 10000)
      4  FROM   dual
      5  CONNECT BY
      6     LEVEL <= 1000000
      7  /
1000000 rows inserted
Executed in 53,375 seconds

Как видите, на моей не очень быстрой машине (Core2 4300, 1 Gb RAM) вы можете вставлять 20000 записей в секунду в индексированное поле, и эта скорость почти не зависит от типа INDEX используется: обычный или функциональный.

2 голосов
/ 29 января 2009

Вы можете использовать Пакет runstats Тома Кайта для сравнения производительности различных реализаций - каждый из которых выполняется, скажем, 1000 раз в цикле. Например, я только что сравнил LIKE с SUBSTR, и он сказал, что LIKE был быстрее, занимая около 80% времени SUBSTR.

Обратите внимание, что "col LIKE '% xxx%'" отличается от "SUBSTR (col, 5,3) = 'xxx'". Эквивалент LIKE будет:

col LIKE '____xxx%'

с использованием одного '_' для каждого начального символа, который следует игнорировать.

Я думаю, в зависимости от того, как вы это сделаете, результаты будут схожими - это всегда включает в себя сканирование полной таблицы (или, возможно, полного индекса). Индекс на основе функций поможет, только если вы знали смещение подстроки во время создания индекса.

Я весьма обеспокоен, когда вы говорите, что «выбор будет использоваться для удаления каждые несколько секунд». Это скорее говорит о недостатке дизайна, но без знания требований сложно сказать.

UPDATE

Если значения ваших столбцов такие, как 'key1 = abc, key2 = def, keyn = ghi', возможно, вы могли бы подумать о добавлении еще одной таблицы, подобной этой:

 create table key_values
    ( main_table_id references main_table
    , key_value varchar2(50)
    , primary key (fk_col, key_value)
    );

 create index key_values_idx on key_values (key_value);

Разделите значения ключей и сохраните их в этой таблице следующим образом:

main_table_id key_value
123           key1=abc
123           key2=def
123           key3=ghi

(Это можно сделать, например, в триггере AFTER INSERT на main_table)

Тогда ваше удаление может быть:

delete main_table
where id in (select main_table_id from key_values
             where key_value = 'key2=def');
1 голос
/ 29 января 2009

Отдельный ответ на комментарий к оформлению таблицы.

Разве вы не можете, по крайней мере, иметь структуру KEY / VALUE, поэтому вместо сохранения в одном столбце 'key1 = abc, key2 = def, keyn = ghi' у вас будет дочерняя таблица типа

KEY     VALUE
key1    abc
key2    def
key3    ghi

Затем вы можете создать единый индекс по ключу и значению, и ваши запросы будут намного проще (так как я так понимаю, вы действительно ищете точное совпадение по значению данного ключа).

Некоторые люди, вероятно, прокомментируют, что это ужасный дизайн, но я думаю, что он лучше, чем у вас сейчас.

1 голос
/ 29 января 2009

Можете ли вы предоставить немного больше информации?

Запрашиваете ли вы произвольную подстроку строкового столбца или в хранилище строк в столбцах имеется некоторый синтаксис, который позволил бы выполнить некоторую предварительную обработку, чтобы минимизировать повторную работу?

Вы уже провели какие-либо временные тесты для трех вариантов, чтобы определить их относительную производительность по запрашиваемым данным?

1 голос
/ 29 января 2009

Предлагаю пересмотреть вашу логику.

Вместо того, чтобы искать, где находится строка, может быть быстрее проверить, имеет ли она длину> 0 и не является ли она строкой.

Вы можете использовать функцию TRANSLATE в oracle, чтобы преобразовать все нестроковые символы в нули, а затем проверить, равен ли результат нулю.

0 голосов
/ 30 января 2009

Подобно ответу Антона Гоголева, Oracle включает в себя механизм текстового поиска, документированный здесь

Существует также расширяемая индексация, так что вы можете создавать свои собственные структуры индекса, документированные здесь

Как вы и согласились, это очень плохая структура данных, и я думаю, что вы будете изо всех сил пытаться достичь цели удаления содержимого каждые несколько секунд. В зависимости от того, как эти данные поступают на вход, я бы посмотрел на правильное структурирование данных при загрузке, по крайней мере, в той степени, чтобы иметь строки «parent_id», «key_name», «key_value».

0 голосов
/ 29 января 2009

Не уверен насчет улучшения существующих настроек, но Lucene (библиотека полнотекстового поиска; перенесена на многие платформы) действительно может помочь. Синхронизация индекса с БД сопряжена с дополнительными трудностями, но если у вас есть что-то, что напоминает уровень обслуживания в каком-то языке программирования, это становится легкой задачей.

0 голосов
/ 29 января 2009

Если вы всегда будете искать одну и ту же подстроку, тогда использование INSTR и индекса на основе функций имеет смысл для меня. Вы также можете сделать это, если у вас есть небольшой набор константных подстрок, которые вы будете искать, создавая один ФБР для каждой.

Идея Quassnoi REGEXP тоже выглядит многообещающе. В Oracle я еще не использовал регулярные выражения.

Я думаю, что Oracle Text был бы другим путем. Информация об этом здесь

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...