从大表中删除数据

问题描述 投票:5回答:16

我有一个包含约10个字段的表,用于为客户存储GPS信息。随着时间的推移,随着我们增加了更多的客户,该表已经增长到大约1400万行。随着gps数据的到来,服务不断在表中插入一行。 90%的数据都不是真实的,即客户并不关心3个月前的车辆位置,但是最新的数据用于生成跟踪报告。我的目标是编写一个sql来清除早于一个月的数据。

这是我的问题,我无法使用TRUNCATE TABLE,因为我会丢失所有内容?昨天我写了一条带where子句的delete表语句。当我在测试系统上运行它时,它锁定了我的表,并且模拟gps插入间歇性地失败了。当我尝试记录每个删除操作时,我的事务日志也增长到超过6GB。

我首先想到的是从最早的那个开始一次删除一次数据,但我想知道是否有更好的方法。

sql
16个回答
4
投票

尝试一下

存在时(SELECT * FROM table WHERE(删除条件))

BEGIN设定行数1000删除表的位置(删除条件)设置行数0恩德

这将删除以1000为一组的行


0
投票

请注意,大多数数据库在事务处理过程中会将相邻记录锁定在索引中,因此使操作简短会很有帮助。我假设您的插入由于锁定等待超时而失败,因此请在小的突发性事务中删除数据。我建议使用以最旧的1,000个块为增量循环的单线程Perl脚本。我希望您的主键(以及希望聚集索引,以防万一它们最终成为两种不同的事物)可以与时间相关联,因为这将是最好的删除方法。

伪SQL:选择max(primId)<3_months_ago从primId 的表中删除

现在,这是真正有趣的部分:所有这些删除操作可能会使您的索引变得一团糟,并要求对其进行重建以防止计算机变慢。在这种情况下,您要么必须换入最新的从站,要么就遭受一些停机。确保在测试机上测试这种可能的情况。


0
投票

如果使用的是oracle,我会按日期在表和索引上设置一个分区。然后,通过删除分区删除数据...数据将神奇地与该分区一起消失。

这是一个简单的步骤-不会阻塞您的重做日志等。

[here都有基本介绍


0
投票

delete语句是否使用表上的任何索引?通常,可以通过修改语句以使用现有索引或在表上添加索引来获得巨大的性能改进,这有助于提高delete语句执行的查询的性能。

此外,正如其他提到的那样,删除操作应以多个块的形式完成,而不是一个巨大的语句。这样可以防止表锁定太长时间,并防止其他进程超时等待删除完成。


0
投票

删除表时性能非常快-甚至很大。所以这就是我会做的。用Management Studio中的索引编写表的脚本。编辑脚本并运行它以创建表的副本。称为table2。进行选择插入以将要保留的数据寄存到新表中2。重命名旧表,例如tableOld。用原始名称重命名table2。等待。如果没有人向您尖叫,请放下table2。有一些风险。1)检查原始表上是否定义了触发器或约束。它们可能未包含在Management Studio生成的脚本中。2)如果原始表具有标识字段,则可能必须在插入新表之前打开identity_insert。


0
投票

我想出了以下T-SQL脚本,该脚本可获取任意数量的最新数据。

IF EXISTS(SELECT name FROM sys.tables WHERE name = 'tmp_xxx_tblGPSVehicleInfoLog')
BEGIN
    PRINT 'Dropping temp table tmp_xxx_tblGPSVehicleInfoLog'
    DROP TABLE tmp_xxx_tblGPSVehicleInfoLog
END
GO

PRINT 'Creating temp table tmp_xxx_tblGPSVehicleInfoLog'
CREATE TABLE [dbo].[tmp_xxx_tblGPSVehicleInfoLog](
    [GPSVehicleInfoLogId] [uniqueidentifier] NOT NULL,
    [GPSVehicleInfoId] [uniqueidentifier] NULL,
    [Longitude] [float] NULL,
    [Latitude] [float] NULL,
    [GroundSpeed] [float] NULL,
    [Altitude] [float] NULL,
    [Heading] [float] NULL,
    [GPSDeviceTimeStamp] [datetime] NULL,
    [Milliseconds] [float] NULL,
    [DistanceNext] [float] NULL,
    [UpdateDate] [datetime] NULL,
    [Stopped] [nvarchar](1) NULL,
    [StopTime] [datetime] NULL,
    [StartTime] [datetime] NULL,
    [TimeStopped] [nvarchar](100) NULL
) ON [PRIMARY]
GO

PRINT 'Inserting data from tblGPSVehicleInfoLog to tmp_xxx_tblGPSVehicleInfoLog'
SELECT * INTO tmp_xxx_tblGPSVehicleInfoLog 
FROM tblGPSVehicleInfoLog 
WHERE tblGPSVehicleInfoLog.UpdateDate between '03/30/2009 23:59:59' and '05/19/2009  00:00:00'
GO

PRINT 'Truncating table tblGPSVehicleInfoLog'
TRUNCATE TABLE tblGPSVehicleInfoLog
GO

PRINT 'Inserting data from tmp_xxx_tblGPSVehicleInfoLog to tblGPSVehicleInfoLog'
INSERT INTO tblGPSVehicleInfoLog 
SELECT * FROM tmp_xxx_tblGPSVehicleInfoLog 
GO

0
投票

为了防止事务日志变得不受控制,请按以下方式进行修改:

DECLARE @i INT
SET @i = 1
SET ROWCOUNT 10000

WHILE @i > 0
BEGIN
    BEGIN TRAN
        DELETE TOP 1000 FROM dbo.SuperBigTable
        WHERE RowDate < '2009-01-01'
    COMMIT
    SELECT @i = @@ROWCOUNT
END
SET ROWCOUNT 0

这是使用SQL 2005和2008的首选TOP语法的版本:

DECLARE @i INT
SET @i = 1

WHILE @i > 0
BEGIN
    BEGIN TRAN
        DELETE TOP 1000 FROM dbo.SuperBigTable
        WHERE RowDate < '2009-01-01'
    COMMIT
    SELECT @i = @@ROWCOUNT
END

0
投票

我正在分享我的解决方案。我没有索引日期字段。在该过程运行期间,我测试了获取记录计数,插入和更新。他们能够在过程运行时完成。在Azure托管实例中,以绝对最低的配置(通用,4核)运行,我能够在一分钟(约55秒)内清除100万行。

CREATE PROCEDURE [dbo].[PurgeRecords] (
 @iPurgeDays INT = 2,
 @iDeleteRows INT = 1000,
 @bDebug BIT = 1 --defaults to debug mode
)
AS

SET NOCOUNT ON
DECLARE @iRecCount INT = 0 
DECLARE @iCycles INT = 0
DECLARE @iRowCount INT = 1
DECLARE @dtPurgeDate DATETIME = GETDATE() - @iPurgeDays
SELECT @iRecCount = COUNT(1) FROM YOURTABLE WHERE [Created] <= @dtPurgeDate
SELECT @iCycles = @iRecCount / @iDeleteRows
SET @iCycles = @iCycles + 1  --add one my cycle to get the remainder
--purge the rows in groups
WHILE @iRowCount <= @iCycles
 BEGIN
  BEGIN TRY
   IF @bDebug = 0
    BEGIN
     --delete a group of records
     DELETE TOP (@iDeleteRows) FROM YOURTABLE WHERE [Created] <= @dtPurgeDate
    END
   ELSE
    BEGIN
     --display the delete that would have taken place
     PRINT 'DELETE TOP (' + CONVERT(VARCHAR(10), @iDeleteRows) + ') FROM YOURTABLE WHERE [Created] <= ''' + CONVERT(VARCHAR(25), @dtPurgeDate) + ''''
    END
   SET @iRowCount = @iRowCount + 1

  END TRY
  BEGIN CATCH
   --if there are any issues with the delete, raise error and back out
   RAISERROR('Error purging YOURTABLE Records', 16, 1)
   RETURN
  END CATCH
 END
GO

10
投票

我的2美分:

如果使用的是SQL 2005及更高版本,则可以考虑根据日期字段对表进行分区,因此在删除旧记录时,该表不会被锁定。

也许,如果您可以决定dba,可以将日志模型临时更改为Simple,这样它不会增长得太快,它仍然会增长,但是日志不会太详细。


4
投票

最好是创建一个临时表,并仅插入要保留的数据。然后截断原始表并复制回备份。

Oracle语法(SQL Server与之相似)

create table keep as select * from source where data_is_good = 1;
truncate table source;
insert into source select * from keep;

如果源表上有外键,则需要禁用外键。

在Oracle中,索引名称在整个模式中必须唯一,而不仅仅是每个表。在SQL Server中,您可以通过将“保持”重命名为“源”来进一步优化,因为您可以轻松地在两个表上创建相同名称的索引


3
投票

如果您使用的是SQL Server 2005或2008,则滑动窗口分区是perfect的解决方案-即时归档或清除而没有任何明显的锁定。请查看here以获取更多信息。


2
投票

您可以将最近的数据复制到新表中,截断该表然后再复制回该表吗?

当然,您将需要担心在6个月或一年内再次执行此操作。


2
投票

我将按天/月进行手动删除(无论您可以使用的最大单位。)一旦完成第一个操作,然后编写存储的proc程序开始工作,每天都会删除您不删除的最旧数据需要。

DELETE FROM TABLENAME 
WHERE datediff(day,tableDateTime,getdate() > 90

个人而言,我讨厌对生产数据集做一些工作,因为这些数据集遗漏了关键结果会导致某些非常糟糕的事情发生。


2
投票

欢迎使用数据仓库。您需要将数据分为两部分。

  • 实际的应用程序,仅具有当前数据。

  • 历史。

您需要编写一些“ ETL”作业,以将数据从当前移动到历史记录,并删除已移动的历史记录。

您需要定期运行此程序。每天-每周-每月每季度-从技术上来说并不重要。重要的是历史的用途以及谁使用它。


1
投票

我可能会分批完成,因为您已经想到了。另一个选择是将重要数据插入另一个表中,截断GPS表,然后重新插入重要数据。您将有一个小窗口,您将在其中丢失最近的历史数据。该窗口有多小取决于您需要重新插入多少数据。另外,如果表使用自动递增的数字或其他默认值,则您将需要使用原始值。

一旦清理完桌子,就应该安排定期的清理工作。您可能还想根据您的RDBMS查看分区。


1
投票

我假设您无法关闭生产系统(或在清除完成后将GPS结果排队等待插入)。​​

根据您在测试系统上发现的性能,我倾向于一次删除一部分(可能是10%)。

您的表被索引了吗?这可能会有所帮助,但是索引编制过程会对系统产生类似的影响,就像进行一次重大清除一样。

© www.soinside.com 2019 - 2024. All rights reserved.