Tôi có thể cấu trúc lại truy vấn này để chạy nó song song không?


12

Tôi có một truy vấn mất khoảng 3 giờ để chạy trên máy chủ của chúng tôi - và nó không tận dụng xử lý song song. (khoảng 1,15 triệu hồ sơ trong dbo.Deidentified, 300 hồ sơ dbo.NamesMultiWord). Máy chủ có quyền truy cập vào 8 lõi.

  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;

ReplaceMultiwordlà một thủ tục được định nghĩa là:

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

Là lời kêu gọi ReplaceMultiwordngăn chặn hình thành một kế hoạch song song? Có cách nào để viết lại điều này để cho phép song song?

ReplaceMultiword chạy theo thứ tự giảm dần vì một số thay thế là phiên bản ngắn của những người khác và tôi muốn trận đấu dài nhất sẽ thành công.

Ví dụ: có thể có 'Đại học George Washington' và một trường khác từ 'Đại học Washington'. Nếu trận đấu của 'Đại học Washington' là lần đầu tiên, thì 'George' sẽ bị bỏ lại phía sau.

kế hoạch truy vấn

Về mặt kỹ thuật tôi có thể sử dụng CLR, tôi chỉ không quen với cách làm như vậy.


3
Việc gán biến chỉ có hành vi được xác định cho một hàng. Việc SELECT @var = REPLACE ... ORDER BYxây dựng không được đảm bảo để làm việc như bạn mong đợi. Ví dụ Kết nối mục (xem phản hồi từ Microsoft). Vì vậy, chuyển sang SQLCLR có thêm lợi thế là đảm bảo kết quả chính xác, luôn luôn tốt.
Paul White 9

Câu trả lời:


11

UDF đang ngăn chặn sự song song. Nó cũng gây ra spool đó.

Bạn có thể sử dụng CLR và một biểu thức chính được biên dịch để thực hiện tìm kiếm và thay thế. Nó không chặn tính song song miễn là có các thuộc tính bắt buộc và có thể sẽ nhanh hơn đáng kể so với thực hiện 300 REPLACEthao tác TSQL cho mỗi lệnh gọi hàm.

Mã ví dụ dưới đây.

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; 

Điều này phụ thuộc vào sự tồn tại của UDF CLR như bên dưới (điều DataAccessKind.Nonenày có nghĩa là ống chỉ biến mất cũng như có để bảo vệ Halloween và không cần thiết vì điều này không truy cập vào bảng mục tiêu).

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

Tôi chỉ điểm chuẩn này. Sử dụng cùng một bảng và nội dung cho mỗi bảng, CLR mất 3: 03.51 để xử lý 1.174.731 hàng và UDF mất 3: 16,21. Nó đã tiết kiệm thời gian. Trong cách đọc thông thường của tôi, có vẻ như SQL Server không thích song song các truy vấn CẬP NHẬT.
rsjaffe

@rsjaffe đáng thất vọng. Tôi đã hy vọng cho một kết quả tốt hơn nhiều. Kích thước của dữ liệu liên quan là gì? (Tổng chiều dài dữ liệu của tất cả các cột bị ảnh hưởng)
Martin Smith

608 triệu ký tự, 1.216 GB, định dạng là NVARCHAR. Tôi đã nghĩ đến việc thêm một wheremệnh đề bằng cách sử dụng một bài kiểm tra để khớp với biểu thức chính quy, vì hầu hết các bài viết đều không cần thiết - mật độ 'lượt truy cập' nên thấp, nhưng các kỹ năng C # của tôi (tôi là một người C ++) thì không đưa tôi đến đó Tôi đã suy nghĩ theo các dòng của một thủ tục public static SqlBoolean CanReplaceMultiWord(SqlString inputString, SqlXml replacementSpec)sẽ trả về return Regex.IsMatch(inputString.ToString()); nhưng tôi gặp lỗi trên câu lệnh return đó, chẳng hạn như `System.Text.RegularExpressions.Regex là một loại nhưng được sử dụng như một biến.
rsjaffe

4

Dòng dưới cùng : Thêm tiêu chí vào WHEREmệnh đề và chia truy vấn thành bốn truy vấn riêng biệt, một truy vấn cho mỗi trường cho phép máy chủ SQL cung cấp một kế hoạch song song và làm cho truy vấn chạy nhanh gấp 4 lần mà không cần kiểm tra thêm trong WHEREmệnh đề. Chia các truy vấn thành bốn mà không có kiểm tra đã không làm điều đó. Không thêm kiểm tra mà không tách các truy vấn. Tối ưu hóa thử nghiệm giảm tổng thời gian chạy xuống còn 3 phút (từ 3 giờ ban đầu).

UDF ban đầu của tôi mất 3 giờ 16 phút để xử lý 1.174.731 hàng, với 1.216 GB dữ liệu nvarchar được thử nghiệm. Sử dụng CLR do Martin Smith cung cấp trong câu trả lời của mình, kế hoạch thực hiện vẫn không song song và nhiệm vụ mất 3 giờ 5 phút. CLR, kế hoạch thực hiện không song song

Đọc được WHEREcác tiêu chí đó có thể giúp đẩy UPDATEsong song, tôi đã làm như sau. Tôi đã thêm một chức năng vào mô-đun CLR để xem trường có khớp với biểu thức chính quy không:

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

và, trong internal class ReplaceSpecification, tôi đã thêm mã để thực hiện kiểm tra đối với biểu thức chính quy

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

Nếu tất cả các trường được kiểm tra trong một câu lệnh, máy chủ SQL không song song hóa công việc

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

Thời gian thực hiện hơn 4 tiếng rưỡi và vẫn chạy. Kế hoạch thực hiện: Kiểm tra thêm, tuyên bố duy nhất

Tuy nhiên, nếu các trường được phân tách thành các câu lệnh riêng biệt, kế hoạch làm việc song song được sử dụng và mức sử dụng CPU của tôi tăng từ 12% với các kế hoạch nối tiếp đến 100% với các kế hoạch song song (8 lõi).

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;

Thời gian thực hiện 46 phút. Thống kê hàng cho thấy khoảng 0,5% hồ sơ có ít nhất một trận đấu regex. Kế hoạch thực hiện: nhập mô tả hình ảnh ở đây

Bây giờ, kéo chính về thời gian là WHEREmệnh đề. Sau đó, tôi đã thay thế bài kiểm tra regex trong WHEREmệnh đề bằng thuật toán Aho-Corasick được triển khai dưới dạng CLR. Điều này giảm tổng thời gian xuống còn 3 phút 6 giây.

Điều này đòi hỏi những thay đổi sau đây. Tải lắp ráp và các chức năng cho thuật toán Aho-Corasick. Thay đổi WHEREmệnh đề thành

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

Và thêm vào sau đây trước UPDATE

DECLARE @ac NVARCHAR(32);
SET @ac = dbo.CreateAhoCorasick(
  (SELECT NAMES FROM dbo.NamesMultiWord FOR XML RAW, root('root')),
  'en-us:i'
);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.