Đặt SqlClient mặc định thành ARITHABORT ON


32

Điều đầu tiên trước tiên: Tôi đang sử dụng MS SQL Server 2008 với cơ sở dữ liệu ở mức tương thích 80 và kết nối với nó với .Net System.Data.SqlClient.SqlConnection.

Vì lý do hiệu suất, tôi đã tạo một chế độ xem được lập chỉ mục. Do đó, các cập nhật cho các bảng được tham chiếu trong khung nhìn cần phải được thực hiện ARITHABORT ON. Tuy nhiên, trình hồ sơ cho thấy SqlClient đang kết nối với ARITHABORT OFF, vì vậy các cập nhật cho các bảng đó đều thất bại.

Có một cài đặt cấu hình trung tâm để sử dụng SqlClient ARITHABORT ONkhông? Điều tốt nhất tôi có thể tìm thấy là thực hiện thủ công rằng mỗi khi kết nối được mở, nhưng cập nhật cơ sở mã hiện có để thực hiện điều này sẽ là một nhiệm vụ khá lớn vì vậy tôi rất muốn tìm một cách tốt hơn.

Câu trả lời:


28

Dường như cách tiếp cận ưa thích

Tôi có ấn tượng rằng những điều sau đây đã được kiểm tra bởi những người khác, đặc biệt là dựa trên một số ý kiến. Nhưng thử nghiệm của tôi cho thấy hai phương thức này thực sự hoạt động ở cấp độ DB, ngay cả khi kết nối qua .NET SqlClient. Những người này đã được kiểm tra và xác minh bởi những người khác.

Toàn máy chủ

Bạn có thể đặt cài đặt cấu hình máy chủ tùy chọn người dùng thành bất cứ thứ gì hiện đang được chỉnh sửa bit ORvới 64 (giá trị cho ARITHABORT). Nếu bạn không sử dụng bit OR ( |) mà thay vào đó hãy thực hiện một phép gán thẳng ( =) thì bạn sẽ xóa sạch mọi tùy chọn hiện có khác đã được bật.

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

Cấp cơ sở dữ liệu

Điều này có thể được đặt cho mỗi cơ sở dữ liệu thông qua 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;

Phương pháp thay thế

Một tin không vui là tôi đã tìm kiếm rất nhiều về chủ đề này, chỉ để thấy rằng trong nhiều năm qua, rất nhiều người khác đã tìm kiếm rất nhiều về chủ đề này và không có cách nào để định cấu hình hành vi của SqlClient. Một số tài liệu MSDN ngụ ý rằng nó có thể được thực hiện thông qua ConnectionString, nhưng không có Từ khóa nào cho phép thay đổi các cài đặt này. Một tài liệu khác ngụ ý nó có thể được thay đổi thông qua Trình quản lý cấu hình / cấu hình mạng máy khách, nhưng điều đó dường như cũng không thể. Do đó, và thật không may, bạn sẽ cần phải thực hiện SET ARITHABORT ON;thủ công. Dưới đây là một số cách để xem xét:

NẾU bạn đang sử dụng Entity Framework 6 (hoặc mới hơn), bạn có thể thử:

  • Sử dụng cơ sở dữ liệu.ExecuteSqlCommand : context.Database.ExecuteSqlCommand("SET ARITHABORT ON;");
    Lý tưởng nhất là việc này sẽ được thực hiện một lần, sau khi mở kết nối DB và không phải cho mỗi truy vấn.

  • Tạo một thiết bị đánh chặn thông qua một trong hai:

    Điều này sẽ cho phép bạn sửa đổi SQL trước khi nó được thực thi, trong trường hợp đó bạn có thể chỉ cần thêm tiền tố vào : SET ARITHABORT ON;. Nhược điểm ở đây là nó sẽ theo từng truy vấn, trừ khi bạn lưu trữ một biến cục bộ để nắm bắt trạng thái của nó có được thực thi hay không và kiểm tra cho mỗi lần đó (thực sự không phải là nhiều công việc phụ, nhưng sử dụng ExecuteSqlCommandlà có lẽ dễ dàng hơn).

Một trong hai sẽ cho phép bạn xử lý việc này tại một điểm mà không thay đổi bất kỳ mã hiện có nào.

ELSE , bạn có thể tạo một phương thức trình bao bọc thực hiện điều này, tương tự như:

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

  return CommandToExec.ExecuteReader();
}

và sau đó chỉ cần thay đổi các _Reader = _Command.ExecuteReader();tài liệu tham khảo hiện tại để được _Reader = ExecuteReaderWithSetting(_Command);.

Làm điều này cũng cho phép xử lý cài đặt ở một vị trí trong khi chỉ yêu cầu thay đổi mã đơn giản và tối thiểu có thể được thực hiện chủ yếu thông qua Tìm & Thay thế.

Hơn thế nữa ( khác Phần 2), vì đây là một thiết lập mức độ kết nối, nó không cần phải được thực hiện cho mỗi SqlCommand.Execute __ () gọi. Vì vậy, thay vì tạo một trình bao bọc cho ExecuteReader(), hãy tạo một trình bao bọc cho 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;
}

Và sau đó chỉ cần thay thế các _Connection.Open();tài liệu tham khảo hiện có OpenAndSetArithAbort(_Connection);.

Cả hai ý tưởng trên đều có thể được thực hiện theo phong cách OO hơn bằng cách tạo ra một Lớp mở rộng SqlCommand hoặc SqlConnection.

Hoặc Hơn thế nữa ( khác Phần 3), bạn có thể tạo một event handler cho Connection StateChange và có nó thiết lập thuộc tính khi thay đổi kết nối từ Closedđể Opennhư sau:

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

Với vị trí đó, bạn chỉ cần thêm các mục sau vào mỗi nơi bạn tạo một SqlConnectionthể hiện:

_Connection.StateChange += new StateChangeEventHandler(OnStateChange);

Không có thay đổi đối với mã hiện tại là cần thiết. Tôi vừa thử phương pháp này trong một ứng dụng bảng điều khiển nhỏ, thử nghiệm bằng cách in kết quả SELECT SESSIONPROPERTY('ARITHABORT');. Nó trả về 1, nhưng nếu tôi vô hiệu hóa Trình xử lý sự kiện, nó sẽ trả về 0.


Để hoàn thiện, đây là một số điều không hoạt động (hoàn toàn hoặc không hiệu quả):

  • Logon Triggers : Triggers, ngay cả khi đang chạy trong cùng một phiên và ngay cả khi đang chạy trong một giao dịch được bắt đầu rõ ràng, vẫn là một quy trình phụ và do đó các cài đặt của nó ( SETcác lệnh, bảng tạm thời, v.v.) là cục bộ và không tồn tại kết thúc quá trình phụ đó.
  • Thêm SET ARITHABORT ON;vào đầu mỗi thủ tục được lưu trữ:
    • điều này đòi hỏi rất nhiều công việc cho các dự án hiện có, đặc biệt là khi số lượng thủ tục lưu trữ tăng lên
    • điều này không giúp truy vấn ad hoc

Tôi vừa thử nghiệm tạo một cơ sở dữ liệu đơn giản với cả ARITHABORT và ANSI_WARNINGS, đã tạo một bảng có số 0 trong đó và một máy khách .net đơn giản để đọc từ nó. .Net SqlClient hiển thị khi tắt ARITHABORT và bật ANSI_WARNINGS trong thông tin đăng nhập trong hồ sơ sql, và cũng không thể truy vấn với số chia cho 0 như mong đợi. Điều này dường như cho thấy rằng giải pháp ưa thích của việc thiết lập các cờ mức db sẽ KHÔNG hoạt động để thay đổi mặc định cho .net SqlClient.
Mike

tuy nhiên có thể xác nhận rằng cài đặt user_options trên toàn máy chủ hoạt động.
Mike

Tôi cũng đang quan sát rằng với việc SELECT DATABASEPROPERTYEX('{database_name}', 'IsArithmeticAbortEnabled');trả về 1, sys.dm_exec_sments hiển thị tắt arithabort, mặc dù tôi không thấy bất kỳ SET rõ ràng nào trong Profiler. Tại sao điều này sẽ được?
andrew.rockwell

6

lựa chọn 1

Ngoài giải pháp của Sankar , cài đặt hủy bỏ số học ở cấp máy chủ cho tất cả các kết nối sẽ hoạt động:

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

Kể từ SQL 2014, bạn nên bật tất cả các kết nối:

Bạn phải luôn luôn đặt ARITHABORT thành BẬT trong các phiên đăng nhập của mình. Đặt ARITHABORT thành TẮT có thể tác động tiêu cực đến tối ưu hóa truy vấn dẫn đến các vấn đề về hiệu suất.

Vì vậy, đây dường như là giải pháp lý tưởng.

Lựa chọn 2

Nếu tùy chọn 1 không khả thi và bạn sử dụng các quy trình được lưu trữ cho hầu hết các cuộc gọi SQL của mình (mà bạn nên, hãy xem Thủ tục được lưu trữ so với SQL nội tuyến ), sau đó chỉ cần bật tùy chọn trong mỗi quy trình được lưu trữ có liên quan:

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

Tôi tin rằng giải pháp thực sự tốt nhất ở đây là chỉ cần chỉnh sửa mã của bạn, vì nó sai và bất kỳ sửa chữa nào khác chỉ là một cách giải quyết.


Tôi không nghĩ rằng nó giúp thiết lập nó cho SQL Server, khi kết nối .net bắt đầu với set ArithAbort off. Tôi đã hy vọng điều gì đó có thể được thực hiện ở phía .net / C #. Tôi đưa tiền thưởng lên, vì tôi đã thấy khuyến nghị.
Henrik Staun Poulsen 10/03/2015

1
Phía .net / C # là những gì mà Sankar đề cập, vì vậy đây là những lựa chọn duy nhất.
LowlyDBA

Tôi đã thử lựa chọn 1 và nó không có hiệu quả. Các phiên mới vẫn hiển thị là có arithabort = 0. Tôi không gặp vấn đề gì với nó, chỉ cố gắng vượt qua các vấn đề tiềm ẩn.
Đánh dấu Freeman

4

Tôi KHÔNG phải là một chuyên gia ở đây nhưng bạn có thể thử một cái gì đó như dưới đây.

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

Tham chiếu: http://social.msdn.microsoft.com/Forums/en-US/transactsql/thread/d9e3e8ba-4948-4419-bb6b-dd5208bd7547/


Vâng, đó là những gì tôi có nghĩa là bằng cách thực hiện nó bằng tay. Vấn đề là cơ sở mã mà tôi đang làm việc đã tích lũy khá nhiều nợ kỹ thuật khi nói đến lớp truy cập DB, vì vậy tôi sẽ phải cấu trúc lại vài trăm phương thức để thực hiện theo cách này.
Peter Taylor

2

Không có cài đặt nào để buộc SqlClient luôn đặt ARITHABORT, bạn phải đặt cài đặt này như bạn mô tả.

Thú vị từ tài liệu của Microsoft cho SET ARITHABORT : -

Bạn phải luôn luôn đặt ARITHABORT thành BẬT trong các phiên đăng nhập của mình. Đặt ARITHABORT thành TẮT có thể tác động tiêu cực đến tối ưu hóa truy vấn dẫn đến các vấn đề về hiệu suất.

Tuy nhiên, kết nối .Net được mã hóa cứng để tắt mặc định này?

Như một điểm khác, bạn phải rất cẩn thận khi chẩn đoán các vấn đề về hiệu suất với cài đặt này. Các tùy chọn thiết lập khác nhau sẽ dẫn đến các gói truy vấn khác nhau cho cùng một truy vấn. Mã .Net của bạn có thể gặp sự cố về hiệu suất (TẮT ARITHABORT TẮT) và khi bạn chạy cùng một truy vấn TSQL trong SSMS (mặc định SET ARITHABORT ON) thì có thể ổn. Điều này là do gói truy vấn .Net sẽ không được sử dụng lại và một gói mới được tạo. Điều này có khả năng có thể loại bỏ một vấn đề đánh hơi tham số chẳng hạn và cho hiệu suất tốt hơn nhiều.


1
@HenrikStaunPoulsen - Trừ khi bạn bị mắc kẹt khi sử dụng 2000 (hoặc mức độ tương thích 2000), nó sẽ không tạo ra bất kỳ sự khác biệt nào. Nó được ngụ ý bởi ANSI_WARNINGStrong các phiên bản sau và những thứ như chế độ xem được lập chỉ mục hoạt động tốt.
Martin Smith

Lưu ý rằng .Net không được mã hóa cứng để tắt ARITHABORT. SSMS mặc định để cài đặt nó trên . .Net chỉ kết nối và sử dụng mặc định của máy chủ / cơ sở dữ liệu. Bạn có thể tìm thấy các sự cố trên MS Connect về việc người dùng phàn nàn về hành vi mặc định của SSMS. Lưu ý cảnh báo trên trang tài liệu ARITHABORT .
Bacon Bits

2

Nếu nó giúp mọi người tiết kiệm thời gian, trong trường hợp của tôi (Entity Framework Core 2.0.3, API ASP.Net Core, SQL Server 2008 R2):

  1. Không có Thiết bị chặn trên EF Core 2.0 (Tôi nghĩ rằng chúng sẽ sớm được cung cấp vào ngày 2.1)
  2. Không thay đổi cài đặt DB toàn cầu cũng như cài đặt user_optionschấp nhận được đối với tôi (chúng KHÔNG hoạt động - Tôi đã thử nghiệm) nhưng tôi không thể mạo hiểm ảnh hưởng đến các ứng dụng khác.

Một truy vấn đặc biệt từ EF Core, SET ARITHABORT ON;ở trên cùng, KHÔNG hoạt động.

Cuối cùng, giải pháp hiệu quả với tôi là: Kết hợp một thủ tục được lưu trữ, được gọi là truy vấn thô với SETtùy chọn trước khi được EXECphân tách bằng dấu chấm phẩy, như sau:

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

Hấp dẫn. Cảm ơn bạn đã đăng những sắc thái làm việc với EF Core. Chỉ tò mò: những gì bạn đang làm ở đây về cơ bản là tùy chọn trình bao bọc mà tôi đã đề cập trong phần phụ ELSE của phần Phương pháp thay thế trong câu trả lời của tôi? Tôi chỉ tự hỏi bởi vì bạn đã đề cập đến các đề xuất khác trong câu trả lời của tôi hoặc không hoạt động hoặc không khả thi do các ràng buộc khác, nhưng không đề cập đến tùy chọn trình bao bọc.
Solomon Rutzky

@SolomonRutzky nó tương đương với tùy chọn đó, với sắc thái là nó bị giới hạn trong việc thực hiện một thủ tục được lưu trữ. Trong trường hợp của tôi, nếu tôi đặt tiền tố một truy vấn cập nhật thô với TÙY CHỌN (bằng tay hoặc thông qua trình bao bọc) thì nó không hoạt động. Nếu tôi đặt TÙY CHỌN bên trong quy trình được lưu trữ, nó sẽ không hoạt động. Cách duy nhất là thực hiện TÙY CHỌN theo quy trình được lưu trữ EXEC trong cùng một đợt. Đó là cách tôi đã chọn để tùy chỉnh cuộc gọi cụ thể thay vì thực hiện trình bao bọc. Chúng tôi sẽ sớm cập nhật lên SQLServer 2016 và tôi có thể xóa phần này. Cảm ơn câu trả lời của bạn, nếu hữu ích để loại bỏ các tình huống cụ thể.
Chris Amelinckx

0

Dựa trên câu trả lời của Solomon Rutzy , cho 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)
        ...

Sử dụng này System.Data.CommonDbCommandthay vì SqlCommand, và DbConnectionthay vì SqlConnection.

Theo dõi SQL Profiler xác nhận, SET ARITHABORT ONđược gửi khi kết nối mở, trước khi bất kỳ lệnh nào khác được thực thi trong giao dịch.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.