我不得不将大表中的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.
ALTER TABLE在PROD中被杀了几次(在运行了几个小时之后,然后进行了长时间的回滚)其他活动开始失败后“当访问表格中的版本化行'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中的旧(未更改)行,但事实并非如此。
答案 0 :(得分:2)
将列从可空变为不可空导致创建新列,操作已完全记录,并且如果使用RCSI,还会导致生成行版本。
您可以查看此主题以获取更多信息:Why does ALTER COLUMN to NOT NULL cause massive log file growth?
Reguarding
我能理解是否有另一个引用旧的连接
你误解了RSCI是如何运作的。
一旦完成向RCSI的转换,每次更新都将生成行版本,而不依赖于存在或者没有其他对这些行感兴趣的事务
当READ_COMMITTED_SNAPSHOT或ALLOW_SNAPSHOT_ISOLATION时 数据库选项为ON,维护逻辑副本(版本) 在数据库中执行的所有数据修改。每次都是一排 由特定事务修改的数据库实例 引擎存储先前提交的行的图像的版本 在tempdb中。每个版本都标有事务序列号 进行更改的交易。修改行的版本 使用链接列表链接。始终存储最新的行值 在当前数据库中并链接到存储在其中的版本化行 tempdb中。
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日志文件,简单恢复模型,并创建了一个填充了1000000个整数的表dbo.Nums (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;
并做了完全相同的事情:checkpoint + alter table + select from sys.fn_dblog()
我不得不等待5分钟,但tempdb没有错误。
在语句执行期间有PREEMPTIVE_OS_GETDISKFREESPACE
作为等待类型,但猜猜它是什么。
它不是tempdb(只有10Mb + 1Mb并且与我限制它的大小相同),它是我的用户数据库的LOG FILE,只是用于将数据类型从可空变为不可为空 UNDER RCSI < / strong>,已经发展到 1Gb(!!!!)
1Gb的日志,用于更改仅为16Mb的表的1列的可为空性 而且我一直在等待tempdb的增长,但是我的数据库日志文件只能输出1 Gb。
我附上在RC和RCSI下相同操作期间进入日志的图片,因此您可以看到生成行版本对用户数据库的成本远高于tempdb,因此我认为您等待的时间花费在将行版本记录到数据库日志文件中(它们根本不记录在tempdb中)
为了COPY_VERSION_INFO,有许多行修改可能不是你的情况:我的行有一个新的14字节行版本标记因此对该表进行了太多更改,因为我在更改可空性之前更改了隔离级别,但我的情况下的主要影响是由用户数据库日志文件增长产生的,而不是由根本没有增长的tempdb产生的。
P.S。也许你最好把这个问题转移到dbaexchange?