如何提升TSql130Parser解析速度及实现批量解析?
提升TSql130Parser解析速度的方法
- 复用TSql130Parser实例:TSql130Parser是线程安全的,无需每次解析都创建新实例。全局初始化一个实例重复使用,避免频繁实例化带来的开销。
- 减少Token流重复遍历:你的代码中多次遍历
ScriptTokenStream(找PROC标记、提取名称、生成处理后的SQL),可以改为一次遍历完成多个操作,减少枚举开销。 - 并行解析多个脚本:解析操作是CPU密集型任务,可使用
Task.WhenAll并行处理多个脚本的解析,充分利用多核CPU资源。 - 提前过滤无效脚本:先对脚本做简单文本检查(比如是否包含
CREATE PROCEDURE/ALTER PROCEDURE关键字),跳过无需解析的脚本,减少无效解析操作。 - 优化字符串拼接逻辑:用
StringBuilder替代string.Join处理SQL拼接,尤其是处理大脚本时,StringBuilder的性能远高于多次字符串连接。
TSql130Parser的批量解析实现
TSql130Parser没有提供直接的批量解析API,但可以通过两种方式实现批量处理:
方式1:合并脚本后解析(GO分隔)
将多个SQL脚本用GO语句分隔后合并为一个大脚本,解析后拆分每个批处理节点:
// 全局复用的Parser实例 private static readonly TSql130Parser _parser = new TSql130Parser(true); public List<(string ProcedureName, string ProcessedQuery)> BatchParseWithGo(IEnumerable<string> scripts) { var mergedScript = string.Join("\nGO\n", scripts); IList<ParseError> errors; var fragment = _parser.Parse(new StringReader(mergedScript), out errors); var results = new List<(string, string)>(); foreach (var batch in fragment.Batches) { var tokenStream = batch.ScriptTokenStream; if (tokenStream == null) continue; string procedureName = ""; var procToken = tokenStream.FirstOrDefault(t => t.TokenType == TSqlTokenType.Proc || t.TokenType == TSqlTokenType.Procedure); if (procToken != null) { var procIndex = tokenStream.IndexOf(procToken); // 提取存储过程名称(跳过空白字符,取前3个有效Token拼接) var nameParts = tokenStream.Skip(procIndex + 1) .Where(t => t.TokenType != TSqlTokenType.WhiteSpace) .Take(3) .Select(t => t.Text); procedureName = string.Concat(nameParts); } // 生成处理后的SQL(去掉注释和空白) var sb = new StringBuilder(); foreach (var token in tokenStream) { if (token.TokenType != TSqlTokenType.MultilineComment && token.TokenType != TSqlTokenType.SingleLineComment && token.TokenType != TSqlTokenType.WhiteSpace) { sb.Append(token.Text).Append(" "); } } var processedQuery = sb.ToString().Trim(); results.Add((procedureName, processedQuery)); } return results; }
方式2:并行解析单个脚本
利用并行任务同时处理多个独立脚本,适合脚本之间无依赖的场景:
private static readonly TSql130Parser _parser = new TSql130Parser(true); public async Task<List<(string ProcedureName, string ProcessedQuery)>> ParallelParseScripts(IEnumerable<string> scripts) { var tasks = scripts.Select(async script => { string procedureName; var processedQuery = ParseProcedureQueryOptimized(script, out procedureName); return (procedureName, processedQuery); }); return (await Task.WhenAll(tasks)).ToList(); } // 优化后的单个解析方法 public string ParseProcedureQueryOptimized(string query, out string procedureName) { procedureName = ""; try { IList<ParseError> errors; var fragments = _parser.Parse(new StringReader(query), out errors); var tokenStream = fragments.ScriptTokenStream; if (tokenStream == null) return string.Empty; string procText = null; int procIndex = -1; var sb = new StringBuilder(); // 一次遍历完成:找PROC标记、生成处理后的SQL for (int i = 0; i < tokenStream.Count; i++) { var token = tokenStream[i]; // 处理SQL拼接 if (token.TokenType != TSqlTokenType.MultilineComment && token.TokenType != TSqlTokenType.SingleLineComment && token.TokenType != TSqlTokenType.WhiteSpace) { sb.Append(token.Text).Append(" "); } // 记录PROC标记位置 if ((token.TokenType == TSqlTokenType.Proc || token.TokenType == TSqlTokenType.Procedure) && procText == null) { procText = token.Text; procIndex = i; } } if (procIndex == -1) return string.Empty; // 提取存储过程名称 var nameParts = tokenStream.Skip(procIndex + 1) .Where(t => t.TokenType != TSqlTokenType.WhiteSpace) .Take(3) .Select(t => t.Text); procedureName = string.Concat(nameParts); return sb.ToString().Trim(); } catch { procedureName = ""; return string.Empty; } }
对你现有代码的关键优化点
- 把
TSql130Parser实例改为全局静态变量,避免每次解析都实例化。 - 将多次遍历
ScriptTokenStream改为单次遍历,同时完成SQL拼接和PROC标记查找。 - 用
StringBuilder替代string.Join处理SQL拼接,提升大脚本的处理效率。 - 提取名称时用
Skip+Take替代多次Select和Where,简化逻辑并提升性能。
内容的提问来源于stack exchange,提问作者Maryam
相关产品推荐
相关产品推荐

