Tornar SqlClient padrão para ARITHABORT ON

32

Primeiras coisas primeiro: estou usando o MS SQL Server 2008 com um banco de dados no nível de compatibilidade 80 e conectando-o a .Net System.Data.SqlClient.SqlConnection.

Por motivos de desempenho, criei uma exibição indexada. Como resultado, as atualizações nas tabelas referenciadas na exibição precisam ser feitas ARITHABORT ON. No entanto, o criador de perfil mostra que o SqlClient está se conectando e ARITHABORT OFF, portanto, as atualizações nessas tabelas estão falhando.

Existe uma configuração central para usar o SqlClient ARITHABORT ON? O melhor que pude encontrar é executar manualmente que cada vez que uma conexão é aberta, mas atualizar a base de código existente para fazer isso seria uma tarefa bastante grande, por isso estou ansioso para encontrar uma maneira melhor.

Peter Taylor
fonte

Respostas:

28

Abordagem aparentemente preferida

Fiquei com a impressão de que o seguinte já havia sido testado por outras pessoas, especialmente com base em alguns dos comentários. Mas meus testes mostram que esses dois métodos realmente funcionam no nível do banco de dados, mesmo ao conectar via .NET SqlClient. Estes foram testados e verificados por outros.

Em todo o servidor

É possível definir a configuração do servidor de opções do usuário para o que for atualmente definido em bits ORcom 64 (o valor para ARITHABORT). Se você não usar o OR (em bits |), mas fizer uma atribuição direta ( =), eliminará todas as outras opções existentes já ativadas.

DECLARE @Value INT;

SELECT @Value = CONVERT(INT, [value_in_use]) --[config_value] | 64
FROM   sys.configurations sc
WHERE  sc.[name] = N'user options';

IF ((@Value & 64) <> 64)
BEGIN
  PRINT 'Enabling ARITHABORT...';
  SET @Value = (@Value | 64);

  EXEC sp_configure N'user options', @Value;
  RECONFIGURE;
END;

EXEC sp_configure N'user options'; -- verify current state

No nível do banco de dados

Isso pode ser definido por banco de dados via ALTER DATABASE SET :

USE [master];

IF (EXISTS(
     SELECT *
     FROM   sys.databases db
     WHERE  db.[name] = N'{database_name}'
     AND    db.[is_arithabort_on] = 0
   ))
BEGIN
  PRINT 'Enabling ARITHABORT...';

  ALTER DATABASE [{database_name}] SET ARITHABORT ON WITH NO_WAIT;
END;

Abordagens alternativas

A notícia não tão boa é que eu pesquisei bastante esse tópico, apenas para descobrir que, ao longo dos anos, muitos outros pesquisaram muito sobre esse assunto, e não há como configurar o comportamento de SqlClient. Algumas documentações do MSDN indicam que isso pode ser feito por meio de um ConnectionString, mas não há palavras-chave que permitam alterar essas configurações. Outro documento implica que ele pode ser alterado via Gerenciador de Configuração / Configuração de Rede do Cliente, mas isso também não parece possível. Portanto, e, infelizmente, você precisará executar SET ARITHABORT ON;manualmente. Aqui estão algumas maneiras de considerar:

Se você estiver usando o Entity Framework 6 (ou mais recente), você pode tentar:

  • Use Database.ExecuteSqlCommand : context.Database.ExecuteSqlCommand("SET ARITHABORT ON;");
    Idealmente, isso seria executado uma vez, após abrir a conexão com o banco de dados, e não por cada consulta.

  • Crie um interceptor via:

    Isso permitirá que você modifique o SQL antes de ser executado, caso em que você pode simplesmente prefixo com: SET ARITHABORT ON;. A desvantagem aqui é que será por cada consulta, a menos que você armazene uma variável local para capturar o estado de se ela foi executada ou não e teste para cada vez (o que realmente não é muito trabalho extra, mas usar ExecuteSqlCommandé provavelmente mais fácil).

Qualquer um deles permitirá que você lide com isso em um único local sem alterar nenhum código existente.

ELSE , você pode criar um método de wrapper que faça isso, semelhante a:

public static SqlDataReader ExecuteReaderWithSetting(SqlCommand CommandToExec)
{
  CommandToExec.CommandText = "SET ARITHABORT ON;\n" + CommandToExec.CommandText;

  return CommandToExec.ExecuteReader();
}

e depois basta alterar as _Reader = _Command.ExecuteReader();referências atuais _Reader = ExecuteReaderWithSetting(_Command);.

Isso também permite que a configuração seja manipulada em um único local, exigindo apenas alterações de código mínimas e simplistas que podem ser feitas principalmente via Localizar e substituir.

Melhor ainda ( outra parte 2), como essa é uma configuração de nível de conexão, ela não precisa ser executada a cada chamada SqlCommand.Execute __ (). Portanto, em vez de criar um wrapper para ExecuteReader(), crie um wrapper para Connection.Open():

public static void OpenAndSetArithAbort(SqlConnection MyConnection)
{
  using (SqlCommand _Command = MyConnection.CreateCommand())
  {
    _Command.CommandType = CommandType.Text;
    _Command.CommandText = "SET ARITHABORT ON;";

    MyConnection.Open();

    _Command.ExecuteNonQuery();
  }

  return;
}

E então apenas substitua as _Connection.Open();referências existentes OpenAndSetArithAbort(_Connection);.

Ambas as idéias acima podem ser implementadas em mais estilos OO, criando uma classe que estenda SqlCommand ou SqlConnection.

Ou melhor ainda ( outra parte 3), você pode criar um manipulador de eventos para o Connection StateChange e definir a propriedade quando a conexão mudar de Closedpara da Openseguinte maneira:

protected static void OnStateChange(object sender, StateChangeEventArgs args)
{
    if (args.OriginalState == ConnectionState.Closed
        && args.CurrentState == ConnectionState.Open)
    {
        using (SqlCommand _Command = ((SqlConnection)sender).CreateCommand())
        {
            _Command.CommandType = CommandType.Text;
            _Command.CommandText = "SET ARITHABORT ON;";

            _Command.ExecuteNonQuery();
        }
    }
}

Com isso em vigor, você só precisará adicionar o seguinte a cada local em que criar uma SqlConnectioninstância:

_Connection.StateChange += new StateChangeEventHandler(OnStateChange);

Nenhuma alteração no código existente é necessária. Eu apenas tentei esse método em um aplicativo de console pequeno, testando imprimindo o resultado de SELECT SESSIONPROPERTY('ARITHABORT');. Ele retorna 1, mas se eu desativar o Manipulador de Eventos, ele retornará 0.


Por uma questão de completude, eis algumas coisas que não funcionam (ou não são tão eficazes):

  • Disparadores de logon : Os disparadores, mesmo durante a execução na mesma sessão, e mesmo sendo executados em uma transação explicitamente iniciada, ainda são um subprocesso e, portanto, suas configurações ( SETcomandos, tabelas temporárias locais, etc.) são locais e não sobrevivem. o fim desse subprocesso.
  • Adicionando SET ARITHABORT ON;ao início de cada procedimento armazenado:
    • isso requer muito trabalho para projetos existentes, principalmente porque o número de procedimentos armazenados aumenta
    • isso não ajuda a consultas ad hoc
Solomon Rutzky
fonte
Acabei de testar a criação de um banco de dados simples com ARITHABORT e ANSI_WARNINGS desativados, criei uma tabela com zero e um cliente .net simples para ler a partir dele. O .net SqlClient mostrou como definir ARITHABORT desativado e ANSI_WARNINGS ativado no logon no sql profiler, e também falhou na consulta com uma divisão por zero conforme o esperado. Isso parece mostrar que a solução preferida para definir sinalizadores de nível de banco de dados NÃO funcionará para alterar o padrão para .net SqlClient.
Mike
pode confirmar que a configuração de user_options em todo o servidor funciona no entanto.
Mike
Também estou observando que, com o SELECT DATABASEPROPERTYEX('{database_name}', 'IsArithmeticAbortEnabled');retorno 1, sys.dm_exec_sessions mostra um elemento desativado, embora eu não veja nenhum SET explícito no Profiler. Por que isso seria?
andrew.rockwell 27/08
6

Opção 1

Além da solução da Sankar , a configuração da interrupção aritmética no nível do servidor para todas as conexões funcionará:

EXEC sys.sp_configure N'user options', N'64'
GO
RECONFIGURE WITH OVERRIDE
GO

No SQL 2014, é recomendável estar ativado para todas as conexões:

Você sempre deve definir ARITHABORT como ON em suas sessões de logon. Definir ARITHABORT como OFF pode afetar negativamente a otimização de consultas, causando problemas de desempenho.

Portanto, essa parece ser a solução ideal.

opção 2

Se a opção 1 não for viável e você usar procedimentos armazenados para a maioria das chamadas SQL (o que você deve, consulte Procedimentos armazenados versus SQL embutido ), basta ativar a opção em cada procedimento armazenado relevante:

CREATE PROCEDURE ...
AS 
BEGIN
   SET ARITHABORT ON
   SELECT ...
END
GO

Acredito que a melhor solução real aqui é simplesmente editar seu código, pois está errado e qualquer outra correção é apenas uma solução alternativa.

LowlyDBA
fonte
Não acho que ajude a configurá-lo para o SQL Server, quando a conexão .net for iniciada set ArithAbort off. Eu esperava algo que pudesse ser feito no lado .net / C #. Eu ofereci a recompensa, porque eu tinha visto a recomendação.
Henrik Staun Poulsen /
1
O lado .net / C # é o que a Sankar cobriu, então essas são praticamente as únicas opções.
LowlyDBA
Eu tentei a opção 1 e não teve efeito. Novas sessões ainda mostram como arithabort = 0. Não estou tendo problemas com isso, apenas tentando me antecipar a possíveis problemas.
Mark Freeman
4

Eu não sou um especialista aqui, mas você pode tentar algo como abaixo.

String sConnectionstring;
sConnectionstring = "Initial Catalog=Pubs;Integrated Security=true;Data Source=DCC2516";

SqlConnection Conn = new SqlConnection(sConnectionstring);

SqlCommand blah = new SqlCommand("SET ARITHABORT ON", Conn);
blah.ExecuteNonQuery();


SqlCommand cmd = new SqlCommand();
// Int32 rowsAffected;

cmd.CommandText = "dbo.xmltext_import";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = Conn;
Conn.Open();
//Console.Write ("Connection is open");
//rowsAffected = 
cmd.ExecuteNonQuery();
Conn.Close();

Ref: http://social.msdn.microsoft.com/Forums/en-US/transactsql/thread/d9e3e8ba-4948-4419-bb6b-dd5208bd7547/

Sankar Reddy
fonte
Sim, foi isso que eu quis dizer com a execução manual. O problema é que a base de código com a qual estou trabalhando acumulou bastante dívida técnica quando se trata da camada de acesso ao banco de dados, então terei que refatorar algumas centenas de métodos para fazê-lo dessa maneira.
Peter Taylor
2

Não há nenhuma configuração para forçar o SqlClient a sempre ativar o ARITHABORT, você deve defini-lo conforme a descrição.

Curiosamente da documentação da Microsoft para SET ARITHABORT : -

Você sempre deve definir ARITHABORT como ON em suas sessões de logon. Definir ARITHABORT como OFF pode afetar negativamente a otimização de consultas, causando problemas de desempenho.

E, no entanto, a conexão .Net é codificada para desativá-la por padrão?

Como outro ponto, você deve ter muito cuidado ao diagnosticar problemas de desempenho com essa configuração. Diferentes opções de conjunto resultarão em diferentes planos de consulta para a mesma consulta. Seu código .Net pode ter um problema de desempenho (SET ARITHABORT OFF) e, no entanto, quando você executa a mesma consulta TSQL no SSMS (SET ARITHABORT ON por padrão), pode estar tudo bem. Isso ocorre porque o plano de consulta .Net não será reutilizado e um novo plano será gerado. Isso poderia potencialmente eliminar um problema de detecção de parâmetros, por exemplo, e fornecer um desempenho muito melhor.

Andy Jones
fonte
1
@HenrikStaunPoulsen - A menos que você esteja usando o 2000 (ou o nível de compatibilidade 2000), isso não faz nenhuma diferença. Está implícito ANSI_WARNINGSem versões posteriores e coisas como visualizações indexadas funcionam bem.
Martin Smith
Observe que .Net não é codificado para desativar o ARITHABORT. SSMS padrão para defini-lo em . .Net está apenas conectando e usando os padrões de servidor / banco de dados. Você pode encontrar problemas no MS Connect sobre usuários que reclamam do comportamento padrão do SSMS. Observe o aviso na página de documento ARITHABORT .
Bacon Bits
2

Se isso poupar a alguém algum tempo, no meu caso (Entity Framework Core 2.0.3, API do ASP.Net Core, SQL Server 2008 R2):

  1. Não há interceptadores no EF Core 2.0 (acho que eles estarão disponíveis em breve no 2.1)
  2. Não user_optionsfoi aceitável alterar a configuração global do banco de dados nem a configuração (eles funcionam - eu testei), mas não corria o risco de impactar outros aplicativos.

Uma consulta ad-hoc do EF Core, com SET ARITHABORT ON;na parte superior, NÃO funciona.

Finalmente, a solução que funcionou para mim foi: Combinar um procedimento armazenado, chamado como uma consulta bruta, com a SETopção antes EXECseparada por um ponto e vírgula, assim:

// C# EF Core
int result = _context.Database.ExecuteSqlCommand($@"
SET ARITHABORT ON;
EXEC MyUpdateTableStoredProc
             @Param1 = {value1}
");
Chris Amelinckx
fonte
Interessante. Obrigado por postar essas nuances de trabalho com o EF Core. Apenas curioso: o que você está fazendo aqui é essencialmente a opção de invólucro que mencionei na subseção ELSE da seção Abordagens alternativas na minha resposta? Eu estava pensando, porque você mencionou as outras sugestões na minha resposta ou não está funcionando ou não é viável devido a outras restrições, mas não mencionou a opção de wrapper.
Solomon Rutzky 28/02
@SolomonRutzky é equivalente a essa opção, com a nuance de que se limita à execução de um procedimento armazenado. No meu caso, se eu prefixar uma consulta de atualização bruta com a SET OPTION (manualmente ou através do wrapper), ela não funcionará. Se eu colocar o SET OPTION dentro do procedimento armazenado, ele não funcionará. A única maneira era executar SET OPTION seguido pelo procedimento armazenado EXEC no mesmo lote. Foi assim que optei por personalizar a chamada específica em vez de criar o wrapper. Em breve, atualizaremos para o SQLServer 2016 e eu posso limpar isso. Obrigado por sua resposta, se foi útil para descartar cenários específicos.
22818 Chris Amelinckx
0

Com base na resposta de Solomon Rutzy , para EF6:

using System.Data;
using System.Data.Common;

namespace project.Data.Models
{
    abstract class ProjectDBContextBase: DbContext
    {
        internal ProjectDBContextBase(string nameOrConnectionString) : base(nameOrConnectionString)
        {
            this.Database.Connection.StateChange += new StateChangeEventHandler(OnStateChange);
        }

        protected static void OnStateChange(object sender, StateChangeEventArgs args)
        {
            if (args.OriginalState == ConnectionState.Closed
                && args.CurrentState == ConnectionState.Open)
            {
                using (DbCommand _Command = ((DbConnection)sender).CreateCommand())
                {
                    _Command.CommandType = CommandType.Text;
                    _Command.CommandText = "SET ARITHABORT ON;";
                    _Command.ExecuteNonQuery();
                }
            }
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        ...

Isso usa System.Data.Common's em DbCommandvez de SqlCommande em DbConnectionvez de SqlConnection.

Um rastreamento do SQL Profiler confirma, SET ARITHABORT ONé enviado quando a conexão é aberta, antes que outros comandos sejam executados na transação.

CapNCook
fonte