由于文件组 'DEFAULT' 中的磁盘 space 不足,无法为数据库 'TEMPDB' 分配新页面

Facing Could not allocate a new page for database 'TEMPDB' because of insufficient disk space in filegroup 'DEFAULT'

无法为数据库 'TEMPDB' 分配新页面,因为文件组 'DEFAULT' 中的磁盘 space 不足。通过删除文件组中的对象、向文件组中添加其他文件或为文件组中的现有文件设置自动增长来创建必要的 space。

在执行包含 select 条语句且有近 2.5 亿条记录的存储过程时遇到此错误。

即使我在 tempdb 中有大约 650gb space 也面临这个错误。想知道我可以在这方面做些什么

查询:

SELECT u.Id, u.place, u.name, u.lname, LOWER(ue.email) AS Email, MIN(dp.BirthTime) AS Time
FROM tableA u
    JOIN tableB ue ON ue.id = u.id AND ue.Did = 0
    JOIN tableC dp ON dp.Id = u.Id
        JOIN tableB dpe ON dpe.Id = dp.Id
            LEFT JOIN tableB idpe ON idpe.Email = dpe.Email
                LEFT JOIN tableE idp ON idp.Id = idpe.Id
                    LEFT JOIN tableD pidp ON pidp.Id = idp.Id 
        JOIN tableD cp ON dp.Id = cp.Id
where ISNULL(cp.FName,'') = '' AND ISNULL(cp.LName,'') = '' AND ISNULL(cp.IsActive,0) = 0 AND ISNULL(dp.Isinactive,0) = 0
    AND ISNULL(pidp.FName,'') = '' AND ISNULL(pidp.LName,'') = ''
    AND ISNULL(pidp.Isactive,0) = 0 AND ISNULL(idp.IsInactive,0)
    AND ISNULL(u.EHome, '') != ''
GROUP BY u.Id, u.Isactive, u.name, u.EServer, ue.Email

我发现该查询存在一些问题,这些问题会使它变得非常低效:

  1. LEFT JOIN tableD pidp - 您离开加入此 table 然后在 WHERE 子句中应用过滤条件:

    ISNULL(pidp.FName,'') = ''AND ISNULL(pidp.LName,'') = '' AND ISNULL(pidp.Isactive,0) = 0

ISNULL([col name], '') != '' - 非常低效的构造,确保永远不会使用列上的任何索引(如果存在)。 如果列 不可空 则将过滤条件移动到联接并删除 ISNULL:

ON pidp.Id = idp.Id AND pidp.FName = ''
    AND pidp.LName = '' AND pidp.Isactive = 0

这也适用于 ISNULL(idp.IsInactive,0) 参数。

  1. GROUP BY 语句修复由弱连接/糟糕的查询设计引起的问题 - 这是一个非常糟糕的查询设计的标志,很可能是过度使用 temp table 的唯一原因。

我会按照这些思路重新编写查询(我没有在第 1 步中包含建议,因为它们取决于您的特定 table 定义):

SELECT u.Id, u.place, u.name, u.lname, LOWER(ue.email) AS Email, MIN(dp.BirthTime) AS Time
FROM tableA u
    JOIN tableB ue ON ue.id = u.id AND ue.Did = 0
WHERE ISNULL(u.EHome, '') != ''
    AND u.Id IN(
        SELECT dp.id
        FROM tableC dp
            JOIN tableB dpe ON dpe.Id = dp.Id
                LEFT JOIN tableB idpe ON idpe.Email = dpe.Email
                    LEFT JOIN tableE idp ON idp.Id = idpe.Id
                        LEFT JOIN tableD pidp ON pidp.Id = idp.Id 
            JOIN tableD cp ON dp.Id = cp.Id
        where ISNULL(cp.FName,'') = '' AND ISNULL(cp.LName,'') = '' AND ISNULL(cp.IsActive,0) = 0 AND ISNULL(dp.Isinactive,0) = 0
            AND ISNULL(pidp.FName,'') = '' AND ISNULL(pidp.LName,'') = ''
            AND ISNULL(pidp.Isactive,0) = 0 AND ISNULL(idp.IsInactive,0)
         )

TempDB 不是问题。

查询有多个带有非 SARGable 表达式的过滤器。在可以过滤任何 where 子句数据之前,必须处理所有连接。如果您的最终结果(过滤和分组)是 2.5 亿行,我预计连接后的结果将高达数十亿。在进行过滤和分组之前,必须在 tempdb 中具体化该结果。

尝试将此 ISNULL(cp.FName,'') = '' 更改为此 (cp.FName = '' OR cp.FName IS NULL),以及所有类似的表达式。这可能至少允许在连接之前进行一些过滤。

第二步是删除结果集中未使用的连接。您只是 return 从表 U、UE 和 DP 中获取数据。将其他内部连接移动到 exists 子句中

EXISTS (SELECT 1 FROM tableB dpe WHERE dpe.Id = dp.Id)

这会将初始加入产品减少到更易于管理的程度。

您的左连接解析起来会更复杂,但如果不是结果集的一部分,也不应该是连接。我读了你的查询,说你想在数据丢失的地方过滤你的结果。例如,如果 FName 是空字符串、空值或没有匹配记录,则 FName 仅应 return。另一种说法是您不希望看到 FName、LName、IsActive 的记录填充了 nonzero/nonblank 值。

AND NOT EXISTS ( SELECT 1 FROM tableD AS pidp where pidp.Id = idp.Id AND fname <> '' AND lname <> '' AND isactive = 0)

如果所有 3 个都已填充,此语句将排除它们。如果您只想在任何一个字段有值时排除它们,请将 EXISTS 子句中的 AND 更改为 OR

尝试这些更改,我希望您能完全避免臃肿的 TempDB。