【发布时间】:2017-11-22 01:07:18
【问题描述】:
是什么促使 SQL Server 对返回超过 6000 行的查询使用不太理想的执行计划?对于返回所有行的情况,我需要提高查询性能。
我选择所有字段并在索引中包含的相同三列上添加排名。根据返回的行数,查询有两种不同的执行计划,因此执行时间分别为 0.2s 或 3s。
从 1 行返回到 ca。 5000 个查询运行速度很快。从 6000 行到全部返回,查询运行缓慢。
Table1 大约有。 38000 行。数据库在 Azure SQL v12 上运行。
表:
CREATE TABLE [dbo].[Table1](
[ID] [int] IDENTITY(1,1) NOT NULL,
[KOD_ID] [int] NULL,
[SYM] [nvarchar](20) NULL,
[AN] [nvarchar](35) NULL,
[A] [nvarchar](10) NULL,
[B] [nvarchar](2) NULL,
[C] [datetime] NULL,
[D] [datetime] NULL,
CONSTRAINT [PK_Table1] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
GO
CREATE NONCLUSTERED INDEX [IX_Table1] ON [dbo].[Table1]
(
[KOD_ID] ASC,
[SYM] ASC,
[AN] 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)
GO
查询:
SELECT TOP 6000 *, RANK() OVER(ORDER BY KOD_ID ASC, SYM ASC, AN ASC) AS Rank#
FROM [dbo].[Table1]
SELECT TOP 7000 *, RANK() OVER(ORDER BY KOD_ID ASC, SYM ASC, AN ASC) AS Rank#
FROM [dbo].[Table1]
两个查询的执行计划
【问题讨论】:
-
*有必要吗?这禁止使用覆盖索引,将优化器推入一个简单地扫描聚集索引并首先对整个表进行排序的计划,因为它认为非聚集索引不会充分减少 I/O(显然,事实证明,排序需要更多时间)。尝试将ORDER BY KOD_ID ASC, SYM ASC, AN ASC作为一个整体添加到查询中,这可能会使平衡再次有利于索引。 -
@JeroenMostert 绝对没有必要。我已将 * 替换为明确的列名,但这没有帮助。
-
这不是我的意思,我的意思是您是否真的需要 all 列。
*与拼写出来没有区别(性能方面)。 -
理论上,可以通过加入子查询来强制进行书签查找:
SELECT TOP(7000) T.*, T1.Rank# FROM (SELECT TOP(7000) ID, RANK() OVER(ORDER BY KOD_ID ASC, SYM ASC, AN ASC) AS Rank# FROM Table1 ORDER BY ID) T JOIN Table1 ON T.ID = Table1.ID。但是,即使它有效,这也是相当棘手的,因为它会阻止优化器执行单个表扫描,即使这确实是最好的方法。重新排列您的表(如答案中所示)是另一种有前途的方法。 -
@JeroenMostert TOP 子句仅用于发现和演示性能变化。现实生活中的要求是返回所有行,所以加入工作完美。这就是我最终做到的方式。我将剩余的列与 ID 列上的排名子查询
SELECT ID, RANK() OVER(ORDER BY KOD_ID ASC, SYM ASC, AN ASC) AS Rank# FROM [dbo].[Table1]连接起来。
标签: sql sql-server performance sql-execution-plan