Как оптимизировать запрос

9

У меня есть структура базы данных, похожая на эту,

CREATE TABLE [dbo].[Dispatch](
    [DispatchId] [int] NOT NULL,
    [ContractId] [int] NOT NULL,
    [DispatchDescription] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_Dispatch] PRIMARY KEY CLUSTERED 
(
    [DispatchId] ASC,
    [ContractId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

CREATE TABLE [dbo].[DispatchLink](
    [ContractLink1] [int] NOT NULL,
    [DispatchLink1] [int] NOT NULL,
    [ContractLink2] [int] NOT NULL,
    [DispatchLink2] [int] NOT NULL
) ON [PRIMARY]

GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (1, 1, N'Test')
GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (2, 1, N'Test')
GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (3, 1, N'Test')
GO
INSERT [dbo].[Dispatch] ([DispatchId], [ContractId], [DispatchDescription]) VALUES (4, 1, N'Test')
GO
INSERT [dbo].[DispatchLink] ([ContractLink1], [DispatchLink1], [ContractLink2], [DispatchLink2]) VALUES (1, 1, 1, 2)
GO
INSERT [dbo].[DispatchLink] ([ContractLink1], [DispatchLink1], [ContractLink2], [DispatchLink2]) VALUES (1, 1, 1, 3)
GO
INSERT [dbo].[DispatchLink] ([ContractLink1], [DispatchLink1], [ContractLink2], [DispatchLink2]) VALUES (1, 3, 1, 2)
GO

Задача таблицы DispatchLink - связать две записи Dispatch вместе. Между прочим, я использую составной первичный ключ в своей таблице диспетчеризации из-за устаревшего, поэтому я не могу изменить это без большой боли. Также таблица ссылок не может быть правильным способом сделать это? Но опять наследие.

Так что мой вопрос, если я выполню этот запрос

select * from Dispatch d
inner join DispatchLink dl on d.DispatchId = dl.DispatchLink1 and d.ContractId = dl.ContractLink1
or d.DispatchId = dl.DispatchLink2 and d.ContractId = dl.ContractLink2

Я никогда не смогу выполнить поиск индекса по таблице DispatchLink. Всегда выполняется полное сканирование индекса. Это хорошо с несколькими записями, но когда у вас есть 50000 в этой таблице, она сканирует 50000 записей в индексе в соответствии с планом запроса. Это потому, что в предложении объединения есть «ands» и «ors», но я не могу понять, почему SQL не может вместо этого выполнить пару поисков индекса, один для левой части «or», и один для правой стороны «или».

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

ОБНОВЛЕНИЕ: Например, это типы индексов, которые я добавил,

CREATE NONCLUSTERED INDEX IDX1 ON DispatchLink (ContractLink1, DispatchLink1)
CREATE NONCLUSTERED INDEX IDX2 ON DispatchLink (ContractLink2, DispatchLink2)
CREATE NONCLUSTERED INDEX IDX3 ON DispatchLink (ContractLink1, DispatchLink1, ContractLink2, DispatchLink2)

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

Питер
источник
У вас есть индекс на DispatchLinkстоле?
ypercubeᵀᴹ
Я добавил индексы, которые я пробовал выше.
Питер
В вашем запросе: "select * from Dispatch d внутреннее соединение DispatchLink dl для d.DispatchId = dl.DispatchLink1 и d.ContractId = dl.ContractLink1 или d.DispatchId = dl.DispatchLink2 и d.ContractId = dl.ContractLink2" попытаться удалить условие «ИЛИ» и замените его UNION из 2 операторов SELECT, каждый из которых не использует «ИЛИ», также используйте только ключевые столбцы в обоих SELECT вместо «*», просто чтобы сделать тест максимально чистым.
NoChance
Спасибо SQL Kiwi, это то, что я ранее пробовал, но, к сожалению, это не сработало.
питер
1
Можно ли выполнить репликацию более простым запросом: выберите * из Dispatch d Внутреннее объединение DispatchLink dl для d.DispatchId = dl.DispatchLink1 и d.ContractId = dl.ContractLink1 Если да, мы можем дублировать данные в DispatchLink, чтобы результаты оставались действительными ...
АК

Ответы:

12

Оптимизатор может рассмотреть много вариантов плана (в том числе с несколькими поисками), но для дизъюнкций ( ORпредикатов) он не учитывает планы, включающие пересечения индексов по умолчанию. Учитывая индексы:

CREATE CLUSTERED INDEX cx 
ON dbo.DispatchLink (DispatchLink1, ContractLink1);

CREATE NONCLUSTERED INDEX nc1 
ON dbo.DispatchLink (DispatchLink2, ContractLink2);

Мы можем принудительно выполнить поиск индекса (при условии, что SQL Server 2008 или более поздняя версия):

SELECT * 
FROM dbo.Dispatch AS d
INNER JOIN dbo.DispatchLink AS dl WITH (FORCESEEK) ON 
    (d.DispatchId = dl.DispatchLink1 AND d.ContractId = dl.ContractLink1)
    OR (d.DispatchId = dl.DispatchLink2 AND d.ContractId = dl.ContractLink2);

План FORCESEEK

Используя ваши данные выборки, план поиска будет стоить 0,0332551 единиц по сравнению с 0,0068057 для плана сканирования:

План сканирования

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

SELECT * 
FROM dbo.Dispatch AS d
CROSS APPLY
(
    SELECT TOP (1) * FROM
    (
        SELECT * FROM dbo.DispatchLink AS dl
        WHERE dl.DispatchLink1 = d.DispatchId
        AND dl.ContractLink1 = d.ContractId
        UNION ALL
        SELECT * FROM dbo.DispatchLink AS dl
        WHERE dl.DispatchLink2 = d.DispatchId
        AND dl.ContractLink2 = d.ContractId
    ) SQ1
) AS F1;

Этот план выполнения не ищет второй индекс, если находит совпадение по первому:

ПРИМЕНИТЬ ТОП План

Это может работать немного лучше, чем FORCESEEKплан по умолчанию .

Без добавления каких-либо новых индексов мы также можем форсировать поиск в таблице Dispatch:

SELECT * 
FROM dbo.DispatchLink AS dl
JOIN dbo.Dispatch AS d WITH (FORCESEEK) ON
    (d.DispatchId = dl.DispatchLink1 AND d.ContractId = dl.ContractLink1)
    OR (d.DispatchId = dl.DispatchLink2 AND d.ContractId = dl.ContractLink2);

Искать 2

Это может быть лучше или хуже, чем в первом примере, в зависимости от того, сколько строк в каждой из таблиц. APPLY + TOPУлучшение все еще возможно:

SELECT * 
FROM dbo.DispatchLink AS dl
CROSS APPLY
(
    SELECT TOP (1) * FROM
    (
        SELECT * FROM dbo.Dispatch AS d
        WHERE dl.DispatchLink1 = d.DispatchId
        AND dl.ContractLink1 = d.ContractId
        UNION ALL
        SELECT * FROM dbo.Dispatch AS d
        WHERE dl.DispatchLink2 = d.DispatchId
        AND dl.ContractLink2 = d.ContractId
    ) SQ1
) AS F1;
Пол Уайт 9
источник
Это очень полезный ответ. Я задал еще один вопрос: dba.stackexchange.com/questions/23773/analysing-a-query-plan, который показывает реальный план запроса на реальных данных (не на моих тестовых данных). У меня нет знаний, чтобы точно понять, что является узким местом в плане запросов. Возможно, вы можете взглянуть?
Питер
Это действительно интересно, потому что добавление FORCESEEK заставляет мой запрос выполняться за 9 секунд, а не за 10 минут. Обновление статистики не имеет значения. С какой стати анализатор запросов так ошибается?
Питер
Я думаю, что вы правы в отношении дизайна. Что вы имеете в виду, повторяя столбцы? Как бы вы разработали структуру таблицы, которая должна была бы связать две записи Dispatch как связанные? Чтобы уточнить, хотя у «реальной» таблицы есть собственное поле первичного ключа, но да, наличие составного ключа в Dispatch не очень помогает.
Питер
SQL киви. Повторяющиеся столбцы. Понял, спасибо.
Питер