SQL Server有效地删除具有数百万行的一组行

Net*_*zen 10 sql sql-server

我最近问过这个问题: MS SQL在表格中共享身份种子 (很多人想知道为什么)

我有一个表格的以下布局:

表:星星
starId bigint
categoryId bigint
starname varchar(200)

但我的问题是我有数百万行.因此,当我想从表格中删除星星时,它在SQL Server上过于激烈.

我不能使用2005+的内置分区,因为我没有企业许可证.

当我删除时,我总是一次删除整个类别ID.

我想过做这样的设计:

表:Star_1
starId bigint
CategoryId bigint constaint rock = 1
starname varchar(200)

表:Star_2
starId bigint
CategoryId bigint constaint rock = 2
starname varchar(200)

通过这种方式,我可以通过执行简单的删除表来删除整个类别,从而删除O(1)中的数百万行.

我的问题是,在SQL Server中拥有数十万个表是一个问题吗?O(1)的下降对我来说是非常可取的.也许有一个完全不同的解决方案,我没想到?

编辑:

插入后是否曾经修改过星星?没有.

您是否需要查询星级类别?我永远不必查询星级类别.

如果您正在寻找特定星的数据,您会知道要查询的表吗?是

输入数据时,应用程序将如何决定将数据放入哪个表中?在创建categoryId时,在开始时一次性完成星形数据的插入.

有多少个类别?您可以假设将有无限的星级类别.假设每天最多100个星级类别,每天最多不需要30个星级类别.

你真的需要删除整个类别或只删除数据更改的星号吗?是全明星类别.

您是否尝试过分批删除?是的,我们今天这样做,但还不够好.够了.

另一种技术是将记录标记为删除?没有必要将星标记为已删除,因为我们知道整个星级类别都有资格被删除.

他们中有多少比例从未使用过?通常我们会将每个星级类别数据保留几周,但有时需要保留更多.

当你认为一个有用的是永远有用还是以后还需要删除?

不是永远,而是在发出删除类别的手动请求之前.如果是这样的话有多少时间会发生?不常见.

你使用什么样的光盘安排?单个文件组存储,当前没有分区.

你能用sql enterprise吗?没有.有很多人运行这个软件,他们只有sql标准.获得ms sql企业是超出预算的.

Aar*_*ght 34

我的问题是,在SQL Server中拥有数十万个表是一个问题吗?

是.在SQL Server中拥有这么多表是一个很大的问题.SQL Server必须将每个对象作为元数据进行跟踪,并且一旦包含索引,引用约束,主键,默认值等,那么您将讨论数百万个数据库对象.

虽然SQL Server理论上可以处理2 32个对象,但请放心,它会在负载之下开始屈服.

如果数据库没有崩溃,您的开发人员和IT人员几乎肯定会.当我看到超过一千张左右的桌子时,我感到紧张; 给我看一个数十万的数据库,我会跑掉尖叫.

创建数十万个表作为穷人的分区策略将使您无法执行以下任何操作:

  • 编写有效的查询(你如何SELECT多个类别?)
  • 保持独特的身份(正如您已经发现的那样)
  • 保持参照完整性(除非您喜欢管理300,000个外键)
  • 执行远程更新
  • 编写干净的应用代码
  • 保持任何历史
  • 实施适当的安全性(显然用户必须能够启动这些创建/删除 - 非常危险)
  • 正确缓存 - 100,000个表意味着100,000个不同的执行计划都在竞争相同的内存,你可能没有足够的内存;
  • 聘请DBA(因为他们放心,他们会在看到你的数据库后立即退出).

另一方面,在一个表中拥有数十万甚至数百万并不是一个问题- 这就是SQL Server和其他SQL RDBMS设计使用的方式,它们非常好 -为这种情况优化.

O(1)的下降对我来说是非常可取的.也许有一个完全不同的解决方案,我没想到?

数据库中性能问题的典型解决方案是按优先顺序排列:

  • 运行探查器以确定查询中最慢的部分;
  • 如果可能,改进查询(即通过消除非sargable谓词);
  • 规范化或添加索引以消除这些瓶颈;
  • 必要时进行非规范化(通常不适用于删除);
  • 如果涉及级联约束或触发器,请在事务持续时间内禁用它们并手动吹出级联.

但这里的现实是,你并不需要一个"解决方案".

"数百万行"在SQL Server数据库中并不多.通过简单地索引要删除的列,可以非常快速地从数百万个表中删除几千行 - 在本例中CategoryID.SQL Server可以毫不费力地做到这一点.

实际上,删除通常具有O(M log N)复杂度(N =行数,M =要删除的行数).为了实现O(1)删除时间,您首先要牺牲SQL Server提供的几乎所有好处.

O(M log N)可能没有O(1)那么快,但是你所谈论的减速类型(删除几分钟)必须有次要原因.这些数字并没有加起来,为了证明这一点,我已经开始并制定了一个基准:


表格架构:

CREATE TABLE Stars
(
    StarID int NOT NULL IDENTITY(1, 1)
        CONSTRAINT PK_Stars PRIMARY KEY CLUSTERED,
    CategoryID smallint NOT NULL,
    StarName varchar(200)
)

CREATE INDEX IX_Stars_Category
ON Stars (CategoryID)
Run Code Online (Sandbox Code Playgroud)

请注意,此模式甚至没有针对DELETE操作进行真正优化,它是您在SQL Server中可能看到的相当普遍的表模式.如果此表没有关系,那么我们不需要代理键或聚簇索引(或者我们可以将聚簇索引放在类别上).我稍后再说.

样本数据:

这将使用500个类别(即每个类别1:20,000的基数)在表中填充1000万行.您可以调整参数以更改数据量和/或基数.

SET NOCOUNT ON

DECLARE
    @BatchSize int,
    @BatchNum int,
    @BatchCount int,
    @StatusMsg nvarchar(100)

SET @BatchSize = 1000
SET @BatchCount = 10000
SET @BatchNum = 1

WHILE (@BatchNum <= @BatchCount)
BEGIN
    SET @StatusMsg =
        N'Inserting rows - batch #' + CAST(@BatchNum AS nvarchar(5))
    RAISERROR(@StatusMsg, 0, 1) WITH NOWAIT

    INSERT Stars2 (CategoryID, StarName)
        SELECT
            v.number % 500,
            CAST(RAND() * v.number AS varchar(200))
        FROM master.dbo.spt_values v
        WHERE v.type = 'P'
        AND v.number >= 1
        AND v.number <= @BatchSize

    SET @BatchNum = @BatchNum + 1
END
Run Code Online (Sandbox Code Playgroud)

档案脚本

最简单的......

DELETE FROM Stars
WHERE CategoryID = 50
Run Code Online (Sandbox Code Playgroud)

结果:

这是在运行5年的工作站机器 IIRC,32位双核AMD Athlon和便宜的7200 RPM SATA驱动器上测试的.

我使用不同的CategoryID运行了10次测试.最慢的时间(冷缓存)大约是5秒.最快的时间是1秒.

也许没有简单地删除表那么快,但远不及你提到的多分钟删除时间.请记住,这甚至不是一台体面的机器!

但我们可以做得更好......

关于您的问题的一切意味着这些数据不相关.如果您没有关系,则不需要代理键,并且可以删除其中一个索引,将聚簇索引移动到CategoryID列.

现在,作为一项规则,非唯一/非连续列上的聚簇索引不是一个好习惯.但我们只是在这里进行基准测试,所以无论如何我们都会这样做:

CREATE TABLE Stars
(
    CategoryID smallint NOT NULL,
    StarName varchar(200)
)

CREATE CLUSTERED INDEX IX_Stars_Category
ON Stars (CategoryID)
Run Code Online (Sandbox Code Playgroud)

在此上运行相同的测试数据生成器(产生令人难以置信的页面拆分数),同样的删除平均只有62毫秒,190个来自冷缓存(异常值).作为参考,如果索引是非聚簇的(根本没有聚簇索引),则删除时间最多只能达到606毫秒.

结论:

如果您看到几分钟的删除时间- 甚至几秒钟,那么事情就非常非常错误.

可能的因素是:

  • 统计数据不是最新的(这里不应该是一个问题,但如果是,只需运行sp_updatestats);

  • 缺乏索引(尽管奇怪的是,删除IX_Stars_Category第一个示例中的索引实际上会导致更快的整体删除,因为聚簇索引扫描比非聚簇索引删除更快);

  • 选择不当的数据类型.如果你只有几百万行,而不是数十亿美元,那么你就不需要bigintStarID.你肯定不需要它CategoryID- 如果你有少于32,768个类别,那么你甚至可以用a smallint.每行中不必要数据的每个字节都会增加I/O成本.

  • 锁争用.也许问题实际上并不是删除速度; 也许其他一些脚本或进程Star在行上持有锁,DELETE只是坐在那里等待它们放手.

  • 硬件非常差.我能够在一台非常糟糕的机器上运行这个没有任何问题,但是如果你在90年代的Presario或类似的机器上运行这个数据库,这些机器非常不适合托管SQL Server的一个实例,并且它负载很重那么你显然会遇到问题.

  • 非常昂贵的外键,触发器,约束或您未在示例中包含的其他数据库对象,这可能会增加高成本.您的执行计划应该清楚地显示这一点(在上面的优化示例中,它只是一个Clustered Index Delete).

老实说,我想不出任何其他可能性.在SQL Server中删除并不是那么慢.


如果您能够运行这些基准并看到我看到(或更好)的大致相同的性能,那么这意味着问题在于您的数据库设计和优化策略,而不是SQL Server或删除的渐近复杂性.作为一个起点,我建议您阅读一些关于优化的内容:

如果这不能帮助你,那么我可以提供以下补充建议:

  • 升级到SQL Server 2008,它为您提供了无数的压缩选项,可以大大提高I/O性能;

  • 考虑将每类别Star数据预压缩为紧凑的序列化列表(使用BinaryWriter.NET中的类),并将其存储在varbinary列中.这样,每个类别可以有一行.这违反了1NF规则,但是因为无论如何你似乎没有对Star数据库中的个人数据做任何事情,我怀疑你会失去太多.

  • 考虑使用非关系数据库或存储格式,例如db4oCassandra.不是实现已知的数据库反模式(臭名昭着的"数据转储"),而是使用实际为这种存储和访问模式设计的工具.

  • Stack Overflow一次又一次地需要"超级双倍投票".这是其中一次. (3认同)