Як оптимізувати запит


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 не може замість цього здійснити пару пошукових індексів, один для лівого боку "або", і один для правого боку 'або'.

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

ОНОВЛЕННЯ: Наприклад, це типи індексів, які я додаю,

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 Internal join DispatchLink dl на d.DispatchId = dl.DispatchLink1 та d.ContractId = dl.ContractLink1 або d.DispatchId = dl.DispatchLink2 та d.ContractId = dl.ContractId = dl.ContractLd" умова "АБО" та замініть її на 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;

Це дуже корисна відповідь. Я задав ще одне запитання dba.stackexchange.com/questions/23773/analysing-a-query-plan, яке показує реальний план запитів щодо реальних даних (не моїх даних тесту). У мене немає знань, щоб зрозуміти, що саме являє собою вузьке місце в плані запитів. Можливо, ви можете поглянути?
пітер

Це справді цікаво, оскільки додавання "FORCESEEK" робить мій запит за 9 секунд, а не за 10 хвилин. Оновлення статистики не має значення. Чому б інакше аналізатор запитів ставив це так неправильно?
пітер

Я думаю, ти маєш рацію щодо дизайну. Що ви маєте на увазі про повторення стовпців? Як би ви створили структуру таблиці, яка повинна мати зв'язок двох записів диспетчеризації разом як пов'язані? Для уточнення, хоча у "реальної" таблиці є власне поле первинного ключа, але так, складання ключа в Dispatch точно не допомагає.
пітер

SQL Kiwi. Повторення стовпців. Зрозумів дякую.
пітер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.