2

我不得不将大表中的 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。

在其他活动开始失败并出现“访问数据库'MyDatabase'中的表'myOther.Table'中的版本化行时事务中止时,ALTER TABLE在PROD中被杀死了几次(在运行了几个小时之后,然后进行了长时间的回滚) '。未找到请求的版本化行。您的 tempdb 可能空间不足。请参阅 BOL,了解如何配置 tempdb 进行版本化。错误。

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

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

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

4

1 回答 1

2

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

您可以查看此主题以获取更多信息:为什么 ALTER COLUMN to NOT NULL 会导致大量日志文件增长?

重新保护

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

您误解了 RSCI 的工作原理。

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

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

了解基于行版本控制的隔离级别

或者更清楚地写在这里

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

正如您想象的那样,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,只是为了在RCSI下将数据类型从可空更改为不可空,已增长到1Gb (!!!!)

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

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

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

在此处输入图像描述

PS也许你最好把这个问题移到dbaexchange?

于 2017-07-19T13:37:39.200 回答