在SQL Server中删除LOB数据的性能


16

这个问题与此论坛主题有关

在我的工作站和一个企业版两节点虚拟机群集上运行SQL Server 2008 Developer Edition,我将其称为“ alpha群集”。

删除带有varbinary(max)列的行所花费的时间与该列中数据的长度直接相关。起初听起来似乎很直观,但是经过调查,这与我对SQL Server通常如何实际删除行并处理此类数据的理解相矛盾。

该问题源于我们在.NET Web应用程序中看到的删除超时(> 30秒)问题,但是为了进行讨论,我将其简化了。

删除记录后,SQL Server会将其标记为要在事务提交后稍后由Ghost清除任务清除的虚影(请参阅Paul Randal的博客)。在删除varbinary(max)列中分别具有16 KB,4 MB和50 MB数据的三行的测试中,我看到这种情况发生在页面的数据行部分以及事务中日志。

在我看来,奇怪的是,在删除过程中,X锁被放置在所有LOB数据页上,而这些页被重新分配在PFS中。我在事务日志中以及DMV sp_lock和的结果中都看到了这一点。 dm_db_index_operational_statspage_lock_count

如果这些页面尚未在缓冲区高速缓存中,则会在我的工作站和我们的alpha群集上创建一个I / O瓶颈。实际上,page_io_latch_wait_in_ms来自同一DMV的删除实际上是删除的整个持续时间,并且page_io_latch_wait_count与锁定页面的数量相对应。对于我的工作站上的50 MB文件,从一个空的缓冲区高速缓存(checkpoint/ dbcc dropcleanbuffers)开始时,这相当于3秒钟以上,而且毫无疑问,碎片和负载较重时它会更长。

我试图确保它不只是在缓存中分配空间占用了该时间。在执行删除操作而不是checkpoint方法之前,我从其他行中读取了2 GB的数据,这比分配给SQL Server进程的数据还要多。不知道这是否是有效的测试,因为我不知道SQL Server如何对数据进行重新排序。我以为它将总是以旧为新。

此外,它甚至不修改页面。我可以看到这一点dm_os_buffer_descriptors。删除后页面是干净的,而所有三个小,中和大删除的修改页面数均小于20。我还比较DBCC PAGE了查找页面抽样的输出,并且没有变化(仅从ALLOCATEDPFS中删除了该位)。它只是重新分配它们。

为了进一步证明页面查找/取消分配是导致此问题的原因,我尝试使用文件流列而不是vanilla varbinary(max)进行相同的测试。无论LOB大小如何,删除都是固定时间。

所以,首先我的学术问题:

  1. 为什么SQL Server需要查找所有LOB数据页才能X锁定它们?这只是锁在内存中如何表示的详细信息(以某种方式存储在页面中)吗?如果未完全缓存,则这将导致I / O影响很大程度上取决于数据大小。
  2. 为什么X只是为了解除分配而锁定?仅仅取消索引叶与行内部分是否足够,因为取消分配不需要修改页面本身?还有其他方法可以获取锁定保护的LOB数据吗?
  3. 考虑到已经有专门用于此类工作的后台任务,为什么还要完全取消分配页面呢?

也许更重要的是,我的实际问题:

  • 有什么方法可以使删除操作有所不同?我的目标是不管文件大小如何,都恒定时间删除,这与文件流类似,在文件删除之后,任何清除操作都会在后台进行。是配置的东西吗?我会奇怪地存储东西吗?

这是如何重现描述的测试(通过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

以下是在我的工作站上对删除进行概要分析的结果:

| 列类型| 删除大小| 持续时间(毫秒)| 读| 写| 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 |

我们不一定要使用文件流来代替,因为:

  1. 我们的数据大小分布不保证。
  2. 在实践中,我们以很多块添加数据,并且文件流不支持部分更新。我们将需要对此进行设计。

更新1

测试了将数据作为删除的一部分写入事务日志的理论,但事实并非如此。我是否为此测试不正确?见下文。

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

此外,如果将数据写入日志,我希望页面本身会变脏。似乎只记录了解除分配,这与删除后的脏分配相匹配。

更新2

我确实得到了Paul Randal的回应。他申明了必须读取所有页面才能遍历树并找到要释放的页面的事实,并表示没有其他方法可以查找哪些页面。这是对1和2的一半答复(尽管并不能解释对行外数据进行锁定的必要性,但这只是小问题)。

问题3仍未解决:如果已经有后台任务要删除,为什么要提前分配页面?

当然,所有重要的问题:是否有一种方法可以直接缓解(即不解决)这种与大小相关的删除行为?我认为这将是一个更常见的问题,除非我们真的是唯一在SQL Server中存储和删除50 MB行的行?是否其他所有人都可以通过某种形式的垃圾收集工作来解决此问题?


我希望有一个更好的解决方案,但还没有找到。我遇到的情况是记录大量不同大小的行,最大可达1MB +,并且我有一个“清除”过程来删除旧记录。因为删除的速度太慢,所以我不得不将其分为两个步骤-首先删除表之间的引用(这是非常快的),然后删除孤立的行。删除作业平均删除数据约2.2秒/ MB。因此,我当然必须减少争用,因此我在循环内有一个带有“ DELETE TOP(250)”的存储过程,直到不再删除任何行为止。
珠算2015年

Answers:


5

我不能说为什么删除VARBINARY(MAX)确实比文件流效率低得多,但是您可以考虑一个想法,如果您只是想避免在删除这些LOBS时从Web应用程序中超时。您可以将VARBINARY(MAX)值存储在原始表引用的单独表中(将其称为tblLOB)(将其称为tblParent)。

从此处删除记录时,可以从父记录中删除它,然后偶尔进行垃圾收集过程以清理LOB表中的记录。在此垃圾收集过程中,可能还会有其他硬盘驱动器活动,但它至少与前端Web分离,并且可以在非高峰时间执行。


谢谢。这恰恰是我们董事会的选择之一。该表是一个文件系统,我们目前正在将二进制数据从层次结构元分离到一个完全独立的数据库中。我们可以按照您所说的去做,然后删除层次结构行,然后让GC进程清理孤立的LOB行。或为数据设置删除时间戳以实现相同的目标。如果没有令人满意的答案,这就是我们可能采取的方法。
杰里米·罗森伯格

1
对于只有一个时间戳来表示已删除的时间戳,我会保持谨慎。那将起作用,但是最终您将在活动行中占用大量已用空间。在某些时候,您将需要某种gc流程,具体取决于要删除的数量,定期删除较少的内容(而不是偶尔删除)的影响较小。
伊恩·
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.