27

一个用于循环遍历1700 万条记录以删除重复项的查询 现在已经运行了大约16 个小时,我想知道查询是否现在停止,它是否会完成删除语句,或者它是否在运行时被删除询问?事实上,如果我停止它,它会完成删除还是回滚?

我发现当我做一个

 select count(*) from myTable

它返回的行(在执行此查询时)大约比起始行数少 5。显然服务器资源极差,这是否意味着这个过程需要 16 个小时才能找到 5 个重复项(实际上有数千个),而且这可能会运行数天?

这个查询在 2000 行测试数据上花费了 6 秒,并且在该组数据上效果很好,所以我认为完整的数据集需要 15 个小时。

有任何想法吗?

以下是查询:

--Declare the looping variable
DECLARE @LoopVar char(10)


    DECLARE
     --Set private variables that will be used throughout
      @long DECIMAL,
      @lat DECIMAL,
      @phoneNumber char(10),
      @businessname varchar(64),
      @winner char(10)

    SET @LoopVar = (SELECT MIN(RecordID) FROM MyTable)

    WHILE @LoopVar is not null
    BEGIN

      --initialize the private variables (essentially this is a .ctor)
      SELECT 
        @long = null,
        @lat = null,
        @businessname = null,
        @phoneNumber = null,
        @winner = null

      -- load data from the row declared when setting @LoopVar  
      SELECT
        @long = longitude,
        @lat = latitude,
        @businessname = BusinessName,
        @phoneNumber = Phone
      FROM MyTable
      WHERE RecordID = @LoopVar

      --find the winning row with that data. The winning row means 
      SELECT top 1 @Winner = RecordID
      FROM MyTable
      WHERE @long = longitude
        AND @lat = latitude
        AND @businessname = BusinessName
        AND @phoneNumber = Phone
      ORDER BY
        CASE WHEN webAddress is not null THEN 1 ELSE 2 END,
        CASE WHEN caption1 is not null THEN 1 ELSE 2 END,
        CASE WHEN caption2 is not null THEN 1 ELSE 2 END,
        RecordID

      --delete any losers.
      DELETE FROM MyTable
      WHERE @long = longitude
        AND @lat = latitude
        AND @businessname = BusinessName
        AND @phoneNumber = Phone
        AND @winner != RecordID

      -- prep the next loop value to go ahead and perform the next duplicate query.
      SET @LoopVar = (SELECT MIN(RecordID) 
    FROM MyTable
    WHERE @LoopVar < RecordID)
    END
4

12 回答 12

30

不,如果您停止查询执行,sql server 将不会回滚它已经执行的删除操作。oracle 需要明确提交操作查询,否则数据会回滚,但 mssql 不需要。

使用 sql server 它不会回滚,除非您专门在事务的上下文中运行并且您回滚该事务,或者连接在未提交事务的情况下关闭。但我在您的上述查询中没有看到事务上下文。

您也可以尝试重新构建查询以使删除更有效率,但基本上如果您的盒子的规格不符合标准,那么您可能会被卡住等待它。

展望未来,您应该在表上创建一个唯一索引,以使自己不必再经历一次。

于 2008-10-02T12:36:19.457 回答
9

您的查询未包含在事务中,因此它不会回滚各个删除语句已经进行的更改。

我使用以下查询在我自己的 SQL Server 上专门对此进行了测试,即使我取消了查询,ApplicationLog 表也是空的:

declare @count int
select @count = 5
WHILE @count > 0
BEGIN
  print @count
  delete from applicationlog;
  waitfor time '20:00';
  select @count = @count -1
END

但是,您的查询可能需要数天或数周,甚至比 15 小时长得多。您估计每 6 秒可以处理 2000 条记录是错误的,因为您的 while 循环中的每次迭代将花费 1700 万行比 2000 行更长的时间。因此,除非查询 2000 行所需的时间明显少于一秒,否则所有 1700 万行都需要几天时间。

您应该提出一个关于如何有效删除重复行的新问题。

于 2008-10-02T12:32:08.670 回答
2

如果您没有对事务做任何明确的操作,那么连接将处于自动提交事务模式。在这种模式下,每个 SQL 语句都被视为一个事务。

问题是这是否意味着各个 SQL 语句是事务,因此在执行过程中会被提交,或者外部 WHILE 循环是否算作事务。

在MSDN上的 WHILE 构造的描述中似乎没有对此进行任何讨论。但是,由于 WHILE 语句不能直接修改数据库,因此它启动自动提交事务似乎是合乎逻辑的。

于 2008-10-02T12:40:10.587 回答
2

隐式事务

如果没有设置“隐式事务”,则循环中的每次迭代都会提交更改。

任何 SQL Server 都可以设置为“隐式事务”。这是一个数据库设置(默认为关闭)。您还可以在 Management Studio 内的特定查询的属性中包含隐式事务(在查询窗格中单击鼠标右键>选项)、客户端中的默认设置或 SET 语句。

SET IMPLICIT_TRANSACTIONS ON;

无论哪种方式,如果是这种情况,您仍然需要执行显式 COMMIT/ROLLBACK,而不管查询执行是否中断。


隐式交易参考:

http://msdn.microsoft.com/en-us/library/ms188317.aspx

http://msdn.microsoft.com/en-us/library/ms190230.aspx

于 2008-10-03T04:10:17.543 回答
1

我继承了一个系统,该系统的逻辑类似于您在 SQL 中实现的逻辑。在我们的例子中,我们试图使用具有相似名称/地址等的模糊匹配将行链接在一起,并且该逻辑纯粹在 SQL 中完成。在我继承它的时候,我们的表中有大约 300,000 行,根据时间,我们计算出匹配所有行需要 A YEAR。

作为一个实验,看看我可以在 SQL 之外执行多快,我编写了一个程序将 db 表转储到平面文件中,将平面文件读入 C++ 程序,构建我自己的索引,并在那里执行模糊逻辑,然后将平面文件重新导入数据库。在 SQL 中花费一年的时间在 C++ 应用程序中花费了大约 30 秒。

所以,我的建议是,甚至不要尝试你在 SQL 中所做的事情。导出、处理、重新导入。

于 2008-10-02T12:44:36.270 回答
1

到目前为止已执行的 DELETES 将不会回滚。


作为相关代码的原作者,并且已经发出警告,性能将取决于索引,我将提出以下项目来加快速度。

RecordId 最好是 PRIMARY KEY。我不是说身份,我是说主键。使用 sp_help 确认这一点

在评估这个查询时应该使用一些索引。找出这四列中哪一列的重复次数和索引最少...

SELECT *
FROM MyTable
WHERE @long = longitude
  AND @lat = latitude
  AND @businessname = BusinessName
  AND @phoneNumber = Phone

添加此索引之前和之后,检查查询计划以查看是否添加了索引扫描。

于 2008-10-02T13:31:44.223 回答
0

作为一个循环,即使使用适当的索引,您的查询也很难很好地扩展。根据您之前关于此问题的建议,应将查询重写为单个语句。

如果您没有在事务中显式运行它,它只会回滚正在执行的语句。

于 2008-10-02T12:37:36.723 回答
0

我认为如果使用游标使用单遍算法重写此查询,效率会更高。您可以按经度、纬度、BusinessName 和@phoneNumber 对光标表进行排序。您将逐行逐行浏览。如果一行与前一行具有相同的经度、纬度、商家名称和电话号码,则将其删除。

于 2008-10-02T13:12:05.283 回答
0

我认为你需要认真考虑你的方法论。您需要开始思考集合(尽管为了性能,您可能需要批处理,但不是针对 1700 万条记录表逐行处理。)

首先,您的所有记录都有重复项吗?我怀疑不是,所以你要做的第一件事就是将你的处理限制在那些有重复的记录上。由于这是一个大表,您可能需要随着时间的推移分批删除,具体取决于正在进行的其他处理,您首先将要处理的记录拉入自己的表中,然后编制索引。如果您能够同时执行所有操作而无需停止它,您也可以使用临时表,否则在数据库中创建一个表并在最后删除。

类似的东西(注意我没有写创建索引语句,我想你可以自己查一下):

SELECT min(m.RecordID), m.longitude, m.latitude, m.businessname, m.phone  
     into  #RecordsToKeep    
FROM MyTable   m
join 
(select longitude, latitude, businessname, phone
from MyTable
group by longitude, latitude, businessname, phone
having count(*) >1) a 
on a.longitude = m.longitude and a.latitude = m.latitude and
a.businessname = b.businessname and a.phone = b.phone 
group by  m.longitude, m.latitude, m.businessname, m.phone   
ORDER BY CASE WHEN m.webAddress is not null THEN 1 ELSE 2 END,        
    CASE WHEN m.caption1 is not null THEN 1 ELSE 2 END,        
    CASE WHEN m.caption2 is not null THEN 1 ELSE 2 END



while (select count(*) from #RecordsToKeep) > 0
begin
select top 1000 * 
into #Batch
from #RecordsToKeep

Delete m
from mytable m
join #Batch b 
        on b.longitude = m.longitude and b.latitude = m.latitude and
        b.businessname = b.businessname and b.phone = b.phone 
where r.recordid <> b.recordID

Delete r
from  #RecordsToKeep r
join #Batch b on r.recordid = b.recordid

end

Delete m
from mytable m
join #RecordsToKeep r 
        on r.longitude = m.longitude and r.latitude = m.latitude and
        r.businessname = b.businessname and r.phone = b.phone 
where r.recordid <> m.recordID
于 2009-07-24T13:48:19.327 回答
0

还可以尝试考虑另一种删除重复行的方法:

delete t1 from table1 as t1 where exists (
    select * from table1 as t2 where
        t1.column1=t2.column1 and
        t1.column2=t2.column2 and
        t1.column3=t2.column3 and
        --add other colums if any
        t1.id>t2.id
)

我想你的表中有一个整数 id 列。

于 2011-01-10T11:46:22.833 回答
0

如果您的机器没有非常先进的硬件,那么 sql server 可能需要很长时间才能完成该命令。我不确定这个操作是如何在后台执行的,但根据我的经验,这可以通过将记录从数据库中取出并放入内存中以用于使用具有删除重复规则的树结构的程序来更有效地完成用于插入。尝试使用 ODBC 将整个表以块的形式(例如一次 10000 行)读入 C++ 程序。一旦在 C++ 程序中使用和 std::map ,其中 key 是唯一键,而 struct 是一个将其余数据保存在变量中的结构。循环所有记录并插入到地图中。地图插入功能将处理删除重复项。由于在地图内搜索是 lg(n) 时间,因此查找重复项的时间远少于使用 while 循环的时间。然后,您可以删除整个表并将元组从映射中添加回数据库,方法是形成插入查询并通过 odbc 执行它们或构建文本文件脚本并在管理工作室中运行它。

于 2013-10-24T21:04:10.180 回答
-1

我很确定这是否定的。否则交易的意义何在?

于 2008-10-02T12:23:29.643 回答