【问题标题】:Helping Optimizing a SQL Query帮助优化 SQL 查询
【发布时间】:2014-02-06 20:24:51
【问题描述】:

我已经阅读了很多查询优化,并且我已经能够优化其中的大部分。

不过,我有一个非常复杂的查询。它为我的会计账户创造了累积价值。该查询运行时间超过 10 分钟,我认为它应该是一种更好的优化方法,但我没有弄清楚。

我要优化的代码是这样的:

SELECT Empresa, IDCuenta, Año, Periodo, Saldo, 

((SELECT SUM(Saldo) 
FROM
(SELECT Empresa, IDCuenta, ReferenciaOrden,  SUM(Saldo) As Saldo
    FROM                                            
        (SELECT     Empresa, IDCuenta, ReferenciaOrden, SUM(Saldo) As Saldo 
        FROM dbo.GP_ContabilidadTrxActivas
        WHERE FechaTransacción<=GETDATE()
        GROUP BY    Empresa, IDCuenta, ReferenciaOrden
        UNION ALL
         SELECT     Empresa, IDCuenta, ReferenciaOrden, SUM(Saldo) As Saldo 
          FROM dbo.GP_ContabilidadTrxHistoricas
         WHERE FechaTransacción<=GETDATE()
         GROUP BY    Empresa, IDCuenta, ReferenciaOrden
         ) As Base
        GROUP BY Empresa, IDCuenta, ReferenciaOrden) As BaseInt
WHERE BaseInt.IDCuenta=BaseTotal.IDCuenta AND BaseInt.Empresa = BaseTotal.Empresa 
AND BaseInt.ReferenciaOrden<=BaseTotal.ReferenciaOrden
)) As SaldoAcumulado
FROM
(SELECT Empresa, IDCuenta, Año, Periodo, ReferenciaOrden, SUM(Saldo) As Saldo
                        FROM                                            
                          (SELECT     Empresa, IDCuenta, Año, Periodo, ReferenciaOrden, SUM(Saldo) As Saldo 
                          FROM dbo.GP_ContabilidadTrxActivas WITH (INDEX(IX_ReferenciaOrden)
                          WHERE FechaTransacción<=GETDATE()
                          GROUP BY    Empresa, IDCuenta, Año, Periodo,ReferenciaOrden
                           UNION ALL
                           SELECT     Empresa, IDCuenta, Año, Periodo, ReferenciaOrden, SUM(Saldo) As Saldo 
                           FROM dbo.GP_ContabilidadTrxHistoricas WITH (INDEX(IX_ReferenciaOrden)
                           WHERE FechaTransacción<=GETDATE()
                           GROUP BY    Empresa, IDCuenta, Año, Periodo,ReferenciaOrden
                           ) As Base
GROUP BY Empresa, IDCuenta, Año, Periodo, ReferenciaOrden) As BaseTotal 

我为此查询创建的索引是:

CREATE NONCLUSTERED INDEX IX_ReferenciaOrden
ON dbo.GP_ContabilidadTrxHistoricas (IDCuenta ASC, ReferenciaOrden ASC)
INCLUDE (Empresa,  Año, Periodo, Saldo, FechaTransacción);


CREATE NONCLUSTERED INDEX IX_ReferenciaOrden
ON dbo.GP_ContabilidadTrxActivas (IDCuenta ASC, ReferenciaOrden ASC)
INCLUDE (Empresa,  Año, Periodo, Saldo, FechaTransacción);

执行计划显示 87% 的成本在 3 个活动中:Index Seek、Stream Aggregate 和 Merge Join,这是部分估计执行计划的图像:

dbo.GP_ContabilidadTrxHistoricas 有 3.559.617 行,dbo.GP_ContabilidadTrxActivas 有 102.707 行

任何优化它的建议都将受到欢迎。提前致谢。

【问题讨论】:

  • 您是否也尝试过不使用索引 HINTS?这是否给出了相同的执行计划?也许在 cte 中获取 UNION ALL 查询并在之后使用它?您似乎使用了两次该查询,对吗?
  • 您在查询提示后丢失了 )。
  • @SaUce 查询运行正常,我只是错过了在这里输入的问题。
  • @NickyvV 是的,我使用了两次,因为我需要做累计金额。我在没有索引 HINTS 的情况下做到了,但给了我一个更昂贵的执行计划。我会尝试 CTE 来检查是否效果更好。谢谢!

标签: sql sql-server optimization sql-server-2008-r2


【解决方案1】:

首先,将“包含”作为索引的一部分让我感到困惑,因为我从未见过它,因此我对其进行了调查,并在 this post 中找到了很好的解释/答案。重要的注意事项是 INCLUDE 应该在不属于 group by 之类的东西的字段上。您的查询绝对使用列作为 group by 的一部分,并且应该是查询优化的正常覆盖索引的一部分。

其次,可能会浪费您的时间的是,您正在为基线查询中的每条返回记录对列 Saldo 进行相关查询,从而导致每次重复运行时性能下降。我将重组您的查询以具有主要的 FROM 子句,因为这两个查询分别运行一次并分别在列上加入它们。似乎对于每个更深层次的项目,您还需要父级聚合总计。例如,给定区域内的所有销售额都是一列,但还包括与整个区域进行比较的总数。我可能不正确,但看起来就是这样。

因此,我将在每个当前和历史事务表上创建您的索引作为以下键。前 3 列专门用于匹配您的更高级别的聚合,因此 THAT TOO 进行了优化,而无需进入 Ano、Periodo、FechaTransaccion 的粒度级别。

(Empresa、IdCuenta、ReferenciaOrden、Ano、Periodo、FechaTransaccion)包括(saldo)

SELECT 
      BaseTotal.Empresa, 
      BaseTotal.IDCuenta, 
      BaseTotal.Año, 
      BaseTotal.Periodo, 
      BaseTotal.Saldo, 
      SUM( BaseInt.Saldo ) as OrdenSaldo
   FROM
      ( SELECT 
              Empresa,
              IDCuenta,
              ReferenciaOrden,
              Año,
              Periodo,
              SUM(Saldo) As Saldo
           FROM
              ( SELECT
                      Empresa,
                      IDCuenta,
                      ReferenciaOrden,
                      Año,
                      Periodo,
                      SUM(Saldo) As Saldo 
                   FROM 
                      dbo.GP_ContabilidadTrxActivas
                   WHERE 
                      FechaTransacción <= GETDATE()
                   GROUP BY
                      Empresa,
                      IDCuenta,
                      ReferenciaOrden,
                      Año,
                      Periodo
                UNION ALL
                SELECT
                      Empresa,
                      IDCuenta,
                      Año,
                      Periodo,
                      ReferenciaOrden,
                      SUM(Saldo) As Saldo 
                   FROM 
                      dbo.GP_ContabilidadTrxHistoricas
                   WHERE 
                      FechaTransacción <= GETDATE()
                   GROUP BY
                      Empresa,
                      IDCuenta,
                      ReferenciaOrden,
                      Año,
                      Periodo ) As Base
           GROUP BY 
              Empresa,
              IDCuenta,
              ReferenciaOrden,
              Año,
              Periodo ) As BaseTotal 
      JOIN
      ( SELECT 
              Empresa, 
              IDCuenta, 
              ReferenciaOrden,  
              SUM(Saldo) As Saldo
           FROM
              ( SELECT
                      Empresa,
                      IDCuenta,
                      ReferenciaOrden,
                      SUM(Saldo) As Saldo 
                   FROM 
                      dbo.GP_ContabilidadTrxActivas
                   WHERE 
                      FechaTransacción <= GETDATE()
                   GROUP BY
                      Empresa, 
                      IDCuenta, 
                      ReferenciaOrden
                UNION ALL
                SELECT
                      Empresa, 
                      IDCuenta, 
                      ReferenciaOrden, 
                      SUM(Saldo) As Saldo 
                   FROM 
                      dbo.GP_ContabilidadTrxHistoricas
                   WHERE 
                      FechaTransacción <= GETDATE()
                   GROUP BY
                      Empresa, 
                      IDCuenta, 
                      ReferenciaOrden ) As Base
           GROUP BY 
              Empresa, 
              IDCuenta, 
              ReferenciaOrden ) As BaseInt
         ON BaseTotal.Empresa = BaseInt.Empresa
         AND BaseTotal.IDCuenta = BaseInt.IDCuenta
         AND BaseInt.ReferenciaOrden <= BaseTotal.ReferenciaOrden 
   GROUP BY
      BaseTotal.Empresa, 
      BaseTotal.IDCuenta, 
      BaseTotal.Año, 
      BaseTotal.Periodo, 
      BaseTotal.Saldo, 
   ORDER BY
      BaseTotal.Empresa, 
      BaseTotal.IDCuenta, 
      BaseTotal.Año, 
      BaseTotal.Periodo

【讨论】:

  • 它不工作,因为不遵循业务规则。请记住,我需要获取运行总数,我做了以下更改: BaseTotal.ReferenciaOrden = BaseInt.IDCuenta for BaseTotal.ReferenciaOrden
  • @MariPlaza,那么您能否在您的帖子末尾显示一些您希望查询与您提到的“运行总计”列一起显示的示例。在这篇文章和未来的文章中,类似的事情通常会为我和其他人带来更好的初步答案。另外...我修改了查询以希望处理您的汇总值。请也检查一下。
  • @DRappp 好的,感谢您的反馈,我会在以后加入。我查看了您的查询并尝试修改它以使用您的技术获取我期望的数据,但它不起作用。我对运行总计进行了很多研究,这不是一个简单的查询,而且并非所有技术都能正确检索它。感谢您的帮助。
【解决方案2】:

如果你有SQL Server 2005 或更高版本,你可以试试这个:

DECLARE @tempTable TABLE (Empresa VARCHAR(100), IDCuenta INT, Año INT, Periodo INT, ReferenciaOrden INT, Saldo MONEY)

INSERT INTO @tempTable (Empresa, IDCuenta, Año, Periodo, ReferenciaOrden, Saldo)
SELECT Empresa, IDCuenta, Año, Periodo, ReferenciaOrden, SUM(Saldo) AS Saldo
FROM (
    SELECT Empresa, IDCuenta, Año, Periodo, ReferenciaOrden, Saldo
    FROM dbo.GP_ContabilidadTrxActivas
    WHERE FechaTransacción <= GETDATE()
    UNION ALL
    SELECT Empresa, IDCuenta, Año, Periodo, ReferenciaOrden, Saldo
    FROM dbo.GP_ContabilidadTrxHistoricas
    WHERE FechaTransacción <= GETDATE()
) AS Base
GROUP BY Empresa, IDCuenta, Año, Periodo, ReferenciaOrden

SELECT Empresa, IDCuenta, Año, Periodo, Saldo
    , (
        SELECT SUM(Saldo)
        FROM @tempTable AS BaseInt
        WHERE BaseInt.IDCuenta = BaseTotal.IDCuenta
            AND BaseInt.Empresa = BaseTotal.Empresa
            AND BaseInt.ReferenciaOrden <= BaseTotal.ReferenciaOrden
    ) AS SaldoAcumulado
FROM @tempTable AS BaseTotal

还可能创建包含FechaTransacción 字段的索引会有所帮助。因为你用它来过滤表。

【讨论】:

  • 谢谢,我尝试了查询,甚至更优化,它没有给我正确的结果,因为 SaldoAcumulado 产生的值与 Saldo 相同。如果我消除了ReferenciaOrden,我会得到上个月的值,而不是在特定时期之前的累积值。还有其他想法吗?
  • 对不起。我以一种错误的方式阅读了您的查询。您拥有哪个版本的 SQL Server?这很重要。
  • SQL Server 2008 R2 谢谢!
  • 查看更新的答案。在 SQL Server 2008 中,没有其他方法可以计算运行总计。尝试使用表变量。
【解决方案3】:

您正在按日期过滤,我建议您创建这样的索引

CREATE NONCLUSTERED INDEX IX_ReferenciaOrden
ON dbo.GP_ContabilidadTrxHistoricas (FechaTransacción)

如果这对您没有帮助,请尝试在 GROUP By 子句中添加列。这样索引的排序方式与 GROUp BY 要求的方式相同

CREATE NONCLUSTERED INDEX IX_ReferenciaOrden
ON dbo.GP_ContabilidadTrxHistoricas (FechaTransacción, Empresa, IDCuenta, Año, Periodo,ReferenciaOrden)

如果你仍然认为这很慢,请使用 select 子句中的列创建覆盖索引,这样根本不需要访问聚集索引

CREATE NONCLUSTERED INDEX IX_ReferenciaOrden
ON dbo.GP_ContabilidadTrxHistoricas (FechaTransacción, Empresa, IDCuenta, Año, Periodo,ReferenciaOrden)
INCLUDE(Saldo)

您也可以尝试使用 CTE 重新格式化查询

【讨论】:

  • 谢谢,我创建了第三个索引,它略有改善,现在大约 8 分钟。我尝试了 CTE,但没有任何区别,只是改进了查询的顺序。
猜你喜欢
  • 2011-03-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-06
相关资源
最近更新 更多