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

时间:2010-03-25 21:46:24

标签: sql sql-server

我最近问了这个问题: MS SQL share identity seed amongst tables (很多人想知道为什么)

我有一个表的以下布局:

表:星星
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企业是超出预算的。

13 个答案:

答案 0 :(得分:34)

答案 1 :(得分:4)

你必须删除它们吗?通常最好只将IsDeleted位列设置为1,然后在非工作时间异步执行实际删除。

修改:

这是一个黑暗中的镜头,但在CategoryId上添加聚集索引可能会加快删除速度。它也可能会对其他查询产生负面影响。这是你能测试的东西吗?

答案 2 :(得分:2)

这是SQL 2000中的旧技术,分区视图并且仍然是SQL 2005的有效选项。问题确实来自于拥有大量表和与之相关的维护开销。

正如您所说,分区是一项企业功能,但专为此大规模数据删除/滚动窗口效果而设计。

另一个选项是运行批量删除,以避免创建一个非常大的事务,创建数百个小得多的事务,以避免锁定升级并保持每个事务的小。

答案 3 :(得分:2)

拥有单独的表是分区 - 您只需手动管理它,不获得任何管理协助或统一访问(没有视图或分区视图)。

企业版的成本是否比单独构建和维护分区方案的成本更高?

长时间运行删除的替代方法还包括使用相同的模式填充替换表,并简单地排除要删除的行,然后使用sp_rename交换表。

我不明白为什么要定期删除所有类别的明星?据推测,您正在创建新的类别,这意味着您的类别数量必须很大,并且(手动或非手动)分区将非常密集。

答案 4 :(得分:1)

也许在Stars表上将PK设置为非群集,并在categoryid上添加聚簇索引。

除此之外,服务器设置是否在性能最佳实践方面做得很好?这是使用单独的物理磁盘用于数据和日志,而不是使用RAID5等。

答案 5 :(得分:1)

当你说删除数百万行时“对于SQL服务器而言太强烈”,你的意思是什么?你的意思是在删除过程中日志文件增长太多了吗?

您所要做的就是批量执行批量删除:

DECLARE @i INT
SET @i = 1

WHILE @i > 0
BEGIN
    DELETE TOP 10000 FROM dbo.SuperBigTable
        WHERE CategoryID = 743
    SELECT @i = @@ROWCOUNT
END

如果您的数据库处于完全恢复模式,则必须在此过程中运行频繁的事务日志备份,以便它可以重用日志中的空间。如果数据库处于简单模式,则不必执行任何操作。

我唯一的建议是确保在CategoryId中有适当的索引。我甚至可能会建议这是聚集索引。

答案 6 :(得分:1)

如果要优化类别,首先使用类别删除聚类复合索引可能比损坏做得更好。

你也可以描述桌子上的关系。

答案 7 :(得分:1)

听起来事务日志正在努力解决删除的问题。事务日志以单位形式增长,这需要时间,同时分配更多的磁盘空间。

虽然可以使用TRUNCATE命令截断表,但是在不登记事务的情况下无法从表中删除行。但是,这将无条件地删除表中的所有行。

我可以提出以下建议:

  1. 切换到非事务性数据库或可能是平面文件。听起来你不需要事务数据库的原子性。

  2. 尝试以下方法。每次删除x后(取决于大小)发出以下声明

  3. 使用TRUNCATE_ONLY备份日志;

    这简单地截断了事务日志,剩下的空间用于重新填充日志。但是我不确定这会增加多少时间。

答案 8 :(得分:0)

你如何处理明星数据?如果您在任何给定时间只查看一个类别的数据,这可能会有效,但很难维护。每次有新类别时,都必须构建一个新表。如果您想跨类别查询,它会变得更复杂,并且在时间方面可能更昂贵。如果你这样做并且想要跨类别查询,那么视图可能是最好的(但不要在视图之上堆叠视图)。如果您正在寻找特定星的数据,您会知道要查询的表吗?如果没有,那么您将如何确定哪个表或者您要查询它们?输入数据时,应用程序将如何决定将数据放入哪个表中?有多少个类别?顺便提一下,每个人都有一个单独的身份证,使用bigint身份,并将身份与您的唯一身份识别的类别类型相结合。

你真的需要删除整个类别或只删除数据更改的星号吗? 你需要删除,也许你只需要更新信息。

您是否尝试过批量删除(循环中一次删除1000条记录)。这通常比在一个删除语句中删除一百万条记录要快得多。它通常会使表在删除期间不被锁定。

另一种技术是将记录标记为删除。然后,您可以在使用率较低时运行批处理以删除这些记录,并且您的查询可以在排除标记为删除的记录的视图上运行。

鉴于你的答案,我认为你的建议可能是合理的。

答案 9 :(得分:0)

我知道这有点像切线,但SQL Server(或任何关系数据库)真的是这项工作的好工具吗?您实际使用的是什么关系数据库功能?

如果您一次删除整个类别,则根据它不能具有很多参照完整性。数据是只读的,因此您不需要ACID进行数据更新。

听起来像是在使用基本的SELECT查询功能吗?

答案 10 :(得分:0)

只是想到了很多桌子 - 你怎么能意识到......

如何使用动态查询。

  1. 创建具有标识category_id列的类别表。
  2. 为此故事创建插入触发器 - 在其中创建名称由category_id动态制作的星星表。
  3. 在删除时创建触发器 - 在动态创建的sql的帮助下,删除相应的星表。
  4. 选择具体类别的星星,您可以使用返回表格的功能。它将category_id作为参数,并通过动态查询返回结果。
  5. 要插入新类别的星星,首先在类别表格中插入新行,然后将星号插入相应的表格。
  6. 我将进行一些研究的另一个方向是使用xml类型的列来存储星星数据。这里的主要想法是,如果您需要仅按类别操作星星而不是为什么不以xml格式将所有具体类别的星星存储在表格的一个单元格中。不幸的是,我绝对无法想象出这样决定的表现。

    这两种变体都与头脑风暴中的想法一样。

答案 11 :(得分:0)

正如Cade所指出的那样,为每个类别添加一个表是手动分区数据,没有统一访问的好处。

在不使用分区的情况下,数百万行的删除速度与删除表一样快,绝不会有任何删除。

因此,似乎为每个类别使用单独的表可能是一个有效的解决方案。但是,由于您已经声明保留了其中一些类别,并且删除了一些类别,因此这是一个解决方案:

  1. 为每个新星创建一个新星星表 类别。
  2. 等待时间段到期,您决定是否保留该类别的星星。
  3. 如果您计划保留记录,请将记录滚动到主星表中。
  4. 放下桌子。
  5. 这样,您将拥有有限数量的表格,具体取决于您添加类别的比率以及您决定是否需要它们的时间段。

    最终,对于您保留的类别,您将工作量增加一倍,但额外的工作会随着时间的推移而分配。用户体验到聚集索引末尾的插入可能比从中间删除的插入更少。但是,对于那些你没有保留的类别,你节省了大量的时间。

    即使你在技术上没有挽救工作,感知往往是更大的问题。

答案 12 :(得分:0)

我没有得到我对原帖的评论的答案,所以我会做一些假设......

这是我的想法:使用多个数据库,每个类别一个。

您可以免费使用每个Windows版本附带的managed ESE database

使用PersistentDictionary对象,并以这种方式跟踪starid,starname对。如果需要删除类别,只需删除该类别的PersistentDictionary对象。

PersistentDictionary<int, string> starsForCategory = new PersistentDictionary<int, string>("Category1");

这将创建一个名为“Category1”的数据库,您可以在其上使用标准的.NET字典方法(add,exists,foreach等)。