Görünüşe göre, CLR montaj fonksiyonum kilitlenmelere neden oluyor?


9

Uygulamamızın bir Oracle veritabanı veya Microsoft SQL Server veritabanı ile eşit derecede iyi çalışması gerekir. Bunu kolaylaştırmak için, sorgu sözdizimimizi homojenleştirmek üzere bir avuç UDF oluşturduk. Örneğin, SQL Server'da GETDATE () ve Oracle'da SYSDATE vardır. Aynı işlevi yerine getirirler ancak farklı kelimelerdir. Her iki platform için de ortak bir işlev adına ilgili platforma özgü sözdizimini saran NOW () adında bir sarıcı yazdık. Bazıları sadece homojenizasyon uğruna var olmaktan başka hiçbir şey yapmayan başka işlevlerimiz var. Ne yazık ki, bunun SQL Server için bir maliyeti var. Satır içi skaler UDF'ler performansa zarar verir ve paralelliği tamamen devre dışı bırakır. Alternatif olarak, aynı hedeflere ulaşmak için CLR montaj fonksiyonlarını yazdık. Bunu bir istemciye konuşlandırdığımızda sık sık kilitlenmeler yaşamaya başladılar. Bu özel istemci çoğaltma ve yüksek kullanılabilirlik tekniklerini kullanıyor ve burada bir tür etkileşim olup olmadığını merak ediyorum. Ben sadece bir CLR işlevi tanıtmanın böyle sorunlara nasıl neden olacağını anlamıyorum. Referans olarak, orijinal skaler UDF tanımının yanı sıra C #'daki yedek CLR tanımını ve bunun için SQL bildirimini ekledim. Ayrıca yardımcı olursa sağlayabileceğim kilitlenme XML var.

Orijinal UDF

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

CLR Montaj İşlevi

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

CLR İşlevi için SQL Server Bildirimi

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

9
Deterministik skaler CLR fonksiyonları çıkmazlara katkıda bulunmamalıdır. Tabii ki veritabanını okuyan CLR işlevleri olabilir. Sorunuza kilitlenme XML'i eklemelisiniz.
David Browne - Microsoft

Yanıtlar:


7

Hangi SQL Server sürümlerini kullanıyorsunuz?

Çok uzun zaman önce SQL Server 2017'de küçük bir davranış değişikliği gördüğümü hatırlıyorum. Geri dönüp onu not ettiğim yerde bulabilir miyim, ama bir SQLCLR nesnesine erişilirken başlatılan bir şema kilidi ile ilgili olduğunu düşünüyorum gerekir.

Bunu ararken yaklaşımınızla ilgili şunları söyleyeceğim:

  1. Lütfen Sql*giriş parametreleri, dönüş tipleri için türleri kullanın. Bunun SqlStringyerine kullanmalısınız string. SqlStringnull olabilecek bir dizeye çok benzer ( value?SQL Server'a özgü başka bir işleve sahiptir. Tüm Sql*türlerde Valuebeklenen .NET türünü döndüren bir özellik vardır (örneğin, SqlString.Valueiadeler string, SqlInt32iadeler int, SqlDateTimeiadeler DateTimevb.).
  2. Bu tüm yaklaşıma karşı, kilitlenmelerin ilişkili olup olmadığına başlamak için tavsiye ederim. Bunu söylüyorum çünkü:

    1. Deterministik SQLCLR UDF'ler paralel planlara katılabilse bile, büyük olasılıkla basit yerleşik işlevleri taklit etmek için performans hitleri alacaksınız.
    2. SQLCLR API buna izin vermiyor VARCHAR. Her şeyi üstü kapalı olarak basit operasyonlar için NVARCHARve sonra tekrar dönüştürmek konusunda sorun değil misiniz VARCHAR?
    3. SQLCLR API aşırı yüklemeye izin vermez, bu nedenle T-SQL ve / veya PL / SQL'de farklı imzalara izin veren işlevlerin birden fazla sürümüne ihtiyacınız olabilir.
    4. Aşırı yüklemeye izin vermemeye benzer şekilde, NVARCHAR(4000)ve arasında büyük bir fark vardır NVARCHAR(MAX): MAXtip (imzada tek bir tanesine bile sahip olmak), SQLCLR çağrısının imzada herhangi bir MAXtür bulunmadığı sürece iki kat daha uzun sürmesini sağlar (buna inanıyorum gerçek için VARBINARY(MAX)vs VARBINARY(4000)) yanı. Yani, aşağıdakiler arasında karar vermelisiniz:
      • yalnızca NVARCHAR(MAX)basitleştirilmiş bir API'ya sahip olmak için kullanın, ancak 8000 bayt veya daha az dize verisi kullanırken performans isabetini kullanın veya
      • tümü / çoğu / birçok dize işlevi için iki varyasyon oluşturma: biri MAXtürlerle ve biri olmadan (asla 8000 bayttan fazla dize verisi girip çıkmayacağınız garanti edildiğinde). SQL # kitaplığımdaki işlevlerin çoğu için almayı seçtiğim yaklaşım budur : Trim()muhtemelen bir veya daha fazla MAXtürü olan bir işlev ve imza veya sonuç kümesi şemasında hiçbir yerde Trim4k()bir MAXtürü olmayan bir sürüm vardır . "4k" versiyonları kesinlikle daha verimlidir.
    5. Sorudaki örnek verildiğinde işlevselliği taklit etmeye dikkat etmiyorsunuz. LTRIMve RTRIMyalnızca boşlukları String.Trim()kırpırken , .NET beyaz alanı keser (en azından boşluk, sekmeler ve yeni satırlar). Örneğin:

        PRINT LTRIM(RTRIM(N'      a       '));
    6. Ayrıca, sadece T-SQL ve C # 'daki fonksiyonunuzun sadece 3 giriş parametresinden birini kullandığını fark ettim. Bu sadece bir kavram kanıtı mı, yoksa düzeltilmiş kod mu?

1. Sql tiplerini kullanma ipucu için teşekkürler. Şimdi bu değişikliği yapacağım. 2. Burada, bunların kullanımını gerektiren dış güçler vardır. Bu konuda çok heyecanlı değilim ama bana güvenin, alternatiften daha iyi. Orijinal sorum, görünüşte asinine bir fonksiyonun neden var ve kullanıldığına dair biraz açıklama içeriyor.
Russ Suter

@RussSuter Yeniden anlaşıldı: dış kuvvetler. Sadece bu karar verildiğinde bilinmeyen bazı tuzaklara işaret ediyordum. Her iki durumda da, notlarımı bulamıyorum veya senaryoyu hatırladığım birkaç ayrıntıdan yeniden oluşturamıyorum. Sadece 2017'de bir derlemeden işlemler ve kod çağrısı ile ilgili olarak kesinlikle değişen bir şey hatırlıyorum ve daha kötüsü için gereksiz bir değişiklik gibi göründüğü için gerçekten rahatsız oldum ve çalıştığım testler için bunun etrafında çalışmak zorunda kaldım önceki sürümlerde iyi. Yani, lütfen soruda bir kilitlenme XML'e bir bağlantı gönderin.
Solomon Rutzky

Bu ek bilgi için teşekkürler. XML'in bağlantısı: dropbox.com/s/n9w8nsdojqdypqm/deadlock17.xml?dl=0
Russ Suter

@RussSuter Bunu T-SQL inlining ile denediniz mi? Kilitlenme XML'sine bakıldığında (tek bir satır olduğu için kolay değil - tüm yeni satırlar bir şekilde kaldırıldı) 60 ve 78 oturumları arasında bir dizi PAGE kilidi gibi görünüyor. Her iki oturum arasında kilitli 8 sayfa var: biri için 3 sayfa SPID ve 5 diğeri için. Her biri farklı bir işlem kimliğine sahip olduğundan, bu bir paralellik sorunudur. Eğer bu SQLCLR ile ilgiliyse, ironik bir şekilde SQLCLR'nin paralelliği engellememesi gerçeği olabilir. Bu yüzden basit işlevi satır içine koymayı denediğinizi sordum, çünkü bu da kilitlenmeyi gösterebilir.
Solomon Rutzky
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.