Bloqueo de CLR en SQL Server 2014 (Windows 2012R2)

12

Tengo este pequeño CLR que hace una función RegEX en una cadena en columnas.

Cuando se ejecuta en SQL Server 2014 (12.0.2000) en Windows Server 2012R2, el proceso se bloquea con

Mensaje 0, Nivel 11, Estado 0, Línea 0 Se produjo un error grave en el comando actual. Los resultados, si los hay, deben descartarse.

y da un volcado de pila si lo hago

select count (*) from table where (CLRREGEX,'Regex')

pero cuando lo hago

select * from table where (CLRREGEX,'Regex') 

devuelve las filas.

Funciona perfectamente en la misma compilación de SQL Server que se ejecuta en Windows 8.1.

¿Algunas ideas?

- Editar Es tan simple como puede ser

using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlTypes;           //SqlString, SqlInt32, SqlBoolean
using System.Text.RegularExpressions; //Match, Regex
using Microsoft.SqlServer.Server;     //SqlFunctionAttribute
public partial class UserDefinedFunctions
{
    public static readonly RegexOptions Options = RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline;
    [SqlFunction]
    [Microsoft.SqlServer.Server.SqlFunction(IsDeterministic = true, IsPrecise = true)]
    public static SqlBoolean RegExMatch(SqlString input, SqlString pattern)
    {
        if (input.IsNull || pattern.IsNull) //nulls dont qualify for a match
            return SqlBoolean.False;
    return Regex.IsMatch(input.Value, pattern.Value, RegexOptions.IgnoreCase);
    }
}

Entonces, con pequeños cambios, esto funciona ahora: la lección principal en C # parece ser la misma que en TSQL, tenga cuidado con la conversión de datos implícita.

using System;
using System.Text;
using System.Data.SqlTypes;           //SqlString, SqlInt32, SqlBoolean
using System.Text.RegularExpressions; //Match, Regex
using Microsoft.SqlServer.Server;     //SqlFunctionAttribute
public partial class UserDefinedFunctions
{
public static readonly RegexOptions Options = RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;

    [Microsoft.SqlServer.Server.SqlFunction(IsDeterministic = true, IsPrecise = true, DataAccess = DataAccessKind.Read)]
    public static SqlBoolean RegExMatch(SqlString input, SqlString pattern)
{
    if (input.IsNull || pattern.IsNull) //nulls dont qualify for a match
        return SqlBoolean.False;
    string sqldata = input.ToString();
    string regex = pattern.ToString();
    return Regex.IsMatch(sqldata, regex);
 }
Spörri
fuente
¿Esto sucede para todos los patrones o solo para este? Podría ser un patrón ineficiente (es decir, retroceso excesivo o capturas innecesarias). Debería buscar establecer la propiedad MatchTimeout (nuevo en .NET Framework 4.5). ¿Codificó usted mismo la función RegEx? Si es así, ¿está utilizando métodos RegEx estáticos o de instancia? ¿El SqlFunctionmétodo está marcado como IsDeterministic=true? ¿El conjunto está marcado como SAFE?
Solomon Rutzky
2
¿Qué tan grandes son estas mesas? Además, ¿podría verificar si el plan estimado para las declaraciones del problema tiene un operador paralelo? En caso afirmativo, ¿podría comprobar si el problema se produce sin paralelismo, es decir, con una sugerencia MAXDOP = 1.
Amit Banerjee
2
El código se ve bien, excepto por el [SqlFunction]atributo duplicado . ¿Es ese el código exacto? No creo que eso compile. La distinción de Framework versión 2.0 / 3.0 / 3.5 no es un problema ya que está utilizando 4.0 / 4.5 / 4.5.x / etc o lo que sea que esté en ese servidor ya que está en SQL Server 2014, que está vinculado a CLR versión 4. Es el servidor que muestra el problema de 32 bits? ¿Cuánta memoria tiene en comparación con los otros servidores? ¿Y ha verificado los registros de SQL Server justo después de recibir ese error?
Solomon Rutzky
2
La versión exacta de .NET no está relacionada con el problema, aunque sería bueno saber si todos los servidores están en al menos 4.5 ya que eso significaría que puede usar la nueva MatchTimeoutpropiedad. Pero tampoco creo que ese sea realmente el problema si solo está pasando 5 caracteres como máximo. Que es posible que éste equipo dispone de una instalación corrupta de .NET Framework, y que puede ser reparada una vez trucha actividades pesqueras han dejado ;-). Además, [0-9].*es simple pero también ineficiente, ya que coincide con todos los caracteres, si los hay, después del primer dígito; usar solo [0-9]para un IsMatches mejor.
Solomon Rutzky
1
¿Por qué cambió DataAccessKinda Read? Eso solo lo ralentiza y no está haciendo ningún acceso a datos. Además, me doy cuenta de que parece estar funcionando ahora, pero sería cauteloso con el uso del ToString()método en lugar de la Valuepropiedad, ya que no creo que ToString maneje las codificaciones correctamente, o algo así. ¿Para qué está configurada la recopilación de sus bases de datos? Por supuesto, acabo de volver a leer uno de sus comentarios anteriores y veo que la columna es VARCHAR en lugar de NVARCHAR. ¿Ese campo tiene una clasificación diferente a la de la base de datos?
Solomon Rutzky

Respuestas:

4

El problema es un conflicto de configuración regional entre el sistema operativo Windows y SQL Server (específicamente la base de datos donde se carga el ensamblado). Puede ejecutar la siguiente consulta para ver en qué están configurados ambos:

SELECT os_language_version,
       DATABASEPROPERTYEX(N'{name of DB where Assembly exists}', 'LCID') AS 'DatabaseLCID'
FROM   sys.dm_os_windows_info;

Si son diferentes, entonces definitivamente puede tener un comportamiento "extraño", como lo que está viendo. El problema es que:

  • SqlStringincluye más que solo el texto en sí: incluye la clasificación predeterminada de la base de datos en la que existe el ensamblado. La recopilación se compone de dos piezas de información: la información de la configuración regional (es decir, LCID) y las opciones de comparación (es decir, SqlCompareOptions) que detallan la sensibilidad a mayúsculas, acentos, kana, ancho o todo (binario y binario2).
  • Las operaciones de cadena en .NET, a menos que se especifique explícitamente una configuración regional, utilice la información de la configuración regional del subproceso actual, que se establece en Windows (es decir, el sistema operativo / SO).

El conflicto generalmente ocurre cuando se hace referencia a un parámetro SqlString sin usar .Valueo .ToString()tal que haga una conversión implícita a SqlString. En ese caso, causaría una excepción diciendo que los LCID no coinciden.

Aparentemente, existen otros escenarios, como realizar comparaciones de cadenas (¿algunas / todas?), Incluso cuando se usa Regex como lo muestra este caso (aunque hasta ahora no he podido reproducir esto).

Algunas ideas para soluciones:

Ideal (siempre se cumplirán las expectativas con respecto a cómo funcionan las comparaciones):

  • Cambie el LCID de Windows o SQL Server (idioma predeterminado) para que ambos coincidan

Menos que ideal (el comportamiento de la configuración regional de Windows podría no ser las mismas reglas para la igualdad y la clasificación, por lo que podría haber resultados inesperados):

  • Use el .ToStringmétodo o la .Valuepropiedad, que devuelven la cadena sin el LCID de SQL Server para que todas las operaciones usen el LCID del sistema operativo.

Podría ayudar:

  • Tal vez use en SqlCharslugar de, SqlStringya que no trae consigo la información LCID y de recopilación de SQL Server
  • Especifique que la cultura no importa a través de StringComparison.InvariantCulture:
    • String.Compare(string, string, StringComparison.InvariantCulture) o String.Compare(string, string, StringComparison.InvariantCultureIgnoreCase)
    • Para Regex, especifique RegexOptions.CultureInvariant
Solomon Rutzky
fuente
1

Actualizado..

La localización es diferente entre el motor SQL y el servidor de ventana como @srutzky señala:

os_language_version SqlServerLCID
1033 1039

El siguiente cambio en el código: la configuración de la opción RegexOptions.CultureInvariantevita el error. El código sin cambios no bloqueará SQL Server 2012 en Windows Server 2012R2 con la misma configuración de idioma, pero lo hace en SQL Server 2014.

using System;
using System.Text;
using System.Data.SqlTypes;           //SqlString, SqlInt32, SqlBoolean
using System.Text.RegularExpressions; //Match, Regex
using Microsoft.SqlServer.Server;     //SqlFunctionAttribute
public partial class UserDefinedFunctions
{
public static readonly RegexOptions Options = RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant;

    [Microsoft.SqlServer.Server.SqlFunction(IsDeterministic = true, IsPrecise = true)]
    public static SqlBoolean RegExMatch(SqlString input, SqlString pattern)
{
    if (input.IsNull || pattern.IsNull) //nulls dont qualify for a match
        return SqlBoolean.False;
    string sqldata = input.ToString();
    string regex = pattern.ToString();
    return Regex.IsMatch(sqldata, regex);
 }
Spörri
fuente
¿Puede ejecutar lo siguiente en el servidor que estaba fallando SELECT os_language_version, SERVERPROPERTY('LCID') AS 'SqlServerLCID' FROM sys.dm_os_windows_info;? Es muy posible que el problema haya sido un conflicto en la configuración del idioma. Su solución aún podría ser la mejor opción, pero en general no debería ser necesario usarla en ToString()lugar de la Valuepropiedad en SqlStrings. Por lo tanto, sería bueno confirmar la situación.
Solomon Rutzky
Publiqué una respuesta para aclarar, pero el problema no debería resolverse configurando RegexOptions.CultureInvariantya que no pasa la Optionsvariable Regex.IsMatch(sqldata, regex). Lo que cambió entre su código original y el nuevo código de trabajo es que pasó de usar SqlString.Valuea SqlString.ToString(). Sospecho que verías el mismo comportamiento fijo si cambias a usar SqlChars. Pero solo haría eso como prueba. El mejor enfoque es cambiar el LCID de Windows o SQL Server para que coincida con el otro. También puede eliminar la variable estática Opciones.
Solomon Rutzky
Hola. Gracias por aceptar mi respuesta :). Solo para mencionar, investigué más y, si entendí lo que estaba viendo, si bien estoy en lo cierto acerca de que la causa raíz es un LCID diferente entre el sistema operativo y SQL Server, no está, o no debería estar, relacionado con la .Valuepropiedad de un SqlStringque aparentemente devuelve el mismo valor interno que el .ToString()método. Todavía estoy investigando y actualizaré mi respuesta con lo que encuentre :).
Solomon Rutzky
Ajusté mi respuesta a la luz de la nueva información. No puedo reproducir este escenario. ¿Es el código en la pregunta realmente lo que estabas / estás usando? La única diferencia real entre ellos es que el que usa los errores RegexOptions.IgnoreCasey el otro no. He configurado un entorno similar: Windows (8.0) usando LCID de 1033, SQL Server DB tiene LCID de 1039, usando el mismo RegEx que publicó, haciendo un COUNT(*)en un VARCHARcampo lleno de GUID, usando un patrón de '[0-3â].*', en una tabla con 10 millones de hileras. Es SQL Server 2012, no 2014, aunque no creo que eso deba importar.
Solomon Rutzky
1
Gracias por todas las respuestas. El código en la pregunta es lo que estaba usando. Tuve una expresión regular realmente complicada, pero logré bloquear esto usando una muy simple. Cambiar la configuración RegexOptions.CultureInvariant detuvo el comportamiento
Spörri