Yos*_*ari 8 sql-server sql-server-2012 merge
我试图将数据合并到的目标表有 ~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 (...)
Run Code Online (Sandbox Code Playgroud)
我第一次运行这个(即当表为空时)它导致成功,并插入了一行。
我第二次使用相同的数据集运行此程序时,返回错误:
无法创建大小为 8410 的行,该行大于允许的最大行大小 8060。
为什么我第二次尝试合并已经插入的同一行会导致错误。如果该行超过了最大行大小,则预计它不可能首先插入它。
所以我尝试了两件事,(成功了!):
为什么使用合并更新不成功,而插入可以,直接更新也可以?
更新:
设法找到实际的行大小 - 4978。我创建了一个只有这一行的新表,并通过这种方式找到行大小:
而且我仍然没有看到超出允许限制的东西。
更新(2):
努力使这种复制不需要任何额外的辅助对象,并且数据将(有点)混淆。
在 2012 版和 2008 版的几台服务器上尝试了这一点,并且能够在所有服务器中完全重现。
Pau*_*ite 10
为什么我第二次尝试合并已经插入的同一行会导致错误。如果该行超过了最大行大小,则预计它不可能首先插入它。
首先,感谢您提供复制脚本。
问题不在于 SQL Server 无法插入或更新特定用户可见 行。正如您所指出的,已经插入到表中的行从根本上来说肯定不会太大,SQL Server 无法处理。
出现此问题是因为 SQL ServerMERGE
实现在执行计划的中间步骤期间添加了计算信息(作为额外的列)。出于技术原因需要此额外信息,以跟踪每行是否应导致插入、更新或删除;并且还与 SQL Server 在更改索引期间一般避免瞬时键冲突的方式有关。
SQL Server 存储引擎要求索引在任何时候都是唯一的(在内部,包括任何隐藏的唯一符)——因为每一行都被处理——而不是在整个事务的开始和结束时。在更复杂的MERGE
场景中,这需要拆分(将更新转换为单独的删除和插入)、排序和可选的折叠(将同一键上的相邻插入和更新转换为更新)。更多信息。
顺便说一句,请注意,如果目标表是堆(删除聚集索引以查看这一点),则不会发生此问题。我不建议将此作为修复,只是提及它以突出始终保持索引唯一性(在当前情况下为集群)与拆分-排序-折叠之间的联系。
在简单的 MERGE
查询中,使用合适的唯一索引,以及源行和目标行之间的直接关系(通常使用ON
具有所有关键列的子句进行匹配),查询优化器可以简化大部分通用逻辑,从而产生相对简单的计划不需要 Split-Sort-Collapse 或 Segment-Sequence Project 来检查目标行是否只触及一次。
在具有更多不透明逻辑的复杂 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])
...
Run Code Online (Sandbox Code Playgroud)
这会导致许多额外的计算列,主要与拆分和更新转换为插入/更新对时所需的数据相关联。这些额外的列导致中间行超过了早期排序中允许的 8060 字节 - 过滤器之后的行:
请注意,过滤器在其输出列表中有1,319 列(表达式和基本列)。附加调试器会显示引发致命异常时的调用堆栈:
顺便指出的问题是不是在后台-除了有被转换成一个关于警告潜在的行太大。
为什么使用合并更新不成功,而插入可以,直接更新也可以?
直接更新的内部复杂性与MERGE
. 这是一个从根本上更简单的操作,往往会更好地简化和优化。删除NOT MATCHED
子句还可以消除足够的复杂性,从而在某些情况下不会产生错误。然而,repro 不会发生这种情况。
最终,我的建议是避免MERGE
执行更大或更复杂的任务。我的经验是单独的插入/更新/删除语句趋于优化更好,更简单易懂,而且还经常进行更好的整体,相比MERGE
。
归档时间: |
|
查看次数: |
2164 次 |
最近记录: |