Aparentemente, mi función de ensamblaje CLR está causando puntos muertos


9

Nuestra aplicación debe funcionar igualmente bien con una base de datos Oracle o una base de datos Microsoft SQL Server. Para facilitar esto, creamos un puñado de UDF para homogeneizar nuestra sintaxis de consulta. Por ejemplo, SQL Server tiene GETDATE () y Oracle tiene SYSDATE. Realizan la misma función pero son palabras diferentes. Escribimos un UDF contenedor llamado NOW () para ambas plataformas que envuelve la sintaxis específica de la plataforma relevante en un nombre de función común. Tenemos otras funciones similares, algunas de las cuales esencialmente no hacen más que existir únicamente por el bien de la homogeneización. Desafortunadamente, esto tiene un costo para SQL Server. Las UDF escalares en línea causan estragos en el rendimiento y deshabilitan completamente el paralelismo. Como alternativa, escribimos funciones de ensamblaje CLR para lograr los mismos objetivos. Cuando implementamos esto en un cliente, comenzaron a experimentar puntos muertos frecuentes. Este cliente en particular está utilizando técnicas de replicación y alta disponibilidad y me pregunto si hay algún tipo de interacción aquí. Simplemente no entiendo cómo la introducción de una función CLR podría causar problemas como este. Como referencia, he incluido la definición original de UDF escalar, así como la definición de reemplazo de CLR en C # y la declaración de SQL correspondiente. También tengo un XML de punto muerto que puedo proporcionar si eso ayuda.

UDF original

CREATE FUNCTION [fn].[APAD]
(
    @Value VARCHAR(4000)
    , @tablename VARCHAR(4000) = NULL
    , @columnname VARCHAR(4000) = NULL
)

RETURNS VARCHAR(4000)
WITH SCHEMABINDING
AS

BEGIN
    RETURN LTRIM(RTRIM(@Value))
END
GO

Función de ensamblaje de CLR

[SqlFunction(IsDeterministic = true)]
public static string APAD(string value, string tableName, string columnName)
{
    return value?.Trim();
}

Declaración de SQL Server para la función CLR

CREATE FUNCTION [fn].[APAD]
(
    @Value NVARCHAR(4000),
    @TableName NVARCHAR(4000),
    @ColumnName NVARCHAR(4000)
) RETURNS NVARCHAR(4000)
AS
EXTERNAL NAME ASI.fn.APAD
GO

99
Las funciones CLR escalares deterministas no deberían contribuir a los puntos muertos. Por supuesto, las funciones CLR que leen la base de datos podrían. Debe incluir el punto muerto XML en su pregunta.
David Browne - Microsoft

Respuestas:


7

¿Qué versión (s) de SQL Server está utilizando?

Recuerdo haber visto un ligero cambio en el comportamiento en SQL Server 2017 no hace mucho tiempo. Tendré que volver y ver si puedo encontrar dónde lo anoté, pero creo que tenía que ver con un bloqueo de esquema que se iniciaba cuando se accedía a un objeto SQLCLR.

Mientras estoy buscando eso, diré lo siguiente con respecto a su enfoque:

  1. Utilice los Sql*tipos para los parámetros de entrada, tipos de retorno. Deberías estar usando en SqlStringlugar de string. SqlStringes muy similar a una cadena anulable (tu value?, pero tiene otra funcionalidad incorporada en que es SQL Server específico. Todos los Sql*tipos tienen una Valuepropiedad que devuelve el tipo .NET esperada (por ejemplo, SqlString.Valuevuelve string, SqlInt32vuelve int, SqlDateTimevuelve DateTime, etc).
  2. Para empezar, recomendaría contra todo este enfoque, independientemente de si los puntos muertos están relacionados o no. Digo esto porque:

    1. Incluso con los UDF SQLCLR deterministas que pueden participar en planes paralelos, lo más probable es que obtenga éxitos de rendimiento para emular funciones integradas simplistas.
    2. La API SQLCLR no lo permite VARCHAR. ¿Estás de acuerdo con convertir implícitamente todo NVARCHARy luego nuevamente VARCHARpara operaciones simples?
    3. La API SQLCLR no permite la sobrecarga, por lo que es posible que necesite varias versiones de funciones que sí permiten diferentes firmas en T-SQL y / o PL / SQL.
    4. Similar a no permitir la sobrecarga, hay una gran diferencia entre NVARCHAR(4000)y NVARCHAR(MAX): el MAXtipo (que tiene incluso uno solo en la firma) hace que la llamada SQLCLR tome el doble de tiempo que no tener ningún MAXtipo en la firma (creo que esto se cumple cierto para VARBINARY(MAX)vs VARBINARY(4000)también). Por lo tanto, debe decidir entre:
      • usando solo NVARCHAR(MAX)para tener una API simplificada, pero tome el impacto en el rendimiento cuando use 8000 bytes o menos de datos de cadena, o
      • creando dos variaciones para todas / la mayoría de las funciones de cadena: una con MAXtipos y otra sin ellas (para cuando se garantiza que nunca ingrese o salga más de 8000 bytes de datos de cadena). Este es el enfoque que elegí para la mayoría de las funciones en mi biblioteca SQL # : hay una Trim()función que probablemente tiene uno o más MAXtipos, y una Trim4k()versión que nunca tiene un MAXtipo en ninguna parte del esquema de firma o conjunto de resultados. Las versiones "4k" son absolutamente más eficientes.
    5. No tiene cuidado de emular la funcionalidad dado el ejemplo en la pregunta. LTRIMy RTRIMsolo recortar espacios, mientras que .NET String.Trim()recorta el espacio en blanco (al menos espacio, pestañas y líneas nuevas). Por ejemplo:

        PRINT LTRIM(RTRIM(N'      a       '));
    6. Además, acabo de notar que su función, tanto en T-SQL como en C #, solo usa 1 de los 3 parámetros de entrada. ¿Es esto solo una prueba de concepto o un código redactado?

1. Gracias por el consejo sobre el uso de los tipos SQL. Haré ese cambio ahora. 2. Hay fuerzas externas en el trabajo aquí que requieren el uso de ellos. No estoy entusiasmado, pero confía en mí, es mejor que la alternativa. Mi pregunta original contiene un poco de la explicación de por qué existe una función aparentemente tonta y se está utilizando.
Russ Suter

@RussSuter Entendido re: fuerzas externas. Solo estaba señalando algunas trampas que podrían no haberse conocido cuando se tomó esa decisión. De cualquier manera, no puedo encontrar mis notas o reproducir el escenario a partir de los pocos detalles que recuerdo de él. Solo recuerdo que algo cambió definitivamente en 2017 con respecto a las transacciones y el código de llamada de un ensamblado, y me molestó mucho , ya que parecía un cambio innecesario para peor, y tuve que solucionarlo para lo que estaba probando que funcionó bien en versiones anteriores. Por lo tanto, publique un enlace en la pregunta al punto muerto XML.
Solomon Rutzky

Gracias por esa información adicional. Aquí hay un enlace al XML: dropbox.com/s/n9w8nsdojqdypqm/deadlock17.xml?dl=0
Russ Suter

@RussSuter ¿Has probado esto con la incorporación del T-SQL? Mirando el punto muerto XML (que no es fácil ya que es una sola línea, todas las líneas nuevas se eliminaron de alguna manera) parece ser una serie de bloqueos de PÁGINA entre las sesiones 60 y 78. Hay 8 páginas bloqueadas entre ambas sesiones: 3 por una SPID y 5 para el otro. Cada uno con una ID de proceso diferente, por lo que este es un problema de paralelismo. Si esto está relacionado con SQLCLR, irónicamente podría ser el hecho de que SQLCLR no está evitando el paralelismo. Es por eso que le pregunté si ha intentado poner la función simple en línea, ya que también podría mostrar el punto muerto.
Solomon Rutzky
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.