【问题标题】:Why does ALTER TABLE ... ALTER COLUMN ... fill the version store in TempDB为什么 ALTER TABLE ... ALTER COLUMN ... 填充 TempDB 中的版本存储
【发布时间】:2017-12-24 18:07:09
【问题描述】:

我不得不将大表中的 BIGINT 列从可空更改为不可空。

ALTER TABLE my.Table ALTER COLUMN myColumn BIGINT NOT NULL

在我们的 UAT 和 RC 环境中运行此程序大约需要 3 个小时,并发活动水平较低。 UAT 和 RC 都反映了 PROD,因此都是很好的测试平台。考虑到桌子的大小和套件的性能,3 小时是合理的。

据我所知,相关配置是 snapshot_isolation_state = 0,is_read_committed_snapshot_on = 1。

在其他活动开始失败并出现“访问表 'myOther.Table'数据库'MyDatabase'。未找到请求的版本化行。您的 tempdb 可能空间不足。请参阅 BOL,了解如何配置 tempdb 进行版本化。”错误。

第三次在 PROD 中运行它时,我安排关闭所有其他活动。大约 4 小时后,很明显有些东西不起作用。使用Troubleshooting tempdb growth due to Version Store usage 中的初始查询,我可以看到版本存储是 TempDB 的大部分,但 ALTER TABLE 连接没有被阻塞,CPU 和 IO 缓慢增加,所以我确信它还活着,我看到的唯一等待是 SOS_SCHEDULER_YIELD。没有其他重要的联系。

又过了几个小时,我决定为 TempDB 添加一些空间。 ALTER TABLE 很快就完成了。

有人可以解释为什么 ALTER TABLE 停止了吗?我可以理解是否有另一个连接引用 my.Table 中的旧(未更改)行,但绝对不是这种情况。

【问题讨论】:

  • 你所谓的“my.Table”在这些环境中包含多少条记录?
  • @BuhakeSindi - 大约 2 亿行,平均记录大小大约 50 字节。

标签: sql-server sql-server-2012 alter-table tempdb


【解决方案1】:

将列从可为空更改为不可为空会导致创建新列,操作被完全记录,并且如果您使用 RCSI,还会导致生成行版本。

您可以查看此主题以获取更多信息:Why does ALTER COLUMN to NOT NULL cause massive log file growth?

保护

我可以理解是否有另一个引用旧的连接 my.Table 中的(未更改的)行,但绝对不是这种情况。

您误解了 RSCI 的工作原理。

一旦完成向 RCSI 的转换,每次更新都会生成行版本,这与是否存在对这些行感兴趣的其他事务无关

当 READ_COMMITTED_SNAPSHOT 或 ALLOW_SNAPSHOT_ISOLATION 数据库选项为 ON,维护逻辑副本(版本) 在数据库中执行的所有数据修改。每次一行都是 由特定事务修改,数据库的实例 引擎存储该行先前提交的图像的一个版本 在临时数据库中。每个版本都标有交易序号 进行更改的交易。修改行的版本 使用链接列表链接。始终存储最新的行值 在当前数据库中并链接到存储在 临时数据库。

Understanding Row Versioning-Based Isolation Levels

或者更清楚地写成here

当 READ_COMMITTED_SNAPSHOT 或 ALLOW_SNAPSHOT_ISOLATION 数据库选项为 ON,更新和删除事务 特定数据库必须维护行版本即使没有 使用基于行版本控制的隔离级别的事务。 使用行版本构建一致的数据快照涉及 系统资源(CPU 和内存),并可能产生 I/O 活动。因为记录版本存储在 tempdb 中, 当更多时,性能更好,发出的 I/O 的数量更少 tempdb 页面可以存储在内存中以进行行版本控制。

正如您想象的那样,ALTER TABLE 在 1 个事务中运行,因此行版本在此事务的所有持续时间内都处于活动状态(它们可以存活得更久,直到执行对它们感兴趣的语句,但由于没有人感兴趣,最小的“预期寿命”是拥有交易的持续时间) ..................................................... ..................................

更新:

我试图在 SQL Server 2012 上重现该问题:

我将 tempdb autogrowth 设置为 0(tempdata 设置为 10Mb,templog 设置为 1Mb)并创建了一个 20Mb 数据文件 + 10 Mb 日志文件的新数据库,简单的恢复模型,并创建了一个表 dbo.Nums 填充了 1000000 个整数(bigint, null) 这样:

select top 1000000 row_number() over(order by 1/0) as n
into dbo.Nums
from sys.all_columns c1 cross join sys.all_columns c2;

然后我做了一个检查点并将一列从 null 更改为 not null:

alter table dbo.nums alter column n bigint not null

这花了 0 秒,在此操作之前我的表大小约为 16Mb,它仍然保持在 16Mb 左右,没有日志文件增长,我将在图片中显示日志文件的内容。

然后我删除了表,重新创建并更改了我的数据库:

alter database rcsi set read_committed_snapshot on;

并且做了完全相同的事情:检查点 + 更改表 + 从 sys.fn_dblog() 中选择

我不得不等待 5 分钟,但 tempdb 没有给出错误。 在语句执行期间有 PREEMPTIVE_OS_GETDISKFREESPACE 作为等待类型,但猜猜它是什么。 它不是 tempdb(只有 10Mb + 1Mb 并且与我限制它的大小相同),它是我的用户数据库的 LOG FILE,它只是用于将数据类型从可为空更改为不可为空UNDER RCSI,已增长到 1Gb (!!!!)

1Gb 的日志用于更改仅 16Mb 表的 1 列的可空性 我一直在等待的不是 tempdb 增长,而是为我的 db 日志文件减少 1 Gb。

我附上了在 RC 和 RCSI 下同一操作期间记录的内容的图片,因此您可以看到生成行版本对用户数据库的成本比对 tempdb 的成本高得多,所以我认为您等待的时间都花在了将行版本记录到数据库日志文件中(它们根本没有记录到 tempdb 中)

Becides COPY_VERSION_INFO,有很多行修改可能不是您的情况:我的行有一个新的 14 字节行版本标记,因此对该表进行了太多更改,因为我在更改可空性之前更改了隔离级别,但在我的案例中,主要影响是由用户 db 日志文件增长产生的,而不是由根本没有增长的 tempdb 产生的。

附:也许您最好将此问题移至 dbaexchange?

【讨论】:

  • 第一个链接与事务日志有关,这不是我的问题所在。第二个链接很好,但文章还指出“当 tempdb 已满时,更新操作将停止生成版本并继续成功......”在这种情况下,为什么我的 ALTER TABLE 停止了?
  • 第一个链接包含一个复制代码,显示创建了一个新列,将现有数据传输给它,删除旧列。正如您所问的“有人可以解释发生了什么吗?”我提供了一个链接,它不仅解释而且显示发生了什么
  • 道歉 - 我已经通过改进语言澄清了最后一个问题。
  • 你是对的,如果 ALTER TABLE 不能再生成版本,它应该不会有问题,但我不确定问题出在这些行版本上。执行操作时 sys.dm_os_waiting_tasks 中有什么内容?
  • 我没有查看 sys.dm_os_waiting_tasks 但在 sysprocesses 中除了 SOS_SCHEDULER_YIELD 之外没有看到 lastwaittype 的任何内容。我的 IO 不是很好,所以如果有很多 IO,我会期望在 lastwaittype 中看到一些 IO 等待。
猜你喜欢
  • 2011-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-11
  • 2011-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多