Установите для SqlClient значение по умолчанию ARITHABORT ON

32

Перво-наперво: я использую MS SQL Server 2008 с базой данных на уровне совместимости 80 и подключаюсь к ней с помощью .Net 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

База данных уровня

Это может быть установлено для каждой базы данных через 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;

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

Плохая новость заключается в том, что я много занимался поиском по этой теме, но обнаружил, что в течение многих лет многие другие занимались поиском по этой теме, и нет способа настроить поведение. из SqlClient. Некоторая документация MSDN подразумевает, что это может быть сделано через ConnectionString, но нет ключевых слов, которые позволили бы изменить эти настройки. Другой документ подразумевает, что его можно изменить с помощью Client Network Configuration / Configuration Manager, но это также не представляется возможным. Следовательно, и, к сожалению, вам нужно будет выполнить SET ARITHABORT ON;вручную. Вот несколько способов рассмотреть:

Если вы используете Entity Framework 6 (или новее), вы можете попробовать:

  • Использовать Database.ExecuteSqlCommand : в context.Database.ExecuteSqlCommand("SET ARITHABORT ON;");
    идеале это должно быть выполнено один раз, после открытия соединения с БД, а не для каждого запроса.

  • Создайте перехватчик с помощью:

    Это позволит вам изменить SQL перед выполнением, в этом случае вы можете просто префикс его: SET ARITHABORT ON;. Недостатком здесь является то , что он будет в каждом запросе, если вы не хранить локальную переменную для фиксации состояния или не было выполнено и тест для этого каждый раз (который на самом деле не так много дополнительной работы, но с использованием ExecuteSqlCommandв наверное проще).

Любой из них позволит вам справиться с этим в одном месте без изменения какого-либо существующего кода.

В противном случае , вы можете создать метод-оболочку, который делает это, подобно:

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

  return CommandToExec.ExecuteReader();
}

а затем просто измените текущие _Reader = _Command.ExecuteReader();ссылки на _Reader = ExecuteReaderWithSetting(_Command);.

Это также позволяет обрабатывать настройки в одном месте, при этом требуются лишь минимальные и упрощенные изменения кода, которые в основном можно выполнить с помощью функции «Найти и заменить».

Еще лучше ( остальное часть 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 Server Profiler, а также сбой запроса с делением на ноль, как и ожидалось. Кажется, это показывает, что предпочтительное решение установки флагов уровня БД НЕ будет работать для изменения значения по умолчанию для .net SqlClient.
Майк
может подтвердить, что настройка user_options на сервере работает, однако.
Майк
Я также наблюдаю, что при SELECT DATABASEPROPERTYEX('{database_name}', 'IsArithmeticAbortEnabled');возврате 1 sys.dm_exec_sessions отключает arithabort, хотя я не вижу явных SET в Profiler. С чего бы это?
andrew.rockwell
6

Опция 1

Помимо решения Sankar , настройка арифметического прерывания на уровне сервера будет работать для всех соединений:

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

Начиная с SQL 2014 рекомендуется использовать все соединения:

Вы всегда должны устанавливать ARITHABORT на ON в своих сеансах входа. Установка ARITHABORT на OFF может отрицательно повлиять на оптимизацию запросов, что приведет к проблемам с производительностью.

Так что это, казалось бы, идеальное решение.

Вариант 2

Если вариант 1 нежизнеспособен и вы используете хранимые процедуры для большинства ваших вызовов SQL (что следует сделать, см. « Хранимые процедуры против встроенного SQL» ), просто включите параметр в каждой соответствующей хранимой процедуре:

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

Я считаю, что лучшее реальное решение здесь - просто отредактировать ваш код, так как это неправильно, а любое другое исправление - это просто обходной путь.

LowlyDBA
источник
Я не думаю, что это помогает установить его для 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 на ON в своих сеансах входа. Установка 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, ASP.Net Core API, 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отправляется при открытии соединения, прежде чем какие-либо другие команды будут выполнены в транзакции.

CapNCook
источник