ทำให้ SqlClient เป็นค่าเริ่มต้นเป็น ARITHABORT ON


32

สิ่งแรกแรก: ผมใช้ MS SQL Server 2008 ด้วยฐานข้อมูลที่ระดับความเข้ากันได้ 80 System.Data.SqlClient.SqlConnectionและการเชื่อมต่อกับสุทธิของ

สำหรับเหตุผลด้านประสิทธิภาพฉันได้สร้างมุมมองที่จัดทำดัชนีแล้ว ARITHABORT ONเป็นผลให้การปรับปรุงตารางการอ้างอิงในมุมมองความจำเป็นที่จะต้องทำด้วย อย่างไรก็ตามผู้สร้างโปรไฟล์แสดงว่า SqlClient เชื่อมต่ออยู่ARITHABORT OFFดังนั้นการอัพเดทตารางเหล่านั้นจึงล้มเหลว

มีการตั้งค่ากลางเพื่อให้ SqlClient ใช้งานARITHABORT ONหรือไม่ สิ่งที่ดีที่สุดที่ฉันสามารถค้นหาได้คือการดำเนินการด้วยตนเองทุกครั้งที่มีการเปิดการเชื่อมต่อ แต่การอัปเดตฐานรหัสที่มีอยู่เพื่อทำสิ่งนี้จะเป็นงานที่ค่อนข้างใหญ่ดังนั้นฉันจึงกระตือรือร้นที่จะหาวิธีที่ดีกว่า

คำตอบ:


28

วิธีที่ต้องการดูเหมือน

ฉันรู้สึกว่าสิ่งต่อไปนี้ผ่านการทดสอบแล้วโดยผู้อื่นโดยเฉพาะอย่างยิ่งจากความคิดเห็นบางส่วน แต่การทดสอบของฉันแสดงให้เห็นว่าทั้งสองวิธีทำแน่นอนการทำงานในระดับ DB แม้ในขณะที่เชื่อมต่อผ่าน SqlClient.NET สิ่งเหล่านี้ได้รับการทดสอบและตรวจสอบโดยผู้อื่น

เซิร์ฟเวอร์กว้าง

คุณสามารถตั้งค่าการกำหนดค่าเซิร์ฟเวอร์ตัวเลือกผู้ใช้ให้เป็นแบบบิตที่ชาญฉลาดORด้วย 64 (ค่าสำหรับARITHABORT) หากคุณไม่ได้ใช้ bit-wise หรือ ( |) แต่ทำการมอบหมายแบบตรง ( =) คุณจะลบตัวเลือกอื่น ๆ ที่เปิดใช้งานอยู่

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 (หรือใหม่กว่า) คุณสามารถลองทำสิ่งใดสิ่งหนึ่งต่อไปนี้:

  • ใช้Database.ExecuteSqlCommand : โดยหลักแล้วcontext.Database.ExecuteSqlCommand("SET ARITHABORT ON;");
    สิ่งนี้จะถูกเรียกใช้งานครั้งเดียวหลังจากเปิดการเชื่อมต่อฐานข้อมูลและไม่ต่อแต่ละแบบสอบถาม

  • สร้างinterceptorผ่าน:

    นี้จะช่วยให้คุณสามารถปรับเปลี่ยน 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);

การทำเช่นนี้ยังช่วยให้สามารถจัดการการตั้งค่าในสถานที่เดียวในขณะที่ต้องการการเปลี่ยนแปลงรหัสขั้นต่ำและง่ายที่สุดที่สามารถทำได้ผ่านการค้นหาและแทนที่

ยังดีกว่า ( อื่น ๆส่วนที่ 2) เนื่องจากนี่เป็นการตั้งค่าระดับการเชื่อมต่อจึงไม่จำเป็นต้องดำเนินการสำหรับการเรียก SqlCommand แต่ละครั้งเรียกใช้ __ () ดังนั้นแทนที่จะสร้างเสื้อคลุมสำหรับ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);

แนวคิดทั้งสองข้างต้นสามารถนำไปใช้ในรูปแบบ OO ได้มากขึ้นโดยการสร้าง Class ที่ขยาย SqlCommand หรือ SqlConnection

หรือยังดีกว่า ( อื่น ๆส่วนที่ 3) คุณสามารถสร้างตัวจัดการเหตุการณ์สำหรับการเชื่อมต่อ 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มา แต่ถ้าฉันปิดการใช้งาน Event Handler มันจะกลับ0มา


เพื่อความสมบูรณ์นี่คือบางสิ่งที่ไม่สามารถใช้งานได้ (อย่างใดอย่างหนึ่งหรือไม่ได้ผล):

  • การเข้าสู่ระบบทริกเกอร์ : ทริกเกอร์แม้ในขณะที่ทำงานในเซสชั่นเดียวกันและแม้ว่าการทำงานภายในทรานแซคชันที่เริ่มต้นอย่างชัดเจนยังคงเป็นกระบวนการย่อยและด้วยเหตุนี้การตั้งค่า ( SETคำสั่งตารางชั่วคราวในท้องถิ่น ฯลฯ ) จุดสิ้นสุดของกระบวนการย่อยนั้น
  • การเพิ่มSET ARITHABORT ON;ไปยังจุดเริ่มต้นของแต่ละโพรซีเดอร์ที่เก็บไว้:
    • สิ่งนี้ต้องการงานจำนวนมากสำหรับโครงการที่มีอยู่โดยเฉพาะอย่างยิ่งเมื่อจำนวนขั้นตอนการจัดเก็บเพิ่มขึ้น
    • สิ่งนี้ไม่ได้ช่วยในการค้นหาเฉพาะกิจ

ฉันเพิ่งทดสอบการสร้างฐานข้อมูลอย่างง่ายด้วย ARITHABORT และ ANSI_WARNINGS ทั้งคู่ปิดสร้างตารางที่มีศูนย์ในนั้นและไคลเอนต์. net แบบธรรมดาที่จะอ่านจากมัน .net SqlClient แสดงว่าเป็นการตั้งค่า ARITHABORT และ ANSI_WARNINGS เปิดในการเข้าสู่ระบบใน sql profiler และล้มเหลวในการสืบค้นด้วยการหารด้วยศูนย์ตามที่คาดไว้ สิ่งนี้ดูเหมือนว่าจะแสดงให้เห็นว่าทางออกที่ต้องการของการตั้งค่าสถานะระดับ db จะไม่ทำงานสำหรับการเปลี่ยนค่าเริ่มต้นสำหรับ. net SqlClient
Mike

สามารถยืนยันได้ว่าการตั้งค่า user_options ทั่วทั้งเซิร์ฟเวอร์ทำงานได้อย่างไร
Mike

ฉันยังสังเกตเห็นว่าเมื่อSELECT DATABASEPROPERTYEX('{database_name}', 'IsArithmeticAbortEnabled');ส่งคืน 1 sys.dm_exec_sessions จะแสดง arithabort off แม้ว่าฉันจะไม่เห็น SETs ใด ๆ ที่ชัดเจนใน 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 เป็นปิดอาจส่งผลเสียต่อการเพิ่มประสิทธิภาพการค้นหาที่นำไปสู่ปัญหาด้านประสิทธิภาพ

ดังนั้นนี่จึงเป็นทางออกที่ดี

ตัวเลือก 2

หากตัวเลือกที่ 1 ไม่สามารถใช้งานได้และคุณใช้กระบวนงานที่เก็บไว้สำหรับการโทร SQL ส่วนใหญ่ของคุณ (ซึ่งคุณควรดูขั้นตอนการจัดเก็บเทียบกับ Inline SQL ) จากนั้นเปิดใช้งานตัวเลือกในแต่ละขั้นตอนการจัดเก็บที่เกี่ยวข้อง

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

ฉันเชื่อว่าทางออกที่แท้จริงที่ดีที่สุดในที่นี้คือแก้ไขโค้ดของคุณเพราะมันผิดและการแก้ไขอื่น ๆ ก็เป็นเพียงวิธีแก้ปัญหาเท่านั้น


ฉันไม่คิดว่ามันจะช่วยให้การตั้งค่าไว้ในเซิร์ฟเวอร์ SQL เมื่อการเชื่อมต่อ .net set ArithAbort offเริ่มออกด้วย ฉันหวังว่าจะมีบางอย่างที่สามารถทำได้ในด้าน. net / C # ฉันใส่เงินรางวัลเพราะฉันเห็นคำแนะนำ
Henrik Staun Poulsen

1
ด้าน. net / C # คือสิ่งที่ครอบคลุมของ Sankar ดังนั้นสิ่งเหล่านี้จึงเป็นเพียงทางเลือกเดียว
LowlyDBA

ฉันลองตัวเลือก 1 และมันไม่มีผลกระทบ เซสชั่นใหม่ยังคงแสดงว่ามี arithabort = 0 ฉันไม่มีปัญหาใด ๆ จากมันเพียงแค่พยายามที่จะนำหน้าปัญหาที่อาจเกิดขึ้น
Mark Freeman

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

Ref: http://social.msdn.microsoft.com/Forums/en-US/transactsql/thread/d9e3e8ba-4948-4419-bb6b-dd5208bd7547/


ใช่นั่นคือสิ่งที่ฉันหมายถึงโดยการดำเนินการด้วยตนเอง สิ่งหนึ่งคือรหัสฐานข้อมูลที่ฉันทำงานด้วยมีหนี้ทางเทคนิคเพิ่มขึ้นเล็กน้อยเมื่อพูดถึงเลเยอร์การเข้าถึงฐานข้อมูลดังนั้นฉันจะต้องปรับวิธีการสองสามร้อยวิธีนี้
Peter Taylor

2

ไม่มีการตั้งค่าให้บังคับให้ SqlClient ตั้งค่า ARITHABORT เสมอคุณต้องตั้งค่านี้ตามที่คุณอธิบาย

ที่น่าสนใจจากเอกสารของ Microsoft สำหรับ SET ARITHABORT : -

คุณควรตั้งค่า ARITHABORT เป็น ON ในเซสชันการเข้าสู่ระบบของคุณเสมอ การตั้งค่า ARITHABORT เป็นปิดอาจส่งผลเสียต่อการเพิ่มประสิทธิภาพการค้นหาที่นำไปสู่ปัญหาด้านประสิทธิภาพ

และการเชื่อมต่อ. net นั้นยากที่จะตั้งรหัสนี้โดยปริยาย?

อีกจุดหนึ่งคุณจะต้องระมัดระวังเป็นอย่างมากเมื่อวินิจฉัยปัญหาด้านประสิทธิภาพด้วยการตั้งค่านี้ ตัวเลือกชุดที่แตกต่างกันจะส่งผลให้เกิดแผนการสอบถามที่แตกต่างกันสำหรับแบบสอบถามเดียวกัน รหัส. Net ของคุณอาจประสบปัญหาด้านประสิทธิภาพ (SET ARITHABORT OFF) และเมื่อคุณเรียกใช้แบบสอบถาม TSQL เดียวกันใน SSMS (SET ARITHABORT ON เป็นค่าเริ่มต้น) อาจเป็นเรื่องปกติ นี่เป็นเพราะแผนแบบสอบถาม. Net จะไม่ถูกนำมาใช้ซ้ำและสร้างแผนใหม่ สิ่งนี้อาจช่วยกำจัดปัญหาการดมพารามิเตอร์และให้ประสิทธิภาพที่ดีขึ้นมาก


1
@HenrikStaunPoulsen - ยกเว้นว่าคุณติดขัดโดยใช้ระดับความเข้ากันได้ของ 2000 (หรือระดับความเข้ากันได้ 2000) แต่ก็ไม่ได้สร้างความแตกต่างอะไรเลย โดยนัยANSI_WARNINGSในรุ่นที่ใหม่กว่าและสิ่งต่าง ๆ เช่นมุมมองที่จัดทำดัชนีทำงานได้ดี
Martin Smith

โปรดทราบว่า. Net ไม่ได้รับการกำหนดรหัสตายตัวเพื่อตั้งค่า ARITHABORT SSMSเริ่มต้นที่การตั้งค่าบน . Net เป็นเพียงการเชื่อมต่อและใช้ค่าเริ่มต้นเซิร์ฟเวอร์ / ฐานข้อมูล คุณสามารถค้นหาปัญหาใน MS Connect เกี่ยวกับผู้ใช้ที่บ่นเกี่ยวกับพฤติกรรมเริ่มต้นของ SSMS หมายเหตุ: คำเตือนบนหน้า doc ARITHABORT ที่
เบคอนบิต

2

ถ้ามันช่วยให้ใครบางคนในเวลาของฉันในกรณีของฉัน (Entity Framework Core 2.0.3, ASP.Net Core API, SQL Server 2008 R2):

  1. ไม่มี Interceptors ใน 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 เพียงแค่อยากรู้อยากเห็น: เป็นสิ่งที่คุณกำลังทำอะไรที่นี่เป็นหลักตัวเลือกกระดาษห่อที่ผมกล่าวถึงในส่วนอื่นส่วนย่อยของสลับวิธีส่วนในคำตอบของฉันได้อย่างไร ฉันแค่สงสัยเพราะคุณพูดถึงคำแนะนำอื่น ๆ ในคำตอบของฉันไม่ทำงานหรือไม่ทำงานเนื่องจากข้อ จำกัด อื่น ๆ แต่ไม่ได้พูดถึงตัวเลือกเสื้อคลุม
โซโลมอน Rutzky

@ SolomonRutzky เทียบเท่ากับตัวเลือกนั้นโดยมีความแตกต่างกันนิดหน่อยว่าจะถูก จำกัด ให้ดำเนินการตามขั้นตอนที่เก็บไว้ ในกรณีของฉันถ้าฉันนำหน้าเคียวรีการอัพเดตแบบ raw ด้วย SET OPTION (ด้วยตนเองหรือผ่าน wrapper) มันไม่ทำงาน หากฉันวาง SET OPTION ไว้ในกระบวนงานที่เก็บไว้มันไม่ทำงาน วิธีเดียวคือการทำ SET OPTION ตามด้วย EXEC ที่เก็บไว้ในชุดเดียวกัน ซึ่งเป็นวิธีที่ฉันเลือกที่จะปรับแต่งการโทรที่เฉพาะเจาะจงแทนการทำเสื้อคลุม ในไม่ช้าเราจะอัปเดตเป็น SQLServer 2016 และฉันจะล้างข้อมูลนี้ขอบคุณสำหรับคำตอบของคุณหากเป็นประโยชน์ในการทิ้งสถานการณ์ที่เฉพาะเจาะจง
Chris Amelinckx

0

อาคารในคำตอบของซาโลมอน Rutzyสำหรับ 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และแทนDbConnectionSqlConnection

การติดตามของผู้สร้างโปรไฟล์ SQL ยืนยันSET ARITHABORT ONจะถูกส่งเมื่อการเชื่อมต่อเปิดขึ้นก่อนที่คำสั่งอื่น ๆ จะถูกดำเนินการในการทำธุรกรรม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.