这个问题涉及到这个论坛主题 。
在我的工作站上运行SQL Server 2008 Developer Edition,并在Enterprise Edition双节点虚拟机群集中引用“alpha群集”。
使用varbinary(max)列删除行的时间与该列中数据的长度直接相关。 这听起来可能听起来很直观,但经过调查后,它与我对SQL Server如何实际删除一般行并处理这种数据的理解相冲突。
问题源于我们在.NET Web应用程序中看到的删除超时(> 30秒)问题,但是为了讨论的缘故,我简化了它。
当logging被删除时,SQL Server将其标记为ghost,在事务提交之后的一段时间内被Ghost Cleanup Task清理掉(参见Paul Randal的博客 )。 在testing中,分别在varbinary(max)列中删除三行,分别为16 KB,4 MB和50 MB数据,我在页面上看到这种情况发生在数据的行内部分以及事务login。
我觉得奇怪的是,在删除过程中,X锁被放置在所有的LOB数据页上,并且页在PFS中被释放。 我在事务日志以及sp_lock
和dm_db_index_operational_stats
DMV( page_lock_count
)的结果中看到了这一点。
这会在我的工作站和我们的alpha群集上创build一个I / O瓶颈,如果这些页面不在缓冲区caching中的话。 实际上,来自同一个DMV的page_io_latch_wait_in_ms
实际上是整个删除的持续时间, page_io_latch_wait_count
与locking页面的数量相对应。 对于我的工作站上的50 MB文件,当空缓冲区caching( checkpoint
/ dbcc dropcleanbuffers
)开始时,这将转换为3秒以上,并且我毫不怀疑,对于繁重的碎片和负载,时间会更长。
我试图确定,这不仅仅是在caching中分配占用空间的空间。 在执行删除操作之前,我从其他行读取了2 GB的数据,而不是分配给SQL Server进程的checkpoint
方法。 不知道这是否是一个有效的testing,因为我不知道SQL Server如何洗牌数据。 我认为它会一直推出新的赞成新的。
此外,它甚至不修改页面。 这我可以看到与dm_os_buffer_descriptors
。 删除后的页面是干净的,而对于所有三个小的,中等的和大的删除,修改的页面的数量都小于20。 我还比较了DBCC PAGE
输出以查找查找页面的样本,并且没有任何更改(仅从PFS中删除了ALLOCATED
位)。 它只是释放他们。
为了进一步certificate页面查找/解除分配导致的问题,我尝试使用文件stream列而不是香草varbinary(最大)相同的testing。 无论LOB大小如何,删除都是恒定的。
所以,首先我的学术问题:
也许更重要的是,我的实际问题是:
以下是如何重现所描述的testing(通过SSMS查询窗口执行):
CREATE TABLE [T] ( [ID] [uniqueidentifier] NOT NULL PRIMARY KEY, [Data] [varbinary](max) NULL ) DECLARE @SmallID uniqueidentifier DECLARE @MediumID uniqueidentifier DECLARE @LargeID uniqueidentifier SELECT @SmallID = NEWID(), @MediumID = NEWID(), @LargeID = NEWID() -- May want to keep these IDs somewhere so you can use them in the deletes without var declaration INSERT INTO [T] VALUES (@SmallID, CAST(REPLICATE(CAST('a' AS varchar(max)), 16 * 1024) AS varbinary(max))) INSERT INTO [T] VALUES (@MediumID, CAST(REPLICATE(CAST('a' AS varchar(max)), 4 * 1024 * 1024) AS varbinary(max))) INSERT INTO [T] VALUES (@LargeID, CAST(REPLICATE(CAST('a' AS varchar(max)), 50 * 1024 * 1024) AS varbinary(max))) -- Do this before test CHECKPOINT DBCC DROPCLEANBUFFERS BEGIN TRAN -- Do one of these deletes to measure results or profile DELETE FROM [T] WHERE ID = @SmallID DELETE FROM [T] WHERE ID = @MediumID DELETE FROM [T] WHERE ID = @LargeID -- Do this after test ROLLBACK
以下是在我的工作站上分析删除的一些结果:
| 列types| 删除大小| 持续时间(毫秒)| 阅读| 写入| CPU | -------------------------------------------------- ------------------ | VarBinary | 16 KB | 40 | 13 | 2 | 0 | | VarBinary | 4 MB | 952 | 2318 | 2 | 0 | | VarBinary | 50 MB | 2976 | 28594 | 1 | 62 | -------------------------------------------------- ------------------ | FileStream | 16 KB | 1 | 12 | 1 | 0 | | FileStream | 4 MB | 0 | 9 | 0 | 0 | | FileStream | 50 MB | 1 | 9 | 0 | 0 |
我们不一定只是使用文件stream,因为:
testing了一个理论,即将数据写入事务日志作为删除的一部分,这似乎并不是这种情况。 我testing了这个不正确吗? 见下文。
SELECT MAX([Current LSN]) FROM fn_dblog(NULL, NULL) --0000002f:000001d9:0001 BEGIN TRAN DELETE FROM [T] WHERE ID = @ID SELECT SUM( DATALENGTH([RowLog Contents 0]) + DATALENGTH([RowLog Contents 1]) + DATALENGTH([RowLog Contents 3]) + DATALENGTH([RowLog Contents 4]) ) [RowLog Contents Total], SUM( DATALENGTH([Log Record]) ) [Log Record Total] FROM fn_dblog(NULL, NULL) WHERE [Current LSN] > '0000002f:000001d9:0001'
对于超过5 MB大小的文件,返回1651 | 171860
1651 | 171860
。
此外,如果将数据写入日志,我希望这些页面本身很脏。 只有解除分配似乎被logging,这与删除后的脏东西相匹配。
我得到了Paul Randal的回复。 他肯定了这样一个事实:它必须阅读所有的页面才能遍历树,并find哪些页面要释放,并且指出没有其他方法来查找哪些页面。 这是1和2的一半答案(虽然没有解释需要locking行外数据,但这是小土豆)。
问题3仍然是开放的:如果已经有一个后台任务来执行删除清理,为什么要先分配页面呢?
当然,还有一个重要的问题:是否有一种方法可以直接缓解(即不能解决)这种依赖于大小的删除行为? 我会认为这将是一个更常见的问题,除非我们真的是唯一的存储和删除SQL Server中的50 MB行? 有没有其他人在那里用某种forms的垃圾收集工作来解决这个问题?
我不能说为什么删除一个VARBINARY(MAX)比文件stream更加低效,但是如果你只是在删除这些LOBS时试图避免你的web应用程序超时,那么你可以考虑一个想法。 您可以将VARBINARY(MAX)值存储在原始表引用的单独表(让我们称之为tblLOB)中(让我们调用这个tblParent)。
从这里,当你删除一条logging时,你可以从父logging中删除它,然后有一个偶尔的垃圾收集过程进入并清理LOB表中的logging。 在这个垃圾收集过程中可能会有额外的硬盘驱动器活动,但它至less将与前端networking分离,并且可以在非高峰时段执行。