2008-11-07 2 views
2

Прежде всего, некоторые фоне.Speed ​​up sql JOIN

У нас есть система обработки заказов, в которой сотрудники вводят платежные данные о заказах в приложении, которое хранит его в базе данных SQL Server 2000. Эта база данных не является реальной биллинговой системой: это просто место для хранения, так что записи могут быть запущены в систему мейнфреймов посредством ночного пакетного процесса.

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

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

Затем выполняется процесс аудита, чтобы убедиться, что все, что изначально было введено персоналом, можно отчитаться где-то. Эта проверка принимает форму запроса SQL мы бежим, и это выглядит примерно так:

SELECT * 
FROM [StaffEntry] s with (nolock) 
LEFT JOIN [MainFrame] m with (nolock) 
    ON m.ItemNumber = s.ItemNumber 
     AND m.Customer=s.Customer 
     AND m.CustomerPO = s.CustomerPO -- purchase order 
     AND m.CustPORev = s.CustPORev -- PO revision number 
LEFT JOIN [Rejected] r with (nolock) ON r.OrderID = s.OrderID 
WHERE s.EntryDate BETWEEN @StartDate AND @EndDate 
    AND r.OrderID IS NULL AND m.MainFrameOrderID IS NULL 

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

Я уверен, что проблема заключается в ПРИСОЕДИНЕНИИ от таблицы StaffEntry до таблицы MainFrame. Так как данные сохраняются для каждого порядка с начала времени (2003 год в этой системе), они, как правило, немного большие. Значения OrderID и EntryDate, используемые в таблице StaffEntry, не сохраняются при импорте в мэйнфрейм, поэтому это соединение немного сложнее. И, наконец, поскольку я ищу записи в таблице MainFrame, которых не существует, после выполнения JOIN у нас есть этот уродливый IS NULL в предложении where.

Таблица StaffEntry индексируется EntryDate (сгруппировано) и отдельно по адресу Customer/PO/rev. MainFrame индексируется клиентом и номером зарядки мейнфрейма (сгруппированы, это необходимо для других систем) и отдельно заказчиком/PO/Rev. Rejected не индексируется вообще, но он небольшой, и тестирование показывает, что это не проблема.

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

+0

Какие показатели у вас на столах? – 2008-11-07 16:46:43

+0

Не могли бы вы предоставить нам план выполнения запроса? – kasperjj 2008-11-07 16:49:42

+0

Индексы теперь включены, но план выполнения не будет работать, потому что он не будет хорошо соответствовать запутанной версии запроса, который я опубликовал. – 2008-11-07 17:22:23

ответ

1

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

1

Это не имеет смысла:

SELECT * 
FROM [StaffEntry] s 
LEFT JOIN [MainFrame] m ON m.ItemNumber = s.ItemNumber 
    AND m.Customer=s.Customer 
    AND m.CustomerPO = s.CustomerPO -- purchase order 
    AND m.CustPORev = s.CustPORev -- PO revision number 
LEFT JOIN [Rejected] r ON r.OrderID = s.OrderID 
WHERE s.EntryDate BETWEEN @StartDate AND @EndDate 
    AND r.OrderID IS NULL AND s.OrderID IS NULL 

если s.OrderID IS NULL, то r.OrderID = s.OrderID никогда не будет правдой, так что никогда не будет включен ни одной строки из [Rejected], таким образом, как указано, это эквивалентно:

SELECT * 
FROM [StaffEntry] s 
LEFT JOIN [MainFrame] m ON m.ItemNumber = s.ItemNumber 
    AND m.Customer=s.Customer 
    AND m.CustomerPO = s.CustomerPO -- purchase order 
    AND m.CustPORev = s.CustPORev -- PO revision number 
WHERE s.EntryDate BETWEEN @StartDate AND @EndDate 
    AND s.OrderID IS NULL 

Вы уверены, что код, который вы опубликовали, является правильным?

+0

Ах. Похоже, вы избили меня до 27 секунд. lol – 2008-11-07 16:51:12

+0

D'Oh! Ты прав. Я починил это. – 2008-11-07 16:54:53

5

Прежде всего, вы можете избавиться от второй LEFT JOIN.

Ваш WHERE удалял какие-либо совпадения во всяком случае ... Например, если S.OrderID был равен 1, и там был R.OrderID со значением 1, применение NULL IS в WHERE не позволило бы его , Поэтому он будет возвращать только те записи, где s.OrderID IS NULL, если я правильно его читаю ...

Во-вторых, если вы имеете дело с большим количеством данных, добавление в подсказке таблицы NOLOCK обычно выигрывает «Больно. Предполагая, что вы не возражаете против возможности прочтения здесь или там: -P Обычно стоит того, чтобы рискнуть.

SELECT * 
FROM [StaffEntry] s (nolock) 
LEFT JOIN [MainFrame] m (nolock) ON m.ItemNumber = s.ItemNumber 
    AND m.Customer=s.Customer 
    AND m.CustomerPO = s.CustomerPO -- purchase order 
    AND m.CustPORev = s.CustPORev -- PO revision number 
WHERE s.EntryDate BETWEEN @StartDate AND @EndDate 
    AND s.OrderID IS NULL 

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

«, так что я ищу записей в таблице MainFrame, что дон» t существует, после выполнения JOIN мы имеем , что уродливый IS NULL в пункте . "

Хорошо ... Но пытаетесь ли вы ограничить его тем, где эти записи в таблице MainFrame не существуют? Если это так, вы хотите, чтобы это выражалось и в ГДЕ, верно? Так что-то вроде этого ...

SELECT * 
FROM [StaffEntry] s (nolock) 
LEFT JOIN [MainFrame] m (nolock) ON m.ItemNumber = s.ItemNumber 
    AND m.Customer=s.Customer 
    AND m.CustomerPO = s.CustomerPO -- purchase order 
    AND m.CustPORev = s.CustPORev -- PO revision number 
WHERE s.EntryDate BETWEEN @StartDate AND @EndDate 
    AND s.OrderID IS NULL AND m.ItemNumber IS NULL 

Если это то, что вы собирались с первоначальным заявлением, возможно, вы можете избавиться от s.OrderID IS NULL проверка?

+0

+1 для подсказки nolock на SQL Server 2000 – 2008-11-07 16:53:50

1

В дополнение к тому, что предложил Kasperjj (который, как я согласен, должен быть первым), вы можете использовать временные таблицы для ограничения объема данных. Теперь, я знаю, я знаю, что все говорят, чтобы держаться подальше от временных таблиц. И i Обычно делать, но иногда, стоит попробовать, потому что вы можете уменьшить количество данных, чтобы присоединиться к этому методу; это ускоряет общий запрос. (конечно, это зависит от того, насколько сильно вы можете сжать результирующие наборы.)

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

Удачи!

С уважением, Frank

PS: Я забыл упомянуть, что если вы хотите попробовать этот метод температура таблицы, вы должны были бы экспериментировать с различными индексами и первичными ключами на временных таблицах. В зависимости от объема данных могут помочь индексы и ПК.

1

Индексирование на всех таблицах будет иметь важное значение. Если вы не можете много сделать с индексированием в столбцах [MainFrame], используемых в соединении, вы также можете предварительно ограничить строки, которые будут искать в [MainFrame] (и [Отклонено]), хотя это уже похоже на то, что оно имеет PK), указав диапазон дат - если окно даты должно быть примерно одинаковым. Это может сократить правую часть этого соединения.

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

Вы можете использовать m.EntryDate в течение нескольких дней или месяцев вашего диапазона. Но если у вас уже есть индексы на Mainframe, возникает вопрос, почему они не используются, или если они используются, почему производительность настолько медленная.

0

Update:
В случае это не было уже очевидно, что я сделал ошибку в коде для первоначального вопроса. Это теперь исправлено, но, к сожалению, это означает, что некоторые из лучших ответов здесь фактически идут совершенно неверно.

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

. План выполнения из первоначального прогона показал 78% затрат на сканирование с кластерным индексом в таблице StaffEntry и 11% стоимости на поиск индекса для таблицы MainFrame, а затем 0% стоимости самого соединения. Запуск с использованием узкого диапазона дат, который изменяется на 1% для индекса, равного StaffEntry, 1% для поиска индекса «MainFrame» и 93% для сканирования таблицы Rejected. Это «фактические» планы, не оцениваемые.

1

попробуйте изменить LEFT JOIN [Отклонено] г с (NOLOCK) ON r.OrderID = s.OrderID в RIGHT JOIN MERGE:

SELECT ... 
FROM [Rejected] r 
    RIGHT MERGE JOIN [StaffEntry] s with (nolock) ON r.OrderID = s.OrderID 
    LEFT JOIN [MainFrame] m with (nolock) ON....