带有相关子查询的 While 循环的 SQL Server 性能调整

Posted

技术标签:

【中文标题】带有相关子查询的 While 循环的 SQL Server 性能调整【英文标题】:SQL Server Performance tuning of While loop with correlated sub query 【发布时间】:2013-01-22 14:07:22 【问题描述】:

在我的项目中,我遇到了以下 T-SQL 代码的挑战。

    step1 使用父模块及其订阅用户填充 UserModules 表 step2 在 Modules_Hierarchy 表中检查与 step1 中的模块关联的子模块,并通过将子模块与父模块订阅的用户映射,将有效记录插入到 UserModules 表中。 此步骤将递归重复,直到找到所有子模块。

问题:

在第 2 步中,WHILE 循环和 SELECT 语句使用相关子查询,并且表 UserModules 是 INSERT 和相关 SELECT 子句的一部分,这会妨碍性能,并且查询经常会因以下 LOCK 升级问题而失败。

ModulesUsers 表中的最终数据大小为 4200 万,预计还会增长。

错误消息:“SQL Server 数据库引擎的实例此时无法获取 LOCK 资源。当活动用户较少时重新运行您的语句。请数据库管理员检查此实例的锁和内存配置,或检查长时间运行的事务。”

如何优化此查询,即第 2 步来解决问题?

第一步:

INSERT INTO UserModules(ModuleID, UserID)
  SELECT ModuleID, UserID
  FROM TABLEA a
  INNER JOIN TABLEB b ON a.ID = b.ID

第二步:

DECLARE @cnt int
SET @cnt = 1

WHILE( @cnt > 0 )      
BEGIN      

  SET @cnt = (SELECT COUNT(DISTINCT s.moduleid)
              FROM Modules_Hirarchy s WITH (nolock), Modules t      
              WHERE s.ParentModuleId = t.ModuleId      
              ------------      
                AND NOT EXISTS       
                 (SELECT ModuleId + EndUserId 
                  FROM UserModules  r      
                  WHERE s.moduleid = r.moduleid 
                    AND t.EndUserId = r.EndUserId)
                AND s.moduleid + t.EndUserId NOT IN 
                  (SELECT CAST(ModuleId AS varchar) + EndUserId 
                   FROM UserModules ))      

  IF @cnt = 0      
    BREAK      

  INSERT INTO UserModules (ModuleId, EndUserId)      
    SELECT DISTINCT s.moduleid, t.EndUserId       
    FROM Modules_Hirarchy s WITH (nolock), UserModules  t      
    WHERE s.ParentModuleId = t.ModuleId      
      AND NOT EXISTS       
       (SELECT ModuleId + EndUserId 
        FROM UserModules  r      
        WHERE s.moduleid = r.moduleid 
          AND t.EndUserId = r.EndUserId)

END  

【问题讨论】:

任何合理的现代 SQL Server 版本 (>= 2005) 都有 CTEs,旨在执行这种递归,而无需编写显式循环代码。 请发布一些示例数据 【参考方案1】:

一些数据可供使用

create table #UserModules(ModuleID int, UserID int)

create table #Modules_Hirarchy(ParentModuleID int, ChildModuleID int)

insert into #UserModules (ModuleID , UserID)
values(1,1)
,(2,1)
,(3,1)
,(4,1)
,(5,1)
,(6,2)
,(7,2)

insert into #Modules_Hirarchy(ParentModuleID , ChildModuleID )
values (null,1)
,(1,2)
,(2,3)
,(3,4)
,(3,5)
,(null,6)
,(6,7)

分辨率

with cts(ModuleID, UserID,parentModule ) as 
(
select a.ModuleID, a.UserID , CAST(null as int)as parentModule --, cAST(null as int)as b
from #UserModules a join #Modules_Hirarchy  b on a.ModuleID = b.ChildModuleID 
where b.ParentModuleID is null

union all

select b.ChildModuleID as ModuleID, a.UserID, b.ParentModuleID
from cts a join #Modules_Hirarchy b 
on a.ModuleID = b.ParentModuleID

)
select *
into #RESULT
from cts

编辑 很难说:)对许多变量 但是你应该做些什么来提高查询效率

    ModuleID ParentModuleID ChildModuleID列上单独的非聚集索引

    您可能不想查询所有组,而只想查询一个 显式过滤出尽可能多的组 声明

    选择 a.ModuleID、a.UserID、CAST(null as int)作为 parentModule 从 #UserModules a 加入 #Modules_Hirarchy b on a.ModuleID = b.ChildModuleID 其中 b.ParentModuleID 为 null,a.ModuleId 在 (listOfModules)

    为列 (ParentModuleID, ChildModuleID) 添加唯一索引,因为非唯一行可能会导致大量行重复

除了它依赖于 ParentModuleID ChildModuleID 上的数据选择性,但你无能为力

我认为它适用于大数据集,因为谓词很简单,只要数据选择性很高

【讨论】:

我有一个问题,这个解决方案可以被称为巨大的记录,即 40 多百万条记录。 感谢您的建议。我在我的项目中尝试了这个解决方案,它工作得很好,但有一个例外,即有时它会因“MyDatabase”上的错误事务日志已满而失败。请建议我解决此问题的方法。 呃,我真的不知道,我认为你应该将整个交易作为新问题发布,发布信息你的日志文件有多大等等等等

以上是关于带有相关子查询的 While 循环的 SQL Server 性能调整的主要内容,如果未能解决你的问题,请参考以下文章

带有MySql PDO的while循环内的while循环

带有“exists”子句和多个表的 SQL 子查询

优化具有 While 循环和交叉应用的 T-SQL 查询

SQL嵌套子查询和相关子查询的执行过程有啥区别

SQL嵌套子查询和相关子查询的执行过程有啥区别

DB2 SQL更新与子查询相关的多于1列[duplicate]