为什么第二次尝试合并已经插入的同一行却导致错误。如果该行超过最大行大小,则可能无法将其插入到第一位。
首先,感谢您的复制脚本。
问题不在于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
。