Imposta SqlClient come predefinito su ARITHABORT ON


32

Per prima cosa: sto usando MS SQL Server 2008 con un database a livello di compatibilità 80 e mi sto collegando con .Net System.Data.SqlClient.SqlConnection.

Per motivi di prestazioni ho creato una vista indicizzata. Di conseguenza, è necessario eseguire gli aggiornamenti delle tabelle a cui fa riferimento la vista ARITHABORT ON. Tuttavia, il profiler mostra che SqlClient si sta connettendo ARITHABORT OFF, quindi gli aggiornamenti a tali tabelle non riescono.

Esiste un'impostazione di configurazione centrale per utilizzare SqlClient ARITHABORT ON? Il migliore che sono riuscito a trovare è quello di eseguire manualmente ogni volta che viene aperta una connessione, ma l'aggiornamento della base di codice esistente per fare questo sarebbe un compito abbastanza grande, quindi sono desideroso di trovare un modo migliore.

Risposte:


28

Approccio apparentemente preferito

Avevo l'impressione che quanto segue fosse già stato testato da altri, soprattutto sulla base di alcuni dei commenti. Ma i miei test dimostrano che questi due metodi funzionano davvero a livello di DB, anche quando ci si collega tramite .NET SqlClient. Questi sono stati testati e verificati da altri.

A livello di server

È possibile impostare l' impostazione di configurazione del server delle opzioni utente su qualsiasi valore attualmente bit-saggio ORcon 64 (il valore per ARITHABORT). Se non si utilizza OR ( |) bit per bit ma si esegue invece un'assegnazione diretta ( =), verranno eliminate tutte le altre opzioni esistenti già abilitate.

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

A livello di database

Questo può essere impostato per database tramite 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;

Approcci alternativi

La non buona notizia è che ho fatto molte ricerche su questo argomento, solo per scoprire che nel corso degli anni molti altri hanno fatto molte ricerche su questo argomento e non c'è modo di configurare il comportamento di SqlClient. Una parte della documentazione MSDN implica che può essere eseguita tramite ConnectionString, ma non esistono parole chiave che consentano di modificare queste impostazioni. Un altro documento implica che può essere modificato tramite la configurazione della rete client / Configuration Manager, ma neanche questo sembra possibile. Quindi, e piuttosto sfortunatamente, dovrai eseguirlo SET ARITHABORT ON;manualmente. Ecco alcuni modi per considerare:

SE stai utilizzando Entity Framework 6 (o più recente), puoi provare:

  • Usa Database.ExecuteSqlCommand : context.Database.ExecuteSqlCommand("SET ARITHABORT ON;");
    idealmente dovrebbe essere eseguito una volta, dopo aver aperto la connessione DB, e non per ogni query.

  • Crea un intercettore tramite:

    Questo vi permetterà di modificare il codice SQL prima che sia eseguito, nel qual caso si può semplicemente premettere ad essa: SET ARITHABORT ON;. Il rovescio della medaglia qui è che sarà per ogni query, a meno che non memorizzi una variabile locale per catturare lo stato del fatto che sia stata eseguita o meno e verificarla ogni volta (che in realtà non è molto lavoro extra, ma usando ExecuteSqlCommandè probabilmente più facile).

Ognuno di questi ti permetterà di gestirlo in un punto senza cambiare alcun codice esistente.

Inoltre , è possibile creare un metodo wrapper che faccia questo, simile a:

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

  return CommandToExec.ExecuteReader();
}

e poi basta cambiare i _Reader = _Command.ExecuteReader();riferimenti attuali per essere _Reader = ExecuteReaderWithSetting(_Command);.

In questo modo è anche possibile gestire le impostazioni in un'unica posizione, richiedendo al contempo modifiche minime e semplicistiche al codice che possono essere eseguite principalmente tramite Trova e sostituisci.

Meglio ancora ( Else Part 2), poiché si tratta di un'impostazione a livello di connessione, non è necessario eseguirla per ogni chiamata SqlCommand.Execute __ (). Quindi, invece di creare un wrapper per ExecuteReader(), crea un wrapper per 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 poi basta sostituire i _Connection.Open();riferimenti esistenti per essere OpenAndSetArithAbort(_Connection);.

Entrambe le idee di cui sopra possono essere implementate in più stile OO creando una classe che estende SqlCommand o SqlConnection.

O ancora meglio ( Else Parte 3), è possibile creare un gestore di eventi per la connessione StateChange e lo hanno impostato la proprietà quando cambia la connessione da Closeda Opencome segue:

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

Con quello in atto, devi solo aggiungere quanto segue in ogni luogo in cui crei SqlConnectionun'istanza:

_Connection.StateChange += new StateChangeEventHandler(OnStateChange);

Non sono necessarie modifiche al codice esistente. Ho appena provato questo metodo in una piccola app per console, testando stampando il risultato di SELECT SESSIONPROPERTY('ARITHABORT');. Ritorna 1, ma se disabilito il gestore eventi, ritorna 0.


Per completezza, ecco alcune cose che non funzionano (né affatto né altrettanto efficacemente):

  • Trigger di accesso : i trigger, anche durante l'esecuzione nella stessa sessione, e anche se eseguiti all'interno di una transazione avviata in modo esplicito, sono ancora un processo secondario e quindi le sue impostazioni ( SETcomandi, tabelle temporanee locali, ecc.) Sono locali e non sopravvivono la fine di quel sottoprocesso.
  • Aggiunta SET ARITHABORT ON;all'inizio di ogni procedura memorizzata:
    • ciò richiede molto lavoro per i progetti esistenti, specialmente all'aumentare del numero di procedure memorizzate
    • questo non aiuta le query ad hoc

Ho appena provato a creare un semplice database con ARITHABORT e ANSI_WARNINGS entrambi disattivati, ho creato una tabella con zero in esso e un semplice client .net per leggere da esso. .Net SqlClient ha mostrato di aver disattivato ARITHABORT e ANSI_WARNINGS nel login in sql profiler e ha anche fallito la query con una divisione per zero come previsto. Questo sembra mostrare che la soluzione preferita di impostazione dei flag di livello db NON funzionerà per modificare il valore predefinito per .net SqlClient.
Mike,

può confermare che l'impostazione user_options a livello di server FUNZIONA comunque.
Mike,

Sto anche osservando che con la SELECT DATABASEPROPERTYEX('{database_name}', 'IsArithmeticAbortEnabled');restituzione di 1, sys.dm_exec_sessions mostra arithabort spento, anche se non vedo alcun SET esplicito in Profiler. Perché dovrebbe essere?
andrew.rockwell,

6

opzione 1

A parte la soluzione di Sankar , funzionerà l'impostazione dell'aritmetica di interruzione a livello del server per tutte le connessioni:

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

A partire da SQL 2014 si consiglia di essere attivo per tutte le connessioni:

È sempre necessario impostare ARITHABORT su ON nelle sessioni di accesso. L'impostazione di ARITHABORT su OFF può influire negativamente sull'ottimizzazione delle query con conseguenti problemi di prestazioni.

Quindi questa sembrerebbe la soluzione ideale.

opzione 2

Se l'opzione 1 non è praticabile e si utilizzano le procedure memorizzate per la maggior parte delle chiamate SQL (cosa che si dovrebbe fare, vedere Stored procedure vs. SQL inline ), abilitare semplicemente l'opzione in ciascuna procedura memorizzata rilevante:

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

Credo che la migliore soluzione reale qui sia semplicemente modificare il codice, poiché è sbagliato e qualsiasi altra correzione è semplicemente una soluzione alternativa.


Non credo che aiuti a impostarlo per SQL Server, quando inizia la connessione .net set ArithAbort off. Speravo in qualcosa che potesse essere fatto sul lato .net / C #. Ho messo la taglia, perché avevo visto la raccomandazione.
Henrik Staun Poulsen,

1
Il lato .net / C # è quello che Sankar ha coperto, quindi queste sono praticamente le uniche opzioni.
LowlyDBA

Ho provato l'opzione 1 e non ha avuto alcun effetto. Le nuove sessioni mostrano ancora con arithabort = 0. Non sto avendo alcun problema da esso, sto solo cercando di anticipare potenziali problemi.
Mark Freeman,

4

Non sono un esperto qui, ma puoi provare qualcosa di simile di seguito.

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

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


Sì, questo è ciò che intendevo eseguendolo manualmente. Il fatto è che la base di codice con cui sto lavorando ha accumulato un bel po 'di debito tecnico quando si tratta del livello di accesso al DB, quindi dovrò riformattare alcune centinaia di metodi per farlo in questo modo.
Peter Taylor,

2

Non esiste alcuna impostazione per forzare SqlClient a impostare sempre ARITHABORT, è necessario impostarlo come descritto.

È interessante notare dalla documentazione Microsoft per SET ARITHABORT : -

È sempre necessario impostare ARITHABORT su ON nelle sessioni di accesso. L'impostazione di ARITHABORT su OFF può influire negativamente sull'ottimizzazione delle query con conseguenti problemi di prestazioni.

Eppure la connessione .Net è codificata per disattivarla per impostazione predefinita?

Come altro punto, devi fare molta attenzione quando diagnostichi problemi di prestazioni con questa impostazione. Diverse opzioni di impostazione comporteranno piani di query diversi per la stessa query. Il tuo codice .Net potrebbe riscontrare un problema di prestazioni (SET ARITHABORT OFF) e tuttavia quando esegui la stessa query TSQL in SSMS (SET ARITHABORT ON per impostazione predefinita) potrebbe andare bene. Questo perché il piano di query .Net non verrà riutilizzato e verrà generato un nuovo piano. Questo potrebbe potenzialmente eliminare un problema di sniffing dei parametri, ad esempio, e fornire prestazioni molto migliori.


1
@HenrikStaunPoulsen - A meno che tu non sia bloccato usando 2000 (o livello di compatibilità 2000) non fa alcuna differenza. È implicito da ANSI_WARNINGSon nelle versioni successive e cose come le viste indicizzate funzionano bene.
Martin Smith,

Notare che .Net non è codificato per disattivare ARITHABORT. SSMS default impostandolo su . .Net si sta semplicemente connettendo e utilizzando le impostazioni predefinite del server / database. Puoi trovare problemi su MS Connect in merito agli utenti che si lamentano del comportamento predefinito di SSMS. Nota l'avvertimento nella pagina doc di ARITHABORT .
Pezzi di pancetta

2

Se fa risparmiare tempo a qualcuno, nel mio caso (Entity Framework Core 2.0.3, API ASP.Net Core, SQL Server 2008 R2):

  1. Non ci sono intercettori su EF Core 2.0 (penso che saranno presto disponibili su 2.1)
  2. Né modificare l'impostazione DB globale né l'impostazione user_optionsera accettabile per me (funzionano - ho testato) ma non ho potuto rischiare di influire su altre applicazioni.

Una query ad hoc di EF Core, con SET ARITHABORT ON;nella parte superiore, NON funziona.

Infine, la soluzione che ha funzionato per me è stata: la combinazione di una procedura memorizzata, chiamata come query non elaborata con l' SETopzione prima EXECseparata da un punto e virgola, in questo modo:

// C# EF Core
int result = _context.Database.ExecuteSqlCommand($@"
SET ARITHABORT ON;
EXEC MyUpdateTableStoredProc
             @Param1 = {value1}
");

Interessante. Grazie per aver pubblicato queste sfumature di collaborazione con EF Core. Solo curioso: è quello che stai facendo qui essenzialmente l'opzione wrapper che ho citato nella sottosezione ELSE della sezione Approcci alternativi nella mia risposta? Mi stavo solo chiedendo perché hai menzionato gli altri suggerimenti nella mia risposta o non funzionanti o non praticabili a causa di altri vincoli, ma non hai menzionato l'opzione wrapper.
Solomon Rutzky,

@SolomonRutzky è equivalente a quell'opzione, con la sfumatura che si limita all'esecuzione di una procedura memorizzata. Nel mio caso, se ho il prefisso di una query di aggiornamento non elaborata con SET OPTION (manualmente o tramite il wrapper) non funziona. Se inserisco l'opzione OPTION nella procedura memorizzata, non funziona. L'unico modo era eseguire SET OPTION seguito dalla stored procedure EXEC nello stesso batch. Il che è il modo in cui ho scelto di personalizzare la chiamata specifica invece di creare il wrapper. Presto aggiorneremo a SQL Server 2016 e potrò ripulirlo. Grazie per la risposta, se è stato utile per scartare scenari specifici.
Chris Amelinckx,

0

Basandosi sulla risposta di Solomon Rutzy , per 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)
        ...

Questo utilizza System.Data.Common' DbCommandinvece di SqlCommande DbConnectioninvece di SqlConnection.

Una traccia di SQL Profiler conferma, SET ARITHABORT ONviene inviata all'apertura della connessione, prima che vengano eseguiti altri comandi nella transazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.