优化 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?
对于不属于联接或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" 他们。
我是一名软件开发人员,最近 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?
对于不属于联接或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" 他们。