2016-02-12 50 views
1

概述 我在SQL Server 2008 R2中有一个表(EODBalances),它有很大的行数(~2亿)。基本上它是在一个会计系统(总账)中,它的作用是为会计系统中的每个账户存储期末余额。从SQL Server表中删除重复行的更简单,更有效的方法

表定义

[EodBalances](
[EodBalanceId] [int] IDENTITY(1,1) NOT NULL, 
[AccountId] [int] NOT NULL, 
[Created] [datetime] NOT NULL, 
[Balance] [decimal](19, 4) NOT NULL, 
[RowVersion] [timestamp] NOT NULL 

账户的#是成倍的增长,从而导致在EODBalances表中的行#以同样增长的任务。除了指数增长之外,现有问题之一是,即使账户余额没有变化,我们每天都会为每个账户添加一个新行。我的任务是通过删除每个帐户的重复行来减少此表中的行数。我重构了每天晚上更新此表的存储过程,以便只在余额发生变化时才添加新行。这当然只会在未来发生。

问题 我面临的任务是清理表中的历史行,这是与重复删除相关的特定类型的问题。我需要保留表中任何账户余额的原始(第一)条目,但删除期末余额不变的后续行。只要它发生变化,我需要保留该特定行,然后再次删除后续行,直到它再次发生变化。等等...

我已经尝试了几种不同的方法来实现这一点,但他们都是非常低效的,除了他们需要运行的时间,有像海量日志文件的副作用(这是一种痛苦当数据库被日志出货时)。我现在的解决方案是创建表的副本,并将要保留的行复制到原始表中并将其删除。完成此操作后,我删除原始表并将该副本重命名为原始名称。这可行,但比我在升级窗口中可用的时间要多。

有没有人有类似的问题,并找到一个更好的方式来处理它?

+0

从巨大的表中删除行非常耗时。我不知道你是否尝试过使用分区。如果没有,我建议你以适当的方式对表进行分区,然后在每个不同的分区上进行更新和删除数据。你需要使用分区切换技术。搜索它 – FLICKER

+0

未来“重新设计”工作的好主意 - 但我最近发现分区只能在企业SQL *服务器中使用,并不是我们所有人都为此而感到幸运。 – PaulG

回答

2

这里有一个过程,我想出了类似情况的概述:

  • 设计一个算法来确定要删除的重复的行。使用group by,min(),max(),row_number(),无论如何,有几种方法可以做到这一点,它们都被发布了很多次,而且听起来你已经有了一个。

  • 正如你所指出的,这是一个很大的工作要做。

  • 将大块的工作分解成小块,并一次处理一块。随着时间推移这项工作,以保持您的交易日志大小的控制。如果(比如说)每小时执行一次t-log备份,那么每小时只能运行一次这个过程,以使事务日志保持较小并且t-log备份文件不会太过失控。

  • 如何分割它?根据你的数据,我会通过AccountId说。在每个批次中处理一个数字(1,10,100,1000?),无论您的条件是否合理(参考上面的事务日志膨胀)。

  • 如何管理所有这些?创建一个“清除日志”表。使用需要检查的所有AccountId填充它(即,您不必为其添加新帐户)。做一些形式的循环,每个帐户运行一次Delete例程,或者每10个帐户运行一次,或者其他。清除之后,将清除日志表中的帐户标记为“已处理”,并且不要再处理它。记录删除了多少行以及工作完成的时间,以便您可以跟踪进度。

    • 最后一步是调度。使它成为一个存储过程,并将SQL代理作业配置为每过一次(t日志备份周期)调用此过程。安排它在可行的窗口中运行 - 整天如果是“非侵入式”的,或者周日上午的小时(如果系统足够清晰的话)。 (我现在有一个周末跑完16个小时,在“最后”差异备份和每周完整备份之间)。
  • 让它运行直到完成工作。如果工作必须尽快完成,则可能必须支付特惠日志大小,工作时间内的性能以及其他任何其他优惠。

+0

@ FLICKER的评论也适用,但设置表格分区非常不平凡,并且根据您的当前状态(在生产,使用中和痛苦中)似乎不切实际。 –

2

我会为此创建一个新表,然后重新加载数据。识别行并不困难。你需要识别组。它是这样的:

select e.*, 
     row_number() over (partition by AccountId, balance, grp order by created) as seqnum 
from (select e.*, 
      (row_number() over (partition by AccountId order by created) - 
       row_number() over (partition by AccountId, balance order by created) 
      ) as grp 
     from EODBalances e 
    ) e; 

带seqnum的行先行。

然后,我会做这样的事情:

select * 
into temp_EODBalances 
from (select e.*, 
      row_number() over (partition by AccountId, balance, grp order by created) as seqnum 
     from (select e.*, 
        (row_number() over (partition by AccountId order by created) - 
        row_number() over (partition by AccountId, balance order by created) 
        ) as grp 
      from EODBalances e 
      ) e 
    ) e 
where seqnum = 1; 

然后,我将测试“地狱”出表的。最后,当满意时(以及备份原表后),我会这样做:

truncate table EODBalances; 

insert into EODBalances(. . .) 
    select . . . 
    from temp_EODBalances; 
+0

我正要发表评论,我认为最有效的方法是将其作为窗口化SQL语句(使用OVER语法) - 就像您在上面详细介绍的一样。假设Hamish具有必要的时间窗口和存储空间来(几乎)复制这张巨大的表格(并处理生成的日志),那么我看不到比这更好的方法(鉴于我的知识有限)。如果他不这样做,那么我同意菲利普的观点,即在许多时间窗口内以块的形式进行分组是很好的选择。 – PaulG

+0

谢谢戈登。我尝试在本地机器上运行您的建议t-sql实时副本,但由于内存不足而失败。 – hamish

相关问题