Haga que SqlClient tenga el valor predeterminado ARITHABORT ON

32

Lo primero es lo primero: estoy usando MS SQL Server 2008 con una base de datos en el nivel de compatibilidad 80, y me conecto con .Net System.Data.SqlClient.SqlConnection.

Por razones de rendimiento, he creado una vista indizada. Como resultado, las actualizaciones de las tablas a las que se hace referencia en la vista deben realizarse ARITHABORT ON. Sin embargo, el generador de perfiles muestra que SqlClient se está conectando ARITHABORT OFF, por lo que las actualizaciones de esas tablas están fallando.

¿Existe una configuración central para hacer uso de SqlClient ARITHABORT ON? Lo mejor que he podido encontrar es ejecutar manualmente cada vez que se abre una conexión, pero actualizar la base de código existente para hacerlo sería una tarea bastante grande, así que estoy ansioso por encontrar una mejor manera.

Peter Taylor
fuente

Respuestas:

28

Enfoque aparentemente preferido

Tenía la impresión de que los siguientes ya habían sido probados por otros, especialmente en base a algunos de los comentarios. Pero mis pruebas muestran que estos dos métodos realmente funcionan en el nivel de base de datos, incluso cuando se conectan a través de .NET SqlClient. Estos han sido probados y verificados por otros.

En todo el servidor

Puede establecer la configuración de configuración del servidor de opciones de usuario para que sea lo que está OReditado actualmente con 64 (el valor para ARITHABORT). Si no utiliza el OR ( |) a nivel de bits, sino que realiza una asignación directa ( =), eliminará cualquier otra opción existente ya habilitada.

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

Nivel de base de datos

Esto se puede configurar por base de datos a través de 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;

Enfoques alternativos

La noticia no tan buena es que he buscado mucho sobre este tema, solo para descubrir que a lo largo de los años muchos otros han buscado mucho sobre este tema, y ​​no hay forma de configurar el comportamiento de SqlClient. Parte de la documentación de MSDN implica que se puede hacer a través de ConnectionString, pero no hay palabras clave que permitan modificar esta configuración. Otro documento implica que se puede cambiar a través de Client Network Configuration / Configuration Manager, pero eso tampoco parece posible. Por lo tanto, y desafortunadamente, deberá ejecutarlo SET ARITHABORT ON;manualmente. Aquí hay algunas formas de considerar:

SI está utilizando Entity Framework 6 (o más reciente), puede intentar:

  • Use Database.ExecuteSqlCommand : context.Database.ExecuteSqlCommand("SET ARITHABORT ON;");
    idealmente, esto se ejecutaría una vez, después de abrir la conexión de base de datos, y no por cada consulta.

  • Cree un interceptor a través de:

    Esto le permitirá modificar el SQL antes de ser ejecutado, en cuyo caso puede simplemente como prefijo: SET ARITHABORT ON;. La desventaja aquí es que será por cada consulta, a menos que almacene una variable local para capturar el estado de si se ha ejecutado o no y probar eso cada vez (lo que realmente no es mucho trabajo adicional, pero usar ExecuteSqlCommandes probablemente más fácil).

Cualquiera de ellos le permitirá manejar esto en un solo lugar sin cambiar ningún código existente.

ELSE , podría crear un método de envoltura que haga esto, similar a:

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

  return CommandToExec.ExecuteReader();
}

y luego simplemente cambia las _Reader = _Command.ExecuteReader();referencias actuales a be _Reader = ExecuteReaderWithSetting(_Command);.

Hacer esto también permite que la configuración se maneje en una sola ubicación, mientras que solo requiere cambios de código mínimos y simplistas que se pueden hacer principalmente a través de Buscar y reemplazar.

Mejor aún ( otra parte 2), dado que esta es una configuración de nivel de conexión, no necesita ejecutarse por cada llamada SqlCommand.Execute __ (). Entonces, en lugar de crear un contenedor para ExecuteReader(), cree un contenedor para 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;
}

Y luego simplemente reemplace las _Connection.Open();referencias existentes para ser OpenAndSetArithAbort(_Connection);.

Ambas ideas anteriores se pueden implementar en más estilo OO creando una Clase que amplíe SqlCommand o SqlConnection.

O mejor aún ( Else Parte 3), se puede crear un controlador de eventos para la conexión y la tengan StateChange Se establece la propiedad cuando los cambios de conexión de Closeda Opende la siguiente manera:

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

Con eso en su lugar, solo necesita agregar lo siguiente a cada lugar donde cree una SqlConnectioninstancia:

_Connection.StateChange += new StateChangeEventHandler(OnStateChange);

No se necesitan cambios en el código existente. Acabo de probar este método en una pequeña aplicación de consola, probando imprimiendo el resultado de SELECT SESSIONPROPERTY('ARITHABORT');. Regresa 1, pero si desactivo el controlador de eventos, regresa 0.


En aras de la exhaustividad, aquí hay algunas cosas que no funcionan (en absoluto o no de manera tan efectiva):

  • Activadores de inicio de sesión : los activadores, incluso mientras se ejecutan en la misma sesión, e incluso si se ejecutan dentro de una transacción iniciada explícitamente, siguen siendo un subproceso y, por lo tanto, su configuración ( SETcomandos, tablas temporales locales, etc.) son locales y no sobreviven El final de ese subproceso.
  • Agregando SET ARITHABORT ON;al comienzo de cada procedimiento almacenado:
    • Esto requiere mucho trabajo para los proyectos existentes, especialmente a medida que aumenta el número de procedimientos almacenados
    • esto no ayuda a las consultas ad hoc
Solomon Rutzky
fuente
Acabo de probar la creación de una base de datos simple con ARITHABORT y ANSI_WARNINGS desactivados, creé una tabla con cero y un cliente .net simple para leer. El .net SqlClient se mostró como desactivando ARITHABORT y ANSI_WARNINGS en el inicio de sesión en sql profiler, y también falló la consulta con una división por cero como se esperaba. Esto parece mostrar que la solución preferida de establecer indicadores de nivel db NO funcionará para cambiar el valor predeterminado para .net SqlClient.
Mike
Sin embargo, puede confirmar que la configuración de las opciones de usuario en todo el servidor NO FUNCIONA.
Mike
También estoy observando que al SELECT DATABASEPROPERTYEX('{database_name}', 'IsArithmeticAbortEnabled');devolver 1, sys.dm_exec_sessions muestra arithabort apagado, aunque no veo ningún SET explícito en Profiler. ¿Por qué sería esto?
andrew.rockwell
6

Opción 1

Además de la solución de Sankar , funcionará la configuración de aborto aritmético en el nivel del servidor para todas las conexiones:

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

A partir de SQL 2014, se recomienda activar todas las conexiones:

Siempre debe establecer ARITHABORT en ON en sus sesiones de inicio de sesión. Establecer ARITHABORT en OFF puede afectar negativamente la optimización de consultas, lo que genera problemas de rendimiento.

Entonces, esta parece ser la solución ideal.

opcion 2

Si la opción 1 no es viable y utiliza procedimientos almacenados para la mayoría de sus llamadas SQL (lo cual debería hacer, consulte Procedimientos almacenados vs. SQL en línea ), entonces simplemente active la opción en cada procedimiento almacenado relevante:

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

Creo que la mejor solución real aquí es simplemente editar su código, ya que está mal y cualquier otra solución es simplemente una solución.

LowlyDBA
fuente
No creo que ayude configurarlo para SQL Server, cuando la conexión .net comienza con set ArithAbort off. Esperaba algo que pudiera hacerse en el lado .net / C #. Puse la recompensa, porque había visto la recomendación.
Henrik Staun Poulsen
1
El lado .net / C # es lo que cubre Sankar, por lo que estas son prácticamente las únicas opciones.
LowlyDBA
Intenté la Opción 1 y no tuvo ningún efecto. Las nuevas sesiones aún muestran que tiene arithabort = 0. No tengo ningún problema, solo trato de adelantarme a posibles problemas.
Mark Freeman
4

No soy un experto aquí, pero puedes probar algo como a continuación.

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
fuente
Sí, eso es lo que quise decir al ejecutarlo manualmente. La cuestión es que la base de código con la que estoy trabajando ha acumulado una gran deuda técnica en lo que respecta a la capa de acceso a la base de datos, por lo que tendré que refactorizar unos cientos de métodos para hacerlo de esta manera.
Peter Taylor
2

No hay una configuración que obligue a SqlClient a activar ARITHABORT siempre, debe configurarlo como lo describe.

Curiosamente, de la documentación de Microsoft para SET ARITHABORT : -

Siempre debe establecer ARITHABORT en ON en sus sesiones de inicio de sesión. Establecer ARITHABORT en OFF puede afectar negativamente la optimización de consultas, lo que genera problemas de rendimiento.

¿Y sin embargo, la conexión .Net está codificada para configurar esto de forma predeterminada?

Como otro punto, debe tener mucho cuidado al diagnosticar problemas de rendimiento con esta configuración. Las diferentes opciones establecidas darán como resultado diferentes planes de consulta para la misma consulta. Su código .Net podría experimentar un problema de rendimiento (SET ARITHABORT OFF) y, sin embargo, cuando ejecuta la misma consulta TSQL en SSMS (SET ARITHABORT ON por defecto) podría estar bien. Esto se debe a que el plan de consulta .Net no se reutilizará y se generará un nuevo plan. Esto podría eliminar un problema de detección de parámetros, por ejemplo, y proporcionar un rendimiento mucho mejor.

Andy Jones
fuente
1
@HenrikStaunPoulsen: a menos que esté atascado con 2000 (o nivel de compatibilidad 2000), no hay ninguna diferencia. Está implícito ANSI_WARNINGSen en versiones posteriores y cosas como las vistas indexadas funcionan bien.
Martin Smith
Tenga en cuenta que .Net no está codificado para desactivar ARITHABORT. SSMS por defecto a configurarlo en . .Net solo se conecta y usa los valores predeterminados del servidor / base de datos. Puede encontrar problemas en MS Connect sobre usuarios que se quejan del comportamiento predeterminado de SSMS. Tenga en cuenta la advertencia en la página del documento ARITHABORT .
Bacon Bits
2

Si ahorra algo de tiempo a alguien, en mi caso (Entity Framework Core 2.0.3, ASP.Net Core API, SQL Server 2008 R2):

  1. No hay interceptores en EF Core 2.0 (creo que estarán disponibles pronto en 2.1)
  2. Ni cambiar la configuración global de la base de datos ni la configuración user_optionsfue aceptable para mí (sí funcionan, lo probé), pero no podía arriesgarme a afectar otras aplicaciones.

Una consulta ad-hoc de EF Core, con SET ARITHABORT ON;en la parte superior, NO funciona.

Finalmente, la solución que funcionó para mí fue: Combinar un procedimiento almacenado, llamado como una consulta sin procesar con la SETopción antes EXECseparada por un punto y coma, como este:

// C# EF Core
int result = _context.Database.ExecuteSqlCommand($@"
SET ARITHABORT ON;
EXEC MyUpdateTableStoredProc
             @Param1 = {value1}
");
Chris Amelinckx
fuente
Interesante. Gracias por publicar estos matices de trabajar con EF Core. Es curioso: ¿lo que está haciendo aquí es esencialmente la opción de envoltura que mencioné en la subsección ELSE de la sección de enfoques alternativos en mi respuesta? Me preguntaba porque mencionó que las otras sugerencias en mi respuesta no funcionaban o no eran viables debido a otras restricciones, pero no mencionó la opción de envoltura.
Solomon Rutzky
@SolomonRutzky es equivalente a esa opción, con el matiz de que se limita a ejecutar un procedimiento almacenado. En mi caso, si prefijo una consulta de actualización sin procesar con SET OPTION (manualmente o mediante el contenedor) no funciona. Si pongo SET OPTION dentro del procedimiento almacenado, no funciona. La única forma era hacer SET OPTION seguido del procedimiento almacenado EXEC en el mismo lote. De esta manera, opté por personalizar la llamada específica en lugar de hacer el contenedor. Pronto actualizaremos a SQLServer 2016 y puedo limpiar esto. Gracias por su respuesta, si fue útil para descartar escenarios específicos.
Chris Amelinckx
0

Sobre la base de la respuesta de Solomon Rutzy , para 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)
        ...

Esto usa System.Data.Common's en DbCommandlugar de SqlCommandy en DbConnectionlugar de SqlConnection.

Una traza de SQL Profiler confirma que SET ARITHABORT ONse envía cuando se abre la conexión, antes de que se ejecuten otros comandos en la transacción.

CapNCook
fuente