从大表中删除数据

时间:2009-05-13 16:00:02

标签: sql

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

这是我的问题我不能使用TRUNCATE TABLE,因为我会失去一切? 昨天我用where子句写了一个删除表语句。当我在测试系统上运行它时,它锁定了我的表,并且模拟gps插入间歇性地失败。此外,我的事务日志在尝试记录每次删除时增长到超过6GB。

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

16 个答案:

答案 0 :(得分:10)

我的2美分:

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

也许,如果你处于做出dba决定的位置,你可以暂时将你的日志模型改为Simple,这样它就不会长得太快,它仍然会增长,但日志不会太详细

答案 1 :(得分: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中,您可以通过将“keep”重命名为“source”来进一步优化它,因为您可以在两个表上轻松创建相同名称的索引

答案 2 :(得分:4)

试试这个

WHILE EXISTS(SELECT * FROM table WHERE(删除条件))

BEGIN
  SET ROWCOUNT 1000
  DELETE表WHERE(删除条件)
  SET ROWCOUNT 0

这将删除1000个组中的行

答案 3 :(得分:3)

如果您使用的是SQL Server 2005或2008,滑动窗口分区就是完美解决方案 - 即时存档或清除,没有任何可察觉的锁定。请查看here以获取更多信息。

答案 4 :(得分:2)

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

当然,那么你需要担心在6个月或一年内再次这样做。

答案 5 :(得分:2)

我会按天/月进行手动删除(无论你可以逃脱的最大单位是什么。)一旦你做了第一个,然后写一个存储过程开始每天开始删除你最旧的数据需要。

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

就个人而言,我讨厌对生产数据集做一些事情,其中​​一个遗漏的密钥会导致一些非常糟糕的事情发生。

答案 6 :(得分:2)

欢迎使用Data Warehousing。您需要将数据拆分为两部分。

  • 实际应用程序,仅包含当前数据。

  • 历史。

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

您需要定期运行此功能。每日 - 每周 - 每月一季 - 在技术上无关紧要。重要的是历史的使用和使用者。

答案 7 :(得分:1)

我可能会按照你已经想到的那样分批进行。另一种选择是将重要数据插入另一个表,截断GPS表,然后重新插入重要数据。你会有一个小窗口,你会错过最近的历史数据。该窗口的小小将取决于您需要重新插入的数据量。此外,如果表格使用自动增量数字或其他默认值,您需要小心,以便使用原始值。

清理好桌子后,应安排定期清洁工作。您可能还希望根据您的RDBMS来查看分区。

答案 8 :(得分:1)

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

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

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

答案 9 :(得分:0)

请记住,大多数数据库在事务期间锁定索引中的相邻记录,因此保持简短操作将会很有帮助。我假设您的插入在锁定等待超时时失败,因此在小型突发事务中删除您的数据。我建议使用单线程Perl脚本,以最早的1,000块增量循环。我希望你的主键(希望聚集索引能够以某种方式最终成为两种不同的东西)与时间相关联,因为这是最好的删除方式。

PseudoSQL:     选择max(primId)< 3个月前     从表中删除primId< maxPrimId limit 1000

现在,这是非常有趣的部分:所有这些删除可能会使您的索引变得混乱,并且需要重建它们以防止机器变慢。在这种情况下,您要么必须交换最新的从站,要么只是遭受一些停机。确保在测试机器上测试这种可能的情况。

答案 10 :(得分:0)

如果您正在使用oracle,我会在您的表和索引上按日期设置分区。然后你通过删除分区来删除数据......数据会神奇地消失掉分区。

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

所有这些here

都有一个基本的介绍

答案 11 :(得分:0)

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

另外,正如其他提到的,删除应该在多个块中完成,而不是一个巨大的语句。这可以防止表被锁定太长时间,并让其他进程超时等待删除完成。

答案 12 :(得分:0)

当放下一张桌子时性能非常快 - 即使是一张非常大的桌子。所以这就是我要做的。使用Management Studio中的索引编写完整的表格。编辑脚本并运行它以创建表的副本。称之为table2。执行选择插入以将要保留的数据驻留到新表2中。重命名旧表,比如tableOld。使用原始名称重命名table2。等待。如果没有人尖叫你掉桌2。 存在一些风险。 1)检查原始表上是否定义了触发器或约束。它们可能不会包含在管理工作室生成的脚本中。 2)如果原始表具有标识字段,则在插入新表之前可能必须打开identity_insert。

答案 13 :(得分: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

答案 14 :(得分: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

答案 15 :(得分: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