重构“极端”SQL查询

时间:2008-11-26 14:41:31

标签: sql sql-server-2005 refactoring

我有一位业务用户尝试编写自己的SQL查询以获取项目统计报告(例如任务数量,里程碑等)。查询开始声明80多列的临时表。然后,在近500行代码中,临时表中有近70个UPDATE语句,每个代码都包含自己的一组业务规则。它使用临时表中的SELECT *完成。

由于时间限制和“其他因素”,这已经匆忙投入生产,现在我的团队仍然坚持支持它。性能是令人震惊的,虽然由于一些整洁,它很容易阅读和理解(虽然代码味道很讨厌)。

我们应该考虑哪些关键领域,以加快速度并遵循良好做法?

11 个答案:

答案 0 :(得分:5)

首先,如果这不会导致业务问题,请将其保留直至出现问题。等到它出现问题,然后解决所有问题。

当你决定修复它时,检查是否有一个声明导致你的大部分速度问题...... issolate并修复它。

如果速度问题超过了所有语句,并且您可以将它们全部组合到一个SELECT中,这可能会节省您的时间。我曾经把这样的过程(没有那么多的更新)转换成SELECT,运行它的时间从3分钟到3秒以下(没有糟糕......我简直不敢相信)。顺便说一句,如果某些数据来自链接服务器,请不要尝试此操作。

如果您因任何原因不想或不想这样做,那么您可能需要调整现有的proc。以下是我要看的一些内容:

  1. 如果要在临时表上创建索引,请等到初始INSERT之后再填充它。

  2. 调整初始INSERT以插入尽可能多的列。你可以通过这样做来消除一些更新。

  3. 在运行更新之前为临时表编制索引。在更新后,不要在更新语句所针对的任何列上创建索引。

  4. 如果您的表格和分组允许,请对您的更新进行分组。只有80列的70个更新很多,听起来可能有机会这样做。

  5. 祝你好运

答案 1 :(得分:2)

我要做的第一件事是检查以确保定期运行活动索引维护作业。如果没有,则重建所有现有索引,或者如果不可能,至少更新统计信息。

我要做的第二件事就是设置一个跟踪(如here所述)并找出导致读取次数最多的语句。

然后我将使用“显示实际执行计划”在SSMS中运行并使用跟踪计算结果。通过这种方式,您应该能够确定是否存在可以提高性能的缺失索引。

编辑:如果您要进行投票,请留下评论原因。

答案 2 :(得分:2)

就像任何重构一样,确保您在每次更改后都有自动验证重构的方法(您可以使用查询来自行编写,根据已知的良好基线检查开发输出)。这样,您始终匹配已知的良好数据。当您进入决定是否切换到新版本的流程并希望并行运行几次以确保正确性的阶段时,这将使您对方法的正确性有高度的信心。

我还想记录批次中所有测试批次和流程的运行时间,因此我可以判断批次中的某个特定流程是否在某个时间点受到不利影响。我可以获得流程的平均时间,并查看改进趋势或发现潜在问题。这也让我能够确定批次中最容易发现的成果。

答案 3 :(得分:2)

  

然后有近70个更新   临时表的语句   每行近500行代码   包含自己的一小部分   商业规则。它结束了   来自临时表的SELECT *。

实际上,这听起来可以很好地遵循和理解,每个更新语句都会根据特定目的和业务规则集对表做一件事。我认为维护500行代码的程序包含一个或几个选择语句,这些语句执行“一切”,使用15个左右的连接构建,并且案例语句等分散在各处,维护起来要困难得多。虽然它会带来更好的表现。

使用SQL有点困境,编写简洁明了的代码(使用多个更新,创建函数等)似乎总会对性能产生很大的负面影响。尝试一次完成所有事情,这在其他编程语言中被认为是不好的做法,似乎是面向集合语言的核心。

答案 4 :(得分:1)

好吧,既然你告诉我们关于这个存储过程的唯一事情就是它有一个80+列临时表,我唯一能推荐的就是删除那个表,然后重写其余部分以免去除它

答案 5 :(得分:1)

如果这是一个生成存储过程的报告,它的运行频率是多少?如果只需要每天运行一次并且在夜间运行,那么性能有多大问题?

如果不是,我建议您在选择重新编写它时要小心,因为有可能会破坏您的数据。

这听起来像应该被引入到SSIS包中的那种东西,建立一个新的永久表,结果只需要运行一次。

希望这是有道理的

答案 6 :(得分:1)

您可以尝试的一件事是用表变量替换临时表。有时候这种情况会更快,而有些情况则更快,你只需要尝试一下即可看到。

查看70个更新语句。可以将它们中的任何一个组合起来吗?如果撰写的人没有使用CASE语句,则可能会少做一些陈述。

要注意的其他显而易见的事情 - 消除任何游标,将任何子查询更改为连接到表或派生表。

答案 7 :(得分:1)

或许改写。一种硬件解决方案是确保数据库临时表在“快速”驱动器上运行,可能是固态磁盘(SSD),或者可以在内存中进行全部管理。

我的猜测是,这个“解决方案”是由掌握并依赖电子表格的人开发的,他们可能对“规范化”数据库不太了解 - 如何构建和填充表以保留数据以用于报告目的也许BI商业智能软件可以利用其复杂性并且具有适应性。

您没有说“正在运行更新过程的位置”。更新过程是作为SQL脚本从单独的计算机(桌面)运行,而不是数据所在的服务器吗?这种方法可能会产生严重的瓶颈和开销。如果是这样,请考虑直接在服务器上作为本地作业运行整个更新过程,作为编译的存储过程,绕过网络和(多个)游标管理开销。它可以有一个预定的运行时间和一个受控的优先级,在非高峰业务数据使用时间内完成。

评估更新语句序列确实需要'commit'语句的频率...保存一堆提交行可以显着改善整体更新时间。数据库客户端驱动程序软件中可能有一些设置可能会产生显着差异。

用于更新条件的查询是否可以作为静态“视图”计算,而这些“视图”又可以跨多个更新语句共享?视图可以保留在频繁访问的内存数据/查询行中。在确定提交最佳之前,可以确定可以进行多少更新数据的性能调整。

可能值得评估是否可以使用触发器来替换批处理作业更新序列。您没有说明所使用的数据来自多少个表......这可能有助于决策制定。我不知道您是否可以选择将触发器添加到从中收集数据的数据库表。如果是这样,向多个表添加一些触发器并不会真正降低整体系统性能,但可能会在该更新过程中节省大量时间。您可以尝试使用触发器一次替换一个更新语句,并查看结果是否与以前相同。基于相同的更新过程创建类似的临时表,然后仔细测试向临时表提供更新的触发器是否可以替换单个更新语句。也许你可能有一种“数据仓库”应用程序。查看如何设置表的“星形”模式以保留用于报告的汇总业务数据可能很有用。

创建一个全面的缓存“视图”,每天通过查询更新一次,反映更新可能是另一种探索方法。

答案 8 :(得分:0)

您应该获得一个工具,可以让您获得应用运行所有查询的解释计划。对于性能提升的SQL重型应用程序来说,这是最好的选择。如果您阅读并解释说明计划告诉您的内容。如果您使用的是Oracle,我们以前使用的是Qwest的TOAD(?)我认为。这是一个很棒的工具。

答案 9 :(得分:0)

我建议查看所涉及的表,最终结果,并从头开始查看是否可以以更有效的方式完成查询。保持查询以验证新查询是否与旧查询完全相同,但尝试忘记用于获取最终结果的所有方法。

答案 10 :(得分:-1)

我会从头开始重写它。

你说你明白它应该做什么,所以不应该那么困难。而且我敢打赌,这段代码的要求会不断变化,所以如果你现在不重写它,你最终可能会保留一些丑陋的怪物