Setzen Sie SqlClient standardmäßig auf ARITHABORT ON

32

Das Wichtigste zuerst: Ich verwende MS SQL Server 2008 mit einer Datenbank der Kompatibilitätsstufe 80 und verbinde mich mit .Net System.Data.SqlClient.SqlConnection.

Aus Performancegründen habe ich eine indizierte Ansicht erstellt. Daher müssen Aktualisierungen von Tabellen, auf die in der Ansicht verwiesen wird, durchgeführt werden ARITHABORT ON. Der Profiler zeigt jedoch an, dass SqlClient eine Verbindung herstellt ARITHABORT OFF, sodass Aktualisierungen dieser Tabellen fehlschlagen.

Gibt es eine zentrale Konfigurationseinstellung, um SqlClient zu verwenden ARITHABORT ON? Das Beste, was ich finden konnte, ist das manuelle Ausführen jedes Mal, wenn eine Verbindung geöffnet wird, aber das Aktualisieren der vorhandenen Codebasis, um dies zu tun, wäre eine ziemlich große Aufgabe, daher bin ich bestrebt, einen besseren Weg zu finden.

Peter Taylor
quelle

Antworten:

28

Scheinbar bevorzugter Ansatz

Ich hatte den Eindruck, dass das Folgende bereits von anderen getestet wurde, insbesondere aufgrund einiger Kommentare. Meine Tests haben jedoch gezeigt, dass diese beiden Methoden tatsächlich auf DB-Ebene funktionieren, selbst wenn eine Verbindung über .NET hergestellt wird SqlClient. Diese wurden von anderen getestet und verifiziert.

Serverweit

Sie können die Serverkonfigurationseinstellung für Benutzeroptionen so festlegen , dass sie ORmit 64 (dem Wert für ARITHABORT) den aktuellen Bit- Wert annimmt . Wenn Sie nicht das bitweise ODER ( |) verwenden, sondern eine direkte Zuordnung ( =) vornehmen, werden alle anderen bereits aktivierten Optionen gelöscht.

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

Datenbankebene

Dies kann pro Datenbank über ALTER DATABASE SET eingestellt werden :

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;

Alternative Ansätze

Die nicht so gute Nachricht ist, dass ich viel nach diesem Thema gesucht habe, nur um festzustellen, dass im Laufe der Jahre viele andere viel nach diesem Thema gesucht haben und es keine Möglichkeit gibt, das Verhalten zu konfigurieren von SqlClient. Einige MSDN-Dokumentationen implizieren, dass dies über einen ConnectionString erfolgen kann, es gibt jedoch keine Schlüsselwörter, mit denen diese Einstellungen geändert werden könnten. Ein anderes Dokument impliziert, dass es über Client-Netzwerkkonfiguration / Configuration Manager geändert werden kann, aber dies scheint auch nicht möglich zu sein. Daher und leider müssen Sie SET ARITHABORT ON;manuell ausführen . Hier sind einige Möglichkeiten zu berücksichtigen:

Wenn Sie Entity Framework 6 (oder neuer) verwenden, können Sie Folgendes versuchen:

  • Use Database.ExecuteSqlCommand : Im context.Database.ExecuteSqlCommand("SET ARITHABORT ON;");
    Idealfall wird dies einmal nach dem Öffnen der DB-Verbindung ausgeführt und nicht für jede Abfrage.

  • Erstellen Sie einen Abfangjäger über:

    Dies ermöglicht Ihnen , die SQL zu ändern , bevor es ausgeführt wird, in welchem Fall Sie einfach mit ihm Präfix kann: SET ARITHABORT ON;. Der Nachteil hierbei ist, dass dies bei jeder Abfrage der Fall ist, es sei denn, Sie speichern eine lokale Variable, um den Status zu erfassen, ob sie ausgeführt wurde, und testen sie jedes Mal (was wirklich nicht so viel zusätzliche Arbeit ist, aber mit ExecuteSqlCommandist wahrscheinlich einfacher).

In beiden Fällen können Sie dies an einer Stelle erledigen, ohne den vorhandenen Code zu ändern.

Anderenfalls könnten Sie eine Wrapper-Methode erstellen, die dies ausführt, ähnlich wie:

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

  return CommandToExec.ExecuteReader();
}

und dann einfach die aktuellen _Reader = _Command.ExecuteReader();referenzen ändern _Reader = ExecuteReaderWithSetting(_Command);.

Auf diese Weise kann die Einstellung auch an einem einzigen Ort vorgenommen werden, während nur minimale und vereinfachte Codeänderungen erforderlich sind, die meist über Suchen und Ersetzen vorgenommen werden können.

Besser noch ( sonst Teil 2), da dies eine Einstellung auf Verbindungsebene ist, muss sie nicht bei jedem Aufruf von SqlCommand.Execute __ () ausgeführt werden. Anstatt einen Wrapper für zu ExecuteReader()erstellen, erstellen Sie einen Wrapper für 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;
}

Und dann einfach die vorhandenen _Connection.Open();Referenzen ersetzen lassen OpenAndSetArithAbort(_Connection);.

Beide oben genannten Ideen können im OO-Stil implementiert werden, indem eine Klasse erstellt wird, die entweder SqlCommand oder SqlConnection erweitert.

Oder noch besser ( Else Teil 3), können Sie einen Ereignishandler für das Connection Statechange erstellen und haben sie die Eigenschaft , wenn die Verbindung ändert sich von Closedzu Openwie folgt:

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

Damit müssen Sie nur an jeder Stelle, an der Sie eine SqlConnectionInstanz erstellen, Folgendes hinzufügen :

_Connection.StateChange += new StateChangeEventHandler(OnStateChange);

Es sind keine Änderungen am vorhandenen Code erforderlich. Ich habe diese Methode gerade in einer kleinen Konsolen-App ausprobiert, indem ich das Ergebnis von teste SELECT SESSIONPROPERTY('ARITHABORT');. Es kehrt zurück 1, aber wenn ich den Event Handler deaktiviere, kehrt es zurück 0.


Der Vollständigkeit halber sind hier einige Dinge, die nicht (entweder überhaupt oder nicht so effektiv) funktionieren:

  • Anmelde-Trigger : Trigger sind selbst dann, wenn sie in derselben Sitzung ausgeführt werden und wenn sie innerhalb einer explizit gestarteten Transaktion ausgeführt werden, immer noch ein Unterprozess. Daher sind die Einstellungen ( SETBefehle, lokale temporäre Tabellen usw.) lokal und überleben nicht das Ende dieses Unterprozesses.
  • Hinzufügen SET ARITHABORT ON;am Anfang jeder gespeicherten Prozedur:
    • Dies erfordert eine Menge Arbeit für vorhandene Projekte, insbesondere wenn die Anzahl der gespeicherten Prozeduren zunimmt
    • Dies hilft nicht bei Ad-hoc-Anfragen
Solomon Rutzky
quelle
Ich habe gerade das Erstellen einer einfachen Datenbank mit deaktiviertem ARITHABORT und ANSI_WARNINGS getestet, eine Tabelle mit Null darin und einen einfachen .NET-Client zum Lesen daraus erstellt. Der .NET SqlClient zeigte an, dass ARITHABORT deaktiviert und ANSI_WARNINGS aktiviert war, und die Abfrage schlug erwartungsgemäß mit einer Division durch Null fehl. Dies scheint zu zeigen, dass die bevorzugte Lösung zum Festlegen von Datenbankebenen-Flags NICHT zum Ändern der Standardeinstellung für .net SqlClient funktioniert.
Mike
kann bestätigen, dass die serverweite Einstellung user_options DOES funktioniert.
Mike
Ich beobachte auch, dass mit der SELECT DATABASEPROPERTYEX('{database_name}', 'IsArithmeticAbortEnabled');Rückgabe von 1 sys.dm_exec_sessions Arithabort ausschaltet, obwohl ich in Profiler keine expliziten SETs sehe. Warum sollte das so sein?
andrew.rockwell
6

Option 1

Abgesehen von der Lösung von Sankar funktioniert das Festlegen der Einstellung für den arithmetischen Abbruch auf Serverebene für alle Verbindungen:

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

Ab SQL 2014 wird empfohlen, für alle Verbindungen aktiviert zu sein :

Sie sollten ARITHABORT in Ihren Anmeldesitzungen immer auf ON setzen. Das Setzen von ARITHABORT auf OFF kann sich negativ auf die Abfrageoptimierung auswirken und zu Leistungsproblemen führen.

Dies scheint also die ideale Lösung zu sein.

Option 2

Wenn Option 1 nicht durchführbar ist und Sie für die meisten Ihrer SQL-Aufrufe gespeicherte Prozeduren verwenden (siehe Gespeicherte Prozeduren vs. Inline-SQL ), aktivieren Sie einfach die Option in jeder relevanten gespeicherten Prozedur:

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

Ich glaube, die beste echte Lösung besteht darin, Ihren Code einfach zu bearbeiten, da dies falsch ist und jede andere Korrektur lediglich eine Problemumgehung darstellt.

LowlyDBA
quelle
Ich denke nicht, dass es hilft, es für den SQL Server einzurichten, wenn die .net-Verbindung mit beginnt set ArithAbort off. Ich habe auf etwas gehofft, das auf der .net / C # -Seite getan werden kann. Ich habe das Kopfgeld ausgesetzt, weil ich die Empfehlung gesehen hatte.
Henrik Staun Poulsen
1
Die .net / C # -Seite wird von Sankar abgedeckt, daher sind dies so ziemlich die einzigen Optionen.
LowlyDBA
Ich habe Option 1 ausprobiert und es hatte keine Wirkung. In neuen Sessions wird immer noch arithabort = 0 angezeigt. Ich habe keine Probleme damit, sondern versuche nur, potenziellen Problemen einen Schritt voraus zu sein.
Mark Freeman
4

Ich bin hier kein Experte, aber Sie können etwas wie unten versuchen.

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
quelle
Ja, das habe ich damit gemeint, dass ich es manuell ausgeführt habe. Die Sache ist, dass die Codebasis, mit der ich arbeite, eine Menge technischer Schulden in Bezug auf die DB-Zugriffsschicht hat, sodass ich ein paar hundert Methoden überarbeiten muss, um dies auf diese Weise zu tun.
Peter Taylor
2

Es gibt keine Einstellung, die SqlClient zwingt, ARITHABORT immer zu aktivieren. Sie müssen dies wie beschrieben einstellen.

Interessanterweise aus der Microsoft-Dokumentation zu SET ARITHABORT : -

Sie sollten ARITHABORT in Ihren Anmeldesitzungen immer auf ON setzen. Das Setzen von ARITHABORT auf OFF kann sich negativ auf die Abfrageoptimierung auswirken und zu Leistungsproblemen führen.

Und doch ist die .Net-Verbindung fest programmiert, um dies standardmäßig zu aktivieren?

Außerdem müssen Sie bei der Diagnose von Leistungsproblemen mit dieser Einstellung sehr vorsichtig sein. Unterschiedliche Set-Optionen führen zu unterschiedlichen Abfrageplänen für dieselbe Abfrage. Bei Ihrem .Net-Code kann ein Leistungsproblem auftreten (SET ARITHABORT OFF). Wenn Sie jedoch dieselbe TSQL-Abfrage in SSMS ausführen (SET ARITHABORT ON standardmäßig), ist dies möglicherweise in Ordnung. Dies liegt daran, dass der .Net-Abfrageplan nicht wiederverwendet und ein neuer Plan generiert wird. Dies könnte beispielsweise ein Parameter-Sniffing-Problem beseitigen und die Leistung erheblich verbessern.

Andy Jones
quelle
1
@HenrikStaunPoulsen - Wenn Sie nicht mit 2000 (oder 2000 Kompatibilitätsstufe) arbeiten, macht es keinen Unterschied. Es wird ANSI_WARNINGSin späteren Versionen von on impliziert, und Dinge wie indizierte Ansichten funktionieren einwandfrei.
Martin Smith
Beachten Sie, dass .Net nicht fest programmiert ist, um ARITHABORT auszuschalten. SSMS defaults es Einstellung auf . .Net stellt lediglich eine Verbindung her und verwendet die Standardeinstellungen für Server / Datenbanken. In MS Connect gibt es Probleme mit Benutzern, die sich über das Standardverhalten von SSMS beschweren. Beachten Sie die Warnung auf der ARITHABORT-Dokumentseite .
Bacon Bits
2

Wenn es jemandem Zeit spart, in meinem Fall (Entity Framework Core 2.0.3, ASP.Net Core API, SQL Server 2008 R2):

  1. Es gibt keine Interceptors auf EF Core 2.0 (Ich denke, sie werden bald auf 2.1 verfügbar sein)
  2. Weder die Einstellung der globalen Datenbank noch die Einstellung der user_optionsDatenbank war für mich akzeptabel (sie funktionieren - ich habe sie getestet), aber ich konnte nicht riskieren, andere Anwendungen zu beeinträchtigen.

Eine Ad-hoc-Abfrage von EF Core mit SET ARITHABORT ON;oben funktioniert NICHT.

Die Lösung, die für mich funktioniert hat, war: Eine gespeicherte Prozedur, die als unformatierte Abfrage aufgerufen wird, mit der SETOption EXECkombinieren, bevor sie durch ein Semikolon getrennt wird:

// C# EF Core
int result = _context.Database.ExecuteSqlCommand($@"
SET ARITHABORT ON;
EXEC MyUpdateTableStoredProc
             @Param1 = {value1}
");
Chris Amelinckx
quelle
Interessant. Vielen Dank, dass Sie diese Nuancen der Zusammenarbeit mit EF Core veröffentlicht haben. Nur neugierig: Ist das, was Sie hier tun, im Wesentlichen die Wrapper-Option, die ich in meiner Antwort im ELSE- Unterabschnitt des Abschnitts " Alternative Ansätze " erwähnt habe? Ich habe mich nur gewundert, weil Sie die anderen Vorschläge in meiner Antwort erwähnt haben, die entweder nicht funktionieren oder aufgrund anderer Einschränkungen nicht durchführbar sind, aber die Wrapper-Option nicht erwähnt haben.
Solomon Rutzky
@SolomonRutzky entspricht dieser Option mit der Nuance, dass sie auf die Ausführung einer gespeicherten Prozedur beschränkt ist. In meinem Fall funktioniert es nicht, wenn ich einer unformatierten Aktualisierungsabfrage die SET OPTION voranstelle (manuell oder über den Wrapper). Wenn ich die SET OPTION in die gespeicherte Prozedur setze, funktioniert es nicht. Die einzige Möglichkeit bestand darin, SET OPTION auszuführen, gefolgt von der gespeicherten EXEC-Prozedur im selben Stapel. Auf diese Weise habe ich mich entschieden, den spezifischen Anruf anzupassen, anstatt den Wrapper zu erstellen. In Kürze werden wir ein Update auf SQL Server 2016 durchführen und ich kann dieses Problem beheben. Vielen Dank für Ihre Antwort, wenn Sie beim Verwerfen bestimmter Szenarien hilfreich waren.
Chris Amelinckx
0

Aufbauend auf der Antwort von Solomon Rutzy für 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)
        ...

Dies verwendet System.Data.Common's DbCommandanstelle von SqlCommandund DbConnectionanstelle von SqlConnection.

Eine SQL Profiler-Ablaufverfolgung SET ARITHABORT ONwird beim Öffnen der Verbindung gesendet, bevor andere Befehle in der Transaktion ausgeführt werden.

CapNCook
quelle