对大字符串进行操作永远不会完成 MSSQL 2012

Posted

技术标签:

【中文标题】对大字符串进行操作永远不会完成 MSSQL 2012【英文标题】:Operating on a large string never completes MSSQL 2012 【发布时间】:2018-03-29 15:47:05 【问题描述】:

我每天都在下载一个大文件。到目前为止,我的 .NET 程序已经在后台线程中处理它;但是,我们偶尔会遇到有人在完成之前重新启动的问题,并且必须重新开始。

我正在尝试将其迁移到 SQL Server。我可以非常快速地将整个文件转储为 varchar(max)。这个想法是在写入行后触发 SQL Server 中的长时间运行的操作。

但是,它永远不会回来。我的猜测是最终会,因为我的拆分功能已经过很好的测试并且我已经使用了多年,但我已经等了 7 分钟,即使我从循环中抽象出来并且只在第一行上操作。我从来没有拿回第一行。

必须有另一种方法来处理大数据,在数十万行的情况下,每行不需要 10 分钟。

我查看了 STRING_SPLIT,但不幸的是,我们现在是 2012 年,它不可用。

我的拆分函数(原始代码来自这里的某个地方。它在较小的 varchars 上完美运行)。

ALTER FUNCTION [dbo].[fnSplitReturnVarchar](
@sInputList VARCHAR(max) -- List of delimited items
, @sDelimiter VARCHAR(10) = ',' -- delimiter that separates items
) RETURNS @List TABLE (rowNum int identity,item VARCHAR(max))

BEGIN
DECLARE @sItem VARCHAR(max)
WHILE CHARINDEX(@sDelimiter,@sInputList,0) <> 0
 BEGIN
  SELECT
    @sItem=RTRIM(LTRIM(SUBSTRING(@sInputList,1,CHARINDEX( @sDelimiter,@sInputList,0)-1))),
    @sInputList=RTRIM(LTRIM(SUBSTRING(@sInputList,CHARINDEX(@sDelimiter, @sInputList,0)+LEN(@sDelimiter),LEN(@sInputList))))

IF LEN(@sItem) > 0
  INSERT INTO @List SELECT @sItem
END

IF LEN(@sInputList) > 0
 INSERT INTO @List SELECT @sInputList -- Put the last item in
 RETURN
END

同样,这适用于小字符串。例如:

SELECT * FROM dbo.fnSplitReturnVarchar('12#45','#')

返回

  rowNum    item
   1         12
   2         45

但是,此 varchar 的 LEN(@String) 为 2,379,052。 我在 char(10) 上拆分,最终需要在逗号上拆分 - 但我什至无法让 char(10) 拆分返回。

例如:

DECLARE @YesterdayFullStringFut varchar(max) = (SELECT DataString 
        FROM CMESettleStaging 
        WHERE [Date] = CAST(DATEADD(DAY,-1,GETDATE()) AS date) 
        AND DataType = 'Future')

SELECT LEN(@YesterdayFullStringFut) --2379052

DECLARE @ProcessLine varchar(max) = (SELECT item 
        FROM dbo.fnSplitReturnVarchar(@YesterdayFullStringFut,char(10)) 
        WHERE rowNum = 2) --never returns, 8:30 longest wait - too long.
SELECT @ProcessLine

如何处理大数据?在 .NET 中花了很长时间,但没有任何地方接近这么长。

【问题讨论】:

使用 SSIS 包和 SQL 作业来导入文件。或者最坏的情况下使用 c# SQL 不太适合拆分字符串 构建以下字符串拆分器之一:sqlservercentral.com/articles/Tally+Table/72993 @Brad 我已经告诉所有者我们可能需要一个单独的程序或服务来处理这个问题。根据这些 cmets/answers,我们很可能会走这条路。 是的,拆分器会在大字符串上削弱您的服务器。我最喜欢的是上面链接中来自@Xedni 的 Jeff Moden 的作品。这里还有其他一些很棒的选择。 sqlperformance.com/2012/07/t-sql-queries/split-strings 你最好的性能选择是使用 CLR 函数——但是一旦你走这条路,只要外部处理是一个选项,你也可以使用它。那里的大多数字符串拆分器旨在处理简单的列表,而不是 2 MB 的大型怪物。 【参考方案1】:

使用 2016 内置的 STRING_SPLIT,我们能够获得非常好的性能。我们在本地使用 2012,但已经将 Azure 设置为 140 compat (2017),其中包括 STRING_SPLIT。所以,我写了下面的代码,我们从几个小时的处理时间(在 .NET 中最快是 82 分钟)到大约 45 秒,完全在 SQL 中。

DECLARE @Results TABLE([1] varchar(50), [2] varchar(50), [3] varchar(50), [4] varchar(50), [5] varchar(50),[6] varchar(250), [7] varchar(50), [8] varchar(50), [9] varchar(50), [10] varchar(50),
                       [11] varchar(50), [12] varchar(50), [13] varchar(50), [14] varchar(50), [15] varchar(50),[16] varchar(50), [17] varchar(50), [18] varchar(50), [19] varchar(50), [20] varchar(50),
                       [21] varchar(50), [22] varchar(50))
--@Results to perm table.
DECLARE @Temp TABLE ([value] varchar(max))
INSERT INTO @Temp
SELECT [value] FROM STRING_SPLIT(@data,char(10))

DECLARE @innerData varchar(100)
DECLARE cur CURSOR FOR
SELECT [value] FROM @Temp

OPEN cur
FETCH NEXT FROM cur
INTO @innerData;

WHILE @@FETCH_STATUS = 0
BEGIN
    INSERT INTO @Results
    SELECT p2.[1],p2.[2],p2.[3],p2.[4],p2.[5],p2.[6],p2.[7],p2.[8],p2.[9],p2.[10],
           p2.[11],p2.[12],p2.[13],p2.[14],p2.[15],p2.[16],p2.[17],p2.[18],p2.[19],p2.[20],
           p2.[21],p2.[22]
    FROM(
        SELECT ROW_NUMBER() OVER(Order BY @innerData) AS rowNum,* FROM STRING_SPLIT(@innerData,',')         
    ) p
    PIVOT
    (
        MAX([value])
        FOR rowNum IN ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],
                       [11],[12],[13],[14],[15],[16],[17],[18],[19],[20],
                       [21],[22])
    ) p2
    FETCH NEXT FROM cur INTO @innerData;
END
SELECT * FROM @Results

这在 47 秒内处理了 24,039 行,这是一个显着的改进,对我们来说效果很好。

【讨论】:

以上是关于对大字符串进行操作永远不会完成 MSSQL 2012的主要内容,如果未能解决你的问题,请参考以下文章

AVPlayer seekToTime 永远不会完成

主队列中添加的同步操作(dispatch_sync)永远不会被执行,会死锁原因

execvp() 永远不会在管道上完成

对大字符串进行更快的操作

Weka 中的 KNN 算法永远不会在大型数据集上完成

Phonegap iOS ajax 请求永远不会完成