ฉันสามารถ refactor แบบสอบถามนี้เพื่อให้ทำงานในแบบคู่ขนานได้หรือไม่


12

ฉันมีแบบสอบถามที่ใช้เวลาประมาณ 3 ชั่วโมงในการทำงานบนเซิร์ฟเวอร์ของเรา - และมันไม่ได้ใช้ประโยชน์จากการประมวลผลแบบขนาน (ประมาณ 1.15 ล้านระเบียนในdbo.Deidentified300 ระเบียนในdbo.NamesMultiWord) เซิร์ฟเวอร์มีการเข้าถึง 8 คอร์

  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;

และReplaceMultiwordเป็นกระบวนการที่กำหนดเป็น:

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

การเรียกร้องให้ReplaceMultiwordป้องกันการก่อตัวเป็นแผนขนานหรือไม่ มีวิธีที่จะเขียนสิ่งนี้เพื่อให้มีความเท่าเทียมกันหรือไม่?

ReplaceMultiword ทำงานตามลำดับจากมากไปน้อยเนื่องจากการทดแทนบางอย่างเป็นเวอร์ชั่นย่อของผู้อื่นและฉันต้องการให้การจับคู่ที่ยาวที่สุดประสบความสำเร็จ

ตัวอย่างเช่นอาจมี 'มหาวิทยาลัยจอร์จวอชิงตัน' และอีกแห่งหนึ่งจาก 'มหาวิทยาลัยวอชิงตัน' หากการแข่งขัน 'Washington University' เป็นครั้งแรกดังนั้น 'George' จะถูกทิ้งไว้ข้างหลัง

แผนแบบสอบถาม

ในทางเทคนิคฉันสามารถใช้ CLR ฉันไม่คุ้นเคยกับวิธีการทำเช่นนั้น


3
การกำหนดตัวแปรนั้นมีลักษณะการทำงานที่กำหนดไว้สำหรับแถวเดียวเท่านั้น การSELECT @var = REPLACE ... ORDER BYก่อสร้างไม่รับประกันว่าจะทำงานตามที่คุณคาดหวัง ตัวอย่างรายการเชื่อมต่อ (ดูการตอบสนองจาก Microsoft) ดังนั้นการเปลี่ยนมาใช้ SQLCLR มีข้อดีเพิ่มเติมของการรับประกันผลลัพธ์ที่ถูกต้องซึ่งเป็นสิ่งที่ดีเสมอ
พอลไวท์ 9

คำตอบ:


11

UDF กำลังป้องกันการขนาน นอกจากนี้ยังทำให้สปูลนั้น

คุณสามารถใช้ CLR และ regex ที่คอมไพล์เพื่อทำการค้นหาและแทนที่ มันไม่ได้ปิดกั้นการขนานกันตราบใดที่มีคุณสมบัติที่จำเป็นและน่าจะเร็วกว่าการดำเนินการ 300 TSQL REPLACEต่อการเรียกใช้ฟังก์ชัน

โค้ดตัวอย่างอยู่ด้านล่าง

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; 

สิ่งนี้ขึ้นอยู่กับการมีอยู่ของ CLR UDF ดังต่อไปนี้ ( DataAccessKind.Noneควรหมายความว่าสปูลหายไปเช่นเดียวกับการป้องกันฮาโลวีนและไม่จำเป็นเนื่องจากไม่สามารถเข้าถึงตารางเป้าหมายได้)

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

ฉันเพิ่งเปรียบเทียบสิ่งนี้ การใช้ตารางและเนื้อหาเดียวกันสำหรับแต่ละรายการ CLR ใช้เวลา 3: 03.51 เพื่อประมวลผล 1,174,731 แถวและ UDF ใช้เวลา 3: 16.21 มันประหยัดเวลา ในการอ่านแบบไม่เป็นทางการของฉันดูเหมือนว่า SQL Server จะเกลียดการขนานแบบสอบถาม UPDATE
rsjaffe

@rsjaffe น่าผิดหวัง ฉันหวังว่าจะได้ผลลัพธ์ที่ดีกว่ามาก ข้อมูลมีขนาดเท่าใด (ผลรวมของความยาวข้อมูลของคอลัมน์ที่ได้รับผลกระทบทั้งหมด)
Martin Smith

608 ล้านตัวอักษร, 1.216 GB, รูปแบบคือ NVARCHAR ฉันกำลังคิดที่จะเพิ่มwhereประโยคโดยใช้การทดสอบเพื่อจับคู่กับ regex เนื่องจากการเขียนส่วนใหญ่ไม่จำเป็น - ความหนาแน่นของ 'hits' ควรต่ำ แต่ทักษะ C # ของฉัน (ฉันเป็น C ++ ผู้ชาย) ไม่ได้ รับฉันไปที่นั่น ฉันคิดตามบรรทัดของโพรซีเดอร์public static SqlBoolean CanReplaceMultiWord(SqlString inputString, SqlXml replacementSpec)ที่จะส่งคืนreturn Regex.IsMatch(inputString.ToString()); แต่ฉันได้รับข้อผิดพลาดในข้อความสั่งคืนเช่น `System.Text.RegularExpressions.Regex เป็นประเภทที่ใช้ แต่เป็นตัวแปร
rsjaffe

4

Bottom line : การเพิ่มเกณฑ์ไปยังส่วนWHEREคำสั่งและแยกแบบสอบถามเป็นสี่แบบสอบถามแยกต่างหากหนึ่งรายการสำหรับแต่ละเขตข้อมูลอนุญาตให้เซิร์ฟเวอร์ SQL จัดทำแผนคู่ขนานและทำให้แบบสอบถามเรียกใช้ 4X เร็วเท่าที่มีโดยไม่ต้องทดสอบเพิ่มเติมในWHEREข้อ การแบ่งคำถามออกเป็นสี่ข้อโดยไม่ต้องทำการทดสอบไม่ได้ทำเช่นนั้น ไม่มีการเพิ่มการทดสอบโดยไม่แยกแบบสอบถาม การเพิ่มประสิทธิภาพการทดสอบลดเวลาการทำงานทั้งหมดเป็น 3 นาที (จากเดิม 3 ชั่วโมง)

UDF ดั้งเดิมของฉันใช้เวลา 3 ชั่วโมง 16 นาทีในการประมวลผล 1,174,731 แถวโดยทดสอบข้อมูล nvarchar 1.216 GB การใช้ CLR ที่มาร์ตินสมิ ธ ให้ไว้ในคำตอบของเขาแผนปฏิบัติการยังไม่ขนานกันและใช้เวลา 3 ชั่วโมง 5 นาที CLR แผนปฏิบัติการไม่ขนานกัน

เมื่ออ่านแล้วว่าWHEREเกณฑ์สามารถช่วยผลักดันUPDATEให้ขนานฉันทำต่อไปนี้ ฉันได้เพิ่มฟังก์ชั่นในโมดูล CLR เพื่อดูว่าเขตข้อมูลมีการจับคู่กับ 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());
}

และในinternal class ReplaceSpecificationฉันเพิ่มรหัสเพื่อดำเนินการทดสอบกับ regex

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

ถ้าเขตข้อมูลทั้งหมดจะถูกทดสอบในคำสั่งเดียวเซิร์ฟเวอร์ SQL ไม่ขนานงาน

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 ชั่วโมงและยังคงทำงานอยู่ แผนการดำเนินการ: เพิ่มการทดสอบคำสั่งเดียว

อย่างไรก็ตามถ้าเขตข้อมูลถูกแยกออกเป็นงบแยกมีการใช้แผนการทำงานแบบขนานและการใช้งาน CPU ของฉันเพิ่มขึ้นจาก 12% เมื่อใช้แผนอนุกรมเป็น 100% โดยใช้แผนขนาน (8 แกน)

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 นาที สถิติแถวแสดงให้เห็นว่าประมาณ 0.5% ของระเบียนมีการจับคู่ regex อย่างน้อยหนึ่งรายการ แผนการดำเนินการ: ป้อนคำอธิบายรูปภาพที่นี่

ตอนนี้การลากหลักตรงเวลาคือWHEREข้อ ฉันแทนที่การทดสอบ regex ในWHEREclause ด้วยอัลกอริทึม Aho-Corasick ที่ใช้เป็น CLR เวลาทั้งหมดนี้ลดลงเป็น 3 นาที 6 วินาที

ต้องมีการเปลี่ยนแปลงต่อไปนี้ โหลดชุดประกอบและฟังก์ชั่นสำหรับอัลกอริทึม Aho-Corasick เปลี่ยนWHEREข้อเป็น

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

และเพิ่มรายการต่อไปนี้ก่อน UPDATE

DECLARE @ac NVARCHAR(32);
SET @ac = dbo.CreateAhoCorasick(
  (SELECT NAMES FROM dbo.NamesMultiWord FOR XML RAW, root('root')),
  'en-us:i'
);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.