Mar*_*lli 15 performance sql-server optimization index-tuning sql-server-2014 query-performance
我正在优化一些查询。
对于下面的查询,
SET STATISTICS IO ON;
DECLARE @OrderStartDate DATETIME2 = '27 feb 2016';
DECLARE @OrderEndDate DATETIME2 = '28 feb 2016';
SELECT o.strBxOrderNo
, o.sintOrderStatusID
, o.sintOrderChannelID
, o.sintOrderTypeID
, o.sdtmOrdCreated
, o.sintMarketID
, o.strOrderKey
, o.strOfferCode
, o.strCurrencyCode
, o.decBCShipFullPrice
, o.decBCShipFinal
, o.decBCShipTax
, o.decBCTotalAmount
, o.decWrittenTotalAmount
, o.decBCWrittenTotalAmount
, o.decBCShipOfferDisc
, o.decBCShipOverride
, o.decTotalAmount
, o.decShipTax
, o.decShipFinal
, o.decShipOverride
, o.decShipOfferDisc
, o.decShipFullPrice
, o.lngAccountParticipantID
, CONVERT(DATE, o.sdtmOrdCreated, 120) as OrderCreatedDateConverted
FROM tablebackups.dbo.tblBOrder o
WHERE o.sdtmOrdCreated >= @OrderStartDate
AND o.sdtmOrdCreated < @OrderEndDate
AND EXISTS (
SELECT *
FROM tablebackups.dbo.tblBOrderItem oi
WHERE oi.strBxOrderNo = o.strBxOrderNo
AND oi.decCatItemPrice > 0
)
OPTION (RECOMPILE);
Run Code Online (Sandbox Code Playgroud)
我创建了以下过滤索引:
-- table dbo.tblBorderItem
CREATE NONCLUSTERED INDEX IX_tblBOrderItem_decCatItemPrice_INCL
ON dbo.tblBorderItem
(
strBxOrderNo ASC
, sintOrderSeqNo ASC
, decCatItemPrice
)
INCLUDE
(
blnChargeShipping
, decBCCatItemPrice
, decBCCostPrice
, decBCFinalPrice
, decBCOfferDiscount
, decBCOverrideDiscount
, decBCTaxAmount
, decCostPrice
, decFinalPrice
, decOfferDiscount
, decOverrideDiscount
, decTaxAmount
, decWasPrice
, dtmOrdItemCreated
, sintOrderItemStatusId
, sintOrderItemType
, sintQuantity
, strItemNo
)
WHERE decCatItemPrice > 0
WITH (DROP_EXISTING = ON, FILLFACTOR = 95);
Run Code Online (Sandbox Code Playgroud)
此索引不仅特别用于此查询,还有其他查询使用此相同索引,因此是 INCLUDED 列。
特别是对于这个查询,我只想检查 (EXISTS) 订单是否有任何项目 where decCatItemPrice > 0
。
SQL Server 正在执行索引扫描,如下图所示。
请注意,我没有从项目表中选择任何列。
此项目表中有 164,309,397 个在活。我想避免在那里进行扫描。
问题:
为什么 SQL Server 不进行索引查找?
为了改进这个查询,我还应该考虑其他因素/事情吗?
(4537 row(s) affected) Table 'tblBorder'. Scan count 1, logical reads
116, physical reads 0, read-ahead reads 0, lob logical reads 0, lob
physical reads 0, lob read-ahead reads 0. Table 'tblBorderItem'. Scan
count 1, logical reads 689, physical reads 0, read-ahead reads 0, lob
logical reads 0, lob physical reads 0, lob read-ahead reads 0.
(1 row(s) affected)
Run Code Online (Sandbox Code Playgroud)
这是表tblBorderItem的定义和索引
IF OBJECT_ID('[dbo].[tblBorderItem]') IS NOT NULL
DROP TABLE [dbo].[tblBorderItem]
GO
CREATE TABLE [dbo].[tblBorderItem] (
[strBxOrderNo] VARCHAR(20) NOT NULL,
[sintOrderSeqNo] SMALLINT NOT NULL,
[sintOrderItemStatusId] SMALLINT NOT NULL,
[sintNameStructureID] SMALLINT NOT NULL,
[strItemNo] VARCHAR(20) NOT NULL,
[sintQuantity] SMALLINT NOT NULL,
[strCurrencyCode] VARCHAR(3) NOT NULL,
[decCostPrice] DECIMAL(18,4) NOT NULL,
[decCatItemPrice] DECIMAL(18,2) NOT NULL,
[decOfferDiscount] DECIMAL(18,2) NOT NULL,
[decOverrideDiscount] DECIMAL(18,2) NOT NULL,
[decFinalPrice] DECIMAL(18,2) NOT NULL,
[decTaxAmount] DECIMAL(18,2) NOT NULL,
[strBCCurrencyCode] VARCHAR(3) NOT NULL,
[decBCCostPrice] DECIMAL(18,4) NOT NULL,
[decBCCatItemPrice] DECIMAL(18,4) NOT NULL,
[decBCOfferDiscount] DECIMAL(18,4) NOT NULL,
[decBCOverrideDiscount] DECIMAL(18,4) NOT NULL,
[decBCFinalPrice] DECIMAL(18,4) NOT NULL,
[decBCTaxAmount] DECIMAL(18,4) NOT NULL,
[dtmOrdItemCreated] DATETIME NOT NULL,
[blnChargeShipping] BIT NOT NULL,
[lngTimeOfOrderQtyOnHand] INT NULL,
[sdtmTimeOfOrderDueDate] SMALLDATETIME NULL,
[lngProdSetSeqNo] INT NULL,
[lngProdRelationId] INT NULL,
[lngProdRelationMemberId] INT NULL,
[decWasPrice] DECIMAL(18,2) NULL,
[sintOrderItemType] SMALLINT NULL,
[tsRowVersion] TIMESTAMP NULL,
[sdtmOrderItemStatusUpdated] SMALLDATETIME NULL,
CONSTRAINT [PK_tblBOrderItem]
PRIMARY KEY CLUSTERED
([strBxOrderNo] asc, [sintOrderSeqNo] asc)
WITH FILLFACTOR = 100)
GO
CREATE NONCLUSTERED INDEX
[IX_tblBOrderItem__dtmOrdItemCreated]
ON [dbo].[tblBorderItem] ([dtmOrdItemCreated] asc)
WITH FILLFACTOR = 100
CREATE NONCLUSTERED INDEX [IX_tblBOrderItem__sintOrderItemStatusId]
ON [dbo].[tblBorderItem] ([sintOrderItemStatusId] asc)
INCLUDE ([sdtmOrderItemStatusUpdated],
[sintOrderSeqNo], [strBxOrderNo], [strItemNo])
WITH FILLFACTOR = 100
CREATE NONCLUSTERED INDEX [IX_tblBOrderItem__
sintOrderItemStatusId_decFinalPrice_
sdtmOrderItemStatusUpdated_
include_strBxOrderNo]
ON [dbo].[tblBorderItem]
([sintOrderItemStatusId] asc,
[decFinalPrice] asc,
[sdtmOrderItemStatusUpdated] asc)
INCLUDE ([strBxOrderNo])
WITH FILLFACTOR = 100
CREATE NONCLUSTERED INDEX [IX_tblBOrderItem__strBxOrderNo]
ON [dbo].[tblBorderItem]
([strBxOrderNo] asc)
WITH FILLFACTOR = 100
CREATE NONCLUSTERED INDEX [IX_tblBOrderItem__strItemNo]
ON [dbo].[tblBorderItem] ([strItemNo] asc)
WITH FILLFACTOR = 100
CREATE NONCLUSTERED INDEX
[IX_tblBOrderItem_decCatItemPrice_INCL]
ON [dbo].[tblBorderItem]
([strBxOrderNo] asc, [sintOrderSeqNo] asc, [decCatItemPrice] asc)
INCLUDE ([blnChargeShipping],
[decBCCatItemPrice], [decBCCostPrice], [decBCFinalPrice],
[decBCOfferDiscount], [decBCOverrideDiscount],
[decBCTaxAmount], [decCostPrice], [decFinalPrice],
[decOfferDiscount], [decOverrideDiscount],
[decTaxAmount], [decWasPrice], [dtmOrdItemCreated],
[sintOrderItemStatusId], [sintOrderItemType],
[sintQuantity], [strItemNo])
WHERE ([decCatItemPrice]>(0))
WITH FILLFACTOR = 95
Run Code Online (Sandbox Code Playgroud)
这是表tblBorder的定义和索引
IF OBJECT_ID('[dbo].[tblBorder]') IS NOT NULL
DROP TABLE [dbo].[tblBorder]
GO
CREATE TABLE [dbo].[tblBorder] (
[strBxOrderNo] VARCHAR(20) NOT NULL,
[uidOrderUniqueID] UNIQUEIDENTIFIER NOT NULL,
[sintOrderStatusID] SMALLINT NOT NULL,
[sintOrderChannelID] SMALLINT NOT NULL,
[sintOrderTypeID] SMALLINT NOT NULL,
[blnIsBasket] BIT NOT NULL,
[sdtmOrdCreated] SMALLDATETIME NOT NULL,
[sintMarketID] SMALLINT NOT NULL,
[strOrderKey] VARCHAR(20) NOT NULL,
[strOfferCode] VARCHAR(20) NOT NULL,
[lngShippedToParticipantID] INT NOT NULL,
[lngOrderedByParticipantID] INT NOT NULL,
[lngShipToAddressID] INT NOT NULL,
[lngAccountAddressID] INT NOT NULL,
[lngAccountParticipantID] INT NOT NULL,
[lngOrderedByAddressID] INT NOT NULL,
[lngOrderTakenBy] INT NOT NULL,
[strCurrencyCode] VARCHAR(3) NOT NULL,
[decShipFullPrice] DECIMAL(18,2) NOT NULL,
[decShipOfferDisc] DECIMAL(18,2) NOT NULL,
[decShipOverride] DECIMAL(18,2) NOT NULL,
[decShipFinal] DECIMAL(18,2) NOT NULL,
[decShipTax] DECIMAL(18,2) NOT NULL,
[strBCCurrencyCode] VARCHAR(3) NOT NULL,
[decBCShipFullPrice] DECIMAL(18,4) NOT NULL,
[decBCShipOfferDisc] DECIMAL(18,4) NOT NULL,
[decBCShipOverride] DECIMAL(18,4) NOT NULL,
[decBCShipFinal] DECIMAL(18,4) NOT NULL,
[decBCShipTax] DECIMAL(18,4) NOT NULL,
[decTotalAmount] DECIMAL(18,2) NOT NULL,
[decBCTotalAmount] DECIMAL(18,4) NOT NULL,
[decWrittenTotalAmount] DECIMAL(18,2) NULL,
[decBCWrittenTotalAmount] DECIMAL(18,4) NULL,
[blnProRataShipping] BIT NOT NULL,
[blnChargeWithFirstShipment] BIT NOT NULL,
[sintShippingServiceLevelID] SMALLINT NOT NULL,
[sintShippingMethodID] SMALLINT NOT NULL,
[sdtmDoNotShipUntil] SMALLDATETIME NULL,
[blnHoldUntilComplete] BIT NOT NULL,
[tsRowVersion] TIMESTAMP NULL,
CONSTRAINT [PK_tblBOrder]
PRIMARY KEY CLUSTERED
([strBxOrderNo] asc) WITH FILLFACTOR = 100)
GO
CREATE NONCLUSTERED INDEX
[IX_tblBOrder__lngAccountAddressID]
ON [dbo].[tblBorder]
([lngAccountAddressID] asc, [sintOrderStatusID] asc)
WITH FILLFACTOR = 100
CREATE NONCLUSTERED INDEX
[IX_tblBOrder__lngAccountParticipantID]
ON [dbo].[tblBorder]
([lngAccountParticipantID] asc)
WITH FILLFACTOR = 100
CREATE NONCLUSTERED INDEX
[IX_tblBOrder__lngOrderedByAddressID]
ON [dbo].[tblBorder]
([lngOrderedByAddressID] asc, [sintOrderStatusID] asc)
WITH FILLFACTOR = 100
CREATE NONCLUSTERED INDEX
[IX_tblBOrder__lngOrderedByParticipantID]
ON [dbo].[tblBorder] ([lngOrderedByParticipantID] asc)
WITH FILLFACTOR = 100
CREATE NONCLUSTERED INDEX
[IX_tblBOrder__lngShippedToParticipantID]
ON [dbo].[tblBorder]
([lngShippedToParticipantID] asc)
WITH FILLFACTOR = 100
CREATE NONCLUSTERED INDEX
[IX_tblBOrder__lngShipToAddressID]
ON [dbo].[tblBorder]
([lngShipToAddressID] asc, [sintOrderStatusID] asc)
WITH FILLFACTOR = 100
CREATE NONCLUSTERED INDEX
[IX_tblBOrder__sdtmOrdCreated_sintMarketID__include_strBxOrderNo]
ON [dbo].[tblBorder]
([sdtmOrdCreated] asc, [sintMarketID] asc)
INCLUDE ([strBxOrderNo])
WITH FILLFACTOR = 100
CREATE NONCLUSTERED INDEX
[IX_tblBOrder_sdtmOrdCreated_INCL]
ON [dbo].[tblBorder]
([sdtmOrdCreated] asc)
INCLUDE ([decBCShipFinal], [decBCShipFullPrice],
[decBCShipOfferDisc], [decBCShipOverride],
[decBCShipTax], [decBCTotalAmount], [decBCWrittenTotalAmount],
[decShipFinal], [decShipFullPrice], [decShipOfferDisc],
[decShipOverride], [decShipTax], [decTotalAmount],
[decWrittenTotalAmount], [lngAccountParticipantID],
[lngOrderedByParticipantID], [sintMarketID],
[sintOrderChannelID], [sintOrderStatusID],
[sintOrderTypeID], [strBxOrderNo], [strCurrencyCode],
[strOfferCode], [strOrderKey])
WITH FILLFACTOR = 100
CREATE NONCLUSTERED
INDEX [IX_tblBOrder_sintMarketID_sdtmOrdCreated]
ON [dbo].[tblBorder]
([sintMarketID] asc, [sdtmOrdCreated] asc)
INCLUDE ([sintOrderChannelID], [strBxOrderNo])
WITH FILLFACTOR = 100
CREATE NONCLUSTERED
INDEX [IX_tblBOrder__sintOrderChannelID_sdtmOrdCreated_INCL]
ON [dbo].[tblBorder]
([sintOrderChannelID] asc, [sdtmOrdCreated] asc)
INCLUDE ([decBCShipFinal], [decBCShipFullPrice],
[decBCShipTax], [decShipFinal], [decShipFullPrice],
[decShipTax], [lngAccountParticipantID], [sintMarketID],
[sintOrderTypeID], [strBxOrderNo],
[strCurrencyCode], [strOrderKey])
WITH FILLFACTOR = 100
CREATE NONCLUSTERED INDEX [IX_tblBOrder_strBxOrderNo_sdtmOrdCreated_incl]
ON [dbo].[tblBorder] ([strBxOrderNo] asc,
[sdtmOrdCreated] asc)
INCLUDE ([sintOrderChannelID], [sintOrderTypeID], [sintMarketID],
[strOrderKey], [lngAccountParticipantID], [strCurrencyCode],
[decShipFullPrice], [decShipFinal], [decShipTax],
[decBCShipFullPrice], [decBCShipFinal],
[decBCShipTax])
Run Code Online (Sandbox Code Playgroud)
结论
我在 LIVE 系统上应用了我的索引,并更新了我的存储过程以使用 SMALLDATETIME,以便匹配数据库中涉及的列的数据类型。
之后,在查看查询计划时,我看到了下图:
这正是我想要的。
我认为这种情况下的查询优化器在两种环境中都做得很好,可以得到最好的查询计划,我很高兴我没有添加任何查询提示。
我从发布的 3 个答案中学到了。感谢Max Vernon、Paul White和Daniel Hutmacher的回答。
Pau*_*ite 16
如果您希望查询优化器获得良好的结果,则需要注意数据类型。
您的变量输入为datetime2:
DECLARE @OrderStartDate datetime2 = '27 feb 2016';
DECLARE @OrderEndDate datetime2 = '28 feb 2016';
Run Code Online (Sandbox Code Playgroud)
但是与这些进行比较的列的类型是smalldatetime(正如sdtm前缀所暗示的那样!):
[sdtmOrdCreated] SMALLDATETIME NOT NULL
Run Code Online (Sandbox Code Playgroud)
类型不兼容使得优化器很难通过类型转换计算出最终的基数估计,如执行计划 xml 所示:
DECLARE @OrderStartDate datetime2 = '27 feb 2016';
DECLARE @OrderEndDate datetime2 = '28 feb 2016';
Run Code Online (Sandbox Code Playgroud)
当前的估计可能准确,也可能不准确(可能不准确)。修复类型不兼容可能会也可能不会完全解决您的计划选择问题,但这是我在深入研究问题之前首先要解决的(简单!)事情:
DECLARE @OrderStartDate smalldatetime = CONVERT(smalldatetime, '20160227', 112);
DECLARE @OrderEndDate smalldatetime = CONVERT(smalldatetime, '20160228', 112);
Run Code Online (Sandbox Code Playgroud)
在决定重写查询或使用提示之前,始终检查基数估计的准确性以及任何差异的原因。
有关动态查找的更多详细信息,请参阅我的 SQLblog.com 文章“动态查找和隐藏的隐式转换”。
更新:修复数据类型为您提供了您想要的搜索计划。之前的类型转换导致的基数估计错误给了你更慢的计划。
Han*_*non 11
SQL Server 正在执行索引扫描,因为它认为这比查找每个所需的行要便宜。考虑到您的设置中的选择,SQL Server 很可能是正确的。
请注意,SQL Server 实际上可能正在对索引进行范围扫描,而不是扫描整个索引。
如果您为两个表提供 DDL,以及您可能拥有的其他索引,我们可能能够帮助您减少资源密集度。
作为旁注,永远不要使用这样的日期文字。代替:
DECLARE @OrderStartDate DATETIME2 = '27 feb 2016';
DECLARE @OrderEndDate DATETIME2 = '28 feb 2016';
Run Code Online (Sandbox Code Playgroud)
用这个:
DECLARE @OrderStartDate DATETIME2 = '2016-02-27T00:00:00.0000';
DECLARE @OrderEndDate DATETIME2 = '2016-02-28T00:00:00.0000';
Run Code Online (Sandbox Code Playgroud)
Aaron 的帖子可能有助于澄清这一点。
要添加到Max 的答案中,我可能会尝试将您的查询分成两部分:
DECLARE @OrderStartDate DATETIME2 = {d '2016-02-27'};
DECLARE @OrderEndDate DATETIME2 = {d '2016-02-28'};
--- Work variable declarations:
DECLARE @minOrderNo varchar(20), @maxOrderNo varchar(20);
--- Find the lowest and highest order number respectively for
--- your date range:
SELECT @minOrderNo=MIN(strBxOrderNo),
@maxOrderNo=MAX(strBxOrderNo)
FROM dbo.tblBOrder o
WHERE o.sdtmOrdCreated >= @OrderStartDate AND
o.sdtmOrdCreated < @OrderEndDate;
--- Join orders and order items on their respective clustering keys.
SELECT o.strBxOrderNo
, o.sintOrderStatusID
, o.sintOrderChannelID
, o.sintOrderTypeID
, o.sdtmOrdCreated
, o.sintMarketID
, o.strOrderKey
, o.strOfferCode
, o.strCurrencyCode
, o.decBCShipFullPrice
, o.decBCShipFinal
, o.decBCShipTax
, o.decBCTotalAmount
, o.decWrittenTotalAmount
, o.decBCWrittenTotalAmount
, o.decBCShipOfferDisc
, o.decBCShipOverride
, o.decTotalAmount
, o.decShipTax
, o.decShipFinal
, o.decShipOverride
, o.decShipOfferDisc
, o.decShipFullPrice
, o.lngAccountParticipantID
, CONVERT(DATE, o.sdtmOrdCreated, 120) as OrderCreatedDateConverted
FROM dbo.tblBOrder AS o
INNER /*MERGE*/ JOIN dbo.tblBOrderItem AS oi ON
o.strBxOrderNo>=@minOrderNo AND --- OrderNo filter on "orders"
o.strBxOrderNo<=@maxOrderNo AND
oi.strBxOrderNo=o.strBxOrderNo AND --- Equijoin
oi.strBxOrderNo>=@minOrderNo AND --- OrderNo filter on "order items"
oi.strBxOrderNo<=@maxOrderNo AND
oi.decCatItemPrice > 0 --- Item price filter on "order items"
OPTION (RECOMPILE);
Run Code Online (Sandbox Code Playgroud)
此查询将 (a) 消除 Sort 运算符(这是昂贵的,因为它阻塞并需要内存授予),(b) 创建一个合并连接(您可以使用连接提示强制执行,但它应该在有足够数据的情况下自动发生)。作为奖励,它还将 (c) 消除索引扫描。
总而言之,MIN/MAX 查询使用 Orders 表上的高度优化索引从日期列上的非聚集索引中识别一系列订单号(包含在聚集键中):
然后,您可以在各自的聚集索引上合并连接两个表:
显然,我没有您要测试的数据,但我想这应该是一个性能非常好的解决方案。
归档时间: |
|
查看次数: |
2488 次 |
最近记录: |