如何解决SQL Server 中的树结构查询
使用 Azure SQL Server,我有一个表将组织的树结构存储在一个表中,如下所示:
CREATE TABLE [dbo].[UserManagement_GroupDef]
(
[ID] [bigint] IDENTITY(1,1) NOT NULL,[PortalId] [bigint] NOT NULL,[GroupType] [int] NOT NULL,[ParentGroupId] [bigint] NULL,[GroupName] [nvarchar](2048) NOT NULL,[IsDefault] [bit] NOT NULL,[Email] [nvarchar](256) NULL,[PhoneNumber] [varchar](10) NULL,[UserManagementId] [nvarchar](450) NULL,[Address] [nvarchar](450) NULL,[Suite] [nvarchar](450) NULL,[City] [nvarchar](450) NULL,[State] [nvarchar](450) NULL,[Position] [nvarchar](450) NULL,[Zip] [nvarchar](450) NULL,[IsDeleted] [bit] NOT NULL,CONSTRAINT [PK_UserManagement_GroupDef] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF,IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
所有列都被编入索引。
[ParentGroupId]
引用同一表中的另一行。总而言之,该表代表了一个树结构。
在下面的查询中,我试图找到组 ID 155
的所有后代。
WITH tblChild AS
(
SELECT
ID,ParentGroupId,[IsDeleted],[PortalId],[GroupType],[GroupName],[IsDefault],[Email],[PhoneNumber],[UserManagementId],[Address],[Suite],[City],[State],[Position],[Zip]
FROM UserManagement_GroupDef
WHERE ID = 155 AND IsDeleted = 0
UNION ALL
SELECT
UserManagement_GroupDef.ID,UserManagement_GroupDef.ParentGroupId,UserManagement_GroupDef.[IsDeleted],UserManagement_GroupDef.[PortalId],UserManagement_GroupDef.[GroupType],UserManagement_GroupDef.[GroupName],UserManagement_GroupDef.[IsDefault],UserManagement_GroupDef.[Email],UserManagement_GroupDef.[PhoneNumber],UserManagement_GroupDef.[UserManagementId],UserManagement_GroupDef.[Address],UserManagement_GroupDef.[Suite],UserManagement_GroupDef.[City],UserManagement_GroupDef.[State],UserManagement_GroupDef.[Position],UserManagement_GroupDef.[Zip]
FROM UserManagement_GroupDef
JOIN
tblChild
ON
UserManagement_GroupDef.ParentGroupId = tblChild.ID
WHERE tblChild.IsDeleted = 0
)
SELECT
tblChild.ID,tblChild.ParentGroupId,tblChild.[IsDeleted],tblChild.[PortalId],tblChild.[GroupType],tblChild.[GroupName],tblChild.[IsDefault],tblChild.[Email],tblChild.[PhoneNumber],tblChild.[UserManagementId],tblChild.[Address],tblChild.[Suite],tblChild.[City],tblChild.[State],tblChild.[Position],tblChild.[Zip]
FROM tblChild
该表目前只有不到 7000 条记录。此特定查询返回的结果不到 2000 行。结果最终是正确的,但执行需要大约 20 秒。
有没有办法加快这个查询或达到相同的结果,只是更快?
执行计划:
https://www.brentozar.com/pastetheplan/?id=S1k0Qdrzd
表格索引:
CREATE NONCLUSTERED INDEX [IX_UserManagement_GroupDef] ON [dbo].[UserManagement_GroupDef]
(
[IsDeleted] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF,DROP_EXISTING = OFF,ONLINE = OFF) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [IX_UserManagement_GroupDef_2] ON [dbo].[UserManagement_GroupDef]
(
[ParentGroupId] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF,ONLINE = OFF) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [IX_UserManagement_GroupDef_3] ON [dbo].[UserManagement_GroupDef]
(
[IsDeleted] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF,ONLINE = OFF) ON [PRIMARY]
CREATE NONCLUSTERED INDEX [umg_query_index] ON [dbo].[UserManagement_GroupDef]
(
[ParentGroupId] ASC
)
INCLUDE(
[ID],[Zip]
)
WHERE ([IsDeleted]=(0))
WITH (STATISTICS_NORECOMPUTE = OFF,ONLINE = OFF) ON [PRIMARY]
解决方法
看起来正在发生的事情是编译器无法将外部谓词IsDeleted = 0
下推到CTE中。因此它不能使用 O. Jones 推荐的 umg_query_index
。
为什么会这样,是因为递归部分的每次运行都可能返回 IsDeleted = 1
行,这些行需要反馈到下一次运行中。虽然它们确实不会出现在最终结果中,但这样的行仍然可能具有确实需要出现在最终结果集中的子行。所以没有办法从递归连接中消除它们。
您有两个选择:
- 更改您的
IX_UserManagement_GroupDef_2
索引,使其也包含IsDeleted
列,它不需要在键中,它可以是INCLUDE
:
CREATE NONCLUSTERED INDEX [IX_UserManagement_GroupDef_2] ON [dbo].[UserManagement_GroupDef]
([ParentGroupId] ASC) INCLUDE (IsDeleted)
WITH (DROP_EXISTING = ON,ONLINE = ON) ON [PRIMARY]
- 可能是更好的选择,自己下推谓词。然后它将在 CTE 的两个部分使用 O. Jones 索引。
WITH tblChild AS
(
SELECT *
FROM UserManagement_GroupDef
WHERE ID = 155 AND IsDeleted = 0
UNION ALL
SELECT
u.*
FROM
UserManagement_GroupDef u
JOIN
tblChild ON u.ParentGroupId = tblChild.ID
WHERE u.IsDeleted = 0
)
SELECT *
FROM tblChild
此查询与原始查询具有不同的语义。它不会返回父项为 IsDeleted = 1
的任何行。
此时您还可以将新索引更改为过滤索引,因为这意味着不会存储任何 IsDeleted
行。
CREATE NONCLUSTERED INDEX umg_query_index ON UserManagement_GroupDef
(ParentGroupId) INCLUDE (IsDeleted) WHERE IsDeleted = 0
WITH (DROP_EXISTING = ON,ONLINE = ON) ON [PRIMARY];
您必须在索引中包含 IsDeleted
列,否则由于当前实现的逻辑错误,优化器可能无法使用它。
进一步说明:单列索引通常没有那么有用,一般应该避免。服务器尝试将两个索引合并在一起是不值得的,它会扫描聚集索引。
我建议您删除索引 IX_UserManagement_GroupDef
和 IX_UserManagement_GroupDef_3
,因为它们现在完全是多余的。
试试这个:
CREATE INDEX umg_query_index ON UserManagement_GroupDef (IsDeleted,ParentGroupId);
更好的是,在 SSMS 中运行查询。在运行之前,右键单击查询窗口并启用 Show Actual Execution Plan。
接着看执行计划。它可能会为您推荐合适的索引。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。