Является ли WHERE ID IN (1, 2, 3, 4, 5, ...) наиболее эффективным? - PullRequest
15 голосов
/ 06 октября 2009

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

Если мое понимание работы баз данных в целом правильное, то наиболее эффективным является простое использование конструкции WHERE ID IN (1, 2, 3, 4, 5, ...) и динамическое построение запросов. Суть проблемы заключается в том, что входные списки идентификаторов будут действительно произвольными, и поэтому независимо от того, насколько умна база данных или насколько умно мы ее реализуем, у нас всегда есть случайное подмножество целых чисел, с которого нужно начинать, и в конечном итоге каждый подход должен внутренне сводиться к чему-то вроде WHERE ID IN (1, 2, 3, 4, 5, ...) в любом случае.

В Интернете можно найти множество подходов. Например, один включает объявление табличной переменной, передачу списка идентификаторов в процедуру хранения в виде строки с разделителями-запятыми, разделение ее в процедуре сохранения, вставку идентификаторов в переменную таблицы и присоединение к ней основной таблицы, то есть что-то вроде это:

-- 1. Temporary table for ID’s:
DECLARE @IDS TABLE (ID int);

-- 2. Split the given string of ID’s, and each ID to @IDS.
-- Omitted for brevity.

-- 3. Join the main table to @ID’s:
SELECT MyTable.ID, MyTable.SomeColumn
FROM MyTable INNER JOIN @IDS ON MyTable.ID = @IDS.ID;

Если оставить в стороне проблемы с манипулированием строками, я думаю, что в этом случае, по сути, происходит то, что на третьем этапе SQL Server говорит: «Спасибо, это хорошо, но мне просто нужен список идентификаторов», и это сканирует табличную переменную @IDS, а затем n ищет в MyTable, где n - это число идентификаторов. Я провел некоторые элементарные оценки производительности и проверил план запросов, и, похоже, именно это и происходит. Таким образом, переменная таблицы, конкатенация и разбиение строк и все дополнительные INSERT не имеют смысла.

Я прав? Или я что-то упустил? Есть ли действительно какой-нибудь умный и более эффективный способ? По сути, я хочу сказать, что SQL Server должен выполнить n поиска индекса независимо от того, что и, и формулировка запроса как WHERE ID IN (1, 2, 3, 4, 5, ...) является наиболее простым способом его запросить.

Ответы [ 12 ]

11 голосов
/ 06 октября 2009

Ну, это зависит от того, что на самом деле происходит. Как пользователь выбирает эти идентификаторы?

Кроме того, это не просто эффективность; есть также безопасность и правильность, о которых нужно беспокоиться. Когда и как пользователь сообщает базе данных о своем выборе идентификатора? Как вы включаете их в запрос?

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

Я скажу вам, что вы вряд ли добьетесь гораздо большей производительности, чем IN (1,2,3..n) для небольшого (сгенерированного пользователем) n. Но вам нужно подумать о том, как вы генерируете этот запрос. Собираетесь ли вы использовать динамический SQL? Если так, как вы защитите его от инъекций? Сможет ли сервер кэшировать план выполнения?

Кроме того, использование дополнительной таблицы часто просто проще. Допустим, вы создаете корзину для сайта электронной коммерции. Вместо того, чтобы беспокоиться о отслеживании клиентской части корзины или сеанса, лучше обновлять таблицу ShoppingCart каждый раз, когда пользователь делает выбор. Это также позволяет избежать всей проблемы безопасной установки значения параметра для вашего запроса, поскольку вы вносите только одно изменение за раз.

Не забудьте старую поговорку (с извинениями Бенджамин Франклин ):

Тот, кто променял бы правильность на производительность, не заслуживает ни

6 голосов
/ 06 октября 2009

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

5 голосов
/ 06 октября 2009

Предложение IN не гарантирует INDEX SEEK. Я столкнулся с этой проблемой перед использованием SQL Mobile Edition в Pocket с очень небольшим объемом памяти. Замена IN (list) списком предложений OR увеличила мой запрос примерно на 400%.

Другой подход заключается в том, чтобы иметь временную таблицу, в которой хранятся идентификаторы, и присоединять ее к целевой таблице, но если эта операция используется слишком часто, постоянная / индексированная таблица может помочь оптимизатору.

3 голосов
/ 06 октября 2009

Для меня IN (...) не является предпочтительным вариантом по многим причинам, включая ограничение количества параметров.

В продолжение примечания Яна Зича относительно производительности с использованием различных реализаций временных таблиц, вот некоторые цифры из плана выполнения SQL:

  • Решение XML: 99% времени - разбор XML
  • Процедура, разделенная запятыми, с использованием UDF из CodeProject : 50% сканирование временной таблицы, 50% поиск по индексу. Можно усомниться, если это наиболее оптимальная реализация парсинга строк, но я не хотел создавать ее сам (я с удовольствием протестирую другую).
  • CLR UDF для разделения строки: 98% - поиск по индексу.

Вот код для CLR UDF:

public class SplitString
{
    [SqlFunction(FillRowMethodName = "FillRow")]
    public static IEnumerable InitMethod(String inputString)
    {
        return inputString.Split(',');
    }

    public static void FillRow(Object obj, out int ID)
    {
        string strID = (string)obj;
        ID = Int32.Parse(strID);
    }
}

Так что я должен согласиться с Яном, что решение XML неэффективно. Поэтому, если список через запятую должен передаваться как фильтр, простой CLR UDF представляется оптимальным с точки зрения производительности.

Я проверил поиск записи 1К в таблице 200К.

2 голосов
/ 06 октября 2009

У таблицы var есть проблемы: использование временной таблицы с индексом имеет преимущества для статистики.

Предполагается, что таблица var всегда имеет одну строку, тогда как временная таблица имеет статистику, которую может использовать оптимизатор.

Разобрать CSV легко: см. Вопросы справа ...

1 голос
/ 27 августа 2013

В SQL Server 2008 или более поздней версии вы должны использовать табличные параметры .

2008 упрощает передачу разделенного запятыми списка в SQL Server с помощью этого метода.

Вот отличный источник информации и тесты производительности по теме:

Массивы-в-SQL-2008

Вот отличный урок:

проходя стол многозначные-параметры-в-SQL-Server-2008

1 голос
/ 07 октября 2009

Когда-то давным-давно я обнаружил, что в конкретной СУБД, с которой я работал, список IN был более эффективным вплоть до некоторого порога (который был, IIRC, что-то вроде 30-70), а после этого он было более эффективно использовать временную таблицу для хранения списка значений и соединения с временной таблицей. (СУБД значительно упростила создание временных таблиц, но даже несмотря на накладные расходы по созданию и заполнению временной таблицы, запросы выполнялись в целом быстрее.) Это было связано с обновленной статистикой по основным таблицам данных (но это также помогло обновите статистику для временной таблицы).

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

1 голос
/ 06 октября 2009

По сути, я бы согласился с вашим наблюдением; Оптимизатор SQL Server в конечном итоге выберет лучший план для анализа списка значений и, как правило, будет соответствовать одному и тому же плану, независимо от того, используете ли вы

WHERE IN

или

WHERE EXISTS

или

JOIN someholdingtable ON ...

Очевидно, что есть другие факторы, которые влияют на выбор плана (например, покрытие индексов и т. Д.). Причина, по которой у людей есть различные методы для передачи этого списка значений в хранимую процедуру, заключается в том, что до SQL 2008 не было простого способа передачи нескольких значений. Вы можете сделать список параметров (WHERE IN (@ param1, @ param2) ...), или вы можете проанализировать строку (метод, который вы показали выше). Начиная с SQL 2008, вы также можете передавать переменные таблицы, но общий результат остается тем же.

Так что да, не имеет значения, как вы получаете список переменных для запроса; Однако есть и другие факторы, которые могут оказать некоторое влияние на производительность указанного запроса, как только вы получите список переменных там.

0 голосов
/ 06 октября 2009
select t.*
from (
    select id = 35 union all
    select id = 87 union all
    select id = 445 union all
    ...
    select id = 33643
) ids
join my_table t on t.id = ids.id

Если набор ids для поиска невелик, это может повысить производительность, разрешив обработчику запросов выполнить поиск по индексу. Если оптимизатор посчитает, что сканирование таблицы будет быстрее, чем, скажем, поиск по сотне индексов, оптимизатор будет давать указания механизму запросов.

Обратите внимание, что механизмы запросов имеют тенденцию обрабатывать

select t.*
from my_table t
where t.id in (35, 87, 445, ..., 33643)

как эквивалент

select t.*
from my_table t
where t.id = 35 or t.id = 87 or t.id = 445 or ... or t.id = 33643

и обратите внимание, что механизмы запросов, как правило, не могут выполнять поиск индекса по запросам с дизъюнктивными критериями поиска. Например, хранилище данных Google AppEngine вообще не будет выполнять запрос с дизъюнктивными критериями поиска, поскольку будет выполнять только те запросы, для которых известно, как выполнить поиск по индексу.

0 голосов
/ 06 октября 2009

Чтобы ответить на вопрос напрямую, невозможно передать (динамический) список аргументов процедуре SQL Server 2005. Поэтому в этих случаях большинство людей передают список идентификаторов, разделенных запятыми, что я и сделал.

Начиная с SQL 2005, я предпочитаю передачу и XML-строку, которую также очень легко создавать на стороне клиента (c #, python, другой SQL SP) и "нативный" для работы с 2005:

CREATE PROCEDURE myProc(@MyXmlAsSTR NVARCHAR(MAX)) AS BEGIN
    DECLARE @x XML
    SELECT @x = CONVERT(XML, @MyXmlAsSTR)

Затем вы можете присоединиться к базовому запросу напрямую, выбрав XML как (не проверено):

SELECT      t.*
FROM        myTable t
INNER JOIN  @x.nodes('/ROOT/ROW') AS R(x)
        ON  t.ID = x.value('@ID', 'INTEGER')

при прохождении <ROOT><ROW ID="1"/><ROW ID="2"/></ROOT>. Просто помните, что XML - это CaSe-SensiTiv.

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