Paralel olarak çalışmasını sağlamak için bu sorguyu yeniden düzenleyebilir miyim?


12

Sunucumuzda - çalıştırmak için yaklaşık 3 saat süren bir sorgu var ve paralel işleme avantajı yok. (yaklaşık 1,15 milyon kayıt dbo.Deidentified, 300 kayıt dbo.NamesMultiWord). Sunucu 8 çekirdeğe erişebilir.

  UPDATE dbo.Deidentified 
     WITH (TABLOCK)
  SET IndexedXml = dbo.ReplaceMultiWord(IndexedXml),
      DE461 = dbo.ReplaceMultiWord(DE461),
      DE87 = dbo.ReplaceMultiWord(DE87),
      DE15 = dbo.ReplaceMultiWord(DE15)
  WHERE InProcess = 1;

ve ReplaceMultiwordşu şekilde tanımlanan bir prosedürdür:

SELECT @body = REPLACE(@body,Names,Replacement)
 FROM dbo.NamesMultiWord
 ORDER BY [WordLength] DESC
RETURN @body --NVARCHAR(MAX)

ReplaceMultiwordParalel plan oluşturulmasını önleme çağrısı var mı ? Paralelliğe izin vermek için bunu yeniden yazmanın bir yolu var mı?

ReplaceMultiword Bazı değiştirmeler diğerlerinin kısa sürümleri olduğundan ve en uzun eşleşmenin başarılı olmasını istiyorum çünkü azalan sırada çalışır.

Örneğin, 'George Washington Üniversitesi' ve 'Washington Üniversitesi'nden başka bir üniversite olabilir. Eğer 'Washington Üniversitesi' maçı ilk olsaydı, 'George' geride kalacaktı.

sorgu planı

Teknik olarak CLR kullanabilirim, bunu nasıl yapacağımı bilmiyorum.


3
Değişken ataması, yalnızca tek bir satır için tanımlanmış davranışa sahiptir. Yapının SELECT @var = REPLACE ... ORDER BYbeklediğiniz gibi çalışacağı garanti edilmez. Örnek Connect öğesi (Microsoft'un yanıtına bakın). Bu nedenle, SQLCLR'ye geçmek, her zaman iyi olan doğru sonuçları garanti etmenin ek avantajına sahiptir.
Paul White 9

Yanıtlar:


11

UDF paralelliği önlüyor. Ayrıca bu makaraya neden oluyor.

Aramanızı ve değiştirmenizi yapmak için CLR ve derlenmiş bir normal ifadeyi kullanabilirsiniz. Gerekli öznitelikler mevcut olduğu sürece paralelliği engellemez ve REPLACEişlev çağrısı başına 300 TSQL işlemi gerçekleştirmekten önemli ölçüde daha hızlı olacaktır .

Örnek kod aşağıdadır.

DECLARE @X XML = 
(
    SELECT Names AS [@find],
           Replacement  AS [@replace]
    FROM  dbo.NamesMultiWord 
    ORDER BY [WordLength] DESC
    FOR XML PATH('x'), ROOT('spec')
);

UPDATE dbo.Deidentified WITH (TABLOCK)
SET    IndexedXml = dbo.ReplaceMultiWord(IndexedXml, @X),
       DE461 = dbo.ReplaceMultiWord(DE461, @X),
       DE87 = dbo.ReplaceMultiWord(DE87, @X),
       DE15 = dbo.ReplaceMultiWord(DE15, @X)
WHERE  InProcess = 1; 

Bu, aşağıdaki gibi bir CLR UDF'nin varlığına bağlıdır ( DataAccessKind.Nonemakaraların kaybolduğu ve Cadılar Bayramı koruması için var olduğu ve hedef tabloya erişmediği için gerekli olmadığı anlamına gelmelidir).

using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Xml;

public partial class UserDefinedFunctions
{
    //TODO: Concurrency?
    private static readonly Dictionary<string, ReplaceSpecification> cachedSpecs = 
                        new Dictionary<string, ReplaceSpecification>();

    [SqlFunction(IsDeterministic = true,
                 IsPrecise = true,
                 DataAccess = DataAccessKind.None,
                 SystemDataAccess = SystemDataAccessKind.None)]
    public static SqlString ReplaceMultiWord(SqlString inputString, SqlXml replacementSpec)
    {
        //TODO: Implement something to drop things from the cache and use a shorter key.
        string s = replacementSpec.Value;
        ReplaceSpecification rs;

        if (!cachedSpecs.TryGetValue(s, out rs))
        {
            var doc = new XmlDocument();
            doc.LoadXml(s);
            rs = new ReplaceSpecification(doc);
            cachedSpecs[s] = rs;
        }

        string result = rs.GetResult(inputString.ToString());
        return new SqlString(result);
    }


    internal class ReplaceSpecification
    {
        internal ReplaceSpecification(XmlDocument doc)
        {
            Replacements = new Dictionary<string, string>();

            XmlElement root = doc.DocumentElement;
            XmlNodeList nodes = root.SelectNodes("x");

            string pattern = null;
            foreach (XmlNode node in nodes)
            {
                if (pattern != null)
                    pattern = pattern + "|";

                string find = node.Attributes["find"].Value.ToLowerInvariant();
                string replace = node.Attributes["replace"].Value;
                 //TODO: Escape any special characters in the regex syntax
                pattern = pattern + find;
                Replacements[find] = replace;
            }

            if (pattern != null)
            {
                pattern = "(?:" + pattern + ")";
                Regex = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
            }


        }
        private Regex Regex { get; set; }

        private Dictionary<string, string> Replacements { get; set; }


        internal string GetResult(string inputString)
        {
            if (Regex == null)
                return inputString;

            return Regex.Replace(inputString,
                                 (Match m) =>
                                 {
                                     string s;
                                     if (Replacements.TryGetValue(m.Value.ToLowerInvariant(), out s))
                                     {
                                         return s;
                                     }
                                     else
                                     {
                                         throw new Exception("Missing replacement definition for " + m.Value);
                                     }
                                 });
        }
    }
}

Ben sadece bunu kıyasladım. Her biri için aynı tablo ve içeriği kullanan CLR, 1.174.731 satırı işlemek için 3: 03.51 ve UDF 3: 16.21 aldı. Zaman kazandı. Sıradan okumalarımda, SQL Server'ın UPDATE sorgularını paralelleştirmek için tiksindirilmiş gibi görünüyor.
rsjaffe

@rsjaffe hayal kırıklığı. Bundan çok daha iyi bir sonuç beklerdim. İlgili verilerin boyutu nedir? (Etkilenen tüm sütunların veri uzunluğu toplamı)
Martin Smith

608 milyon karakter, 1.216 GB, biçim NVARCHAR'dır. whereYazımların çoğu gereksiz olduğu için regex ile eşleşme için bir test kullanarak bir cümle eklemeyi düşünüyordum - 'hit'lerin yoğunluğu düşük olmalı, ancak C # becerilerim (C ++ adamım) beni oraya götür. Ben bir prosedür çizgisinde düşünüyordum public static SqlBoolean CanReplaceMultiWord(SqlString inputString, SqlXml replacementSpec)dönecekti return Regex.IsMatch(inputString.ToString()); ama gibi o dönüş ekstresinde hataları almak `System.Text.RegularExpressions.Regex türüdür ama bir değişken gibi kullanılır.
rsjaffe

4

Alt satır : Cümleye ölçüt ekleme WHEREve sorguyu dört ayrı sorguya bölme, her alan için bir tane SQL sunucusunun paralel bir plan sağlamasına izin verdi ve sorguyu, WHEREyan tümcede fazladan test yapılmadan 4X kadar hızlı çalıştırdı . Test olmadan sorguları dörde bölmek bunu yapmadı. Sorguları bölmeden testi de eklemedi. Testin optimize edilmesi toplam çalışma süresini 3 dakikaya düşürdü (orijinalden 3 saat).

Orijinal UDF'm, 1.216 GB nvarchar verisi test edilerek 1.174.731 satırı işlemek için 3 saat 16 dakika sürdü. Cevabında Martin Smith tarafından sağlanan CLR'yi kullanarak yürütme planı hala paralel değildi ve görev 3 saat 5 dakika sürdü. CLR, yürütme planı paralel değil

Bu WHEREkriterleri okuduktan sonra, a'yı UPDATEparalel hale getirmeye yardımcı olabilir , aşağıdakileri yaptım. Alanın normal ifadeyle eşleşip eşleşmediğini görmek için CLR modülüne bir işlev ekledim:

[SqlFunction(IsDeterministic = true,
         IsPrecise = true,
         DataAccess = DataAccessKind.None,
         SystemDataAccess = SystemDataAccessKind.None)]
public static SqlBoolean CanReplaceMultiWord(SqlString inputString, SqlXml replacementSpec)
{
    string s = replacementSpec.Value;
    ReplaceSpecification rs;
    if (!cachedSpecs.TryGetValue(s, out rs))
    {
        var doc = new XmlDocument();
        doc.LoadXml(s);
        rs = new ReplaceSpecification(doc);
        cachedSpecs[s] = rs;
    }
    return rs.IsMatch(inputString.ToString());
}

ve internal class ReplaceSpecificationregex'e karşı testi yürütmek için kod ekledim

    internal bool IsMatch(string inputString)
    {
        if (Regex == null)
            return false;
        return Regex.IsMatch(inputString);
    }

Tüm alanlar tek bir ifadede test edilirse, SQL sunucusu çalışmayı paralelleştirmez

UPDATE dbo.DeidentifiedTest
SET IndexedXml = dbo.ReplaceMultiWord(IndexedXml, @X),
    DE461 = dbo.ReplaceMultiWord(DE461, @X),
    DE87 = dbo.ReplaceMultiWord(DE87, @X),
    DE15 = dbo.ReplaceMultiWord(DE15, @X)
WHERE InProcess = 1
    AND (dbo.CanReplaceMultiWord(IndexedXml, @X) = 1
    OR DE15 = dbo.ReplaceMultiWord(DE15, @X)
    OR dbo.CanReplaceMultiWord(DE87, @X) = 1
    OR dbo.CanReplaceMultiWord(DE15, @X) = 1);

4 1/2 saatin üzerinde çalışma ve hala çalışıyor. Yürütme planı: Test eklendi, tek açıklama

Ancak, alanlar ayrı ifadelere ayrılırsa, paralel bir çalışma planı kullanılır ve CPU kullanımım seri planlarla% 12'den paralel planlarla (8 çekirdek)% 100'e gider.

UPDATE dbo.DeidentifiedTest
SET IndexedXml = dbo.ReplaceMultiWord(IndexedXml, @X)
WHERE InProcess = 1
    AND dbo.CanReplaceMultiWord(IndexedXml, @X) = 1;

UPDATE dbo.DeidentifiedTest
SET DE461 = dbo.ReplaceMultiWord(DE461, @X)
WHERE InProcess = 1
    AND dbo.CanReplaceMultiWord(DE461, @X) = 1;

UPDATE dbo.DeidentifiedTest
SET DE87 = dbo.ReplaceMultiWord(DE87, @X)
WHERE InProcess = 1
    AND dbo.CanReplaceMultiWord(DE87, @X) = 1;

UPDATE dbo.DeidentifiedTest
SET DE15 = dbo.ReplaceMultiWord(DE15, @X)
WHERE InProcess = 1
    AND dbo.CanReplaceMultiWord(DE15, @X) = 1;

46 dakika çalışma süresi. Sıra istatistikleri, kayıtların yaklaşık% 0.5'inin en az bir normal ifade eşleşmesine sahip olduğunu gösterdi. Yürütme planı: resim açıklamasını buraya girin

Şimdi, zamanın ana sürüklemesi WHEREmaddeydi. Daha sonra, cümledeki normal ifade testini CLR olarak uygulanan Aho-Corasick algoritmasıWHERE ile değiştirdim . Bu toplam süreyi 3 dakika 6 saniyeye düşürdü.

Bu, aşağıdaki değişiklikleri gerektiriyordu. Aho-Corasick algoritması için montaj ve fonksiyonları yükleyin. Yan WHEREtümcesi olarak değiştirin

WHERE  InProcess = 1 AND dbo.ContainsWordsByObject(ISNULL(FieldBeingTestedGoesHere,'x'), @ac) = 1; 

Ve ilk önce aşağıdakileri ekleyin UPDATE

DECLARE @ac NVARCHAR(32);
SET @ac = dbo.CreateAhoCorasick(
  (SELECT NAMES FROM dbo.NamesMultiWord FOR XML RAW, root('root')),
  'en-us:i'
);
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.