Conclusão : adicionar critérios à WHERE
cláusula e dividir a consulta em quatro consultas separadas, uma para cada campo, permitiu ao SQL Server fornecer um plano paralelo e fez com que a consulta fosse executada 4X o mais rápido que havia, sem o teste extra da WHERE
cláusula. Dividir as consultas em quatro sem o teste não fez isso. Nem foi adicionado o teste sem dividir as consultas. A otimização do teste reduziu o tempo total de execução para 3 minutos (a partir das 3 horas originais).
Minha UDF original levou 3 horas e 16 minutos para processar 1.174.731 linhas, com 1.216 GB de dados nvarchar testados. Usando o CLR fornecido por Martin Smith em sua resposta, o plano de execução ainda não era paralelo e a tarefa levou 3 horas e 5 minutos.
Tendo lido esses WHERE
critérios, poderia ajudar a empurrar um UPDATE
para paralelo, fiz o seguinte. Adicionei uma função ao módulo CLR para ver se o campo correspondia ao 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());
}
e, internal class ReplaceSpecification
adicionei o código para executar o teste no regex
internal bool IsMatch(string inputString)
{
if (Regex == null)
return false;
return Regex.IsMatch(inputString);
}
Se todos os campos forem testados em uma única instrução, o SQL Server não paralelizará o trabalho
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);
Tempo para executar mais de 4 horas e meia e ainda em execução. Plano de execução:
No entanto, se os campos forem separados em instruções separadas, um plano de trabalho paralelo será usado, e o uso da minha CPU passará de 12% nos planos seriais para 100% nos planos paralelos (8 núcleos).
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;
Tempo para executar 46 minutos. As estatísticas de linha mostraram que cerca de 0,5% dos registros tinham pelo menos uma correspondência de regex. Plano de execução:
Agora, o principal problema no tempo era a WHERE
cláusula. Substituí o teste regex na WHERE
cláusula pelo algoritmo Aho-Corasick implementado como um CLR. Isso reduziu o tempo total para 3 minutos e 6 segundos.
Isso exigiu as seguintes alterações. Carregue a montagem e as funções do algoritmo Aho-Corasick. Mude a WHERE
cláusula para
WHERE InProcess = 1 AND dbo.ContainsWordsByObject(ISNULL(FieldBeingTestedGoesHere,'x'), @ac) = 1;
E adicione o seguinte antes do primeiro UPDATE
DECLARE @ac NVARCHAR(32);
SET @ac = dbo.CreateAhoCorasick(
(SELECT NAMES FROM dbo.NamesMultiWord FOR XML RAW, root('root')),
'en-us:i'
);
SELECT @var = REPLACE ... ORDER BY
é garantido que a construção funcione conforme o esperado. Exemplo de item de conexão (consulte a resposta da Microsoft). Portanto, a mudança para o SQLCLR tem a vantagem adicional de garantir resultados corretos, o que é sempre bom.