You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何提升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;
    }
}
对你现有代码的关键优化点
  1. 把TSql130Parser实例改为全局静态变量,避免每次解析都实例化。
  2. 将多次遍历ScriptTokenStream改为单次遍历,同时完成SQL拼接和PROC标记查找。
  3. 用StringBuilder替代string.Join处理SQL拼接,提升大脚本的处理效率。
  4. 提取名称时用Skip+Take替代多次Select和Where,简化逻辑并提升性能。

内容的提问来源于stack exchange,提问作者Maryam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 21:33:23