web-dev-qa-db-fra.com

Définir SqlClient par défaut sur ARITHABORT ON

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 System.Data.SqlClient.SqlConnection De .Net.

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 avec ARITHABORT ON. Cependant, le profileur montre que SqlClient se connecte avec ARITHABORT OFF, Donc les mises à jour de ces tables échouent.

Existe-t-il un paramètre de configuration central pour que SqlClient utilise ARITHABORT ON? Le mieux 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.

32
Peter Taylor

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 bien 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 options utilisateur tel qu'il est actuellement au niveau du bit ORed avec 64 (la valeur de ARITHABORT). Si vous n'utilisez pas le bit OR (|)) Mais effectuez une affectation directe (=), Vous supprimerez toutes les autres options existantes déjà activé.

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

Niveau 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 pas de mots-clés permettant 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 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 devrait être 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 son exécution, auquel cas vous pouvez simplement le préfixer avec: SET ARITHABORT ON;. L'inconvénient ici est que ce sera pour chaque requête, sauf si vous stockez une variable locale pour capturer l'état de l'exécution ou non et le tester à chaque fois (ce qui n'est vraiment pas beaucoup de travail supplémentaire, mais en utilisant ExecuteSqlCommand est 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 changez simplement les références actuelles de _Reader = _Command.ExecuteReader(); en _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.

Encore mieux ( Else Partie 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 __ (). Ainsi, 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 références existantes de _Connection.Open(); par OpenAndSetArithAbort(_Connection);.

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

Ou mieux encore ( Else Partie 3), vous pouvez créer un gestionnaire d'événements pour le Connection StateChange et le faire définir la propriété lorsque la connexion passe de Closed à Open comme 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, vous n'avez qu'à ajouter ce qui suit à chaque endroit où vous créez une instance SqlConnection:

_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, testant en imprimant le résultat de SELECT SESSIONPROPERTY('ARITHABORT');. Il renvoie 1, Mais si je désactive le gestionnaire d'événements, il renvoie 0.


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

  • Logon Triggers : 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 (commandes SET, locales tables temporaires, etc.) lui sont locales et ne survivent pas à la fin de ce sous-processus.
  • Ajout de 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
29
Solomon Rutzky

Option 1

Mis à part 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, c'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 solution réelle ici est de simplement modifier votre code, car il est incorrect et tout autre correctif n'est qu'une solution de contournement.

6
LowlyDBA

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/

4
Sankar Reddy

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.

2
Andy Jones

Si cela fait gagner du temps à quelqu'un, dans mon cas (Entity Framework Core 2.0.3, API ASP.Net Core, 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'option SET avant EXEC séparée par un point-virgule, comme ceci:

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

S'appuyant sur 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's DbCommand au lieu de SqlCommand et DbConnection au lieu de SqlConnection.

Une trace du Générateur de profils SQL confirme, SET ARITHABORT ON est envoyé à l'ouverture de la connexion, avant l'exécution de toute autre commande dans la transaction.

0
CapNCook