Aparentemente, minha função de montagem CLR está causando conflitos?

9

Nosso aplicativo precisa funcionar igualmente bem com um banco de dados Oracle ou Microsoft SQL Server. Para facilitar isso, criamos um punhado de UDFs para homogeneizar nossa sintaxe de consulta. Por exemplo, o SQL Server possui GETDATE () e o Oracle possui SYSDATE. Eles executam a mesma função, mas são palavras diferentes. Escrevemos um wrapper UDF chamado NOW () para ambas as plataformas que agrupa a sintaxe específica da plataforma relevante em um nome de função comum. Temos outras funções desse tipo, algumas das quais nada fazem senão que existem apenas em prol da homogeneização. Infelizmente, isso tem um custo para o SQL Server. UDFs escalares em linha causam estragos no desempenho e desabilitam completamente o paralelismo. Como alternativa, escrevemos funções de montagem CLR para atingir os mesmos objetivos. Quando implantamos isso em um cliente, eles começaram a enfrentar conflitos frequentes. Esse cliente em particular está usando técnicas de replicação e alta disponibilidade e estou me perguntando se há algum tipo de interação acontecendo aqui. Só não entendo como a introdução de uma função CLR causaria problemas como este. Para referência, incluí a definição UDF escalar original, bem como a definição CLR de substituição no C # e a declaração SQL. Também tenho XML de impasse que posso fornecer, se isso ajudar.

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

Função de montagem CLR

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

Declaração do SQL Server para função CLR

CREATE FUNCTION [fn].[APAD]
(
    @Value NVARCHAR(4000),
    @TableName NVARCHAR(4000),
    @ColumnName NVARCHAR(4000)
) RETURNS NVARCHAR(4000)
AS
EXTERNAL NAME ASI.fn.APAD
GO
Russ Suter
fonte
9
Funções CLR escalares determinísticas não devem contribuir para conflitos. É claro que as funções CLR que lêem o banco de dados podem. Você deve incluir o XML do impasse na sua pergunta.
David Browne - Microsoft

Respostas:

7

Quais versões do SQL Server você está usando?

Lembro-me de ter visto uma ligeira mudança de comportamento no SQL Server 2017 há não muito tempo. Vou ter que voltar e ver se consigo descobrir onde anotei isso, mas acho que tinha a ver com um bloqueio de esquema sendo iniciado quando um objeto SQLCLR estava sendo acessado.

Enquanto procuro isso, direi o seguinte sobre sua abordagem:

  1. Por favor, use os Sql*tipos para parâmetros de entrada, tipos de retorno. Você deveria estar usando em SqlStringvez de string. SqlStringé muito semelhante a uma corda anulável (o seu value?, mas tem outra funcionalidade construído em que é SQL Server específicos. Todos os Sql*tipos têm uma Valuepropriedade que retorna o tipo .NET esperado (por exemplo, SqlString.Valueretornos string, SqlInt32retornos int, SqlDateTimeretornos DateTime, etc).
  2. Eu recomendaria contra toda essa abordagem, se os impasses estão relacionados ou não. Eu digo isso porque:

    1. Mesmo com os UDFs SQLCLR determinísticos capazes de participar de planos paralelos, é provável que você obtenha resultados de desempenho por emular funções internas simplistas.
    2. A API SQLCLR não permite VARCHAR. Você concorda com a conversão implícita de tudo NVARCHARe depois novamente VARCHARpara operações simples?
    3. A API SQLCLR não permite sobrecarga, portanto, você pode precisar de várias versões de funções que permitam assinaturas diferentes em T-SQL e / ou PL / SQL.
    4. Semelhante a não permitir sobrecarga, há uma grande diferença entre NVARCHAR(4000)e NVARCHAR(MAX): o MAXtipo (com um único na assinatura) faz com que a chamada SQLCLR demore o dobro do tempo sem ter nenhum MAXtipo na assinatura (acredito que isso ocorra verdadeiro para VARBINARY(MAX)vs VARBINARY(4000)também). Então, você precisa decidir entre:
      • usando apenas NVARCHAR(MAX)para ter uma API simplificada, mas obtenha o desempenho atingido quando você estiver usando 8000 bytes ou menos de dados de sequência ou
      • criando duas variações para todas / a maioria / muitas funções de string: uma com MAXtipos e uma sem (para quando você garante que nunca ultrapassará 8000 bytes de dados de string dentro ou fora). Essa é a abordagem que eu escolhi para a maioria das funções na minha biblioteca SQL # : existe uma Trim()função que provavelmente possui um ou mais MAXtipos e uma Trim4k()versão que nunca possui um MAXtipo em nenhum lugar do esquema da assinatura ou do conjunto de resultados. As versões "4k" são absolutamente mais eficientes.
    5. Você não está sendo cuidadoso em emular a funcionalidade, conforme o exemplo da pergunta. LTRIMe RTRIMapenas aparar espaços, enquanto o .NET String.Trim()apara os espaços em branco (pelo menos espaço, guias e novas linhas). Por exemplo:

        PRINT LTRIM(RTRIM(N'      a       '));
    6. Além disso, notei que sua função, tanto em T-SQL quanto em C #, usa apenas 1 dos 3 parâmetros de entrada. Isso é apenas uma prova de conceito ou código editado?
Solomon Rutzky
fonte
1. Obrigado pela dica sobre o uso dos tipos Sql. Eu vou fazer essa mudança agora. 2. Existem forças externas em ação aqui que requerem o uso delas. Não estou empolgado com isso, mas confie em mim, é melhor que a alternativa. Minha pergunta original contém um pouco da explicação de por que uma função aparentemente asinina existe e está sendo usada.
26819 Russ Suter
@RussSuter Entendido re: forças externas. Eu estava apenas apontando algumas armadilhas que talvez não fossem conhecidas quando essa decisão foi tomada. De qualquer forma, não consigo encontrar minhas anotações ou reproduzir o cenário com poucos detalhes que me lembro. Só me lembro de algo definitivamente mudando em 2017 com relação às transações e chamada de código de um assembly, e fiquei muito irritado com isso, pois parecia uma mudança desnecessária para pior, e tive que contornar o que estava testando que funcionava bem em versões anteriores. Portanto, poste um link na pergunta no XML do impasse.
Solomon Rutzky 26/04/19
Obrigado por essa informação adicional. Aqui está um link para o XML: dropbox.com/s/n9w8nsdojqdypqm/deadlock17.xml?dl=0
Russ Suter
@RussSuter Você já tentou isso com o inline do T-SQL? Observando o XML do impasse (que não é fácil, pois é uma única linha - todas as novas linhas foram removidas de alguma forma), parece haver uma série de bloqueios de PAGE entre as sessões 60 e 78. Há 8 páginas bloqueadas entre as duas sessões: 3 para uma SPID e 5 para o outro. Cada um com um ID de processo diferente, portanto, esse é um problema de paralelismo. Se isso estiver relacionado ao SQLCLR, pode ser irônico que o SQLCLR não esteja impedindo o paralelismo. É por isso que perguntei se você tentou colocar a função simples em linha, pois isso também pode mostrar o impasse.
Solomon Rutzky 26/04/19