Bisakah saya menolak permintaan ini untuk menjalankannya secara paralel?

12

Saya memiliki permintaan yang membutuhkan waktu sekitar 3 jam untuk berjalan di server kami - dan itu tidak memanfaatkan pemrosesan paralel. (sekitar 1,15 juta catatan masuk dbo.Deidentified, 300 catatan masuk dbo.NamesMultiWord). Server memiliki akses ke 8 core.

  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;

dan ReplaceMultiwordmerupakan prosedur yang didefinisikan sebagai:

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

Apakah seruan untuk ReplaceMultiwordmencegah pembentukan rencana paralel? Apakah ada cara untuk menulis ulang ini untuk memungkinkan paralelisme?

ReplaceMultiword berjalan dalam urutan menurun karena beberapa penggantian adalah versi pendek dari yang lain, dan saya ingin pertandingan terlama berhasil.

Misalnya, mungkin ada 'Universitas George Washington' dan yang lain dari 'Universitas Washington'. Jika pertandingan 'Universitas Washington' adalah yang pertama, maka 'George' akan tertinggal.

rencana permintaan

Secara teknis saya bisa menggunakan CLR, saya hanya tidak terbiasa dengan cara melakukannya.

rsjaffe
sumber
3
Tugas variabel hanya memiliki perilaku yang ditentukan untuk satu baris. The SELECT @var = REPLACE ... ORDER BYkonstruksi tidak dijamin untuk bekerja seperti yang Anda harapkan. Contoh Sambungkan item (lihat respons dari Microsoft). Jadi, beralih ke SQLCLR memiliki keuntungan tambahan untuk menjamin hasil yang benar, yang selalu menyenangkan.
Paul White 9

Jawaban:

11

UDF mencegah paralelisme. Ini juga menyebabkan gulungan itu.

Anda bisa menggunakan CLR dan regex yang dikompilasi untuk melakukan pencarian dan penggantian. Itu tidak memblokir paralelisme selama atribut yang diperlukan ada dan kemungkinan akan secara signifikan lebih cepat daripada melakukan 300 REPLACEoperasi TSQL per panggilan fungsi.

Contoh kode di bawah ini.

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; 

Ini tergantung pada keberadaan CLR UDF seperti di bawah ini ( DataAccessKind.Noneseharusnya berarti spool menghilang serta ada untuk perlindungan Halloween dan tidak diperlukan karena ini tidak mengakses tabel target).

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);
                                     }
                                 });
        }
    }
}
Martin Smith
sumber
Saya hanya membandingkan ini. Menggunakan tabel dan konten yang sama untuk masing-masing, CLR mengambil 3: 03.51 untuk memproses 1.174.731 baris, dan UDF mengambil 3: 16.21. Itu memang menghemat waktu. Dalam bacaan santai saya, sepertinya SQL Server enggan untuk memparalelkan permintaan UPDATE.
rsjaffe
@rsjaffe mengecewakan. Saya berharap hasil yang lebih baik dari itu. Berapa ukuran data yang terlibat? (Jumlah panjang data semua kolom yang terpengaruh)
Martin Smith
608 juta karakter, 1.216 GB, formatnya adalah NVARCHAR. Saya berpikir untuk menambahkan whereklausa menggunakan tes untuk mencocokkan dengan regex, karena sebagian besar penulisan tidak perlu - kepadatan 'hit' harus rendah, tetapi keterampilan C # saya (saya seorang pria C ++) tidak bawa aku kesana Saya berpikir sepanjang baris prosedur public static SqlBoolean CanReplaceMultiWord(SqlString inputString, SqlXml replacementSpec)yang akan kembali return Regex.IsMatch(inputString.ToString()); tetapi saya mendapatkan kesalahan pada pernyataan kembali itu, seperti `System.Text.RegularExpressions.Regex adalah tipe tetapi digunakan seperti variabel.
rsjaffe
4

Intinya : Menambahkan kriteria ke WHEREklausa dan memecah kueri menjadi empat kueri terpisah, satu untuk setiap bidang memungkinkan server SQL untuk menyediakan rencana paralel dan membuat kueri berjalan 4X secepat yang dimilikinya tanpa pengujian tambahan dalam WHEREklausa. Memisahkan kueri menjadi empat tanpa tes tidak berhasil. Juga tidak menambahkan tes tanpa memecah pertanyaan. Mengoptimalkan tes mengurangi total waktu berjalan menjadi 3 menit (dari aslinya 3 jam).

UDF asli saya memerlukan 3 jam 16 menit untuk memproses 1.174.731 baris, dengan 1.216 GB data nvarchar diuji. Menggunakan CLR yang disediakan oleh Martin Smith dalam jawabannya, rencana eksekusi masih tidak paralel dan tugasnya memakan waktu 3 jam dan 5 menit. CLR, rencana eksekusi tidak paralel

Setelah membaca bahwa WHEREkriteria dapat membantu mendorong suatu UPDATEparalel, saya melakukan hal berikut. Saya menambahkan fungsi ke modul CLR untuk melihat apakah bidang tersebut cocok dengan regex:

[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());
}

dan, dalam internal class ReplaceSpecification, saya menambahkan kode untuk menjalankan tes terhadap regex

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

Jika semua bidang diuji dalam satu pernyataan, SQL server tidak memparalelkan pekerjaan

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);

Waktu untuk mengeksekusi lebih dari 4 1/2 jam dan masih berjalan. Rencana eksekusi: Test ditambahkan, pernyataan tunggal

Namun, jika bidang dipisahkan ke dalam pernyataan terpisah, rencana kerja paralel digunakan, dan penggunaan CPU saya beralih dari 12% dengan paket seri menjadi 100% dengan paket paralel (8 core).

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;

Waktu untuk mengeksekusi 46 menit. Statistik baris menunjukkan bahwa sekitar 0,5% dari catatan memiliki setidaknya satu pertandingan regex. Rencana eksekusi: masukkan deskripsi gambar di sini

Sekarang, hambatan utama tepat waktu adalah WHEREklausa. Saya kemudian mengganti tes regex dalam WHEREklausa dengan algoritma Aho-Corasick diimplementasikan sebagai CLR. Ini mengurangi total waktu menjadi 3 menit 6 detik.

Ini membutuhkan perubahan berikut. Memuat perakitan dan fungsi untuk algoritma Aho-Corasick. Ubah WHEREklausa menjadi

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

Dan tambahkan berikut ini sebelum yang pertama UPDATE

DECLARE @ac NVARCHAR(32);
SET @ac = dbo.CreateAhoCorasick(
  (SELECT NAMES FROM dbo.NamesMultiWord FOR XML RAW, root('root')),
  'en-us:i'
);
rsjaffe
sumber