更新大小为 5k 的行时出现 8k 行溢出错误

Yos*_*ari 8 sql-server sql-server-2012 update

我正在尝试将一行大小为 5k 的目标表更新为一行大小为 5k 的目标表。

由于它是一行,因此很容易知道行的实际大小:

select *
from sys.dm_db_index_physical_stats(DB_ID('RODS_HSD_ES'), 
OBJECT_ID(N'TBL_BM_HSD_SUBJECT_AN_148_REPRO'), NULL, NULL, 'DETAILED')
Run Code Online (Sandbox Code Playgroud)

复制

表自创建以来没有改变。没有看到它应该失败的任何原因。想法?

Pau*_*ite 10

更新失败的原因与我在回答您之前的问题时所解释的原因大致相似。

在这种情况下,因为你有可能更新多个行,其中一个键列一个的唯一索引改变*,SQL服务器建立了一个计划,其中包括拆分,排序和折叠运营商,以避免中间唯一键冲突(参见本文的详细信息) .

因此引入的 Sort 运算符遇到宽度超过限制的中间行(包括内部开销),因此会引发错误。向OPTION (ROBUST PLAN)更新查询添加提示表明这是不可避免的:

消息 8619,级别 16,状态 2,第 681 行
查询处理器无法生成查询计划,因为需要工作表,并且其最小行大小超过了 8060 字节的最大允许值。需要工作表的一个典型原因是查询中的 GROUP BY 或 ORDER BY 子句。在没有 ROBUST PLAN 提示的情况下重新提交您的查询。

源/目标数据的关系,粗略看一下我不太清楚,但如果你能保证每个更新操作最多影响一行,你可以通过添加TOP (1)到更新语句来避免拆分/排序/折叠的需要:

UPDATE TOP (1) [TBL_BM_HSD_SUBJECT_AN_148_REPRO_TARGET] 
SET ...
Run Code Online (Sandbox Code Playgroud)

不过,这有点骇人听闻。理想情况下,更新语句构造和索引应该向优化器提供足够的信息,以便它可以看到最多更新一行。特别是,编写确定性的更新语句是最佳实践。

鉴于问题的奇怪设计和缺乏清晰度,我什至不会去尝试破译数据关系,或者详细实现这一点所需的查询和索引更改。

* 正如Martin Smith在评论中指出的那样,如果表没有分区,在这种特殊情况下这不会成为问题。如果更新将每行中的键设置为相同的确定性值,则不需要拆分/排序/折叠,除非表也​​按该键进行了分区。因此,此查询的替代解决方案是不在sampletime上对表进行分区


Han*_*non 9

问题与您正在更新集群键这一事实有关,而目标表恰好具有分区方案1。当请求 SQL Server 更新群集键的任何组件时,它必须执行一个UPDATEDELETE,或混合更新,其中一些行就地更新,而另一些则不是。

如果您从目标表中删除聚集索引,您将看到更新有效。

错误消息虽然可能有点误导,但由于更新期间产生的行大小超过了最大长度,因此是准确的。

我建议您考虑将表的结构更改为:

  • 不适VARCHAR(MAX)用于所有这些列。如果在单个列中实际上不需要 2GB 的字符,为什么要以这种方式定义该列?将列定义为实际遇到的最大尺寸。
  • 也许将此表拆分为几个表,其中生成的最大行大小小于 8060 字节。看来你有列的几个逻辑集群,如V_MAX_xxxV_64_xxxV_512_xxx列等。

为了简化您的重现,您可能希望消除光标,并且只执行以下 DML 操作:

UPDATE dbo.TBL_BM_HSD_SUBJECT_AN_148_REPRO_TARGET
SET [sampletime]  = '2015-12-29 01:11:26.687';
Run Code Online (Sandbox Code Playgroud)

上面的列是集群键的组成部分之一,也是分区键(更新其他 CI 键列工作正常)。

聚集索引就位后,您会收到此错误:

消息 511,级别 16,状态 1,第 1 行

无法创建大于允许的最大行大小 8060 的大小为 8287 的行。

该语句已终止。

如果没有聚集索引,语句会成功。


1有趣的是,如果我们从 repro 中消除分区,我们会发现更新成功,即使聚集索引就位。