Выбор большого количества строк по первичному ключу - PullRequest
5 голосов
/ 18 января 2009

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

Часто, но не всегда, выбранные строки являются смежными, поэтому я реализовал механизм, который преобразует выбор в набор предложений диапазона для всех записей, которые объединяются ([PrimaryKey] МЕЖДУ 151 И 217 ) и резервный метод, который выбирает все изолированные записи с предложением IN.

В конце концов я получаю что-то вроде этого

SELECT * FROM Table WHERE ([PrimaryKey] BETWEEN 151 AND 217) OR ([PrimaryKey] BETWEEN 314 AND 378) OR ...
OR [PrimaryKey] IN (1,3,7,14,147...)

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

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

Ответы [ 6 ]

7 голосов
/ 18 января 2009

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

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

Я могу понять намерения вашего механизма. Однако на практике наличие индекса и вера в оптимизатор обычно лучше.

Если вам нужно создать дюжину условий в предложении WHERE, механизм запросов должен проверять каждое из них, пока не найдет совпадение.

Точно так же создание нескольких запросов и их объединение означало бы сканирование индекса или поиск индекса в таблице несколько раз.

Однако верно, что предложение IN может быть очень медленным, когда у вас большой список. В этом случае использование соединения обычно происходит быстрее. По опыту, это всегда мой предпочтительный вариант.

Тем не менее, правда, что использование BETWEEN особенно эффективно для больших диапазонов. Имея это в виду, может быть полезно использовать механизм UNION, ГДЕ первый набор записей использует JOIN, а остальные - МЕЖДУ, при условии, что BETWEEN имеет значительный диапазон.

Значительным моментом является время для подготовки такого запроса. Если SQL Server должен сгенерировать динамический запрос, используя T-SQL, будет два верхних уровня; Время генерации запроса и время его анализа затем генерируют план выполнения. Первый будет доминировать для больших списков и может стоить больше времени, чем сэкономлено при использовании BETWEEN.

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

Задница такая, если бы я не увидел очень заметного увеличения производительности, я бы придерживался объединения. Основными причинами являются инженерные;
- Время развиваться
- Надежность кода (простой всегда надежнее, хитрые трюки)
- Возможность сопровождения кода (поймут ли последующие сопровождающие хитрость?)

Если вы тестируете различные комбинации JOIN и BETWEEN, с или без UNION и т. Д., Мне будет очень интересно увидеть результаты вашей работы.

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

Поскольку пользователь ocdecio правильно предлагает, вы можете присоединиться к временной таблице, которая будет содержать все идентификаторы. Вы также можете попытаться разделить операционные блоки на различные UNION части:

SELECT * FROM Table WHERE [PrimaryKey] BETWEEN 151 AND 217
UNION
SELECT * FROM Table WHERE [PrimaryKey] BETWEEN 314 AND 378
UNION
...
UNION
SELECT * FROM Table WHERE [PrimaryKey] IN (1,3,7,14,147...)
0 голосов
/ 17 марта 2009

Моей первой мыслью было бы посмотреть, как вы тянете в списке клавиш. Это из запроса в другом месте? Если это так, вы пробовали

SELECT * FROM Table WHERE [PrimaryKey] in
(
    SELECT PrimaryKey from SomeOtherTable where Condition = 'met'
)

или если ваши условия в одной таблице, это еще проще

SELECT * FROM Table WHERE condition = 'met'

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

Кстати, извините, если я учу кого-либо сосать яйца, просто мой опыт показывает, что иногда самые простые решения игнорируются.

0 голосов
/ 17 марта 2009

Как вы определяете список значений первичного ключа для выбора? Мне интересно, стоит ли нам смотреть на это далее «вверх по течению» - есть ли какой-то более ранний запрос или запросы, которые вы выполнили, чтобы получить список ключей? Если это так, возможно, вы могли бы выполнить объединение и вообще пропустить поиск ключей.

0 голосов
/ 17 марта 2009

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

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

<a>
  <b>1</b>
  <b>3</b>
  <b>7</b>
  <b>14</b>
  <b>147</b>
</a>

Я дал элементам короткие имена ("a" и "b"), потому что более длинное имя будет означать больше байтов для передачи от клиента к серверу SQL. Вот как вы должны выбрать все содержимое элементов «b» в качестве набора записей:

declare @x xml
set @x = '<a><b>1</b><b>3</b><b>7</b><b>14</b><b>147</b></a>'
select t.item.value('.', 'int') from @x.nodes('//a/b') as t(item)

Хотя синтаксис является загадочным, тип XML может запрашиваться как таблица. Теперь вы должны увидеть, куда это идет. Если мы можем запросить тип XML как таблицу, мы можем пересечь его с другой таблицей:

select * from MyTable where ID in 
  (select t.item.value('.', 'int') from @x.nodes('//a/b') as t(item))

или используя соединение

;with cte as
  (select ID = t.item.value('.', 'int') from @x.nodes('//a/b') as t(item)) 
select * from MyTable inner join cte on MyTable.ID = cte.ID

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

create procedure MyProc @x xml as
begin
  set nocount on
  ;with cte as
    (select ID = t.item.value('.', 'int') from @x.nodes('//a/b') as t(item)) 
  select * from MyTable inner join cte on Table.ID = cte.ID
end

Пример вызова новой хранимой процедуры:

exec MyProc '<a><b>1</b><b>3</b><b>7</b><b>14</b><b>147</b></a>'

Я также обнаружил, что добавление схемы XML для входного фрагмента помогло немного ускорить хранимую процедуру. Я не буду вдаваться в подробности XML-схем здесь, но идея состоит в том, чтобы заранее сообщить SQL, как будет выглядеть фрагмент XML. Вот как мы могли бы ввести нашу схему:

create xml schema collection MyInputSchema as
  '<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <xsd:element name="a">
      <xsd:complexType>
        <xsd:complexContent>
          <xsd:restriction base="xsd:anyType">
            <xsd:sequence>
              <xsd:element name="b" type="xsd:integer" maxOccurs="unbounded" />
            </xsd:sequence>
          </xsd:restriction>
        </xsd:complexContent>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>'

Теперь мы можем связать эту схему с входом нашей хранимой процедуры следующим образом:

create procedure MyProc @x xml(MyInputSchema) as
begin
  set nocount on
  ;with cte as
    (select ID = t.item.value('.', 'int') from @x.nodes('//a/b') as t(item)) 
  select * from MyTable inner join cte on Table.ID = cte.ID
end

Имея все это, я смог отправить фрагмент XML из 43 016 символов с моей клиентской машины на сервер SQL и очень быстро вернуть набор результатов. Я сделал тест 1000 запросов на 10 потоков на общую сумму 10000 запросов. В результате было обработано 72 запроса в секунду. Конечно, ваш доход зависит от вашего аппаратного и программного обеспечения.

ПРИМЕЧАНИЕ. Этот код работает в SQL 2005, а также в 2008 году.

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