Ustaw SqlClient domyślnie na ARITHABORT ON

32

Po pierwsze: używam MS SQL Server 2008 z bazą danych na poziomie kompatybilności 80 i łączę się z nią za pomocą .Net System.Data.SqlClient.SqlConnection.

Ze względu na wydajność utworzyłem widok indeksowany. W związku z tym należy wykonać aktualizacje tabel wymienionych w widoku ARITHABORT ON. Jednak profiler pokazuje, że SqlClient się łączy ARITHABORT OFF, więc aktualizacje tych tabel nie działają.

Czy istnieje centralne ustawienie konfiguracji umożliwiające korzystanie z SqlClient ARITHABORT ON? Najlepsze, co udało mi się znaleźć, to ręcznie wykonać to za każdym razem, gdy połączenie jest otwierane, ale aktualizacja istniejącej bazy kodu w tym celu byłaby dość dużym zadaniem, dlatego chętnie znajdę lepszy sposób.

Peter Taylor
źródło

Odpowiedzi:

28

Pozornie preferowane podejście

Miałem wrażenie, że następujące zostały już przetestowane przez innych, zwłaszcza na podstawie niektórych komentarzy. Ale moje testy pokazują, że te dwie metody rzeczywiście działają na poziomie bazy danych, nawet podczas łączenia przez .NET SqlClient. Zostały one przetestowane i zweryfikowane przez innych.

Cały serwer

Możesz ustawić ustawienie konfiguracji serwera opcji użytkownika tak, aby było to, co jest obecnie bitowo edytowane ORprzy pomocy 64 (wartość dla ARITHABORT). Jeśli nie użyjesz bitowej funkcji OR ( |), ale zamiast tego wykonasz proste przypisanie ( =), usuniesz wszystkie inne istniejące już opcje.

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

Na poziomie bazy danych

Można to ustawić dla każdej bazy danych za pomocą ZMIEŃ ZESTAW BAZY DANYCH :

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;

Alternatywne podejścia

Niezbyt dobra wiadomość jest taka, że ​​dużo szukałem w tym temacie, ale okazało się, że przez lata wiele innych szukało w tym temacie i nie ma sposobu, aby skonfigurować zachowanie z SqlClient. Niektóre dokumenty MSDN sugerują, że można to zrobić za pomocą ConnectionString, ale nie ma słów kluczowych, które pozwoliłyby na zmianę tych ustawień. Inny dokument sugeruje, że można go zmienić za pomocą Menedżera konfiguracji sieci / Menedżera konfiguracji, ale nie wydaje się to również możliwe. Dlatego i raczej niestety trzeba będzie wykonać SET ARITHABORT ON;ręcznie. Oto kilka sposobów na rozważenie:

JEŚLI używasz Entity Framework 6 (lub nowszego), możesz spróbować:

  • Użyj Database.ExecuteSqlCommand : context.Database.ExecuteSqlCommand("SET ARITHABORT ON;");
    Najlepiej byłoby to wykonać raz, po otwarciu połączenia DB, a nie dla każdego zapytania.

  • Utwórz przechwytywacz za pomocą:

    To pozwoli Ci zmodyfikować SQL, zanim zostanie wykonane, w którym to przypadku można po prostu poprzedzić je: SET ARITHABORT ON;. Minusem jest to, że będzie to za każdym zapytaniu, chyba że przechowywanie zmiennej lokalnej uchwycić stan, czy nie został on wykonany test i że za każdym razem (co naprawdę nie jest dużo dodatkowej pracy, ale przy użyciu ExecuteSqlCommandjest prawdopodobnie łatwiej).

Każdy z nich pozwoli ci obsłużyć to w jednym miejscu bez zmiany istniejącego kodu.

ELSE można utworzyć metodę opakowania, która to robi, podobnie jak:

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

  return CommandToExec.ExecuteReader();
}

a następnie po prostu zmień bieżące _Reader = _Command.ExecuteReader();odniesienia na_Reader = ExecuteReaderWithSetting(_Command); .

Dzięki temu ustawienie może być obsługiwane w jednym miejscu, wymagając jedynie minimalnych i uproszczonych zmian kodu, które można w większości wykonać za pomocą funkcji Znajdź i zamień.

Jeszcze lepiej ( Else Part 2), ponieważ jest to ustawienie poziomu połączenia, nie trzeba go wykonywać dla każdego wywołania SqlCommand.Execute __ (). Zamiast tworzyć opakowanie dla ExecuteReader(), utwórz opakowanie dla 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;
}

A następnie wystarczy zastąpić istniejące _Connection.Open();odniesieniaOpenAndSetArithAbort(_Connection); .

Oba powyższe pomysły można zaimplementować w większym stylu OO, tworząc klasę rozszerzającą SqlCommand lub SqlConnection.

Lub jeszcze lepiej ( Else Part 3), możesz utworzyć moduł obsługi zdarzeń dla Connection StateChange i ustawić właściwość, gdy połączenie zmieni się z Closedna Open:

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

W tym miejscu wystarczy dodać następujące elementy do każdego miejsca, w którym utworzono SqlConnectioninstancję:

_Connection.StateChange += new StateChangeEventHandler(OnStateChange);

Nie są wymagane żadne zmiany w istniejącym kodzie. Właśnie wypróbowałem tę metodę w małej aplikacji na konsolę, testując, drukując wynik SELECT SESSIONPROPERTY('ARITHABORT');. Zwraca 1, ale jeśli wyłączę moduł obsługi zdarzeń, zwraca 0.


Dla kompletności, oto kilka rzeczy, które nie działają (albo wcale, albo nie tak skutecznie):

  • Wyzwalacze logowania : Wyzwalacze, nawet jeśli działają w tej samej sesji, a nawet jeśli działają w ramach jawnie rozpoczętej transakcji, są nadal podprocesem, a zatem i jej ustawieniami (SET polecenia, lokalne tabele tymczasowe itp.) Są dla niej lokalne i nie przetrwają koniec tego podprocesu.
  • Dodawanie SET ARITHABORT ON; na początku każdej procedury składowanej:
    • wymaga to dużo pracy w przypadku istniejących projektów, zwłaszcza gdy rośnie liczba procedur przechowywanych
    • nie pomaga to w zapytaniach ad hoc
Solomon Rutzky
źródło
Właśnie przetestowałem utworzenie prostej bazy danych z wyłączonymi ARITHABORT i ANSI_WARNINGS, stworzyłem tabelę z zerową wartością i prostego klienta .net do odczytu. SqlClient .net pokazał, że wyłącza ARITHABORT i ANSI_WARNINGS w logowaniu w sql profilerze, a także nie powiódł się zapytania dzieląc przez zero zgodnie z oczekiwaniami. Wydaje się, że pokazuje to, że preferowane rozwiązanie ustawiania flag poziomu db NIE zadziała w przypadku zmiany domyślnej opcji SqlClient .net.
Mike
może potwierdzić, że ustawienie opcji User_options dla całego serwera DZIAŁA jednak.
Mike
Zauważyłem również, że po SELECT DATABASEPROPERTYEX('{database_name}', 'IsArithmeticAbortEnabled');zwróceniu 1 sys.dm_exec_sessions pokazuje arithabort wyłączony, chociaż nie widzę żadnych wyraźnych zestawów w Profiler. Dlaczego miałoby to być?
andrew.rockwell
6

opcja 1

Oprócz rozwiązania Sankar , ustawienie arytmetycznego ustawienia przerwania na poziomie serwera dla wszystkich połączeń będzie działać:

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

Począwszy od SQL 2014 zaleca się włączenie wszystkich połączeń:

Zawsze powinieneś ustawić ARITHABORT na ON podczas sesji logowania. Ustawienie ARITHABORT na OFF może negatywnie wpłynąć na optymalizację zapytań, prowadząc do problemów z wydajnością.

Wydawałoby się to idealnym rozwiązaniem.

Opcja 2

Jeśli opcja 1 nie jest wykonalna i używasz procedur przechowywanych dla większości swoich wywołań SQL (które powinieneś zobaczyć Procedury składowane vs. Inline SQL ), po prostu włącz tę opcję w każdej odpowiedniej procedurze przechowywanej:

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

Uważam, że najlepszym prawdziwym rozwiązaniem jest po prostu edycja kodu, ponieważ jest on błędny, a każda inna poprawka jest jedynie obejściem.

LowlyDBA
źródło
Nie sądzę, że to pomaga ustawić go dla SQL Servera, kiedy zaczyna się połączenie .net set ArithAbort off. Miałem nadzieję na coś, co można zrobić po stronie .net / C #. Wystawiłem nagrodę, ponieważ widziałem zalecenie.
Henrik Staun Poulsen
1
Sankar opisał stronę .net / C #, więc są to prawie jedyne opcje.
LowlyDBA,
Wypróbowałem opcję 1 i to nie przyniosło efektu. Nowe sesje wciąż pokazują, że mam arytabortę = 0. Nie mam z tym żadnych problemów, staram się wyprzedzić potencjalne problemy.
Mark Freeman
4

NIE jestem tutaj ekspertem, ale możesz spróbować czegoś takiego jak poniżej.

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
źródło
Tak, właśnie to miałem na myśli przez ręczne wykonanie. Chodzi o to, że podstawa kodu, z którą pracuję, narosła spory dług techniczny, jeśli chodzi o warstwę dostępu do bazy danych, więc będę musiał zmienić kilkaset metod, aby to zrobić w ten sposób.
Peter Taylor
2

Nie ma ustawienia, które zmusiłoby SqlClient do zawsze włączania ARITHABORT, musisz to ustawić zgodnie z opisem.

Co ciekawe z dokumentacji Microsoft dla SET ARITHABORT : -

Zawsze powinieneś ustawić ARITHABORT na ON podczas sesji logowania. Ustawienie ARITHABORT na OFF może negatywnie wpłynąć na optymalizację zapytań, prowadząc do problemów z wydajnością.

A jednak połączenie .Net jest ustawione na stałe, aby domyślnie to wyłączyć?

Kolejnym punktem jest konieczność zachowania ostrożności podczas diagnozowania problemów z wydajnością przy tym ustawieniu. Różne opcje zestawu będą skutkować różnymi planami zapytań dla tego samego zapytania. W kodzie .Net może wystąpić problem z wydajnością (USTAW ARITHABORT WYŁ), a jednak po uruchomieniu tego samego zapytania TSQL w SSMS (domyślnie USTAW ARITHABORT) może być w porządku. Wynika to z faktu, że plan zapytań .Net nie zostanie ponownie użyty i wygenerowany zostanie nowy plan. Może to potencjalnie wyeliminować na przykład problem z wąchaniem parametrów i dać znacznie lepszą wydajność.

Andy Jones
źródło
1
@HenrikStaunPoulsen - O ile nie utkniesz przy użyciu 2000 (lub poziomu zgodności z 2000), nie robi to żadnej różnicy. Jest to sugerowane przez ANSI_WARNINGSw późniejszych wersjach i rzeczy takie jak widoki indeksowane działają dobrze.
Martin Smith
Zauważ, że .Net nie jest zakodowany na stałe, aby wyłączyć ARITHABORT. SSMS domyślnie ustawienie go na . .Net po prostu łączy się i używa domyślnych ustawień serwera / bazy danych. W MS Connect można znaleźć problemy dotyczące użytkowników narzekających na domyślne zachowanie SSMS. Zwróć uwagę na ostrzeżenie na stronie dokumentu ARITHABORT .
Bacon Bits,
2

Jeśli oszczędza to trochę czasu, w moim przypadku (Entity Framework Core 2.0.3, ASP.Net Core API, SQL Server 2008 R2):

  1. W EF Core 2.0 nie ma żadnych przechwytywaczy (myślę, że wkrótce będą dostępne w wersji 2.1)
  2. Ani zmiana globalnego ustawienia DB, ani ustawienie nie user_optionsbyło dla mnie do zaakceptowania (działają - testowałem), ale nie mogłem ryzykować wpływu na inne aplikacje.

Kwerenda ad hoc z EF Core z SET ARITHABORT ON; na górze, nie działa.

Wreszcie, rozwiązaniem, które zadziałało dla mnie było: połączenie procedury składowanej, zwanej surowym zapytaniem z SETopcją przed EXECoddzieleniem średnikiem, jak poniżej:

// C# EF Core
int result = _context.Database.ExecuteSqlCommand($@"
SET ARITHABORT ON;
EXEC MyUpdateTableStoredProc
             @Param1 = {value1}
");
Chris Amelinckx
źródło
Ciekawy. Dziękujemy za opublikowanie tych niuansów związanych z pracą z EF Core. Ciekawe: czy to, co tutaj robisz, jest zasadniczo opcją pakowania, o której wspomniałem w podsekcji ELSE sekcji Alternatywne podejścia w mojej odpowiedzi? Zastanawiałem się tylko, ponieważ wspomniałeś inne sugestie w mojej odpowiedzi, które albo nie działają, albo nie są wykonalne z powodu innych ograniczeń, ale nie wspomniałeś o opcji otoki.
Solomon Rutzky
@ SolomonRutzky jest to odpowiednik tej opcji, z tym, że ogranicza się do wykonywania procedury składowanej. W moim przypadku, jeśli poprzedzę zapytanie o nieprzetworzone aktualizacje ustawieniem OPCJI SET (ręcznie lub za pomocą opakowania), to nie działa. Jeśli wstawię SET OPTION wewnątrz procedury składowanej, to nie działa. Jedynym sposobem było wykonanie SET OPTION, a następnie procedura składowana EXEC w tej samej partii. W ten sposób zdecydowałem się na dostosowanie konkretnego połączenia zamiast tworzenia opakowania. Wkrótce zaktualizujemy do SQLServer 2016 i mogę to wyczyścić. Dziękuję za odpowiedź, jeśli pomogło to odrzucić określone scenariusze.
Chris Amelinckx
0

Opierając się na odpowiedzi Solomona Rutzy'ego dla 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)
        ...

To używa System.Data.Common' DbCommandzamiast ' SqlCommandi DbConnectionzamiastSqlConnection .

Śledzenie SQL Profiler potwierdza, że SET ARITHABORT ONjest wysyłane, gdy połączenie zostanie otwarte, przed wykonaniem jakichkolwiek innych poleceń w transakcji.

CapNCook
źródło