Jadikan SqlClient sebagai default untuk ARITHABORT ON

32

Hal pertama yang pertama: Saya menggunakan MS SQL Server 2008 dengan database pada tingkat kompatibilitas 80, dan menghubungkannya dengan .Net System.Data.SqlClient.SqlConnection.

Untuk alasan kinerja, saya telah membuat tampilan yang diindeks. Akibatnya, pembaruan ke tabel yang direferensikan dalam tampilan perlu dilakukan ARITHABORT ON. Namun, profiler menunjukkan bahwa SqlClient terhubung ARITHABORT OFF, sehingga pembaruan ke tabel tersebut gagal.

Apakah ada pengaturan konfigurasi pusat untuk menggunakan SqlClient ARITHABORT ON? Yang terbaik yang bisa saya temukan adalah dengan mengeksekusi secara manual bahwa setiap kali koneksi dibuka, tetapi memperbarui basis kode yang ada untuk melakukan ini akan menjadi tugas yang cukup besar jadi saya ingin menemukan cara yang lebih baik.

Peter Taylor
sumber

Jawaban:

28

Pendekatan yang lebih disukai

Saya mendapat kesan bahwa yang berikut ini sudah diuji oleh orang lain, terutama berdasarkan beberapa komentar. Tetapi pengujian saya menunjukkan bahwa kedua metode ini memang bekerja pada tingkat DB, bahkan ketika menghubungkan melalui .NET SqlClient. Ini telah diuji dan diverifikasi oleh orang lain.

Seluruh server

Anda dapat mengatur pengaturan konfigurasi server opsi pengguna menjadi apa pun yang saat ini dilakukan dengan bit OR64 (nilai untuk ARITHABORT). Jika Anda tidak menggunakan bit-wise OR ( |) tetapi sebaliknya melakukan penugasan langsung ( =) maka Anda akan menghapus semua opsi lain yang sudah ada yang diaktifkan.

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

Tingkat basis data

Ini dapat diatur per-basis data melalui 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;

Pendekatan alternatif

Berita yang tidak begitu baik adalah bahwa saya telah melakukan banyak pencarian pada topik ini, hanya untuk menemukan bahwa selama bertahun-tahun banyak orang lain telah melakukan banyak pencarian pada topik ini, dan tidak ada cara untuk mengkonfigurasi perilaku dari SqlClient. Beberapa dokumentasi MSDN menyiratkan bahwa hal itu dapat dilakukan melalui ConnectionString, tetapi tidak ada kata kunci yang memungkinkan untuk mengubah pengaturan ini. Dokumen lain menyiratkan itu dapat diubah melalui Konfigurasi Jaringan / Manajer Konfigurasi Klien, tetapi itu sepertinya juga tidak mungkin. Karenanya, dan sayangnya, Anda harus menjalankannya SET ARITHABORT ON;secara manual. Berikut adalah beberapa cara untuk dipertimbangkan:

JIKA Anda menggunakan Entity Framework 6 (atau yang lebih baru), Anda dapat mencoba:

  • Gunakan Database.ExecuteSqlCommand : context.Database.ExecuteSqlCommand("SET ARITHABORT ON;");
    Idealnya ini akan dieksekusi sekali, setelah membuka koneksi DB, dan tidak per setiap permintaan.

  • Buat interseptor melalui:

    Ini akan memungkinkan Anda untuk memodifikasi SQL sebelum dijalankan, dalam hal ini Anda hanya dapat awalan dengan: SET ARITHABORT ON;. Kelemahan di sini adalah bahwa itu akan menjadi per setiap permintaan, kecuali Anda menyimpan variabel lokal untuk menangkap status apakah itu telah dieksekusi dan diuji untuk setiap kali (yang sebenarnya bukan pekerjaan ekstra, tetapi menggunakan ExecuteSqlCommandadalah mungkin lebih mudah).

Salah satu dari mereka akan memungkinkan Anda untuk menangani ini di satu tempat tanpa mengubah kode yang ada.

ELSE , Anda bisa membuat metode pembungkus yang melakukan ini, mirip dengan:

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

  return CommandToExec.ExecuteReader();
}

dan kemudian hanya mengubah _Reader = _Command.ExecuteReader();referensi saat ini menjadi _Reader = ExecuteReaderWithSetting(_Command);.

Melakukan hal ini juga memungkinkan pengaturan ditangani di satu lokasi sementara hanya memerlukan perubahan kode yang minimal dan sederhana yang sebagian besar dapat dilakukan melalui Temukan & Ganti.

Lebih baik lagi ( Lain Bagian 2), karena ini adalah pengaturan tingkat koneksi, itu tidak perlu dieksekusi per setiap panggilan SqlCommand.Execute __ (). Jadi alih-alih membuat pembungkus untuk ExecuteReader(), buat pembungkus untuk 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;
}

Dan kemudian ganti saja _Connection.Open();referensi yang ada menjadi OpenAndSetArithAbort(_Connection);.

Kedua ide di atas dapat diimplementasikan dengan gaya OO yang lebih banyak dengan membuat Kelas yang memperluas SqlCommand atau SqlConnection.

Atau Lebih Baik lagi ( Else Part 3), Anda bisa membuat event handler untuk Connection StateChange dan mengatur properti ketika koneksi berubah dari Closedmenjadi Opensebagai berikut:

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

Dengan itu, Anda hanya perlu menambahkan berikut ini ke setiap tempat di mana Anda membuat SqlConnectioninstance:

_Connection.StateChange += new StateChangeEventHandler(OnStateChange);

Tidak diperlukan perubahan kode yang ada. Saya baru saja mencoba metode ini di aplikasi konsol kecil, menguji dengan mencetak hasilnya SELECT SESSIONPROPERTY('ARITHABORT');. Itu kembali 1, tetapi jika saya menonaktifkan Event Handler, itu kembali 0.


Demi kelengkapan, berikut adalah beberapa hal yang tidak berfungsi (baik sama sekali atau tidak seefektif):

  • Pemicu Logon : Pemicu, bahkan ketika berjalan di sesi yang sama, dan bahkan jika berjalan dalam transaksi yang dimulai secara eksplisit, masih merupakan sub-proses dan karenanya pengaturannya ( SETperintah, tabel sementara lokal, dll.) Bersifat lokal untuk itu dan tidak bertahan akhir dari sub-proses itu.
  • Menambahkan SET ARITHABORT ON;ke awal setiap prosedur tersimpan:
    • ini membutuhkan banyak pekerjaan untuk proyek yang ada, terutama karena jumlah prosedur tersimpan meningkat
    • ini tidak membantu permintaan ad hoc
Solomon Rutzky
sumber
Saya baru saja mencoba membuat database sederhana dengan ARITHABORT dan ANSI_WARNINGS, membuat tabel dengan nol di dalamnya, dan klien .net sederhana untuk membacanya. The .net SqlClient ditampilkan sebagai pengaturan ARITHABORT off dan ANSI_WARNINGS dalam login di sql profiler, dan juga gagal kueri dengan pembagian dengan nol seperti yang diharapkan. Ini tampaknya menunjukkan bahwa solusi yang disukai dari pengaturan flag level db TIDAK akan berfungsi untuk mengubah default untuk .net SqlClient.
Mike
dapat mengkonfirmasi bahwa pengaturan user_options di seluruh server TIDAK berfungsi.
Mike
Saya juga mengamati bahwa dengan SELECT DATABASEPROPERTYEX('{database_name}', 'IsArithmeticAbortEnabled');mengembalikan 1, sys.dm_exec_sessions menunjukkan arithabort mati, meskipun saya tidak melihat SET eksplisit di Profiler. Mengapa ini terjadi?
andrew.rockwell
6

Pilihan 1

Selain dari solusi Sankar , pengaturan pengaturan pembatalan aritmatika di tingkat server untuk semua koneksi akan berfungsi:

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

Pada SQL 2014 disarankan untuk berada di semua koneksi:

Anda harus selalu mengatur ARITHABORT ke ON dalam sesi masuk Anda. Menetapkan ARITHABORT ke OFF dapat berdampak negatif terhadap pengoptimalan kueri yang mengarah ke masalah kinerja.

Jadi ini sepertinya akan menjadi solusi ideal.

pilihan 2

Jika opsi 1 tidak dapat dijalankan dan Anda menggunakan prosedur tersimpan untuk sebagian besar panggilan SQL Anda (yang seharusnya Anda lihat, Prosedur Tersimpan vs. SQL Inline ) maka cukup aktifkan opsi di setiap prosedur tersimpan yang relevan:

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

Saya percaya solusi nyata terbaik di sini adalah hanya mengedit kode Anda, karena itu salah dan perbaikan lainnya hanyalah solusi.

LowlyDBA
sumber
Saya tidak berpikir itu membantu pengaturan untuk SQL Server, ketika koneksi .net dimulai dengan set ArithAbort off. Saya berharap untuk sesuatu yang bisa dilakukan di sisi .net / C #. Saya memasang hadiah, karena saya sudah melihat rekomendasi.
Henrik Staun Poulsen
1
Sisi .net / C # adalah apa yang dicakup Sankar, jadi ini adalah satu-satunya pilihan.
LowlyDBA
Saya mencoba Opsi 1 dan itu tidak berpengaruh. Sesi baru masih menunjukkan memiliki arithabort = 0. Saya tidak memiliki masalah dari itu, hanya mencoba untuk mengatasi masalah potensial.
Mark Freeman
4

Saya BUKAN ahli di sini tetapi Anda dapat mencoba sesuatu seperti di bawah ini.

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/

Sankar Reddy
sumber
Ya, itulah yang saya maksud dengan menjalankannya secara manual. Masalahnya adalah bahwa basis kode saya bekerja dengan telah menimbulkan cukup banyak hutang teknis ketika datang ke lapisan akses DB, jadi saya harus refactor beberapa ratus metode untuk melakukannya dengan cara ini.
Peter Taylor
2

Tidak ada pengaturan untuk memaksa SqlClient untuk selalu mengaktifkan ARITHABORT, Anda harus mengatur ini seperti yang Anda jelaskan.

Menariknya dari dokumentasi Microsoft untuk SET ARITHABORT : -

Anda harus selalu mengatur ARITHABORT ke ON dalam sesi masuk Anda. Menetapkan ARITHABORT ke OFF dapat berdampak negatif terhadap pengoptimalan kueri yang mengarah ke masalah kinerja.

Namun koneksi .Net adalah hard-coded untuk menonaktifkannya secara default?

Sebagai poin lain, Anda harus sangat berhati-hati ketika mendiagnosis masalah kinerja dengan pengaturan ini. Pilihan set yang berbeda akan menghasilkan rencana kueri yang berbeda untuk kueri yang sama. Kode .Net Anda dapat mengalami masalah kinerja (SET ARITHABORT OFF) namun saat Anda menjalankan kueri TSQL yang sama di SSMS (SET ARITHABORT ON ON secara default), mungkin tidak masalah. Ini karena. Kueri rencana tidak akan digunakan kembali dan rencana baru dihasilkan. Ini berpotensi menghilangkan masalah mengendus parameter misalnya dan memberikan kinerja yang jauh lebih baik.

Andy Jones
sumber
1
@HenrikStaunPoulsen - Kecuali Anda terjebak menggunakan 2000 (atau 2000 tingkat kompatibilitas) itu tidak ada bedanya. Ini tersirat ANSI_WARNINGSdi dalam versi yang lebih baru dan hal-hal seperti tampilan yang diindeks berfungsi dengan baik.
Martin Smith
Perhatikan bahwa. Net tidak dikodekan untuk menonaktifkan ARITHABORT. SSMS default untuk pengaturan pada . Net hanya menghubungkan dan menggunakan standar server / database. Anda dapat menemukan masalah di MS Connect tentang pengguna yang mengeluh tentang perilaku default SSMS. Catat peringatan di halaman dokumen ARITHABORT .
Bacon Bits
2

Jika ini menghemat waktu, dalam kasus saya (Entity Framework Core 2.0.3, ASP.Net Core API, SQL Server 2008 R2):

  1. Tidak ada Interceptors di EF Core 2.0 (saya pikir mereka akan segera tersedia di 2.1)
  2. Tidak mengubah pengaturan DB global atau pengaturan user_optionsitu dapat diterima untuk saya (mereka DO bekerja - saya diuji) tetapi saya tidak bisa mengambil risiko mempengaruhi aplikasi lain.

Permintaan ad-hoc dari EF Core, dengan SET ARITHABORT ON;di bagian atas, TIDAK berfungsi.

Akhirnya, solusi yang bekerja untuk saya adalah: Menggabungkan prosedur tersimpan, disebut sebagai kueri mentah dengan SETopsi sebelum EXECdipisahkan oleh tanda titik koma, seperti ini:

// C# EF Core
int result = _context.Database.ExecuteSqlCommand($@"
SET ARITHABORT ON;
EXEC MyUpdateTableStoredProc
             @Param1 = {value1}
");
Chris Amelinckx
sumber
Menarik. Terima kasih telah memposting nuansa bekerja dengan EF Core ini. Hanya ingin tahu: apakah yang Anda lakukan di sini pada dasarnya opsi pembungkus yang saya sebutkan di sub-bagian ELSE dari bagian Pendekatan alternatif dalam jawaban saya? Saya hanya ingin tahu karena Anda menyebutkan saran lain dalam jawaban saya baik tidak berfungsi atau tidak layak karena kendala lain, tetapi tidak menyebutkan opsi pembungkus.
Solomon Rutzky
@SolomonRutzky sama dengan opsi itu, dengan nuansa bahwa itu terbatas untuk menjalankan prosedur yang tersimpan. Dalam kasus saya jika saya awali kueri pembaruan mentah dengan SET OPTION (secara manual atau melalui pembungkus) itu tidak berfungsi. Jika saya meletakkan SET OPTION di dalam prosedur tersimpan, itu tidak berfungsi. Satu-satunya cara adalah melakukan SET OPTION diikuti oleh prosedur tersimpan EXEC dalam batch yang sama. Itulah cara saya memilih untuk menyesuaikan panggilan khusus daripada membuat pembungkus. Kami akan segera memperbarui ke SQLServer 2016 dan saya dapat membereskannya. Terima kasih atas jawaban Anda, jika bermanfaat untuk membuang skenario tertentu.
Chris Amelinckx
0

Membangun jawaban Solomon Rutzy , untuk 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)
        ...

Ini menggunakan System.Data.Common's DbCommandbukan SqlCommand, dan DbConnectionbukan SqlConnection.

Jejak SQL Profiler mengkonfirmasi, SET ARITHABORT ONdikirim ketika koneksi terbuka, sebelum perintah lain dijalankan dalam transaksi.

CapNCook
sumber