如何实现基于集合的算法/ UDF


13

我有一个算法,需要对具有800K行和38列的表中的每一行运行。该算法在VBA中实现,并且使用来自某些列的值来操纵其他列来进行一堆数学运算。

我目前正在使用Excel(ADO)来查询SQL,并将VBA与客户端游标一起使用来逐行循环应用该算法。它可以工作,但是需要7个小时才能运行。

VBA代码非常复杂,以至于将其重新编码为T-SQL会花费很多工作。

我已经阅读了有关CLR集成和UDF作为可能路线的信息。我还考虑过将VBA代码放在SSIS脚本任务中,以使其更接近数据库,但可以肯定存在解决此类性能问题的专家方法。

理想情况下,我将能够以基于并行集的方式针对尽可能多的行(所有?)运行算法。

任何帮助都很大程度上取决于如何在此类问题上获得最佳性能。

- 编辑

感谢您的评论,我正在使用MS SQL 2014 Enterprise,这里有更多详细信息:

该算法在时间序列数据中找到特征模式。该算法中的函数执行多项式平滑,加窗,并根据输入标准查找感兴趣的区域,返回十二个值和一些布尔结果。

我的问题更多是关于方法的问题,而不是实际的算法:如果我想一次在多个行上实现并行计算,我有什么选择。

我看到建议重新编码为T-SQL,这是很多工作,但是可能的,但是算法开发人员在VBA中工作,并且更改频繁,因此我需要与T-SQL版本保持同步并重新验证每个更改。

T-SQL是实现基于集合的函数的唯一方法吗?


3
假设您设计得很好,则SSIS可以提供​​一些本机并行化。这是您要寻找的任务,因为您需要逐行进行计算。但这就是说,除非您能提供详细信息(架构,涉及的计算以及这些计算希望完成的工作),否则无法帮助您进行优化。他们说用汇编语言编写代码可以使最快的代码成为
现实

2
如果您独立处理每一行,则可以将800K行拆分为多个N批处理,并NN单独的处理器/计算机上运行算法的实例。另一方面,您的主要瓶颈是什么-将数据从SQL Server传输到Excel或实际计算?如果更改VBA函数以立即返回一些虚拟结果,那么整个过程将花费多长时间?如果仍然需要几个小时,则说明数据传输存在瓶颈。如果花费几秒钟,那么您需要优化执行计算的VBA代码。
弗拉基米尔·巴拉诺夫

它是作为存储过程调用的筛选器: SELECT AVG([AD_Sensor_Data]) OVER (ORDER BY [RowID] ROWS BETWEEN 5 PRECEDING AND 5 FOLLOWING) as 'AD_Sensor_Data' FROM [AD_Points] WHERE [FileID] = @FileID ORDER BY [RowID] ASC 在Management Studio中,为每行调用的该函数花费50mS
medwar19

1
因此,需要50毫秒并执行800000次(11个小时)的查询才耗费时间。@FileID是每一行唯一的还是重复的,因此可以最大程度地减少执行查询的次数?您还可以一次计算所有Fileid到暂存表的滚动平均(使用FileID上的分区),然后查询该表而无需每一行都具有窗口功能。登台表的最佳设置看起来应该是在上具有聚集索引(FileID, RowID)
Mikael Eriksson

1
最好的办法是,如果您以某种方式可以消除触摸每一行的数据库的需要。这意味着您要么必须执行TSQL并可能加入滚动的平均查询,要么为每一行获取足够的信息,以便算法所需的一切都在该行上,如果涉及多个子行(xml),则可能以某种方式进行了编码。
Mikael Eriksson

Answers:


8

关于方法论,我相信您在错误的b树;-)吠叫。

我们所知道的:

首先,让我们合并并回顾一下我们对情况的了解:

  • 需要执行一些复杂的计算:
    • 这需要在该表的每一行上发生。
    • 该算法经常更改。
    • 该算法... [使用]一些列中的值来操纵其他列
    • 当前处理时间为:7小时
  • 桌子:
    • 包含800,000行。
    • 有38列。
  • 应用程序后端:
  • 数据库为SQL Server 2014企业版。
  • 每行都有一个存储过程:

    • 这需要50毫秒(我假设是平均)来运行。
    • 它返回大约4000行。
    • 定义(至少部分)是:

      SELECT AVG([AD_Sensor_Data])
                 OVER (ORDER BY [RowID] ROWS BETWEEN 5 PRECEDING AND 5 FOLLOWING)
                 as 'AD_Sensor_Data'
      FROM   [AD_Points]
      WHERE  [FileID] = @FileID
      ORDER BY [RowID] ASC

我们可以推测的是:

接下来,我们可以一起查看所有这些数据点,以查看是否可以综合其他细节来帮助我们找到一个或多个瓶颈,或者指向一个解决方案,或者至少排除一些可能的解决方案。

评论中的当前思路是,主要问题是SQL Server和Excel之间的数据传输。真的是这样吗?如果对800,000行中的每一行都调用了存储过程,并且每次调用(即每行)花费50毫秒,则总计为40,000秒(而不是ms)。这相当于666分钟(hhmm ;-),或刚刚超过11小时。但是据说整个过程只需要7个小时即可运行。我们已经花了整整4个小时的时间,甚至还增加了时间来进行计算或将结果保存回SQL Server。所以这里不对。

查看存储过程的定义,只有一个输入参数@FileID;没有任何过滤器@RowID。因此,我怀疑正在发生以下两种情况之一:

  • 实际上,实际上并不是每行都调用此存储过程,而是每行调用一次@FileID,这似乎跨越了大约4000行。如果规定的4000行返回的金额相当一致,则在800,000行中只有200个分组。200次执行各耗时50毫秒,这仅相当于这7个小时中的10秒。
  • 如果确实对每一行都调用了该存储过程,那么不会第一次@FileID传入新的消息花费更长的时间来将新行拉入缓冲池,但是接下来的3999次执行通常会更快,因为缓存了吧?

我认为,专注于此“过滤器”存储过程,或从SQL Server到Excel的任何数据传输都是一个红色的鲱鱼

目前,我认为表现不佳的最相关指标是:

  • 有80万行
  • 该操作一次执行一次
  • 该数据被保存回SQL服务器,因此“[用途]从一些列的值可以操纵其他栏目 ” [我的EM PHAS是;-)]

我怀疑:

  • 尽管在数据检索和计算上还有一些改进的余地,但使这些数据变得更好并不能显着减少处理时间。
  • 最大的瓶颈是发布80万条单独的UPDATE报表,即80万条单独的交易。

我的建议(基于当前可用信息):

  1. 您最大的改进领域是一次(即一次事务)更新多行。您应该更新您的流程,以便按照每个流程FileID而不是每个流程来工作RowID。所以:

    1. 将特定行的所有4000行读FileID入数组
    2. 数组应包含表示要操作的字段的元素
    3. 遍历数组,按当前方式处理每一行
    4. 一旦FileID计算完数组中的所有行(即针对此行):
      1. 开始交易
      2. 每个调用一次更新 RowID
      3. 如果没有错误,则提交事务
      4. 如果发生错误,请回滚并适当处理
  2. 如果尚未按定义聚集索引(FileID, RowID),则应考虑该问题(如@MikaelEriksson在对问题的评论中建议的那样)。它不会帮助这些单例UPDATE,但至少会稍微改善聚合操作,例如您在该“过滤器”存储过程中所做的操作,因为它们都基于FileID

  3. 您应该考虑将逻辑移至编译语言。我建议创建一个.NET WinForms应用程序甚至控制台应用程序。我更喜欢Console App,因为它很容易通过SQL Agent或Windows Scheduled Tasks进行计划。是否在VB.NET或C#中完成都无关紧要。VB.NET可能更适合您的开发人员,但是仍然会有一些学习过程。

    我目前没有任何理由转向SQLCLR。如果算法经常更改,那将很烦人,必须一直重新部署Assembly。重建控制台应用程序并将.exe放置在网络上适当的共享文件夹中,这样您就可以运行相同的程序,并且它恰好始终是最新的,应该非常容易做到。

    如果问题是我怀疑的,并且您一次只执行一次UPDATE,我认为将处理完全移到T-SQL中不会有帮助。

  4. 如果将处理移至.NET,则可以使用表值参数(UPDATETVP ),以便将数组传递到存储过程中,该存储过程将对TVP表变量调用JOIN并因此是单个事务。TVP应该比将4000 INSERTs分组到一个事务中要快。但是,通过INSERT1笔交易使用4000 s 以上的TVP带来的收益可能不会像将80万笔单独交易转移到200笔交易(每笔4000行)时看到的那样明显。

    TVP选项不适用于VBA端,但是有人想出了一种值得测试的变通方法:

    从VBA到SQL Server 2008 R2时,如何提高数据库性能?

  5. 如果仅FileIDWHERE子句中使用过滤器proc ,并且如果真的每行都调用proc,那么可以通过缓存第一次运行的结果并将其用于该行的其余行来节省一些处理时间FileID,对?

  6. 一旦你获得了处理完成每写到FileID那么我们就可以开始谈论并行处理。但这在那时可能没有必要:)。假设您要处理3个相当主要的非理想部分:Excel,VBA和800k事务,那么关于SSIS或平行四边形或谁知道什么的任何谈论都是过早的优化/先马后买。 。如果我们可以将这7小时的流程缩短到10分钟或更短的时间,您是否还在考虑其他方法以使其更快?您是否有目标完成时间?请记住,一旦对每个FileID完成处理 因此,如果您使用的是VB.NET控制台应用程序(即命令行.EXE),那么无论是通过SQL Agent CmdExec步骤还是Windows计划任务,都不会阻止您一次运行其中一些FileID :),等等

而且,您始终可以采用“分阶段”方法,并且一次进行一些改进。例如,从每个更新开始,FileID因此对该组使用一个事务。然后,查看是否可以使TVP正常工作。然后查看有关获取该代码并将其移至VB.NET的知识(TVP在.NET中工作,因此可以很好地移植)。


我们不知道那仍然可以帮助您:

  • “筛选器”存储过程是否按RowIDFileID运行?我们甚至拥有该存储过程的完整定义吗?
  • 表的完整架构。这个桌子有几宽?有多少个可变长度字段?多少个字段可以为空?如果有任何可为NULL的值,那么有多少包含NULL?
  • 该表的索引。它是分区的吗?是否使用ROW或PAGE压缩?
  • 此表的MB / GB大小是多少?
  • 该表如何处理索引维护?索引有多分散?统计信息如何更新?
  • 这个7小时的过程正在进行时,是否还有其他过程写入该表?可能的争论来源。
  • 这个7小时的过程正在进行时,是否还有其他过程从该表中读取?可能的争论来源。

更新1:

**关于什么是VBA(应用程序的Visual Basic)以及如何使用它,似乎有些困惑,因此,这只是为了确保我们都在同一个网页上:


更新2:

还有一点要考虑:如何处理连接?VBA代码是在每个操作中打开和关闭连接,还是在过程开始时打开连接并在过程结束时(即7小时后)关闭连接?即使使用连接池(默认情况下应为ADO启用),打开和关闭一次之间的影响仍然会相当大,而不是打开和关闭800,200或1,600,000次。这些值基于至少800,000个UPDATE加上200或800k EXEC(取决于实际执行过滤器存储过程的频率)。

我上面概述的建议自动缓解了连接过多的问题。通过创建一个事务并在该事务中执行所有UPDATE,您将保持该连接打开并为每个重用它UPDATE。从最初的调用是否保持打开以获取每个指定的4000行的连接FileID,还是在该“获取”操作之后关闭并为UPDATEs再次打开,连接的影响都较小,因为我们现在谈论的是两者之间的区别在整个过程中总共有200或400个连接。

更新3:

我做了一些快速测试。请记住,这是一个相当小规模的测试,而不是完全相同的操作(纯INSERT与EXEC + UPDATE)。但是,与连接和事务处理方式有关的时间差异仍然很重要,因此可以在此推断信息具有相对相似的影响。

测试参数:

  • SQL Server 2012 Developer Edition(64位)SP2
  • 表:

     CREATE TABLE dbo.ManyInserts
     (
        RowID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
        InsertTime DATETIME NOT NULL DEFAULT (GETDATE()),
        SomeValue BIGINT NULL
     );
  • 操作方式:

    INSERT INTO dbo.ManyInserts (SomeValue) VALUES ({LoopIndex * 12});
  • 每次测试的插入总数:10,000
  • 每个测试都会重置:(TRUNCATE TABLE dbo.ManyInserts;鉴于此测试的性质,执行FREEPROCCACHE,FREESYSTEMCACHE和DROPCLEANBUFFERS似乎并没有增加太多价值。)
  • 恢复模型:简单(可能在日志文件中有1 GB的空闲空间)
  • 使用事务的测试仅使用单个连接,而不管有多少事务。

结果:

Test                                   Milliseconds
-------                                ------------
10k INSERTs across 10k Connections     3968 - 4163
10k INSERTs across 1 Connection        3466 - 3654
10k INSERTs across 1 Transaction       1074 - 1086
10k INSERTs across 10 Transactions     1095 - 1169

如您所见,即使已经在所有操作之间共享了到数据库的ADO连接,使用显式事务(ADO对象应该能够处理)将它们分组为批次也可以确保显着(即,提高2倍以上)减少整体处理时间。


srutzky提出的建议有一个很好的“中间人”方法,即使用PowerShell从SQL Server中获取所需的数据,调用VBA脚本以处理数据,然后在SQL Server中调用更新SP。 ,将键和更新的值传递回SQL Server。这样,您就可以将基于集合的方法与已有的方法结合起来。
Steve Mangiameli 2015年

@SteveMangiameli嗨,史蒂夫,谢谢你的评论。我会早点回答,但是生病了。我很好奇您的想法与我的建议有何不同。所有迹象表明,仍需要Excel才能运行VBA。还是您建议使用PowerShell替代ADO,并且即使在I / O上速度要快得多,即使仅替换I / O也值得吗?
所罗门·鲁兹基

1
不用担心,您会感觉更好。我不知道会更好。我们不知道我们不知道的事,您已经进行了一些很棒的分析,但是仍然需要做出一些假设。I / O可能足够重要,可以自行替换。我们只是不知道。我只是想提出另一种可能对您建议的内容有用的方法。
Steve Mangiameli 2015年

@SteveMangiameli谢谢。并感谢您澄清这一点。我不确定您的确切方向,所以最好不要做。是的,我同意拥有更多的选择会更好,因为我们不知道对可以进行哪些更改有什么限制:)。
所罗门·鲁兹基

嘿,srutzky,谢谢您的详细考虑!我一直在SQL方面进行回测,以优化索引和查询并尝试查找瓶颈。我现在投资了一个合适的服务器,即36核,1TB剥离的PCIe SSD,因为IO陷入了困境。现在,直接在SSIS中调用VB代码,这似乎为并行执行打开了多个线程。
medwar19

2

恕我直言,并假设无法将VBA子代码重新编码为SQL,您是否考虑过允许VBA脚本在Excel文件中完成评估,然后将结果通过SSIS写回SQL Server?

您可以通过在文件系统对象或服务器中翻转指示器来使VBA子开始和结束(如果您已经将连接配置为写回服务器),然后使用SSIS表达式检查该指示器是否存在disableSSIS解决方案中给定任务的属性(这样,导入过程将一直等到VBA子完成,如果您担心它会超出其计划)。

另外,您可以使VBA脚本以编程方式启动(有点奇怪,但是我过去使用该workbook_open()属性来触发具有这种性质的“解雇”任务)。

如果对VB脚本的评估时间开始成为问题,则可以看到您的VB开发人员是否愿意并能够将其代码移植到SSIS解决方案中的VB脚本任务中-根据我的经验,当以此数量处理数据。

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.