Зробіть SqlClient за замовчуванням ARITHABORT ON


32

Перш за все: я використовую MS SQL Server 2008 з базою даних на рівні сумісності 80 і підключаюсь до неї з .Net's System.Data.SqlClient.SqlConnection.

З міркувань продуктивності я створив індексований вигляд. Як результат, потрібно робити оновлення таблиць, на які посилається подання ARITHABORT ON. Однак профілер показує, що SqlClient з'єднується із ARITHABORT OFF, тому оновлення цих таблиць не вдається.

Чи є параметр центральної конфігурації для використання SqlClient ARITHABORT ON? Найкраще, що мені вдалося знайти, - це виконувати вручну виконання кожного разу, коли з'єднання відкривається, але оновлення існуючої бази коду для цього було б досить великим завданням, тому я прагну знайти кращий спосіб.

Відповіді:


28

Здається, переважний підхід

У мене було враження, що наступне було перевірено вже іншими, особливо на основі деяких коментарів. Але моє тестування показує, що ці два методи дійсно працюють на рівні БД, навіть при підключенні через .NET SqlClient. Вони перевірені та перевірені іншими.

Загальносерверний

Ви можете встановити настройки конфігурації сервера параметрів користувачів такими, якими вони є в даний час, ORз 64 (значення для ARITHABORT). Якщо ви не використовуєте біт-мудрий АБО ( |), а замість цього виконайте пряме призначення ( =), ви викреслите будь-які інші існуючі параметри, які вже увімкнено.

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

Рівень бази даних

Це можна встановити на базу даних за допомогою НАСТРОЙКИ ДЛЯ ДАТАБАЗИ :

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;

Альтернативні підходи

Не надто гарна новина полягає в тому, що я зробив багато пошуків на цю тему, лише щоб виявити, що за ці роки багато інших зробили багато пошуків на цю тему, і немає способу налаштувати поведінку з SqlClient. У деяких документах MSDN випливає, що це можна зробити через ConnectionString, але немає Ключових слів, які дозволяли б змінити ці налаштування. Інший документ означає, що його можна змінити через Менеджер конфігурації / конфігурації мережі клієнтів, але це також не представляється можливим. Отже, і, на жаль, вам потрібно буде виконати SET ARITHABORT ON;вручну. Ось кілька способів розглянути:

Якщо ви використовуєте Entity Framework 6 (або новішу версію), ви можете спробувати:

  • Використовуйте Database.ExecuteSqlCommand : context.Database.ExecuteSqlCommand("SET ARITHABORT ON;");
    В ідеалі це буде виконуватися один раз, після відкриття з'єднання БД, а не для кожного запиту.

  • Створіть перехоплювач за допомогою будь-якого:

    Це дозволить вам змінити SQL перед виконанням, в цьому випадку ви можете просто префікс його: SET ARITHABORT ON;. Недоліком тут є те , що він буде в кожному запиті, якщо ви не зберігати локальну змінну для фіксації стану або не було виконано і тест для цього кожен раз (який на самому ділі не так багато додаткової роботи, але з використанням ExecuteSqlCommandв певно, простіше).

Жоден із них дозволить вам обробити це в одному місці, не змінюючи жодного наявного коду.

ELSE , ви можете створити метод обгортки, який робить це, аналогічно:

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

  return CommandToExec.ExecuteReader();
}

а потім просто змінити поточні _Reader = _Command.ExecuteReader();посилання на _Reader = ExecuteReaderWithSetting(_Command);.

Це також дозволяє обробляти налаштування в одному місці, вимагаючи лише мінімальних та спрощених змін коду, які в основному можна здійснити за допомогою Find & Replace.

Ще краще ( інша частина 2), оскільки це налаштування рівня з'єднання, його не потрібно виконувати для кожного виклику SqlCommand.Execute __ (). Тому замість створення обгортки для цього ExecuteReader()створіть обгортку для 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;
}

А потім просто замініть існуючі _Connection.Open();посилання на OpenAndSetArithAbort(_Connection);.

Обидві вищевказані ідеї можуть бути реалізовані в більш стильовому стилі, створивши клас, який розширює або SqlCommand, або SqlConnection.

Або ще краще ( інша частина 3), ви можете створити обробник подій для Connection StateChange і встановити його властивість, коли з'єднання змінюється Closedна 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();
        }
    }
}

Маючи це на місці, вам потрібно лише додати наступне до кожного місця, де ви створюєте SqlConnectionекземпляр:

_Connection.StateChange += new StateChangeEventHandler(OnStateChange);

Ніяких змін до існуючого коду не потрібно. Я тільки що спробував цей метод у невеликому консольному додатку, перевіривши, надрукувавши результат SELECT SESSIONPROPERTY('ARITHABORT');. Він повертається 1, але якщо я відключу обробник подій, він повертається 0.


Для повноти, ось деякі речі, які не працюють (взагалі чи не так ефективно):

  • Тригери входу : Тригери навіть під час виконання одного і того ж сеансу, і навіть якщо він виконується в рамках явно розпочатої транзакції, все ще є підпроцесом, а значить, його налаштування ( SETкоманди, локальні тимчасові таблиці тощо) є локальними для нього і не виживають закінчення цього підпроцесу.
  • Додавання SET ARITHABORT ON;до початку кожної збереженої процедури:
    • для цього потрібна велика робота для існуючих проектів, тим більше, що збільшується кількість збережених процедур
    • це не допомагає спеціальним запитам

Я щойно перевірив створення простої бази даних з ARITHABORT і ANSI_WARNINGS, вимкнено, створив таблицю з нулем у ній та простий клієнт .net для читання з неї. .Net SqlClient показав вимкнення ARITHABORT та ANSI_WARNINGS у вході в протокол sql, а також провалив запит з поділом на нуль, як очікувалося. Здається, це показує, що бажане рішення встановлення прапорів рівня db НЕ працюватиме для зміни типових для .net SqlClient.
Майк

Ви можете підтвердити, що налаштування параметрів користувача на всій сервері працює.
Майк

Я також зауважую, що, SELECT DATABASEPROPERTYEX('{database_name}', 'IsArithmeticAbortEnabled');повертаючи 1, sys.dm_exec_sesions показує, що arithabort вимкнено, хоча я не бачу явних SET у Profiler. Чому це було б?
andrew.rockwell

6

Варіант 1

Крім рішення Санкара , встановлення арифметичної настройки переривання на рівні сервера для всіх з'єднань буде працювати:

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

Станом на SQL 2014 рекомендується використовувати для всіх з'єднань:

Ви завжди повинні встановити значення ARITHABORT на УВІМКНЕНО у процесі входу. Якщо встановити значення ARITHABORT на OFF, це може негативно впливати на оптимізацію запитів, що призводить до проблем з продуктивністю.

Тож це, здавалося б, ідеальне рішення.

Варіант 2

Якщо варіант 1 нежиттєздатний і ви використовуєте збережені процедури для більшості своїх викликів SQL (про що вам слід, див. Збережені процедури проти Inline SQL ), тоді просто увімкніть цю опцію у кожній відповідній збереженій процедурі:

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

Я вважаю, що найкраще реальне рішення тут - просто відредагувати свій код, оскільки це неправильно, а будь-яке інше виправлення - лише обхідне рішення.


Я не думаю, що це допомагає ввімкнути його для SQL Server, коли з'єднання .net починається з set ArithAbort off. Я сподівався на те, що можна зробити на стороні .net / C #. Я мирився з винагородою, бо бачив рекомендацію.
Генрік Стаун Поульсен

1
Сторона .net / C # - це те, що охоплював Санкар, тож це майже єдині варіанти.
LowlyDBA

Я спробував Варіант 1, і це не мало ефекту. Нові сеанси все ще показують, що мають arithabort = 0. У мене не виникає жодних проблем, я просто намагаюся випередити потенційні проблеми.
Марк Фрімен

4

Я тут не експерт, але ви можете спробувати щось на зразок нижче.

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

Посилання: http://social.msdn.microsoft.com/Forums/en-US/transactsql/thread/d9e3e8ba-4948-4419-bb6b-dd5208bd7547/


Так, це я мав на увазі, виконуючи його вручну. Вся справа в тому, що база даних кодексів, з якою я працюю, накопичила досить багато технічної заборгованості, коли мова йде про рівень доступу до БД, тому мені доведеться переробити кілька сотень методів, щоб зробити це таким чином.
Пітер Тейлор

2

Немає налаштування змушувати SqlClient завжди включати ARITHABORT, ви повинні встановити це так, як ви описуєте.

Цікаво з документації Microsoft для SET ARITHABORT : -

Ви завжди повинні встановити значення ARITHABORT на УВІМКНЕНО у процесі входу. Якщо встановити значення ARITHABORT на OFF, це може негативно впливати на оптимізацію запитів, що призводить до проблем з продуктивністю.

І все ж .Net-з'єднання важко закодовано, щоб вимкнути це за замовчуванням?

Як ще один момент, ви повинні бути дуже обережними, коли діагностуєте проблеми з ефективністю цього налаштування. Різні параметри набору призводять до різних планів запитів для одного і того ж запиту. У вашому коді .Net може виникнути проблема з продуктивністю (SET ARITHABORT OFF), але, якщо ви запустите той самий запит TSQL у SSMS (SET ARITHABORT ON за замовчуванням), це може бути добре. Це пояснюється тим, що план запиту .Net не буде повторно використаний та створений новий план. Це, можливо, може усунути, наприклад, проблему обнюхування параметрів і дати набагато кращі показники.


1
@HenrikStaunPoulsen - Якщо ви не застрягли, використовуючи 2000 (або рівень сумісності 2000), це не має ніякої різниці. Це має на увазі ANSI_WARNINGSв більш пізніх версіях, і такі речі, як індексований вид, працюють добре.
Мартін Сміт

Зауважте, що .Net не є жорстким кодом, щоб вимкнути ARITHABORT. ВСС по замовчуванням встановити його на . .Net - це просто підключення та використання параметрів сервера / бази даних за замовчуванням. Ви можете знайти проблеми на MS Connect, коли користувачі скаржаться на поведінку SSMS за замовчуванням. Зверніть увагу на попередження на сторінці документа ARITHABORT .
Шматочки бекону

2

Якщо це економить когось деякий час, у моєму випадку (Entity Framework Core 2.0.3, API ASP.Net Core, SQL Server 2008 R2):

  1. У EF Core 2.0 немає перехоплювачів (я думаю, вони будуть доступні незабаром 2.1)
  2. Ні зміна глобальної настройки БД, ні налаштування на user_optionsмене було прийнятним (вони НЕ працюють - я протестував), але я не міг ризикувати впливати на інші програми.

Спеціальний запит від EF Core SET ARITHABORT ON;у верхній частині НЕ працює.

Нарешті, рішення, яке працювало для мене, було: поєднання збереженої процедури, викликаної як необроблений запит, з SETопцією, перш ніж EXECрозділятися крапкою з комою, як це:

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

Цікаво. Дякуємо за публікацію цих нюансів роботи з EF Core. Цікаво: це те, що ви робите тут, по суті, з можливістю обгортки, про яку я згадував у підрозділі ELSE в розділі « Альтернативні підходи» у своїй відповіді? Мені було просто цікаво, оскільки ви згадали інші пропозиції у моїй відповіді або не працювали, або не були життєздатними через інші обмеження, але не згадували варіант обгортки.
Соломон Руцький

@SolomonRutzky рівнозначно тому варіанту, з тим нюансом, що він обмежений виконанням збереженої процедури. У моєму випадку, якщо я приставлю необмежений запит на оновлення за допомогою параметра SET OPTION (вручну або через обгортку), він не працює. Якщо я поставив SET OPTION всередині збереженої процедури, він не працює. Єдиний спосіб полягав у тому, щоб зробити SET OPTION з подальшим збереженням EXEC процедури в тій же партії. Яким чином я вирішив налаштувати конкретний дзвінок, а не робити обгортку. Незабаром ми оновимо до SQLServer 2016, і я можу це очистити. Дякую за вашу відповідь, якщо було корисно відкинути конкретні сценарії.
Кріс Амелінккс

0

Спираючись на відповідь Соломона Рутці , для 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)
        ...

Для цього використовується System.Data.Common's DbCommandзамість SqlCommand, а DbConnectionне SqlConnection.

Трасування SQL Profiler підтверджується, SET ARITHABORT ONнадсилається при відкритті з'єднання перед виконанням будь-яких інших команд у транзакції.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.