Définir SqlClient par défaut sur ARITHABORT ON


32

Tout d'abord: j'utilise MS SQL Server 2008 avec une base de données au niveau de compatibilité 80 et je me connecte avec .Net System.Data.SqlClient.SqlConnection.

Pour des raisons de performances, j'ai créé une vue indexée. Par conséquent, les mises à jour des tables référencées dans la vue doivent être effectuées avecARITHABORT ON . Cependant, le profileur montre que SqlClient se connecte avecARITHABORT OFF , donc les mises à jour de ces tables échouent.

Existe-t-il un paramètre de configuration central pour utiliser SqlClient ARITHABORT ON? Le meilleur que j'ai pu trouver est d'exécuter manuellement chaque fois qu'une connexion est ouverte, mais la mise à jour de la base de code existante pour ce faire serait une tâche assez importante, donc je suis impatient de trouver une meilleure façon.

Réponses:


28

Approche apparemment préférée

J'avais l'impression que les éléments suivants avaient déjà été testés par d'autres, en particulier sur la base de certains commentaires. Mais mes tests montrent que ces deux méthodes fonctionnent effectivement au niveau de la base de données, même lors de la connexion via .NET SqlClient. Ceux-ci ont été testés et vérifiés par d'autres.

À l'échelle du serveur

Vous pouvez définir le paramètre de configuration du serveur d' options utilisateur pour qu'il soit ce qu'il est actuellement en bits ORavec 64 (la valeur pour ARITHABORT). Si vous n'utilisez pas OR ( |) au niveau du bit mais effectuez plutôt une affectation directe ( =), vous supprimerez toutes les autres options existantes déjà activées.

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

Au niveau de la base de données

Cela peut être défini par base de données via 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;

Approches alternatives

La moins bonne nouvelle est que j'ai fait beaucoup de recherches sur ce sujet, pour constater qu'au fil des ans, beaucoup d'autres ont fait beaucoup de recherches sur ce sujet, et il n'y a aucun moyen de configurer le comportement de SqlClient. Certaines documentations MSDN impliquent que cela peut être fait via une ConnectionString, mais il n'y a aucun mot-clé qui permettrait de modifier ces paramètres. Un autre document implique qu'il peut être modifié via Client Network Configuration / Configuration Manager, mais cela ne semble pas non plus possible. Par conséquent, et plutôt malheureusement, vous devrez exécuter SET ARITHABORT ON;manuellement. Voici quelques façons de considérer:

SI vous utilisez Entity Framework 6 (ou plus récent), vous pouvez essayer soit:

  • Utilisez Database.ExecuteSqlCommand : context.Database.ExecuteSqlCommand("SET ARITHABORT ON;");
    Idéalement, cela serait exécuté une fois, après l'ouverture de la connexion DB, et non pour chaque requête.

  • Créez un intercepteur via:

    Cela vous permettra de modifier le SQL avant qu'il ne soit exécuté, auquel cas vous pouvez simplement le préfixe avec: SET ARITHABORT ON;. L'inconvénient est que ce sera pour chaque requête, à moins que vous stockez une variable locale pour capturer l'état de savoir si oui ou non il a été exécuté et test pour que chaque fois ( ce qui est vraiment pas que beaucoup de travail supplémentaire, mais en utilisant ExecuteSqlCommandest probablement plus facile).

L'un ou l'autre vous permettra de gérer cela en un seul endroit sans modifier le code existant.

Sinon , vous pouvez créer une méthode wrapper qui fait cela, semblable à:

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

  return CommandToExec.ExecuteReader();
}

puis il suffit de changer les _Reader = _Command.ExecuteReader();références actuelles pour être_Reader = ExecuteReaderWithSetting(_Command); .

Cela permet également de gérer le paramètre dans un seul emplacement tout en ne nécessitant que des modifications de code minimales et simplistes qui peuvent être principalement effectuées via Find & Replace.

Mieux encore ( Else Part 2), car il s'agit d'un paramètre de niveau de connexion, il n'a pas besoin d'être exécuté pour chaque appel SqlCommand.Execute __ (). Donc, au lieu de créer un wrapper pour ExecuteReader(), créez un wrapper pour 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;
}

Et puis il suffit de remplacer les _Connection.Open();références existantes pour êtreOpenAndSetArithAbort(_Connection); .

Les deux idées ci-dessus peuvent être implémentées dans un style plus OO en créant une classe qui étend SqlCommand ou SqlConnection.

Ou mieux encore ( Else Part 3), vous pouvez créer un gestionnaire d'événements pour le Connection StateChange et lui faire définir la propriété lorsque la connexion passe de Closedà Opencomme suit:

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

Avec cela en place, il vous suffit d'ajouter les éléments suivants à chaque endroit où vous créez une SqlConnectioninstance:

_Connection.StateChange += new StateChangeEventHandler(OnStateChange);

Aucune modification du code existant n'est nécessaire. Je viens d'essayer cette méthode dans une petite application console, en testant en imprimant le résultat de SELECT SESSIONPROPERTY('ARITHABORT');. Il revient 1, mais si je désactive le gestionnaire d'événements, il revient 0.


Par souci d'exhaustivité, voici certaines choses qui ne fonctionnent pas (ou pas du tout aussi efficacement):

  • Déclencheurs de connexion : les déclencheurs, même lorsqu'ils s'exécutent dans la même session, et même s'ils s'exécutent dans une transaction démarrée explicitement, sont toujours un sous-processus et donc leurs paramètres (SET commandes, tables temporaires locales, etc.) lui sont locaux et ne survivent pas la fin de ce sous-processus.
  • Ajouter SET ARITHABORT ON; au début de chaque procédure stockée:
    • cela nécessite beaucoup de travail pour les projets existants, d'autant plus que le nombre de procédures stockées augmente
    • cela n'aide pas les requêtes ad hoc

Je viens de tester la création d'une base de données simple avec ARITHABORT et ANSI_WARNINGS tous les deux désactivés, j'ai créé une table avec zéro dedans et un simple client .net pour y lire. Le .net SqlClient s'est affiché comme désactivant ARITHABORT et ANSI_WARNINGS dans la connexion dans le profileur SQL, et a également échoué la requête avec une division par zéro comme prévu. Cela semble montrer que la solution préférée de définition des indicateurs de niveau de base de données ne fonctionnera PAS pour modifier la valeur par défaut pour .net SqlClient.
Mike

peut confirmer que la définition des options utilisateur à l'échelle du serveur FONCTIONNE cependant.
Mike

SELECT DATABASEPROPERTYEX('{database_name}', 'IsArithmeticAbortEnabled');J'observe également qu'avec le retour de 1, sys.dm_exec_sessions affiche arithabort off, bien que je ne vois aucun SET explicite dans Profiler. Pourquoi serait-ce?
andrew.rockwell

6

Option 1

Outre la solution de Sankar , la définition du paramètre d'abandon arithmétique au niveau du serveur pour toutes les connexions fonctionnera:

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

Depuis SQL 2014, il est recommandé d'être activé pour toutes les connexions:

Vous devez toujours définir ARITHABORT sur ON dans vos sessions de connexion. La définition de ARITHABORT sur OFF peut avoir un impact négatif sur l'optimisation des requêtes, entraînant des problèmes de performances.

Cela semble donc être la solution idéale.

Option 2

Si l'option 1 n'est pas viable et que vous utilisez des procédures stockées pour la plupart de vos appels SQL (ce que vous devriez, voir Procédures stockées vs SQL en ligne ), activez simplement l'option dans chaque procédure stockée pertinente:

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

Je crois que la meilleure vraie solution ici est de simplement modifier votre code, car il est incorrect et tout autre correctif n'est qu'une solution de contournement.


Je ne pense pas que cela aide à le configurer pour SQL Server, lorsque la connexion .net démarre avec set ArithAbort off. J'espérais quelque chose qui pourrait être fait du côté .net / C #. J'ai mis la prime, parce que j'avais vu la recommandation.
Henrik Staun Poulsen

1
Le côté .net / C # est ce que Sankar a couvert, donc ce sont à peu près les seules options.
LowlyDBA

J'ai essayé l'option 1 et cela n'a eu aucun effet. Les nouvelles sessions montrent toujours que arithabort = 0. Je n'ai aucun problème avec cela, j'essaie simplement de devancer les problèmes potentiels.
Mark Freeman

4

Je ne suis PAS un expert ici mais vous pouvez essayer quelque chose comme ci-dessous.

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

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


Oui, c'est ce que je voulais dire en l'exécutant manuellement. Le fait est que la base de code avec laquelle je travaille a accumulé un peu de dette technique en ce qui concerne la couche d'accès DB, donc je vais devoir refactoriser quelques centaines de méthodes pour le faire de cette façon.
Peter Taylor

2

Il n'y a aucun paramètre pour forcer SqlClient à toujours activer ARITHABORT, vous devez le définir comme vous le décrivez.

Fait intéressant à partir de la documentation Microsoft pour SET ARITHABORT : -

Vous devez toujours définir ARITHABORT sur ON dans vos sessions de connexion. La définition de ARITHABORT sur OFF peut avoir un impact négatif sur l'optimisation des requêtes, entraînant des problèmes de performances.

Et pourtant, la connexion .Net est codée en dur pour désactiver cela par défaut?

Autre point, vous devez être très prudent lors du diagnostic des problèmes de performances avec ce paramètre. Différentes options définies entraîneront différents plans de requête pour la même requête. Votre code .Net peut rencontrer un problème de performances (SET ARITHABORT OFF) et pourtant, lorsque vous exécutez la même requête TSQL dans SSMS (SET ARITHABORT ON par défaut), cela peut être correct. En effet, le plan de requête .Net ne sera pas réutilisé et un nouveau plan sera généré. Cela pourrait potentiellement éliminer un problème de reniflage de paramètres par exemple et donner de bien meilleures performances.


1
@HenrikStaunPoulsen - Sauf si vous êtes bloqué en utilisant 2000 (ou le niveau de compatibilité 2000), cela ne fait aucune différence. Il est sous-entendu par ANSI_WARNINGSdans les versions ultérieures et des choses comme les vues indexées fonctionnent bien.
Martin Smith

Notez que .Net n'est pas codé en dur pour désactiver ARITHABORT. SSMS par défaut le mettre sur . .Net se connecte et utilise simplement les valeurs par défaut du serveur / base de données. Vous pouvez trouver des problèmes sur MS Connect concernant les utilisateurs se plaignant du comportement par défaut de SSMS. Notez l'avertissement sur la page du document ARITHABORT .
Bacon Bits

2

Si cela fait gagner du temps à quelqu'un, dans mon cas (Entity Framework Core 2.0.3, ASP.Net Core API, SQL Server 2008 R2):

  1. Il n'y a pas d'intercepteurs sur EF Core 2.0 (je pense qu'ils seront bientôt disponibles sur 2.1)
  2. Ni modifier le paramètre de base de données global ni définir le user_optionsétait acceptable pour moi (ils fonctionnent - j'ai testé) mais je ne pouvais pas risquer d'avoir un impact sur d'autres applications.

Une requête ad hoc d'EF Core, avec SET ARITHABORT ON;en haut, ne fonctionne PAS.

Enfin, la solution qui a fonctionné pour moi était la suivante: combiner une procédure stockée, appelée en tant que requête brute avec l' SEToption avant EXECséparée par un point-virgule, comme ceci:

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

Intéressant. Merci d'avoir publié ces nuances de travail avec EF Core. Juste curieux: ce que vous faites ici est-il essentiellement l'option wrapper que j'ai mentionnée dans la sous-section ELSE de la section Approches alternatives dans ma réponse? Je me demandais simplement parce que vous avez mentionné les autres suggestions dans ma réponse soit ne fonctionnant pas ou non viables en raison d'autres contraintes, mais sans mentionner l'option wrapper.
Solomon Rutzky

@SolomonRutzky est équivalent à cette option, avec la nuance qu'elle se limite à l'exécution d'une procédure stockée. Dans mon cas, si je préfixe une requête de mise à jour brute avec SET OPTION (manuellement ou via le wrapper), cela ne fonctionne pas. Si je mets l'option SET OPTION dans la procédure stockée, cela ne fonctionne pas. La seule façon était de faire SET OPTION suivi de la procédure stockée EXEC dans le même lot. C'est ainsi que j'ai choisi de personnaliser l'appel spécifique au lieu de créer le wrapper. Bientôt, nous mettrons à jour SQLServer 2016 et je pourrai le nettoyer.Merci de votre réponse, si cela a été utile pour ignorer des scénarios spécifiques.
Chris Amelinckx

0

S'appuyant sur la réponse de Solomon Rutzy , pour 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)
        ...

Cela utilise System.Data.Common' DbCommandau lieu de SqlCommandet DbConnectionau lieu deSqlConnection .

Une trace du Générateur de profils SQL confirme, SET ARITHABORT ONest envoyée lorsque la connexion s'ouvre, avant l'exécution de toute autre commande dans la transaction.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.