这是我写的一篇关于加速查询的文章。
如果您的查询速度很慢,您可以检查执行计划以了解可能的加速区域。
好吧,我已经这样做了,但发现它并不总是有帮助。相同的执行计划可能需要几秒钟才能运行或进入 never never land 并在 7 分钟后被杀死。
我最近使用了多种技术解决了这个问题,这些技术我以前没有在一个地方提到过,并且想帮助遇到同样情况的其他人。解决方案通常会在 2 秒内返回。
这就是我所做的。
开始查询
这是一个相当基本的查询。它报告销售订单并允许用户指定多达 6 个可选的 where 条件。
• 如果用户没有输入值的标准,例如 Country,则其标准字符串设置为 '' 并且 Country 不被选中。
• 如果用户确实为某个值输入了条件,则其条件字符串由'%..%' 括起来。例如,如果用户输入 'Tin',strCountry 将设置为 '%Tin%' 并选择名称中包含 'Tin' 的所有国家/地区。 (例如阿根廷和马提尼克。)
SELECT Top 1000
SalesHeader.InvoiceNumber
,SalesHeader.CompanyName
,SalesHeader.Street
,SalesHeader.City
,SalesHeader.Region
,SalesHeader.Country
,SalesHeader.SalesDate
,SalesHeader.InvoiceTotal
,SalesLineItem.LineItemNbr
,SalesLineItem.PartNumber
,SalesLineItem.Quantity
,SalesLineItem.UnitPrice
,SalesLineItem.Quantity * SalesLineItem.UnitPrice as ExtPrice
,PartMaster.UnitWeight
,SalesLineItem.Quantity * PartMaster.UnitWeight as ExtWeight
FROM dbo.SalesHeader
left join dbo.SalesLineItem on SalesHeader.InvoiceNumber = SalesLineItem.InvoiceNumber
left join dbo.PartMaster on SalesLineItem.PartNumber = PartMaster.PartNumber
where
(@strCountry = '' or Country like @strCountry)
and
(@strCompanyName = '' or CompanyName like @strCompanyName)
and
(@strPartNumber = '' or SalesLineItem.PartNumber like @strPartNumber)
and
(@strInvoiceNumber = '' or SalesHeader.InvoiceNumber like @strInvoiceNumber)
and
(@strRegion = '' or Region like @strRegion)
and
(@mnyExtPrice = 0 or (SalesLineItem.Quantity * SalesLineItem.UnitPrice) > @mnyExtPrice)
Order By
InvoiceNumber,
Region,
ExtPrice
我是从我工作的数据仓库中获取的。完整查询中有 260,000 条记录。我们将返回的记录限制为 1,000 条,因为用户永远不会想要更多。
有时查询需要 10 秒或更短的时间,有时我们必须在 7 多分钟后终止它。用户不会等待 7 分钟。
我们的想法
有不同的技术可以加快查询速度。以下是我们生成的查询。我将介绍下面使用的每种技术。
这个新查询通常会在 2 秒或更短的时间内返回结果。
SELECT
InvoiceNumber
,Company
,Street
,City
,Region
,Country
,SalesDate
,InvoiceTotal
,LineItemNbr
,PartNumber
,Quantity
,UnitPrice
,ExtPrice
,UnitWeight
,ExtWeight
FROM
(
SELECT top 1000
IdentityID,
ROW_NUMBER() OVER (ORDER BY [SalesDate], [Country], [Company], [PartNumber]) as RowNbr
FROM dbo.SalesCombined with(index(NCI_SalesDt))
where
(@strCountry = '' or Country like @strCountry)
and
(@strCompany = '' or Company like @strCompany)
and
(@strPartNumber = '' or PartNumber like @strPartNumber)
and
(@strInvoiceNumber = '' or InvoiceNumber like @strInvoiceNumber)
and
(@strRegion = '' or Region like @strRegion)
and
(@mnyExtPrice = 0 or ExtPrice > @mnyExtPrice)
) SubSelect
Inner Join dbo.SalesCombined on SubSelect.IdentityID = SalesCombined.IdentityID
Order By
RowNbr
技术 1 - 非规范化数据。
我在两个方面很幸运:
• 数据足够小,可以创建第二个副本。
• 数据没有经常变化。这意味着我可以构建为查询优化的第二个副本,并允许更新需要一段时间。
SalesHeader、SalesLineItem 和 PartMaster 表合并到一个 SalesCombined 表中。
计算的值也存储在 SalesCombined 表中。
请注意,我将原始表格留在原处。更新这些表的所有代码仍然有效。我必须创建额外的代码,然后将更改传播到 SalesCombined 表。
技术 2 - 创建一个整数标识值
这个非规范化表的第一个字段是一个整数标识值。这称为 IdentityID。
即使我们没有对数据进行非规范化,SalesHeader 中的整数标识值也可以用于它与 SalesLineItem 之间的连接,并加快原始查询速度。
技术 3 - 在此整数标识值上创建聚集索引
我在这个 IdentityID 值上创建了一个聚集索引。这是查找记录的最快方法。
技术 4 - 在排序字段上创建唯一的非聚集索引
查询的输出按四个字段排序:SalesDate、Country、Company、PartNumber。所以我在 SalesDate、Country、Company 和 PartNumber 这些字段上创建了一个索引。
然后我将 IdentityID 添加到此索引中。该索引被标记为唯一。这使得 SQL Server 可以尽快从排序字段转到实际记录的地址。
技巧 5:在非聚集索引中包含所有“Where 子句”字段
SQL Server 索引可以包含不属于排序的字段。 (谁想到的?这是个好主意。)如果在索引中包含所有 where 子句字段,SQL Server 就不必查找实际记录来获取这些数据。
这是正常的查找过程:
1)从磁盘读取索引。
2) 转到索引上的第一个条目。
3) 从该条目中找到第一条记录的地址。
4) 从磁盘读取该记录。
5) 查找属于 where 子句的任何字段并应用条件。
6) 确定该记录是否包含在查询中。
如果在索引中包含 where 子句字段:
1)从磁盘读取索引。
2) 转到索引上的第一个条目。
3) 查找属于 where 子句(存储在索引中)的任何字段并应用条件。
4) 确定该记录是否包含在查询中。
CREATE UNIQUE NONCLUSTERED INDEX [NCI_InvcNbr] ON [dbo].[SalesCombined]
(
[SalesDate] ASC,
[Country] ASC,
[CompanyName] ASC,
[PartNumber] ASC,
[IdentityID] ASC
)
INCLUDE [InvoiceNumber],
[City],
[Region],
[ExtPrice]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF,
IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY]
原始查询的执行计划。
Click Here To See Original Query Execution Plan
我们最终查询的执行计划要简单得多 - 开始时,它只是读取索引。
Click Here To See Final Query Execution Plan
技巧 6:创建子查询来查找要输出的每条记录的 IdentityID 及其排序顺序
我创建了一个子查询来查找要输出的记录以及输出它们的顺序。请注意以下几点:
• 技术 7 - 它明确表示要使用包含所有所需字段的 NCI_InvcNbr 索引。
• 技术 8- 它使用 Row_Number 函数为将要输出的每一行生成一个整数。这些值是按照该行 ORDER BY 部分中的字段给出的顺序生成的 1、2 和 3。
技巧 9:创建包含所有值的封闭查询
此查询指定要打印的值。它使用 Row_Number 值来了解打印的顺序。请注意,内连接是在 IdentityID 字段上完成的,该字段使用聚集索引来查找要打印的每条记录。
无济于事的技术
我们尝试了两种没有加快查询速度的技术。这些语句都添加到查询的末尾。
• OPTION (MAXDOP 1) 将处理器的数量限制为一个。这将阻止执行任何并行性。我们在试验查询并在执行计划中具有并行性时尝试了这一点。
• OPTION (RECOMPILE) 导致每次运行查询时都重新创建执行计划。当不同的用户选择可以改变查询结果时,这会很有用。
希望这个有用。