我有一个正在运行的TSQL选择查询"慢"
SELECT
CustomerKey
,ProductKey
,RepresentativeKey
,ReportingDateKey
,SUM(i.InvoiceQuantity) AS InvoiceQuantity
,SUM(i.InvoiceQuantityKg) AS InvoiceQuantityKg
,SUM(i.BrutoInvoiceLineAmount) AS BrutoInvoiceLineAmount
,SUM(i.EndOfYearDiscount) AS EndOfYearDiscount
,SUM(i.NettoInvoiceLineAmount) AS NettoInvoiceLineAmount
,SUM(i.TotalLineCostPrice) AS CostPrice
,SUM(i.MarginAmount) AS MarginAmount
FROM FactInvoices i
WHERE
i.DossierKey =2
AND i.ReportingDate BETWEEN '2016-01-01' AND '2017-12-31'
GROUP BY
CustomerKey
,ProductKey
,RepresentativeKey
,ReportingDateKey
我在SSMS 32bit中运行查询。 执行时间是17-21s,我已经测试过在DossierKey和ReportingDate上添加非聚集索引,但这只会减慢查询速度。
该表有大约6.04M的记录,该结果集返回1M记录。 它在SQL 2016开发人员版上运行。 服务器规格:8核16gb RAM和HDD =>虚拟服务器。
查看执行计划,我无法找到任何改进。 我如何加快速度?更多硬件?但我不认为这会有所帮助,因为在运行此查询时服务器未完全使用。
指数:
CREATE NONCLUSTERED INDEX [_dx1]
ON [dbo].[FactInvoices] ([DossierKey],[ReportingDate])
INCLUDE ([CustomerKey],[ProductKey],[ReportingDateKey],[RepresentativeKey],[InvoiceQuantity],[InvoiceQuantityKg],[BrutoInvoiceLineAmount],[NettoInvoiceLineAmount],[MarginAmount],[EndOfYearDiscount],[TotalLineCostPrice])
感谢。
答案 0 :(得分:3)
对于此查询:
SELECT CustomerKey, ProductKey, RepresentativeKey, ReportingDateKey,
. . .
FROM FactInvoices i
WHERE i.DossierKey = 2 AND
i.ReportingDate BETWEEN '2016-01-01' AND '2017-12-31'
GROUP BY CustomerKey, ProductKey, RepresentativeKey, ReportingDateKey;
我会在FactInvoices(DossierKey, ReportingDate, CustomerKey, ProductKey, RepresentativeKey)
推荐一个索引。前两个是用于WHERE
子句的索引的主要元素。其余三列可用于聚合。您还可以包含查询中使用的所有其他列。
答案 1 :(得分:1)
这是我写的关于加快查询速度的文章。
如果查询速度很慢,则可以检查执行计划中可能的加速区域。
好吧,我这样做了,发现它并不总是有帮助。相同的执行计划可能需要花费几秒钟来运行,否则可能要花7分钟才能被杀死。
最近,我使用以前从未在一个地方提到过的各种技术解决了这个问题,并希望在相同情况下为其他任何人提供帮助。该解决方案通常在2秒内返回。
这就是我所做的。
开始查询
这是一个相当基本的查询。它报告销售订单,并允许用户最多指定6个可选的条件。
•如果用户未输入值的条件(例如,国家/地区),则其条件字符串设置为”,并且未选中国家/地区。
•如果用户确实输入了一个值的条件,则其条件字符串将用'%..%'括起来。例如,如果用户输入“ 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)使每次运行查询时都重新创建执行计划。当不同的用户选择可以改变查询结果时,这很有用。
希望这很有用。
感谢科恩·费里(Korn Ferry)数据库系统高级总监Lane Sandness的帮助。
答案 2 :(得分:-1)
如果您已经将此查询编入索引并且性能仍然不佳,则可以尝试使用DossierKey对表进行分区。
并更改
WHERE i.DossierKey = 2
到
WHERE $PARTITION.partition_function_name( 2)
https://www.cathrinewilhelmsen.net/2015/04/12/table-partitioning-in-sql-server/
https://docs.microsoft.com/en-us/sql/t-sql/functions/partition-transact-sql