Estou executando a seguinte consulta no MS SQL Server 2014 (e também no 2014SP3)
SET NOCOUNT ON
CREATE TABLE #GUIDs(
PartyNames_GUID UNIQUEIDENTIFIER NULL,
Party_GUID UNIQUEIDENTIFIER NULL,
FirstName NVARCHAR(255) NULL
)
insert #GUIDs(Party_GUID)
select top 1 Party_GUID
FROM Party a
join PartyNames b on a.Party_ID = b.Party_ID
--Give the optimizer all kinds of choices.
create index i1 on #GUIDs(PartyNames_GUID)
create index i2 on #GUIDs(Party_GUID)
create index i3 on #GUIDs(Party_GUID, PartyNames_GUID)
create index i4 on #GUIDs(PartyNames_GUID,Party_GUID)
update statistics #GUIDs
SELECT PartyNames.PartyNames_ID, PartyNames.LastName, PartyNames.FirstName
FROM Party INNER JOIN PartyNames ON PartyNames.Party_ID = Party.Party_ID
INNER JOIN #GUIDs ON Party.Party_GUID = #GUIDs.Party_GUID --Hard Match on Party_GUID
AND
(#GUIDs.PartyNames_GUID IS NULL OR PartyNames.PartyNames_GUID = #GUIDs.PartyNames_GUID ) --Optional Match
AND
(#GUIDs.FirstName IS NULL OR PartyNames.FirstName = #GUIDs.FirstName ) --Optional Match
drop table #GUIDs
As tabelas Party e PartyNames têm um índice clusterizado em seus _IDs primários, um índice exclusivo não clusterizado em seus respectivos GUIDs e PartyNames tem um índice na chave estrangeira de Party. O DDL para essas tabelas está incluído no final para não confundir a descrição do problema. Party tem cerca de 1,9 milhões de linhas e PartyNames 1,3 milhões. PartyNames pode ter no máximo 3 registros por Party. A consulta acima leva algumas centenas de milissegundos para ser executada. Mas a mesma consulta é executada em 15 ms ou menos no SQL Server 2012, 2016 e 2019. O esquema em todas as versões é o mesmo, exatamente os mesmos dados são BCP e as estatísticas são atualizadas depois que o BCP é feito e antes de executar a consulta. Aqui está a aparência do plano de execução em 2014 E aqui está o plano de execução nas outras versões do SQL Server, 2012, 2016, 2019
Por que 2014 gera um plano tão ruim, que verifica a chave primária do PartyNames em vez de procurar
[PartyNames].[ix_PartyNames_Party_ID? OU melhor, como as versões superiores e inferiores a 2014 conseguem chegar a um bom plano enquanto 2014 não? O plano gerado pelas outras versões é obviamente melhor, pois consome menos CPU e faz menos IOs. É alguma configuração do servidor em 2014 que está causando isso? O custo do otimizador do SQL Server 2014 é muito diferente das versões anteriores ou posteriores? Qualquer ajuda ou ponteiros são apreciados. Eu odiaria pensar que isso é uma falha no SQL Server 2014. E o interessante é que mesmo que o número de registros seja reduzido para alguns milhares, o SQL Server 2014 continua gerando o mesmo plano ruim. Mas remover a última cláusula AND ,(#GUIDs.FirstName IS NULL OR PartyNames.FirstName = #GUIDs.FirstName ) faz com que o SQL Server 2014 gere o mesmo bom plano que as outras versões. Esta é, obviamente, uma reprodução artificial. Os clientes que executam nosso produto no MS SQL Server 2014 reclamaram de desempenho ruim, enquanto os de outras versões não. A solução de problemas leva a essa reprodução simples.
E aqui está o DDL para a geração do esquema
CREATE TABLE [dbo].[Party](
[Party_ID] [int] IDENTITY(1,1) NOT NULL,
[Party_GUID] [uniqueidentifier] ROWGUIDCOL NOT NULL,
CONSTRAINT [pk_Party_Party_ID] PRIMARY KEY CLUSTERED ( [Party_ID] ASC),
CONSTRAINT [uq_Party_Party_GUID] UNIQUE NONCLUSTERED ( [Party_GUID] ASC)
)
GO
ALTER TABLE [dbo].[Party] ADD CONSTRAINT [df_Party_Party_GUID] DEFAULT (newid()) FOR [Party_GUID]
GO
CREATE TABLE [dbo].[PartyNames](
[PartyNames_ID] [int] IDENTITY(1,1) NOT NULL,
[PartyNames_GUID] [uniqueidentifier] ROWGUIDCOL NOT NULL,
[Party_ID] [int] NOT NULL,
[FirstName] [nvarchar](255) NULL,
[MiddleName] [nvarchar](255) NULL,
[LastName] [nvarchar](255) NULL,
CONSTRAINT [pk_PartyNames_PartyNames_ID] PRIMARY KEY CLUSTERED ([PartyNames_ID] ASC),
CONSTRAINT [uq_PartyNames_PartyNames_GUID] UNIQUE NONCLUSTERED ([PartyNames_GUID] ASC)
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[PartyNames] ADD CONSTRAINT [df_PartyNames_PartyNames_GUID] DEFAULT (newid()) FOR [PartyNames_GUID]
GO
ALTER TABLE [dbo].[PartyNames] WITH NOCHECK ADD CONSTRAINT [fk_PartyNames_Party_ID] FOREIGN KEY([Party_ID])
REFERENCES [dbo].[Party] ([Party_ID])
GO
ALTER TABLE [dbo].[PartyNames] CHECK CONSTRAINT [fk_PartyNames_Party_ID]
GO
CREATE NONCLUSTERED INDEX [ix_PartyNames_Party_ID] ON [dbo].[PartyNames]
(
[Party_ID] ASC
)
GO
Você precisa habilitar o sinalizador de rastreamento documentado e com suporte 4199 (que permite correções do otimizador de consulta) para corrigir esse problema.
Você pode habilitá-lo no nível de consulta para testá-lo, mas isso requer permissões de sysadmin (que esperamos que seu aplicativo não tenha). Um argumento bastante convincente pode ser feito para ativar isso globalmente (como um sinalizador de rastreamento de inicialização), você pode ler algumas discussões sobre isso aqui: Sinalizador de rastreamento 4199 - Ativar globalmente?
Consegui reproduzir seu problema usando os seguintes dados de teste:
Obter um plano estimado para a consulta de teste produz o plano inválido:
O otimizador escolhe varrer o índice clusterizado
dbo.PartyNames
porque, por algum motivo, ele acha que cada linhadbo.PartyNames
será uma correspondência para a linha recuperada dedbo.Party
.Adicionar
OPTION (QUERYTRACEON 4199)
ao final da consulta corrige esse raciocínio defeituoso e produz o bom plano:Para obter mais informações sobre TF 4199, consulte este KB: SQL Server otimizador de consulta hotfix trace flag 4199 service model
Uma solução alternativa é usar o estimador de cardinalidade legado com sinalizador de rastreamento 9481, que também produz o plano bom.