SQL批量删除

时间:2009-05-22 08:08:42

标签: sql sql-server tsql sql-server-administration

我在SQL Server 2005中有一个表,其中有大约40亿行。我需要删除大约20亿这些行。如果我尝试在单个事务中执行此操作,则事务日志将填满并且失败。我没有任何额外的空间来使事务日志更大。我认为最好的方法是批量删除语句(批量为~10000?)。

我可以使用游标执行此操作,但这是一种标准/简单/聪明的方法吗?

P.S。此表没有标识列作为PK。 PK由整数外键和日期组成。

9 个答案:

答案 0 :(得分:8)

您要删除的行与要保留的行有何区别?这对你有用吗?

while exists (select 1 from your_table where <your_condition>)
delete top(10000) from your_table
where <your_condition>

答案 1 :(得分:7)

你可以“蚕食”删除,这也意味着你不会对数据库造成大量负担。如果您的t-log备份每10分钟运行一次,那么您应该可以在相同的时间间隔内运行一次或两次。您可以将其安排为SQL代理作业

尝试这样的事情:

DECLARE @count int
SET @count = 10000

    DELETE  FROM table1 
    WHERE table1id IN (
        SELECT TOP (@count) tableid
        FROM table1
        WHERE x='y'
    )

答案 2 :(得分:3)

听起来这是一次性操作(我希望你),你不需要回到这个批量删除的中间状态 - 如果是这样的话你为什么不切换到SIMPLE事务模式在跑步之前,当你完成后再回到FULL?

这样事务日志就不会增长太多。这在大多数情况下可能并不理想,但我没有看到任何错误(假设如上所述,您不需要返回到删除之间的状态)。

你可以在你的脚本中用smt执行此操作:

ALTER DATABASE myDB SET RECOVERY FULL/SIMPLE

或者您可以设置作业以在每个给定的时间间隔缩小事务日志 - 同时删除正在运行。这有点糟糕,但我认为它可以解决问题。

答案 3 :(得分:2)

好吧,如果您使用SQL Server分区,比如基于日期列,您可能会切换出不再需要的分区。也许考虑未来的实施。

我认为最好的选择可能就是说,以较小批量删除数据,而不是一次性删除,以避免任何潜在的阻塞问题。

您还可以考虑以下方法:

  1. 复制数据以保留在临时表中
  2. 截断原始表以清除所有数据
  3. 将所有内容从临时表移回原始表
  4. 当数据被添加回原始表时,您的索引也将被重建。

答案 4 :(得分:2)

我会做类似于临时表建议的事情,但我会选择要保留的新永久表,删除原始表,然后重命名新表。这应该具有相对较低的转录影响。显然,请记住在重命名后重新创建新表所需的任何索引。

只是我的两个人。

答案 5 :(得分:2)

除了将其放入一个带有截断日志的语句的批处理之外,您还可能想尝试这些技巧:

  • 除了您的其他条件外,添加与聚集索引中第一列匹配的条件
  • 删除表中的所有索引,然后在删除完成后将其放回,如果可能的话,并且不会干扰数据库中发生的任何其他事情,但保留聚集索引

对于上面的第一点,例如,如果您的PK是群集的,那么找到一个与您想要删除每个批次的行数大致匹配的范围并使用它:

DECLARE @max_id INT, @start_id INT, @end_id INT, @interval INT
SELECT @start_id = MIN(id), @max_id = MAX(id) FROM My_Table
SET @interval = 100000  -- You need to determine the right number here
SET @end_id = @start_id + @interval

WHILE (@start_id <= @max_id)
BEGIN
     DELETE FROM My_Table WHERE id BETWEEN @start_id AND @end_id AND <your criteria>

     SET @start_id = @end_id + 1
     SET @end_id = @end_id + @interval
END

答案 6 :(得分:0)

我同意那些希望你循环访问较小记录的人,这比尝试一步完成整个操作要快。您可能会体验到应该包含在循环中的记录数。大约2000个一次似乎是大多数表格中的最佳点,我做了大量的从althouhg需要较少的数量,如500.取决于forign键的数量,记录的大小,触发器等,所以它真的需要一些人试验找到你需要的东西。它还取决于桌子使用的重量。访问量很大的表需要循环的每次迭代才能运行更短的时间。如果您可以在非工作时间运行,或者最好在单用户模式下运行,那么您可以在一个循环中删除更多记录。

如果你不认为你在非工作时间的一个晚上这样做,最好用计数器设计循环,并且每晚只进行一定数量的迭代直到完成。

此外,如果您使用隐式事务而不是显式事务,则可以随时终止循环查询,并且已删除的记录将保持删除,除了当前循环中的那些记录。比试图回滚50万条记录要快得多,因为你已经停止了系统。

在进行此类操作之前,立即备份数据库通常是个好主意。

答案 7 :(得分:0)

以下是我的例子:

-- configure script
-- Script limits - transaction per commit (default 10,000)
-- And time to allow script to run (in seconds, default 2 hours)
--
DECLARE @MAX INT
DECLARE @MAXT INT
--
-- These 4 variables are substituted by shell script.
--
SET @MAX = $MAX
SET @MAXT = $MAXT
SET @TABLE = $TABLE
SET @WHERE = $WHERE

-- step 1 - Main loop
DECLARE @continue INT
-- deleted in one transaction
DECLARE @deleted INT
-- deleted total in script
DECLARE @total INT
SET @total = 0
DECLARE @max_id INT, @start_id INT, @end_id INT, @interval INT
SET @interval = @MAX
SELECT @start_id = MIN(id), @max_id = MAX(id) from @TABLE
SET @end_id = @start_id + @interval

-- timing
DECLARE @start DATETIME
DECLARE @now DATETIME
DECLARE @timee INT
SET @start = GETDATE()
-- 
SET @continue = 1
IF OBJECT_ID (N'EntryID', 'U') IS NULL 
BEGIN
    CREATE TABLE EntryID (startid INT)
    INSERT INTO EntryID(startid) VALUES(@start_id)
END
    ELSE
BEGIN
    SELECT @start_id = startid FROM EntryID
END


WHILE (@continue = 1 AND @start_id <= @max_id)
BEGIN

    PRINT 'Start issued:   ' + CONVERT(varchar(19), GETDATE(), 120)
    BEGIN TRANSACTION
        DELETE 
        FROM @TABLE
        WHERE id BETWEEN @start_id AND @end_id AND @WHERE
        SET @deleted = @@ROWCOUNT
    UPDATE EntryID SET EntryID.startid = @end_id + 1
    COMMIT
    PRINT 'Deleted issued: ' + STR(@deleted) + ' records. ' + CONVERT(varchar(19), GETDATE(), 120) 
    SET @total = @total + @deleted
    SET @start_id = @end_id + 1
    SET @end_id = @end_id + @interval
    IF @end_id > @max_id
        SET @end_id = @max_id

    SET @now = GETDATE()
    SET @timee = DATEDIFF (second, @start, @now)
    if @timee > @MAXT
    BEGIN
    PRINT 'Time limit exceeded for the script, exiting'
    SET @continue = 0
    END
--    ELSE
--    BEGIN
--      SELECT @total 'Removed now', @timee 'Total time, seconds'   
--    END
END

SELECT @total 'Removed records', @timee 'Total time sec' , @start_id 'Next id', @max_id 'Max id', @continue 'COMPLETED? '
SELECT * from EntryID next_start_id

GO

答案 8 :(得分:-1)

简而言之,您不能删除20亿行而不会导致某种主要数据库停机。

您最好的选择可能是将数据复制到临时表并截断原始表,但这将填充您的tempDB,并且将使用与删除数据相同的日志记录。

您需要删除尽可能多的行,直到事务日志填满,然后每次都截断它。 Stanislav Kniazev提供的答案可以通过增加批量大小和添加调用来截断日志文件来修改。