Cómo optimizar la consulta

9

Tengo una estructura de base de datos similar a esta,

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

El objetivo de la tabla DispatchLink es vincular dos registros de envío juntos. Por cierto, estoy usando una clave primaria compuesta en mi tabla de despacho debido al legado, por lo que no puedo cambiar eso sin mucho dolor. Además, la tabla de enlaces puede no ser la forma correcta de hacerlo Pero de nuevo legado.

Entonces mi pregunta, si ejecuto esta consulta

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

Nunca puedo conseguir que haga una búsqueda de índice en la tabla DispatchLink. Siempre hace un escaneo de índice completo. Eso está bien con algunos registros, pero cuando tiene 50000 en esa tabla, escanea 50000 registros en el índice de acuerdo con el plan de consulta. Es porque hay 'ands' y 'ors' en la cláusula de unión, pero no puedo entender por qué SQL no puede hacer un par de búsquedas de índice, una para el lado izquierdo de 'o', y uno para el lado derecho de 'o'.

Me gustaría una explicación para esto, no una sugerencia para hacer que la consulta sea más rápida a menos que se pueda hacer sin ajustar la consulta. La razón es que estoy usando la consulta anterior como un filtro de combinación de replicación de combinación, por lo que desafortunadamente no puedo agregar otro tipo de consulta.

ACTUALIZACIÓN: Por ejemplo, estos son los tipos de índices que he estado agregando,

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)

Por lo tanto, utiliza los índices, pero realiza un escaneo del índice en todo el índice, por lo que 50000 registros escanea 50000 registros en el índice.

Peter
fuente
¿Tienes algún índice sobre la DispatchLinkmesa?
ypercubeᵀᴹ
He agregado los índices que he probado anteriormente.
Peter
En su consulta: "seleccione * de Dispatch d inner join DispatchLink dl en d.DispatchId = dl.DispatchLink1 y d.ContractId = dl.ContractLink1 o d.DispatchId = dl.DispatchLink2 y d.ContractId = dl.ContractLink2" intente eliminar la condición "OR" y reemplácela por UNIÓN de 2 instrucciones SELECT, cada una sin "OR", también use las únicas columnas clave en ambos SELECT en lugar de "*", solo para hacer que la prueba sea lo más pura posible.
NoPuerto
Gracias SQL Kiwi, esto es algo que intenté anteriormente pero desafortunadamente no funcionó.
Peter
1
¿Puede hacer que la replicación emita una consulta más simple: seleccione * desde Dispatch d inner join DispatchLink dl en d.DispatchId = dl.DispatchLink1 y d.ContractId = dl.ContractLink1 En caso afirmativo, podemos duplicar datos en DispatchLink para que los resultados sigan siendo válidos ...
AK

Respuestas:

12

El optimizador puede considerar muchas alternativas de plan (incluidas las que tienen múltiples búsquedas), pero para disyunciones ( ORpredicados) no considera planes que impliquen intersecciones de índice de forma predeterminada. Dados los índices:

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

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

Podemos forzar búsquedas de índice (suponiendo SQL Server 2008 o posterior):

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);

Plan FORCESEEK

Usando sus datos de muestra, el plan de búsqueda cuesta a 0.0332551 unidades en comparación con 0.0068057 para el plan de escaneo:

Plan de escaneo

Hay todo tipo de posibles reescrituras de consultas y sugerencias que podemos probar. Un ejemplo de una reescritura para promover una opción que el optimizador no considera para el plan original es:

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;

Este plan de ejecución no busca el segundo índice si encuentra una coincidencia en el primero:

APLICAR Plan TOP

Esto puede funcionar muy ligeramente mejor que el FORCESEEKplan predeterminado .

Sin agregar ningún índice nuevo, también podemos forzar una búsqueda en la tabla de Despacho:

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);

Busca 2

Esto puede ser mejor o peor que el primer ejemplo dependiendo de cosas como cuántas filas hay en cada una de las tablas. La APPLY + TOPmejora aún es posible:

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;
Paul White 9
fuente
Esa es una respuesta muy útil. He hecho otra pregunta dba.stackexchange.com/questions/23773/analysing-a-query-plan que muestra el plan de consulta real sobre datos reales (no mis datos de prueba). No tengo el conocimiento para entender exactamente cuál es el cuello de botella en el plan de consulta. ¿Quizás puedas echar un vistazo?
Peter
Es realmente interesante porque agregar 'FORCESEEK' hace que mi consulta se ejecute en 9 segundos en lugar de tomar más de 10 minutos. Actualizar estadísticas no hace ninguna diferencia. ¿Por qué si el analizador de consultas se equivoca tanto?
Peter
Creo que tienes razón con respecto al diseño. ¿Qué quieres decir con repetir columnas? ¿Cómo diseñaría una estructura de tabla que tuviera que vincular dos registros de Despacho juntos como relacionados? Para aclarar, aunque la tabla 'real' tiene su propio campo de clave primaria, pero sí, tener una clave compuesta en Dispatch no ayuda exactamente.
Peter
SQL Kiwi. Repetir columnas. Gracias.
Peter