我可以重构该查询以使其并行运行吗?


12

我有一个查询,大约需要3个小时才能在我们的服务器上运行-而且没有利用并行处理。(中有约115万条记录dbo.Deidentified,其中有300条记录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 因为某些替代品是其他替代品的短版,所以它们以降序运行,我希望最长的替代品能够成功。

例如,可能有“乔治华盛顿大学”和“华盛顿大学”。如果首先是“华盛顿大学”比赛,那么“乔治”将被甩在后面。

查询计划

从技术上讲,我可以使用CLR,但我不熟悉该如何使用。


3
变量分配仅对单个行定义了行为。该SELECT @var = REPLACE ... ORDER BY结构不能保证按预期工作。示例Connect项目(请参阅Microsoft的响应)。因此,切换到SQLCLR具有保证正确结果的附加优点,这总是很不错的。
保罗·怀特9

Answers:


11

UDF正在阻止并行性。这也是导致该假脱机的原因。

您可以使用CLR和已编译的正则表达式进行搜索和替换。只要存在所需的属性,它就不会阻止并行性并且可能比REPLACE每个函数调用执行300个TSQL 操作快得多。

示例代码如下。

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

6.08亿字符,1.216 GB,格式为NVARCHAR。我当时在考虑where使用一个与正则表达式匹配的测试来添加一个子句,因为大多数编写都是不必要的-“匹配”的密度应该很低,但是我的C#技能(我是C ++专家)却没有带我去。我一直在考虑public static SqlBoolean CanReplaceMultiWord(SqlString inputString, SqlXml replacementSpec)将要返回的过程的路线,return Regex.IsMatch(inputString.ToString()); 但是在该return语句上会出错,例如`System.Text.RegularExpressions.Regex是一种类型,但像变量一样使用。
rsjaffe

4

底线:在WHERE子句中添加条件并将查询分为四个独立的查询,每个字段一个查询允许SQL Server提供并行计划,并使查询的运行速度是WHERE子句的4 倍,而无需在子句中进行额外的测试。在没有测试的情况下将查询分为四个并没有做到这一点。添加测试而不拆分查询都没有。优化测试可以将总运行时间从最初的3个小时减少到3分钟。

我最初的UDF用了3小时16分钟来处理1,174,731行,并测试了1.216 GB的nvarchar数据。使用马丁·史密斯(Martin Smith)提供的CLR,执行计划仍然不是并行的,任务花费了3小时5分钟。 CLR,执行计划不并行

阅读该WHERE标准可以帮助推动UPDATE并行发展,我做了以下工作。我向CLR模块添加了一个函数,以查看该字段是否与正则表达式匹配:

[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,我添加了代码以针对正则表达式执行测试

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

如果在单个语句中测试了所有字段,则SQL Server不会并行处理工作

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%变为并行计划(8核)的100%。

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%的记录至少有一个正则表达式匹配项。执行计划: 在此处输入图片说明

现在,拖延时间的主要因素是WHERE条款。然后,我用实现为CLR WHEREAho-Corasick算法替换了子句中的正则表达式测试。这将总时间减少到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'
);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.