Sql-Server

使 SqlClient 預設為 ARITHABORT ON

  • October 11, 2019

首先要做的事情:我使用 MS SQL Server 2008 和兼容級別為 80 的數據庫,並使用 .Net 連接到它System.Data.SqlClient.SqlConnection

出於性能原因,我創建了一個索引視圖。因此,視圖中引用的表的更新需要使用ARITHABORT ON. 但是,分析器顯示 SqlClient 正在與 連接ARITHABORT OFF,因此對這些表的更新失敗。

是否有讓 SqlClient 使用的中央配置設置ARITHABORT ON?我能找到的最好的方法是每次打開連接時手動執行,但是更新現有的程式碼庫來做到這一點將是一項相當大的任務,所以我很想找到更好的方法。

看似首選的方法

我的印像是以下內容已經被其他人測試過,特別是基於一些評論。但我的測試表明,這兩種方法確實在數據庫級別有效,即使通過 .NET 連接也是如此SqlClient這些已經過其他人的測試和驗證。

伺服器範圍

您可以將使用者選項伺服器配置設置設置為目前按位OR編輯的 64(的值ARITHABORT)。如果您不使用按位 OR ( |) 而是執行直接賦值 ( =),那麼您將清除已啟用的任何其他現有選項。

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 來完成,但是沒有允許更改這些設置的關鍵字。另一個文件暗示它可以通過客戶端網路配置/配置管理器進行更改,但這似乎也不可能。因此,不幸的是,您將需要SET ARITHABORT ON;手動執行。以下是一些需要考慮的方法:

如果您使用的是 Entity Framework 6(或更新版本),您可以嘗試:

理想情況下,這將在打開數據庫連接後執行一次,而不是每次查詢。

  • 通過以下任一方式創建攔截器:

    • DbConfiguration.AddInterceptor
    • DbInterception.Add這將允許您在執行 SQL 之前對其進行修改,在這種情況下,您可以簡單地為其添加前綴:SET ARITHABORT ON;。這裡的缺點是它將針對每個查詢,除非您儲存一個局部變數來擷取它是否已執行並每次測試的狀態(這實際上並沒有那麼多額外的工作,但使用ExecuteSqlCommandis可能更容易)。

其中任何一個都可以讓您在一個地方處理此問題,而無需更改任何現有程式碼。

ELSE,您可以創建一個執行此操作的包裝器方法,類似於:

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 的類,上述兩個想法都可以以更多的 OO 風格實現。

或者更好的是其他第 3 部分),您可以為 Connection StateChange 創建一個事件處理程序,並讓它在連接更改時設置屬性ClosedOpen如下所示:

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;到每個儲存過程的開頭:

    • 這需要對現有項目進行大量工作,尤其是隨著儲存過程數量的增加
    • 這無助於臨時查詢

選項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

我相信這裡最好的真正解決方案是簡單地編輯您的程式碼,因為它是錯誤的,任何其他修復都只是一種解決方法。

引用自:https://dba.stackexchange.com/questions/2500