it-swarm.it

Imposta SqlClient come predefinito su ARITHABORT ON

Prima di tutto: sto usando MS SQL Server 2008 con un database a livello di compatibilità 80 e mi sto collegando con .Net's System.Data.SqlClient.SqlConnection.

Per motivi di prestazioni ho creato una vista indicizzata. Di conseguenza, gli aggiornamenti delle tabelle a cui fa riferimento la vista devono essere eseguiti con ARITHABORT ON. Tuttavia, il profiler mostra che SqlClient si sta connettendo con 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.

32
Peter Taylor

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 mostrano che questi due metodi funzionano davvero a livello di DB, anche quando ci si connette tramite .NET SqlClient. Questi sono stati testati e verificati da altri.

a livello di server

È possibile impostare opzioni utente l'impostazione di configurazione del server su qualsiasi cosa sia attualmente bit-saggio ORed con 64 (il valore per ARITHABORT). Se non usi bit-wise OR (|) Ma invece esegui un compito diretto (=), Eliminerai tutte le altre opzioni esistenti già abilitato.

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. Alcuni documenti MSDN implicano che può essere fatto tramite ConnectionString, ma non ci sono 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 eseguire 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, questo dovrebbe essere eseguito una volta, dopo aver aperto la connessione DB, e non per ogni query.

  • Crea un intercettore tramite:

    Ciò ti consentirà di modificare l'SQL prima che venga eseguito, nel qual caso puoi semplicemente aggiungerlo come prefisso: 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ù semplice).

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

[~ # ~] else [~ # ~] , potresti 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 quindi basta cambiare l'attuale _Reader = _Command.ExecuteReader(); riferimenti in _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 Parte 2), poiché si tratta di un'impostazione a livello di connessione, non deve essere eseguito 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 quindi basta sostituire i riferimenti _Connection.Open(); esistenti per essere OpenAndSetArithAbort(_Connection);.

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

O meglio ancora ( Else Parte 3), è possibile creare un gestore eventi per Connection StateChange e impostarlo la proprietà quando la connessione cambia da Closed a Open come 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 posto in cui crei un'istanza SqlConnection:

_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');. Restituisce 1, Ma se disabilito il gestore eventi, restituisce 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 (SET comandi, local tabelle temporanee, ecc.) sono locali e non sopravvivono alla fine di quel processo secondario.
  • Aggiunta di 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
29
Solomon Rutzky

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 è consigliato 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 vedere, vedere Stored procedure vs. Inline SQL ), 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é è errato e qualsiasi altra correzione è semplicemente una soluzione alternativa.

6
LowlyDBA

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/

4
Sankar Reddy

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

È interessante notare dal 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 generano 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.

2
Andy Jones

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é modificando l'impostazione DB globale né impostando user_options era accettabile per me (funzionano - ho testato) ma non potevo 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 era: Combinare una procedura memorizzata, chiamata come query non elaborata con l'opzione SET prima di EXEC separata da un punto e virgola, in questo modo:

// C# EF Core
int result = _context.Database.ExecuteSqlCommand([email protected]"
SET ARITHABORT ON;
EXEC MyUpdateTableStoredProc
             @Param1 = {value1}
");
2
Chris Amelinckx

Basandosi su risposta 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 's DbCommand anziché SqlCommand e DbConnection anziché SqlConnection.

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

0
CapNCook