Что является подходящей заменой оператора SQL Like для повышения производительности? - PullRequest
6 голосов
/ 31 декабря 2008

Я работаю над приложением, работающим в Windows Mobile 6, которое должно иметь возможность извлекать все элементы из таблицы элементов, содержащей заданную строку (предоставленную конечным пользователем) в поле описания элемента. Проблема в том, что в таблице примерно 170 000 предметов. Так как мне нужно вернуть все элементы, которые содержат строку в любом месте описания, я вынужден использовать LIKE% string%, что исключает любую возможность использования индекса. Структура данных и таблиц изначально основана на базе данных Progress, в которой есть замечательный оператор содержит любые поля, индексированные по словам. Это не относится к нашему мобильному приложению, поскольку оно использует SQL Server Compact 3.5.

По сути, мой DAL выполняет запрос и получает SqlCeDataReader, а затем использует ItemFactory для создания объекта List, который содержит только совпадающие элементы. Это, очевидно, позволяет нам отделять наши доменные / бизнес-объекты от уровня доступа к данным.

Прекрасно и прекрасно, за исключением 8 м и 42-х, которые требуются для извлечения предметов, когда я ищу все предметы, которые содержат в описании что-то вроде «гольф». Очевидно, что это не приемлемый период времени для конечного пользователя.

Моя первая попытка состояла в том, чтобы вместо этого извлечь все элементы обратно из базы данных, используя SELECT * FROM Item "(с предложением order by в одном из основных проиндексированных полей). В этот момент я запустил проверку IndexOf, проходя через SqlCeDataReader и ItemFactory добавляли элементы в объект List только в том случае, если они содержали запрошенный текст описания. Это улучшило скорость до 1 м 46 с. Не слишком потертый, но все же слишком медленный.

Затем я попробовал другой подход, который показал обещание ... почти ... Во время запуска приложения я попытался создать список, который содержал бы все объекты элементов в базе данных (для выполнения запроса и заполнения всего целого требуется около 2 минут). список, но по крайней мере это только один раз, поскольку приложение инициализируется ... все еще ... тьфу). Как только список заполнен, я могу легко выполнить запросы к этому списку, выполнив следующие действия (надеюсь, мой синтаксис правильный ... Сейчас я не на работе и у меня нет Visual Studio на компьютере. сижу))

List<Item> specificItems = 
    AllItems.FindAll(i => i.Description.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0);

Этот подход сбил его до 21 с. Очень хорошо (все еще медленно, хотя в великой схеме вещей). Однако проблема в том, что использование памяти слишком велико, если я загружаю все элементы из базы данных. Мне пришлось фактически отрезать последние 20 000 элементов (поэтому временные рамки 21-го года, вероятно, были бы больше похожи на 25-е) во время начальной загрузки, потому что генерировалось исключение OutOfMemoryException. Согласно диспетчеру памяти на эмуляторе, у меня все еще было около 20 МБ свободной оперативной памяти, но я слышал, что процессу может быть назначено только 32 МБ или ОЗУ (не уверен, правда ли это для WM 6, но, похоже, поэтому).

Чтобы убедиться, что это было не потому, что я использовал объект List для хранения всех элементов (которые я инстанцировал с необходимой емкостью в его конструкторе, чтобы избежать динамического изменения размера), который я также прочитал, может вызвать дополнительную память Использование, когда это подразумевает вызов EnsureCapacity, я попытался использовать массив Item [] (размер заранее). Это все еще имело проблему с памятью, и разница в размерах была незначительной.

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

Есть идеи? Я поступаю неправильно?

Спасибо, что выслушали (извините, этот пост длинный, я немного обдумал).

О, я должен добавить (просто вкратце), что я использую:

  • Windows Mobile 6
  • Sql Server Compact Edition 3.5
  • C # 3,5

ОБНОВЛЕНИЕ: хотя упомянутый ниже подход «Фильтр Блума» показался мне интересным, я не смог выполнить одно требование (которое я не указывал выше). Я не могу сопоставить слова, содержащиеся в других словах (например, «клуб» не возвращает «клубы»). Из-за этого я был вынужден использовать другой подход (Кент Фредрик ... спасибо за указание на это). Я пометил ответ Кента как правильный, так как его подход отвечал большинству требований (Митч, у вас была та же проблема, что и у фильтра Блума, предложенного Jaunder). Тем не менее, я пошел другим путем (на данный момент ...), чем его путь.

То, что я сделал, это перетянул все объекты предметов в память, только с номерами предметов и описаниями (что ограничивает память, но все равно вызывает более длительную инициализацию, чем мне нравится ... многопоточность и загрузка этой информации за кадром, пока приложение работает, я могу позаботиться об этом, я думаю). Для выполнения поиска я написал свою собственную подпрограмму. Подпрограмма написана в неуправляемом коде c #, который использует два указателя и пару циклов для запуска описания и необходимого соответствующего текста. Если он находит совпадение где-либо в описании, он добавляет номер элемента в массив. После поиска всех элементов новый запрос возвращается в базу данных и получает только совпадающие номера элементов (что очень быстро благодаря индексу в целочисленном поле). Затем эти элементы создаются в Списке со всей информацией (не только номером элемента и описанием). Вся операция занимает приблизительно 5 - 10 секунд (в зависимости от описания), что на данный момент достаточно.

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

Любые предложения по-прежнему приветствуются. Пока я отметил ответ Кента как «самый правильный» на мой вопрос.

Рекомендую Дольчу помочь мне написать рутину содержания.

Ответы [ 3 ]

5 голосов
/ 31 декабря 2008

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

CREATE TABLE WordItemOccurance
(
    [Word] varchar(50) not null,

    ItemId int not null
        constraint FK_Items references ItemTable(ID)
)

Перебирайте все свои элементы, разбивайте их на отдельные слова и добавляйте записи в таблицу occurance по мере их обнаружения.

Создание кластерного индекса в [Word] и присоединение к таблице элементов в ItemId должно быть быстрым.

4 голосов
/ 31 декабря 2008

Я голосовал за Mitch Wheat's ответ, но есть несколько трюков, которые я бы также проверил на эффективность.

Мое большое беспокойство по поводу наличия таблицы, заполненной [char], [int], - вы все еще можете выполнять большие объемы бессмысленных сравнений строк, особенно если вы используете% word% в этой новой таблице. (Дублированные, но не соответствующие нашему поиску записи).

Я бы, вероятно, выбрал экспериментирование с

Words
-----
chars  | word_id 

WordsToEntry
------------
word_id | entry_id 

и посмотрите, является ли издержки базы данных достойным решением этой возможной проблемы (я не могу проверить, извините)

1 голос
/ 31 декабря 2008

Вы можете попробовать использовать фильтр Блума.

  1. Википедия
  2. с использованием цветения фильтры
...