27

重複を削除するために 1700 万件のレコードをループするために使用されるクエリは、 現在約16 時間実行されています。削除ステートメントを終了するかどうか、またはこの実行中に削除されているかどうか、クエリが現在停止されているかどうかを知りたいと思いました。クエリ? 実際、停止した場合、削除またはロールバックが終了しますか?

私がするとき、私はそれを発見しました

 select count(*) from myTable

(このクエリの実行中に) 返される行数は、最初の行数よりも約 5 行少なくなります。明らかに、サーバー リソースは非常に貧弱です。つまり、このプロセスは 5 つの重複 (実際には数千あるのに) を見つけるのに 16 時間かかり、これは何日も実行される可能性があるということですか?

このクエリは、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サーバーでは、特にトランザクションのコンテキストで実行していてそのトランザクションをロールバックしない限り、ロールバックしません。または、トランザクションがコミットされずに接続が閉じます。しかし、上記のクエリにはトランザクション コンテキストが表示されません。

削除をもう少し効率的にするためにクエリを再構築することもできますが、基本的に、ボックスの仕様が不十分な場合は、それを待つことになるかもしれません.

今後は、テーブルに一意のインデックスを作成して、これを再度実行する必要がないようにする必要があります。

于 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 ループの各反復は、2000 行よりも 1700 万行の方がはるかに長くかかるからです。したがって、クエリが 2000 行で 1 秒を大幅に下回る場合を除き、1,700 万行すべてを処理するには数日かかります。

重複行を効率的に削除する方法について、新たな質問をする必要があります。

于 2008-10-02T12:32:08.670 に答える
2

暗黙のトランザクション

「暗黙のトランザクション」が設定されていない場合、ループ内の各反復で変更がコミットされます。

任意の SQL Server を「暗黙のトランザクション」に設定することができます。これはデータベース設定です (デフォルトでは OFF です)。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 に答える
2

トランザクションについて明示的に何もしない場合、接続は自動コミット トランザクションモードになります。このモードでは、すべての SQL ステートメントがトランザクションと見なされます。

問題は、これが個々の SQL ステートメントがトランザクションであり、したがってコミットされていることを意味するのか、それとも外側の WHILE ループがトランザクションとしてカウントされるのかということです。

MSDNの WHILE コンストラクトの説明では、これについての議論はないようです。ただし、WHILE ステートメントはデータベースを直接変更できないため、自動コミット トランザクションを開始しないのは理にかなっているように思われます。

于 2008-10-02T12:40:10.587 に答える
1

この時点までに実行された DELETES はロールバックされません。


問題のコードの元の作成者として、パフォーマンスがインデックスに依存するという警告を発行したので、これを高速化するために次の項目を提案します。

RecordId は PRIMARY KEY にすることをお勧めします。IDENTITY という意味ではなく、PRIMARY KEY という意味です。sp_help を使用してこれを確認します

このクエリを評価するには、いくつかのインデックスを使用する必要があります。これらの 4 つの列のどれが最小の繰り返しとインデックスを持っているかを調べます...

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

このインデックスを追加する前後に、クエリ プランをチェックして、インデックス スキャンが追加されているかどうかを確認します。

于 2008-10-02T13:31:44.223 に答える
1

私は、あなたのようなロジックを SQL で実装したシステムを継承しました。私たちの場合、類似した名前/アドレスなどを持つあいまい一致を使用して行をリンクしようとしていましたが、そのロジックは純粋に SQL で行われました。私がそれを継承した時点で、テーブルには約 300,000 行があり、タイミングによると、それらすべてを一致させるには 1 年かかると計算されました。

SQL の外でどれだけ速く実行できるかを確認するための実験として、db テーブルをフラット ファイルにダンプし、フラット ファイルを C++ プログラムに読み込み、独自のインデックスを作成し、そこでファジー ロジックを実行するプログラムを作成しました。次に、フラット ファイルをデータベースに再インポートします。SQL で 1 年かかったものは、C++ アプリでは約 30 秒かかりました。

したがって、私のアドバイスは、SQL で行っていることを試してはならないということです。輸出、加工、再輸入。

于 2008-10-02T12:44:36.270 に答える
0

方法論を真剣に検討する必要があると思います。セットで考え始める必要があります (ただし、パフォーマンスのためにバッチ処理が必要になる場合がありますが、1,700 万のレコード テーブルに対して行ごとに処理する必要はありません)。

まず、すべてのレコードに重複がありますか? 私はそうは思わないので、最初にやりたいことは、処理を重複のあるレコードだけに制限することです。これは大きなテーブルであり、他の処理が行われていることに応じて、時間をかけてバッチで削除を行う必要がある場合があるため、最初に処理するレコードを独自のテーブルにプルし、次にインデックスを付けます。データベースにテーブルを作成して最後に削除するか、停止することなくこれをすべて同時に実行できる場合は、一時テーブルを使用することもできます。

のようなもの (私は create index ステートメントを書いていないことに注意してください。自分で調べることができると思います):

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 を介して実行するか、テキスト ファイル スクリプトを作成して Management Studio で実行することにより、マップからタプルをデータベースに追加し直すことができます。

于 2013-10-24T21:04:10.180 に答える
0

ループとして、適切なインデックスを使用しても、クエリは適切にスケーリングするのに苦労します。これに関する前の質問の提案に従って、クエリを単一のステートメントに書き直す必要があります。

トランザクション内で明示的に実行していない場合、実行中のステートメントのみがロールバックされます。

于 2008-10-02T12:37:36.723 に答える
0

このクエリは、カーソルを使用したシングルパス アルゴリズムを使用して書き直せば、はるかに効率的だと思います。経度、緯度、BusinessName および @phoneNumber でカーソル テーブルを並べ替えます。行を 1 つずつ移動します。行の経度、緯度、会社名、電話番号が前の行と同じである場合は、それを削除します。

于 2008-10-02T13:12:05.283 に答える
-1

否定派なのは確かです。そうでなければ、トランザクションのポイントは何でしょうか?

于 2008-10-02T12:23:29.643 に答える