优化 SQL 查询,TSQL

Optimize SQL query, TSQL

我是一名软件开发人员,最近 DBA 找我优化我的一个应用程序正在使用的查询。 DBA 报告说,查询在运行时占用了大约 50% 的 CPU 和高 I/O 操作。查询非常简单,我不确定如何优化它。

问题 1:如何优化这个查询?

问题2:这也是我的工作吗,DBA不应该更懂这个吗?请注意,我们没有数据库开发人员,只有 DBA 和软件开发人员。

DB 大约有 30-50 百万条记录,DBA 经常 maintained/monitored,但我不确定如何处理。服务器在专用机器上 Microsoft SQL Server 2005 - 9.00.5057.00 (X64)

PS: 请不要提供通过结构更改来改进数据库的方法,我知道将货币存储为 varchar 是一个糟糕的设计,但它就是是的,我们不能改变数据库结构,只能查询访问它。

感谢您的任何见解。

查询:

SELECT
    COALESCE(CAST([PH].[PAmount] AS decimal(15, 2)) + CAST([PH].[Fee] AS decimal(15, 2)), 0.0) AS [PayAmount],
    [PH].[PDate] AS [PayDate]
FROM [History] AS [PH] WITH (NOLOCK)
WHERE [PH].[PMode] IN ('C', 'P')
    AND [PH].[INNO] = 'XYZ'
    AND [PH].[PStatus] IN ('CONSERVED', 'EXPECTING', 'REFRIGERATED', 'POSTPONED', 'FILED')
    AND [PH].[Locked] = 1
    AND [PH].[PDate] >= 'Jan 1, 2015'
ORDER BY [PH].[PDate] ASC

字段:

PAmount - 非聚集索引,varchar(50)

Fee - 未编入索引,decimal(6,2)

PDate - 聚簇索引,datetime

PMode - 非聚集索引,varchar(5)

INNO - 非聚集索引,varchar(50)

PStatus - 非聚集索引,varchar(50)

Locked - 未编入索引,bit

执行计划: SELECT---Compute Scalar---Filter---NestedLoops-|--Index Seek (Inner Join) | cost 0% Cost 0% Cost 0% Cost 0% | cost 4% |---Key Lookup Cost 96%

你是对的,查询看起来很正常。这是一个直接的查询,只有 'AND' 子句,没有 "NOT NULL" 约束或连接或子选择。条件基本相同(只有日期是相关的)。如果条件中的值(如 'C'、'P'、1、'XYZ'、'CONSERVED' 等)具有足够的选择性,那么您(或 DBA)应该定义一些索引和优化器可以使用它。请 DBA 为 table.

创建适当的索引

您希望得到多少行结果?如果有很多(例如 >> 10,000),ORDER BY 子句可能会花费很多。

我会看看用 ISNULL 代替 COALESCE 是否能得到更好的结果。

另一件事是查看索引。您列出了索引的字段。如果这些字段被多个索引覆盖,我建议为这个查询做一个好的覆盖索引。

覆盖索引是指查询所需的所有数据都包含在索引中的索引。如果查询使用的索引未覆盖,则需要额外的行程(或行程)到 table 以获取其余字段。如果所有数据都在查询中,效率会更高。

查看这些文章:

What are Covering Indexes and Covered Queries in SQL Server?

https://www.simple-talk.com/sql/learn-sql-server/using-covering-indexes-to-improve-query-performance/

对于不属于联接或where 子句的数据,您可以使用include 关键字。包含的字段不是索引的可搜索部分,但会将行程保存到数据库中。

试试下面的索引。 where 子句中的所有字段都是索引的可搜索部分的一部分,并且包括所有不属于 where 子句的返回字段。在查看执行计划后,您可能需要尝试一下订单,但我做了我最好的猜测。

Create Nonclustered Index Ix_Ncl_History_CoveringBigSelect on History(PDate, PMode, INNO, PStatus, Locked) Include (PAmount, Fee)

这是一篇关于包含的列的文章。

看来你对索引有误解。索引不相互组合,所以这不是列 "indexed" 或 "not indexed" 的问题。为单个列创建单独的索引并不好。它是关于拥有多个列的索引,这些列与单个查询非常相关。如果数据库首先在另一列上 select 效率更高,则列上的索引将无助于查询。

我对此有点陈旧,但对于此查询,我建议使用如下所示的索引:

CREATE NONCLUSTERED INDEX [ix_History_XXXXX] ON [History] 
(
    [INNO] ASC,
    [Locked] ASC,
    [PDate] ASC,
    [PMode] ASC
)
INCLUDE ( PStatus, PAmount, Fee)

您可能想要交换 PDate、PMode 和 PStatus,具体取决于它们 selectivity

建立索引时,您希望首先列出最具体的项目。一般的想法是索引按顺序存储每个连续的项目。使用此索引,INNO 的所有 XYZ 值的行将组合在一起,因此查询引擎可以 查找 索引的该部分.下一个最具体的列是 Locked。即使这是一个 bit 值,因为它仅限于一个值,我们仍然能够 seek 直接到索引的一个特定部分,这对整个查询。再说一次:我已经有一段时间没有做这种事情了,所以你也可以在这里列出 PMode;我只是不记得 Sql 服务器查询优化器是否足够聪明,可以有效地处理这两个值。

从现在开始,索引的最佳选择取决于每个查询值对结果的限制程度。由于我们不再能够将所有结果集中到一个 space 中,我们将不得不 扫描 索引的相关部分。我的直觉是接下来使用 Date 值。这将允许扫描从与您的结果匹配的第一个日期开始遍历索引,并帮助它以正确的顺序获取记录,但同样:这只是我的直觉。首先列出 PMode 或 PStatus 可能会做得更好。

最后,INCLUDES 子句中的附加项将允许您完全从索引完成此查询,而无需实际返回到完整的 table。您使用 INCLUDES 子句而不是仅仅将值附加到查询以避免使 Sql 服务器重建索引以更新这些列。这就是为什么 PStatus,例如,如果状态是可以改变的,可能不应该成为主索引的一部分,以及为什么你 可能 最好也离开 Locked 出索引。不过,这些是您需要衡量并自己测试的东西。

我会简单地在以下 table:

上创建索引
CREATE NONCLUSTERED INDEX idx_History_Locked_PMode_INNO_PStatus_PDate_iPAmount_iFee
    ON dbo.History (Locked, PMode, INNO, PStatus, PDate)
    INCLUDE (PAmount, Fee)
WHERE Locked = 1;                -- This is optional, can reduce index size.

这应该会改进您当前的查询。此处应满足所有条件。

如您所说,我假设您无法对数据库执行任何操作,包括索引和结构更改。那么客户端App环境呢,是否强大到可以做客户端计算?

如果是,我建议把计算移到客户端:

  • 不要在查询中转换数据类型,将varchar转换为decimal消耗 CPU 资源。所以直接获取结果并在中进行转换工作 你的应用程序。
  • 对于 IO 问题,请尝试删除 IN 条件,因为 IN 本质上是一个 "OR" 条件。所以将您的查询分成小块使用“=” 条件并发送到您的应用程序,使用您的客户端应用程序 "Union" 他们。