【问题标题】:Refactoring "extreme" SQL queries重构“极端”SQL 查询
【发布时间】:2008-11-26 14:41:31
【问题描述】:

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

由于时间限制和“其他因素”,它被匆忙投入生产,现在我的团队坚持支持它。性能令人震惊,尽管由于进行了一些整理,它相当容易阅读和理解(尽管代码味道很糟糕)。

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

【问题讨论】:

  • 将此信息添加到主要文章中,并尽可能适当地标记它。

标签: sql sql-server-2005 refactoring


【解决方案1】:

首先,如果这不会导致业务问题,则将其搁置,直到它成为问题为止。等到它变成问题,然后解决所有问题。

当您决定修复它时,请检查是否有一个语句导致您的大部分速度问题...隔离并修复它。

如果速度问题出现在所有语句上,并且您可以将它们全部合并到一个 SELECT 中,这可能会节省您的时间。我曾经将这样的 proc(没有那么多更新)转换为 SELECT,运行它的时间从 3 多分钟缩短到 3 秒以下(不,我简直不敢相信)。顺便说一句,如果某些数据来自链接服务器,请不要尝试这样做。

如果您出于某种原因不想或不能这样做,那么您可能需要调整现有的过程。以下是我会看的一些东西:

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

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

  3. 在运行更新之前为临时表建立索引。不要在更新语句所针对的任何列上创建索引,直到它们被更新。

  4. 如果您的表和分组允许,则对您的更新进行分组。 70 次更新对于 80 列来说是相当多的,听起来可能有机会这样做。

祝你好运

【讨论】:

    【解决方案2】:

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

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

    然后我会在 SSMS 中运行“显示实际执行计划”,并用跟踪记录结果。由此您应该能够确定是否缺少可以提高性能的索引。

    编辑:如果您要投反对票,请留言说明原因。

    【讨论】:

      【解决方案3】:

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

      我还想记录所有测试批次和批次中进程的运行时间,这样我就可以判断批次中的某个特定进程是否在某个时间点受到了不利影响。我可以获得流程的平均时间,并查看改进趋势或发现潜在问题。这也让我可以确定批次中我可以做出最大改进的低垂果实。

      【讨论】:

        【解决方案4】:

        然后有将近 70 个 UPDATE 对临时表的语句 几乎 500 行代码,每个 包含他们自己的小套 商业规则。它以 从临时表中选择 *。

        实际上,这听起来可以很好地遵循和理解,每个更新语句都对具有特定目的和一组业务规则的表执行一件事。我认为维护 500 行代码的过程,其中包含一个或几个执行“一切”的选择语句,由 15 个左右的连接构建,以及分散在各处的案例语句等,维护起来要困难得多。虽然它会带来更好的性能..

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

        【讨论】:

          【解决方案5】:

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

          如果不是这样,我建议你在选择重写它时要小心,因为你可能会弄乱你的数字。

          这听起来像是应该被拉到 SSIS 包中的那种东西,它会用结果建立一个新的永久表,因此它只需要运行一次。

          希望这是有道理的

          【讨论】:

            【解决方案6】:

            您可以尝试的一件事是将临时表替换为表变量。有时这会更快,有时则不是,您将不得不尝试并查看。

            查看 70 条更新语句。有可能将它们中的任何一个结合起来吗?如果写作者不使用 CASE 语句,则可能会使用更少的语句。

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

            【讨论】:

              【解决方案7】:

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

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

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

              评估更新语句序列真正需要“提交”语句的频率...节省大量提交行可以显着提高整体更新时间。数据库客户端驱动程序软件中可能有一些设置可以产生显着差异。

              是否可以将用于更新条件的查询分解为静态“视图”,进而可以在多个更新语句之间共享?视图可以将频繁访问的数据/查询行保存在内存中。在确定提交最佳之前可以挂起多少更新数据时,可能需要进行性能调整。

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

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

              【讨论】:

                【解决方案8】:

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

                【讨论】:

                • 感谢 James,现在在问题中提供了更多详细信息。
                【解决方案9】:

                您应该获得一个工具,该工具可让您获得应用程序将运行的所有查询的解释计划。对于 SQL 繁重的应用程序来说,这是提高性能的最佳选择。如果您阅读解释计划告诉您的内容并做出反应。如果您在 Oracle 上,我们过去使用的是 Qwest 的 TOAD(?)我想。这是一个很棒的工具。

                【讨论】:

                  【解决方案10】:

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

                  【讨论】:

                    【解决方案11】:

                    我会从头开始重写它。

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

                    【讨论】:

                    • 好的,但是如果你已经有错误的代码,新代码至少可能更容易长期维护。
                    • 发帖者并没有说它有问题,只是性能很糟糕。
                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2020-11-21
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2016-02-29
                    • 1970-01-01
                    相关资源
                    最近更新 更多