在SQL Server中合并行大小溢出-“无法创建行大小。”


8

我尝试将数据合并到的目标表有〜660列。合并的代码:

MERGE TBL_BM_HSD_SUBJECT_AN_1 AS targetTable
USING
        (                
SELECT * 
FROM TBL_BM_HSD_SUBJECT_AN_1_STAGING
WHERE [ibi_bulk_id] in (20150520141627106) and  id in(101659113)
    ) AS sourceTable 
ON (...)
WHEN MATCHED AND ((targetTable.[sampletime] <= sourceTable.[sampletime]))
        THEN UPDATE SET ...
WHEN NOT MATCHED 
        THEN INSERT (...)
    VALUES (...)

我第一次运行此程序(即,当表为空时)成功,并插入了一行。

我第二次使用相同的数据集运行此错误,但返回错误:
无法创建大小为8410的行,该行大于允许的最大行大小为8060。

为什么第二次尝试合并已经插入的同一行却导致错误。如果该行超过最大行大小,则可能无法将其插入到第一位。

因此,我尝试了两件事(并成功!):

  • 从合并语句中删除“未匹配时”部分
  • 在我尝试合并的同一行上运行更新语句

为什么使用合并更新不会成功,而插入却成功,直接更新也不会成功?

更新:

设法找到实际的行大小-4978。我创建了仅具有此行的新表,并通过以下方式找到行大小: 在此处输入图片说明

而且我仍然看不到超出允许的限制的内容。

UPDATE(2):

完整复制

尽力使此复制不需要任何其他辅助对象,并且将(某种程度上)混淆数据。

在2012年版和2008年版的多台服务器上进行了此尝试,并能够在所有服务器上完全复制。

Answers:


10

为什么第二次尝试合并已经插入的同一行却导致错误。如果该行超过最大行大小,则可能无法将其插入到第一位。

首先,感谢您的复制脚本。

问题不在于SQL Server无法插入或更新特定的用户可见 行。如您所述,已经插入到表中的行从根本上说对于SQL Server而言不能太大。

发生问题是因为SQL Server MERGE实现在执行计划的中间步骤中添加了计算信息(作为额外的列)。出于技术原因,需要这些额外的信息,以跟踪每一行是否应该导致插入,更新或删除;还与SQL Server通常在索引更改期间避免瞬时键冲突的方式有关。

SQL Server存储引擎要求索引在处理每一行时始终是唯一的(内部,包括任何隐藏的唯一标识符),而不是在整个事务的开始和结束时都是唯一的。在更复杂的MERGE情况下,这需要拆分(将更新转换为单独的删除和插入),排序和可选的折叠(将同一键上的相邻插入和更新转换为更新)。更多信息

顺便说一句,请注意,如果目标表是堆,则不会发生此问题(删除聚簇索引以查看此情况)。我不建议将此作为修复程序,只是提及它以突出强调始终保持索引唯一性(在当前情况下为群集)和Split-Sort-Collapse之间的联系。

在具有适当的唯一索引的简单 MERGE查询中,以及源行和目标行之间的直接关系(通常使用具有所有键列的子句进行匹配),查询优化器可以简化许多通用逻辑,从而得出比较简单的计划,不需要拆分排序折叠或分段序列项目来检查目标行仅被触摸一次。ON

在具有不透明逻辑的复杂 MERGE查询中,优化器通常无法应用这些简化,从而暴露了正确处理所需的更多基本复杂的逻辑(尽管存在产品错误,并且有很多)。

您的查询肯定符合复杂条件。该ON子句与索引键不匹配(我理解为什么),并且“源表”是一个涉及排名窗口函数的自联接(同样具有原因):

MERGE MERGE_REPRO_TARGET AS targetTable
USING
(
    SELECT * FROM 
    (
        SELECT 
            *, 
            ROW_NUMBER() OVER (
                PARTITION BY ww,id, tenant 
                ORDER BY 
                (
                    SELECT COUNT(1) 
                    FROM MERGE_REPRO_SOURCE AS targetTable
                    WHERE 
                        targetTable.[ibi_bulk_id] = sourceTable.[ibi_bulk_id] 
                        AND targetTable.[ibi_row_id] <> sourceTable.[ibi_row_id] 
                        AND 
                        (
                            (targetTable.[ww] = sourceTable.[ww]) 
                            AND (targetTable.[id] = sourceTable.[id]) 
                            AND (targetTable.[tenant] = sourceTable.[tenant])
                        ) 
                        AND NOT ((targetTable.[sampletime] <= sourceTable.[sampletime]))
                ),
                sourceTable.ibi_row_id DESC
            ) AS idx
        FROM MERGE_REPRO_SOURCE sourceTable 
        WHERE [ibi_bulk_id] in (20150803110418887)
    ) AS bulkData
    where idx = 1
) AS sourceTable 
ON 
    (targetTable.[ww] = sourceTable.[ww]) 
    AND (targetTable.[id] = sourceTable.[id]) 
    AND (targetTable.[tenant] = sourceTable.[tenant])
...

这导致许多额外的计算列,主要与拆分和将更新转换为插入/更新对时所需的数据相关。这些额外的列会导致中间行在较早的排序-过滤器之后的行中超过了所允许的8060字节:

问题排序

请注意,筛选器的输出列表中有1,319列(表达式和基础列)。附加调试器会在引发致命异常时显示调用堆栈:

堆栈跟踪

顺便指出的问题是不是在后台-除了有被转换成一个关于警告潜在的行太大。

为什么使用合并更新不会成功,而插入却成功,直接更新也不会成功?

直接更新的内部复杂度与相同MERGE。从根本上讲,它是一种更简单的操作,可以简化和优化程序。删除该NOT MATCHED子句还可以消除足够的复杂性,从而在某些情况下不会产生错误。但是,复制不会发生这种情况。

最终,我的建议是避免MERGE执行更大或更复杂的任务。我的经验是,与相比,单独的插入/更新/删除语句倾向于更好地优化,更易于理解并且总体上也表现更好MERGE

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.