2013-10-01 3 views
3

пытается использовать нумерацию страниц, и я получили прекрасную ссылку на SOРазбивка в SQL - вопрос производительности

https://stackoverflow.com/a/109290/1481690

SELECT * 
FROM (SELECT ROW_NUMBER() OVER (ORDER BY OrderDate) AS RowNum, * 
      FROM  Orders 
      WHERE  OrderDate >= '1980-01-01' 
     ) AS RowConstrainedResult 
WHERE RowNum >= 1 
    AND RowNum < 20 
ORDER BY RowNum 

Таж запрос пытаются использовать с дополнительным объединением нескольких таблиц в моем внутреннем Запрос.

подводит несколько проблем производительности в следующих сценариях

WHERE RowNum >= 1 
    AND RowNum < 20 ==>executes faster approx 2 sec 


    WHERE RowNum >= 1000 
    AND RowNum < 1010  ==> more time approx 10 sec 

    WHERE RowNum >= 30000 
    AND RowNum < 30010 ==> more time approx 17 sec 

Everytime я выбрать 10 строк, но огромную разницу во время. Любая идея или предложения?

Я выбрал этот подход как динамически связывание столбцов и формирование запроса. Есть ли другой лучший способ организовать Pagination Query на SQl Server 2008.

Есть ли способ повысить производительность запроса?

Благодаря

+1

Покажите полный запрос, который вы используете, и предоставите 'CREATE TABLE', включая индексы для задействованных таблиц. –

+0

@Peru, какова структура таблицы заказов? – Michael

ответ

1

Даже если вы всегда выбирая одинаковое количество строк, производительность снижается, если вы хотите, чтобы выбрать строки в конце окна данных. Чтобы получить первые 10 строк, двигатель извлекает всего 10 строк; чтобы получить следующие 10, он должен получить 20, отбросить первые 10 и вернуть 10. Чтобы получить 30000 - 30010, он должен прочитать все 30010, пропустить первые 30k и вернуться 10.

Некоторые трюки для повышения производительности (не полный список, здание OLAP полностью пропущено). Вы упомянули о присоединении; если это возможно, присоединяться не к внутреннему запросу, а к результату. Вы также можете попытаться добавить некоторую логику в ORDER BY OrderDate - ASC или DESC зависит от того, какое ведро вы извлекаете. Скажем, если вы хотите захватить «последний» 10, ORDER BY ... DESC будет работать намного быстрее. Иглы сказать, что это должен быть индекс orderDate.

+0

Спасибо @ a1ex07.Есть ли другой лучший способ написать запрос на разбиение на страницы в sql server 2008? – Peru

4

Я всегда проверяю, сколько данных я запрашиваю в запросе и пытаюсь устранить ненужные столбцы, а также строки. Ну, это только очевидные моменты, которые вы, возможно, уже проверяли, но просто хотели указать на случай, если вы еще этого не сделали. В вашем запросе низкая производительность может быть вызвана тем, что вы делаете «Выбрать *». Выбор всех столбцов из таблицы не позволяет прийти с хорошим планом выполнения. Проверьте, нужны ли только выбранные столбцы и убедитесь, что у вас есть правильный индекс покрытия в таблице Заказы.

Поскольку явная функция SKIPP или OFFSET недоступна в версии SQL 2008, нам нужно создать одно и что мы можем создать с помощью INNER JOIN. В одном запросе мы сначала сгенерируем идентификатор с OrderDate, и больше ничего не будет в этом запросе. Мы делаем то же самое во втором запросе, но здесь мы также выбираем некоторые другие заинтересованные столбцы из таблицы ORDER или ALL, если вам нужен ВСЕ столбец. Затем мы присоединяем это для запроса результатов по параметрам ID и OrderDate и ADD SKIPP для первого запроса, где набор данных имеет минимальный размер, что требуется. Попробуйте этот код.

SELECT q2.* 
    FROM 
    (
     SELECT ROW_NUMBER() OVER (ORDER BY OrderDate) AS RowNum, OrderDate 
     FROM  Orders 
     WHERE  OrderDate >= '1980-01-01' 
    )q1 
    INNER JOIN 
    (
     SELECT ROW_NUMBER() OVER (ORDER BY OrderDate) AS RowNum, * 
     FROM  Orders 
     WHERE  OrderDate >= '1980-01-01' 
    )q2 
     ON q1.RowNum=q2.RowNum AND q1.OrderDate=q2.OrderDate AND q1.rownum BETWEEN 30000 AND 30020 

Чтобы дать вам оценку, я попытался это со следующими данными испытаний и независимо от того, какое окно вы запрашиваете результаты обратно менее чем за 2 секунд, и обратите внимание, что таблица КУЧА (нет индекса) Таблица содержит всего 2M строк.Тест выбора запрашивает 10 строк от 50000 до 50,010

Ниже Insert занимает около 8 минут.

IF object_id('TestSelect','u') IS NOT NULL 
     DROP TABLE TestSelect 
    GO 
    CREATE TABLE TestSelect 
    (
     OrderDate DATETIME2(2) 
    ) 
    GO 

    DECLARE @i bigint=1, @dt DATETIME2(2)='01/01/1700' 
    WHILE @I<=2000000 
    BEGIN 

     IF @i%15 = 0 
      SELECT @DT = DATEADD(DAY,1,@dt) 

     INSERT INTO dbo.TestSelect(OrderDate) 
     SELECT @dt 

     SELECT @[email protected]+1 
    END 

Выбор окна 50 000 до 50,010 потребовалось менее 3 секунд.

Выбор последнего одной строки 2000000 до 2000000 также занял 3 секунды.

SELECT q2.* 
    FROM 
    (
     SELECT ROW_NUMBER() OVER (ORDER BY OrderDate) AS RowNum 
       ,OrderDate 
     FROM TestSelect 
     WHERE OrderDate >= '1700-01-01' 
    )q1 
    INNER JOIN 
    (
     SELECT ROW_NUMBER() OVER (ORDER BY OrderDate) AS RowNum 
       ,* 
     FROM TestSelect 
     WHERE OrderDate >= '1700-01-01' 
    )q2 
     ON q1.RowNum=q2.RowNum 
     AND q1.OrderDate=q2.OrderDate 
     AND q1.RowNum BETWEEN 50000 AND 50010 

enter image description here

+1

Спасибо @Anup Shah за подробный ответ. Вы проверите это +1 – Peru

+0

Эй, я протестировал это решение и сравнил его с соответствующим запросом, основанным на моем предложении ниже. На примерной таблице с 100 тыс. Записей это показывает, что ваш запрос потребляет 59% общего времени, в то время как у меня только 41%. Когда я увеличил тестовый образец до 1,6 млн, для записей 50000 - 50010 он улучшился до 69% и 31%, но когда я переместил диапазон до 250000 - 25010, преимущество уменьшилось до 60% против 40%. Наконец, для записей 750000 - 750010 (так справедливо в середине кучи) разница составляет 56% против 44%. Дальнейшее тестирование может оказаться интересным, но, кроме того, мое решение намного проще. – nimdil

+0

@Peru: Здесь есть хороший тест, подтверждающий вашу плохую производительность смещения: http://www.4guysfromrolla.com/webtech/042606-1.shtml. Я думаю, вы, ребята, может быть заинтересованы в [методе поиска] (http://stackoverflow.com/a/19610105/521799), который позволяет подкачки в постоянное время –

0
declare @pageOffset int 
declare @pageSize int 
-- set variables at some point 

declare @startRow int 
set @startRow = @pageOffset * @pageSize 

declare @endRow int 
set @endRow + @pageSize - 1 

SELECT 
    o.* 
FROM 
(
    SELECT 
     ROW_NUMBER() OVER (ORDER BY OrderDate) AS RowNum 
     , OrderId 
    FROM  
     Orders 
    WHERE  
     OrderDate >= '1980-01-01' 
) q1 
INNER JOIN Orders o 
    on q1.OrderId = o.OrderId 
where 
    q1.RowNum between @startRow and @endRow 
order by 
    o.OrderDate 
0

@peru, относительно, если есть лучший способ, и опираться на объяснения, представленные @a1ex07, попробуйте следующее -

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

Например, если заказы таблица имеет «Order_ID» в качестве первичного ключа затем -
Чтобы получить первые десять результатов -
1.

select RowNum, order_id from 
(select 
ROW_NUMBER() OVER (ORDER BY OrderDate) AS RowNum, 
o.order_id 
from orders o where o.order_id > 0 ; 
) 
tmp_qry where RowNum between 1 and 10 order by RowNum; // first 10 

Если предположить, что последний идентификатор заказа возвращено 17 затем,

Чтобы выбрать следующий 10,
2.

select RowNum, order_id from 
(select 
ROW_NUMBER() OVER (ORDER BY OrderDate) AS RowNum, 
o.order_id 
from orders o where o.order_id > 17 ; 
) 
tmp_qry where RowNum between 1 and 10 order by RowNum; // next 10 

Обратите внимание, что значения row-num не изменены. Сравнивается сравниваемое значение идентификатора заказа, которое было изменено.

Если такого ключа нет, подумайте о его добавлении!

2

ROW_NUMBER - это дерьмовый способ сделать разбивку на страницы, так как стоимость операции растет.

Вместо этого вы должны использовать оговорку double ORDER BY.

Скажите, что вы хотите получить документы с ROW_NUMBER between 1200 and 1210. Вместо того чтобы использовать ROW_NUMBER() OVER (...) и позднее связывание результата в WHERE вам лучше:

SELECT TOP(11) * 
FROM (
    SELECT TOP(1210) * 
    FROM [...] 
    ORDER BY something ASC 
) subQuery 
ORDER BY something DESC. 

Обратите внимание, что этот запрос будет давать результат в обратном порядке. Это не должно, вообще говоря, быть проблемой, так как легко изменить набор в пользовательском интерфейсе, то есть C#, тем более что результирующий набор должен быть относительно небольшим.

Последний, как правило, намного быстрее.Обратите внимание, что последнее решение будет значительно улучшено с помощью CLUSTERING (CREATE CLUSTERED INDEX ...) в столбце, который вы используете для сортировки запроса.

Надеюсь, что это поможет.

0

Главный недостаток вашего запроса в том, что он сортирует всю таблицу и вычисляет Row_Number для каждого запроса. Вы можете облегчить жизнь SQL Server, используя меньшее количество столбцов на этапе сортировки (например, как было предложено Anup Shah). Однако вы все равно можете читать, сортировать и вычислять номера строк для каждого запроса.

Альтернативой вычислениям на лету являются значения, которые были вычислены ранее.

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

  1. Добавить столбец ROWNUMBER (или 2-3 колонки) и включить его в качестве первых столбцов в кластерном индексе или создать некластеризованный индекс inde).

  2. Создавайте виды для наиболее часто встречающихся комбинаций, а затем индексируйте их. Он называется индексированными (материализованными) представлениями.

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

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

0

Невероятно, но другого ответа не упомянул быстрый способ сделать подкачки во всех версиях SQL Server, в частности, в отношении вопроса в ФП, где смещение может быть очень медленным для большого количества страниц, как это benchmarked here.

Существует совершенно другой, гораздо более быстрый способ выполнения подкачки в SQL. Это часто называют «методом поиска», как описано в this blog post here.

SELECT TOP 10 * 
FROM Orders 
WHERE OrderDate >= '1980-01-01' 
AND ((OrderDate > @previousOrderDate) 
    OR (OrderDate = @previousOrderDate AND OrderId > @previousOrderId)) 
ORDER BY OrderDate ASC, OrderId ASC 

В @previousOrderDate и @previousOrderId значений являются соответствующими значениями последней записи с предыдущей страницы. Это позволяет вам получать «следующую» страницу. Если направление ORDER BY равно DESC, просто используйте вместо него <.

С помощью вышеуказанного метода вы не можете сразу перейти на страницу 4, не предварительно извлек предыдущие 40 записей. Но часто вы не хотите так далеко прыгать. Вместо этого вы получаете гораздо более быстрый запрос, который мог бы получать данные в постоянное время, в зависимости от вашей индексации. Кроме того, ваши страницы остаются «стабильными», независимо от того, изменяются ли базовые данные (например, на стр. 1, пока вы на стр. 4).

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

Примечание: «Метод поиска» также называется keyset paging.

Смежные вопросы