it-swarm.dev

Ustaw SqlClient domyślnie na ARITHABORT ON

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

Ze względu na wydajność utworzyłem widok indeksowany. W związku z tym należy zaktualizować tabele, do których odwołuje się widok, za pomocą ARITHABORT ON. Jednak profiler pokazuje, że SqlClient łączy się z ARITHABORT OFF, więc aktualizacje tych tabel nie działają.

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

32
Peter Taylor

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.

W całym serwerze

Możesz ustawić opcje użytkownika ustawienie konfiguracji serwera, aby było to, co jest aktualnie bitowe ORed z 64 (wartość dla ARITHABORT). Jeśli nie użyjesz bit-mądrości OR (|), Ale zamiast tego wykonasz proste przypisanie (=)), Usuniesz wszystkie istniejące opcje już włączony.

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

Poziom bazy danych

Można to ustawić dla 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 konieczne będzie ręczne wykonanie SET ARITHABORT ON;. 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ą:

    Umożliwi to modyfikację kodu SQL przed jego wykonaniem, w którym to przypadku możesz po prostu poprzedzić go: SET ARITHABORT ON;. Minusem jest to, że będzie ono dotyczyć każdego zapytania, chyba że przechowasz zmienną lokalną, aby uchwycić stan, czy została ona wykonana, i przetestować ją za każdym razem (co nie jest tak naprawdę dodatkową pracą, ale przy użyciu ExecuteSqlCommand jest prawdopodobnie łatwiejsze).

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

[~ # ~] else [~ # ~] , możesz utworzyć metodę otoki, 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(); referencje 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 Część 2), ponieważ jest to ustawienie poziomu połączenia, nie musi być wykonywane 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 odwołania _Connection.Open(); referencjami na OpenAndSetArithAbort(_Connection);.

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

Lub jeszcze lepiej ( Else Część 3), możesz utworzyć moduł obsługi zdarzeń dla Connection StateChange i ustawić go właściwość, gdy połączenie zmienia się z Closed na Open w następujący sposób:

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

Mając to na miejscu, wystarczy dodać następujące elementy do każdego miejsca, w którym utworzono instancję SqlConnection:

_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):

  • Logon Triggers : 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 niego lokalne i nie przetrwają do końca tego podprocesu.
  • Dodanie 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
29
Solomon Rutzky

Opcja 1

Oprócz rozwiązanie Sankara , ustawienie ustawienia przerwania arytmetycznego 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 jest zalecane włączenie dla wszystkich połączeń:

Zawsze powinieneś ustawiać 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 ), to 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 rzeczywistym rozwiązaniem jest po prostu edycja kodu, ponieważ jest on nieprawidłowy, a każda inna poprawka jest jedynie obejściem.

6
LowlyDBA

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/

4
Sankar Reddy

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

Co ciekawe z dokumentacja Microsoft dla SET ARITHABORT : -

Zawsze powinieneś ustawiać 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ść.

2
Andy Jones

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 user_options był dla mnie do przyjęcia (działają - testowałem), ale nie mogłem ryzykować wpływu na inne aplikacje.

Kwerenda ad hoc z EF Core z SET ARITHABORT ON; u góry, NIE działa.

Wreszcie, rozwiązaniem, które działało dla mnie było: połączenie procedury składowanej, wywoływanej jako surowe zapytanie z opcją SET przed EXEC oddzielone średnikiem, tak jak to:

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

Opierając się na odpowiedź Salomona 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's DbCommand zamiast SqlCommand, i DbConnection zamiast SqlConnection.

Śledzenie SQL Profiler potwierdza, SET ARITHABORT ON jest wysyłane, gdy połączenie zostanie otwarte, zanim jakiekolwiek inne polecenia zostaną wykonane w transakcji.

0
CapNCook