O plano de consultas incorretas do SQL Server 2016 bloqueia o banco de dados uma vez por semana

16

Uma vez por semana, nas últimas 5 semanas, na mesma hora do dia (de manhã cedo, pode se basear na atividade do usuário quando as pessoas começam a usá-lo), o SQL Server 2016 (AWS RDS, espelhado) começa a atingir o tempo limite. consultas.

UPDATE STATISTICS em todas as tabelas sempre o corrige imediatamente.

Após a primeira vez, atualizei todas as estatísticas em todas as tabelas todas as noites (em vez de semanalmente), mas ainda aconteceu (cerca de 8 horas após a execução das estatísticas de atualização, mas não todos os dias em que é executada).

Nesta última vez, ativei o Query Store para ver se consegui encontrar qual consulta / plano de consulta específico era. Eu acho que fui capaz de reduzi-lo a um:

Plano de consulta incorreto

Depois de encontrar essa consulta, adicionei um índice recomendado que estava faltando nessa consulta não usada com frequência (mas que contém muitas tabelas usadas com frequência).

O plano de consulta incorreto estava executando uma Análise de Índice (em uma tabela com apenas 10 mil linhas). Outros planos de consulta retornados em milissegundos, costumavam fazer a mesma varredura. O plano de consulta mais recente, depois de criar o novo índice, apenas procura. Mas mesmo sem esse índice, 99% do tempo, ele retornava em alguns milissegundos, mas, semanalmente, levava mais de 40 segundos.

Isso começou a acontecer após a mudança para o SQL Server 2016 a partir de 2012.

DBCC CHECKDB não retorna erros.

  1. O novo índice corrigirá o problema, fazendo com que nunca mais escolha o plano ruim?
  2. Devo "forçar" o plano que funciona bem agora?
  3. Como garantir que isso não aconteça com outra consulta / plano?
  4. Este é um sintoma de um problema maior?

Índices que acabei de adicionar:

CREATE NONCLUSTERED INDEX idx_AppointmetnAttendee_AttendeeType
ON [dbo].[AppointmentAttendee] ([UserID],[AttendeeType])

CREATE NONCLUSTERED INDEX [idx_appointment_start] ON [dbo].[Appointment]
(
    [ProjectID] ASC,
    [Start] ASC
)
INCLUDE (   [ID],
    [AllDay],
    [End],
    [Location],
    [Notes],
    [Title],
    [CreatedByID]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

Texto completo da consulta:

https://pastebin.com/Z5szPBfu (gerado pelo LINQ, posso / devo otimizar as colunas selecionadas, mas deve ser irrelevante para esse problema)

Nome profissional do som
fonte
Acabei de notar que a verificação nos planos anteriores que não atingiram o tempo limite estava em uma tabela diferente, do mesmo tamanho. Compromisso: 11931 linhas, AppointmentAttendee: 11937 linhas.
Professional Sounding Name

Respostas:

16

Vou responder suas perguntas em uma ordem diferente da que você pediu.

4. Isso é um sintoma de um problema maior?

O novo estimador de cardinalidade no SQL Server 2016 pode estar contribuindo para o problema. O SQL Server 2012 usa o CE herdado e você não teve seu problema nessa versão. O novo estimador de cardinalidade faz suposições diferentes sobre seus dados e pode gerar planos de consulta diferentes para o mesmo SQL. Você pode ter um desempenho melhor em algumas consultas com o CE herdado, dependendo da sua consulta e dos seus dados. Portanto, algumas partes do seu modelo de dados podem não ser a melhor correspondência para o novo CE. Tudo bem, mas você pode precisar contornar o novo CE por enquanto.

Também me preocuparia com o desempenho inconsistente da consulta, mesmo com atualizações diárias de estatísticas. Uma coisa importante a ser observada é que a coleta de estatísticas em todas as tabelas efetivamente eliminará todos os planos de consulta do cache, para que você possa ter um problema com as estatísticas ou com o sniffing de parâmetros. É difícil fazer uma determinação sem muitas informações sobre seu modelo de dados, taxa de alteração de dados, políticas de atualização de estatísticas, como você está chamando seu código etc. O SQL Server 2016 oferece algumas configurações no nível do banco de dados para detecção de parâmetros, o que pode ser útil , mas isso pode afetar todo o aplicativo, em vez de apenas uma consulta problemática.

Vou jogar fora um cenário de exemplo que pode levar a esse comportamento. Você disse:

Alguns usuários podem ter 1 registro de permissão, outros até 20k.

Suponha que você colete estatísticas em todas as tabelas, eliminando todos os planos de consulta. Dependendo dos fatores mencionados acima, se a primeira consulta do dia for contra um usuário com apenas 1 registro de permissão, o SQL Server poderá armazenar em cache um plano que funcione bem para usuários com 1 registro, mas funcione muito bem com usuários com 20k registros. Se a primeira consulta do dia for contra um usuário com 20k registros, você poderá obter um bom plano para 20k registros. Quando o código é executado em um usuário com 1 registro, pode não ser a consulta mais ideal, mas ainda pode terminar em ms. Realmente soa como cheirar parâmetros. Explica por que você nem sempre vê o problema ou por que às vezes leva horas para aparecer.

1. O novo índice solucionará o problema, fazendo com que ele nunca escolha novamente o plano ruim?

Eu acho que um dos índices que você adicionou evitará o problema, porque acessar os dados necessários por meio do índice será mais barato do que fazer uma verificação de índice em cluster na tabela, especialmente quando a verificação não puder terminar mais cedo. Vamos ampliar a parte ruim do plano de consulta:

plano de consulta incorreto

O SQL Server estima que apenas uma linha será retornada da associação em [Permission]e [Project]. Para cada linha na entrada externa, ele fará uma varredura de índice em cluster [Appointment]. Todas as linhas serão varridas nesta tabela, mas somente as que correspondem à filtragem [Start]serão retornadas ao operador de junção. Dentro do operador de junção, os resultados são reduzidos ainda mais.

O plano de consulta descrito acima pode ser bom se realmente houver apenas uma linha enviada para a entrada externa da associação. No entanto, se a estimativa de cardinalidade da associação estiver incorreta e obtivermos, digamos, 1000 linhas, o SQL Server fará 1000 verificações de índice em cluster [Appointment]. O desempenho do plano de consulta é muito sensível a problemas de estimativa.

A maneira mais direta de nunca obter esse plano de consulta novamente seria criar um índice de cobertura na [Appointment]tabela. Algo como um índice [ProjectId]e [Start]deve fazê-lo. Parece que esse é exatamente o [idx_appointment_start]índice que você criou para solucionar o problema. Outra maneira de desencorajar o servidor SQL de escolher o plano de consulta é corrigir a estimativa de cardinalidade da associação em [Permission]e [Project]. As formas típicas de fazer isso incluem alterar o código, atualizar estatísticas, usar o CE herdado, criar estatísticas de várias colunas, fornecer ao SQL Server mais informações sobre variáveis ​​locais, como uma RECOMPILEdica, ou materializar essas linhas em uma tabela temporária. Muitas dessas técnicas não são uma boa abordagem quando você precisa de tempo de resposta no nível de ms ou precisa escrever código por meio de um ORM.

O índice que você criou [AppointmentAttendee]não é uma maneira direta de solucionar o problema. No entanto, você obterá estatísticas de várias colunas no índice e essas estatísticas podem desencorajar o plano de consulta incorreto. O índice pode fornecer uma maneira mais eficiente de acessar os dados, o que também pode desencorajar o plano de consulta incorreto, mas não acho que haja qualquer tipo de garantia de que isso não ocorra novamente apenas com o índice ativado [AppointmentAttendee].

3. Como garantir que isso não aconteça com outra consulta / plano?

Entendo por que você está fazendo essa pergunta, mas é extremamente ampla. Meu único conselho é tentar entender melhor a causa raiz da instabilidade do plano de consulta, validar se você criou os índices corretos para sua carga de trabalho e testar e monitorar cuidadosamente sua carga de trabalho. A Microsoft tem alguns conselhos gerais sobre como lidar com as regressões do plano de consulta causadas pelo novo CE no SQL Server 2016:

O fluxo de trabalho recomendado para atualizar o processador de consultas para a versão mais recente do código é:

  1. Atualize um banco de dados para o SQL Server 2016 sem alterar o nível de compatibilidade do banco de dados (mantenha-o no nível anterior)

  2. Habilite o armazenamento de consulta no banco de dados. Para obter mais informações sobre como ativar e usar o repositório de consultas, consulte Monitorando o desempenho usando o repositório de consultas.

  3. Aguarde tempo suficiente para coletar dados representativos da carga de trabalho.

  4. Altere o nível de compatibilidade do banco de dados para 130

  5. Usando o SQL Server Management Studio, avalie se há regressões de desempenho em consultas específicas após a alteração do nível de compatibilidade

  6. Nos casos em que há regressões, force o plano anterior no armazenamento de consultas.

  7. Se houver planos de consulta que não forçam ou se o desempenho ainda for insuficiente, considere reverter o nível de compatibilidade para a configuração anterior e, em seguida, contratar o Suporte ao Cliente Microsoft.

Não estou dizendo que você precisa fazer o downgrade para o SQL Server 2012 e recomeçar, mas a técnica geral descrita pode ser útil para você.

2. Devo "forçar" o plano que funciona bem agora?

Depende inteiramente de você. Se você acredita que possui um plano de consulta que funciona bem para todos os parâmetros de entrada possíveis, se sente confortável com a funcionalidade do repositório de consultas e deseja a tranqüilidade de forçar um plano de consulta, então siga em frente. Forçar planos de consulta que tiveram regressões faz parte da política de atualização recomendada pela Microsoft para o SQL Server 2016, afinal.

Joe Obbish
fonte