A_V*_*A_V 6 sql-server t-sql execution-plan sql-server-2016
我有这个查询,它过去需要几分钟才能运行,现在需要 6 秒,但它每天运行数千次,所以我想让它更快。
https://www.brentozar.com/pastetheplan/?id=SJMLjJOWm
似乎在此查询期间超过 99% 的 I/O 发生在一次聚集索引扫描上。
这正常吗?这个查询足以证明为它添加任何额外的索引是合理的,所以我想知道我是否在这里遗漏了一些明显的东西。
dbo.GROUP_CONCAT 函数来自这个 github 组装项目 https://github.com/orlando-colamatteo/ms-sql-server-group-concat-sqlclr
SpecsProd 表定义:
CREATE TABLE [dbo].[SpecsProd](
[ID] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
[specsID_1] [int] NULL,
[specsID_2] [int] NULL,
[specID] [int] NOT NULL,
[productID] [int] NOT NULL,
[SpecValue_1] [varchar](1000) NULL,
[SpecValue_2] [varchar](1000) NULL,
[Flock] [bit] NULL,
[SpecValue_1a] [varchar](2000) NULL,
[SpecValue_2a] [varchar](2000) NULL,
CONSTRAINT [PK_SpecsProd] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[SpecsProd] ADD CONSTRAINT [DF_SpecsProd_Flock] DEFAULT ((0)) FOR [Flock]
GO
ALTER TABLE [dbo].[SpecsProd] WITH NOCHECK ADD CONSTRAINT [FK_SpecsProd_Products] FOREIGN KEY([productID])
REFERENCES [dbo].[Products] ([Id_product])
NOT FOR REPLICATION
GO
ALTER TABLE [dbo].[SpecsProd] CHECK CONSTRAINT [FK_SpecsProd_Products]
GO
Run Code Online (Sandbox Code Playgroud)
使用 99% I/O 的聚集索引是 [PK_SpecsProd](第一个)。其他索引也在那里。
ALTER TABLE [dbo].[SpecsProd] ADD CONSTRAINT [PK_SpecsProd] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_ProdID_SpecID] ON [dbo].[SpecsProd]
(
[productID] ASC,
[specID] ASC
)
INCLUDE ( [ID],
[SpecValue_1],
[SpecValue_2]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_SpecID_ProductID] ON [dbo].[SpecsProd]
(
[specID] ASC,
[productID] ASC
)
INCLUDE ( [SpecValue_1],
[SpecValue_2]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [product] ON [dbo].[SpecsProd]
(
[productID] ASC
)
INCLUDE ( [specID],
[SpecValue_1],
[SpecValue_2]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [specId_inc_prodID_specvalue1] ON [dbo].[SpecsProd]
(
[specID] ASC
)
INCLUDE ( [productID],
[SpecValue_1]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [specsID] ON [dbo].[SpecsProd]
(
[specsID_1] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
GO
Run Code Online (Sandbox Code Playgroud)
具有最多列的表格的吉尼斯记录竞争者之一(规格表)
CREATE TABLE [dbo].[Specs](
[ID] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
[Id_spec] [int] NOT NULL,
[CatId] [int] NOT NULL,
[sect] [varchar](50) NULL,
[spec] [varchar](75) NOT NULL,
[format] [varchar](50) NULL,
[unit] [varchar](20) NULL,
[definition] [ntext] NULL,
[ordre] [int] NOT NULL,
[Id_langue] [int] NOT NULL,
[FormField] [varchar](50) NULL,
[List] [varchar](max) NULL,
[filterField] [bit] NOT NULL,
[isFilter] [bit] NOT NULL,
[isCollectionFilter] [bit] NOT NULL,
[quickViewSubcats] [varchar](250) NULL,
[width] [bit] NOT NULL,
[height] [bit] NOT NULL,
[depth] [bit] NOT NULL,
[weight] [bit] NOT NULL,
[DateCreation] [datetime] NOT NULL,
[DateModification] [datetime] NOT NULL,
[quickView] [bit] NOT NULL,
[Visible] [bit] NULL,
[compare] [bit] NOT NULL,
[priceTag] [bit] NOT NULL,
[ConvertionRate] [varchar](20) NULL,
[ConvertionUnit] [varchar](20) NULL,
[searchableLabel] [bit] NULL,
[searchableValue] [bit] NULL,
CONSTRAINT [PK_Specs] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE [dbo].[Specs] ADD CONSTRAINT [DF_Specs_filter] DEFAULT ((0)) FOR [filterField]
GO
ALTER TABLE [dbo].[Specs] ADD CONSTRAINT [DF_Specs_isFilter] DEFAULT ((0)) FOR [isFilter]
GO
ALTER TABLE [dbo].[Specs] ADD CONSTRAINT [DF_Specs_isCollectionFilter] DEFAULT ((0)) FOR [isCollectionFilter]
GO
ALTER TABLE [dbo].[Specs] ADD CONSTRAINT [DF_Specs_width] DEFAULT ((0)) FOR [width]
GO
ALTER TABLE [dbo].[Specs] ADD CONSTRAINT [DF_Specs_height] DEFAULT ((0)) FOR [height]
GO
ALTER TABLE [dbo].[Specs] ADD CONSTRAINT [DF_Specs_depth] DEFAULT ((0)) FOR [depth]
GO
ALTER TABLE [dbo].[Specs] ADD CONSTRAINT [DF_Specs_weight] DEFAULT ((0)) FOR [weight]
GO
ALTER TABLE [dbo].[Specs] ADD CONSTRAINT [DF_Specs_DateCreation] DEFAULT (getdate()) FOR [DateCreation]
GO
ALTER TABLE [dbo].[Specs] ADD CONSTRAINT [DF_Specs_DateModification] DEFAULT (getdate()) FOR [DateModification]
GO
ALTER TABLE [dbo].[Specs] ADD CONSTRAINT [DF_Specs_quickView] DEFAULT ((0)) FOR [quickView]
GO
ALTER TABLE [dbo].[Specs] ADD CONSTRAINT [DF_Specs_compare] DEFAULT ((0)) FOR [compare]
GO
ALTER TABLE [dbo].[Specs] ADD CONSTRAINT [DF_Specs_priceTag] DEFAULT ((0)) FOR [priceTag]
GO
Run Code Online (Sandbox Code Playgroud)
ASYNC_NETWORK_IO
是瓶颈。在您的实际计划中,大约 87% 的时间都花在将结果发送给客户上。您可能需要更改应用程序代码或减少发回的数据量。
<WaitStats>
<Wait WaitType="CMEMTHREAD" WaitTimeMs="33" WaitCount="86" />
<Wait WaitType="SESSION_WAIT_STATS_CHILDREN" WaitTimeMs="1029" WaitCount="55" />
<Wait WaitType="LATCH_EX" WaitTimeMs="2796" WaitCount="308" />
<Wait WaitType="ASYNC_NETWORK_IO" WaitTimeMs="3083" WaitCount="144" />
</WaitStats>
<QueryTimeStats ElapsedTime="3542" CpuTime="7247" />
Run Code Online (Sandbox Code Playgroud)
MAXDOP 22
对于查询来说相当高SELECT
。您是否使用不同的MAXDOP
值测试了此查询以验证 22 确实是最佳选择?如果我不得不猜测的话,我会说您有一个两个套接字服务器,每个套接字有 12 个核心。也许MAXDOP
出于某种原因在实例级别设置为 22。我基于计划中的线程信息:
<ThreadStat Branches="4" UsedThreads="88">
<ThreadReservation NodeId="0" ReservedThreads="24" />
<ThreadReservation NodeId="1" ReservedThreads="24" />
<ThreadReservation NodeId="2" ReservedThreads="16" />
<ThreadReservation NodeId="3" ReservedThreads="24" />
</ThreadStat>
Run Code Online (Sandbox Code Playgroud)
NUMA 节点 2 的调度程序比其他节点少,服务器有 2 个套接字,每个套接字有 12 个调度程序,服务器有 4 个套接字,每个套接字有 6 个调度程序,或者设置了手动软 NUMA。对于 MAXDOP 的常见建议是使用小于每个硬 NUMA 节点的物理核心数量的值。
综上所述,您无需了解所有技术细节即可进行测试。尝试使用不同的 MAXDOP 进行测试(确保多次运行测试),看看是否有帮助。
请注意,我只是查看了查询计划,并没有查看您已有的索引定义。SpecsProd
看起来是你最大的桌子。您可以仅在需要的列上定义覆盖索引。您无法对其进行查找,但会执行更少的 IO,因为仅包含查询中使用的三列的非聚集索引将小于聚集索引。
该Products
表具有第二高的 IO 成本。您已经按聚集索引进行排序,这很好,但聚集索引的大小是表中所有数据的大小。您可以仅在聚集键列上创建非聚集索引以减少 IO。您甚至可以创建它以DESC
使扫描符合并行性,但我不知道这是否会由于表达式而在实践中产生影响TOP
。
您已经在 上有一个覆盖索引Specs
,但过滤器是在谓词而不是搜索谓词中计算的。如果您更改索引或以正确的顺序创建一个包含键列的新索引,您应该能够执行查找而不是扫描。