ループスルーに使用されるクエリ 重複を削除するための1,700万件のレコード 約今実行されています 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
いいえ、クエリの実行を停止しても、SQLサーバーは既に実行した削除をロールバックしません。 Oracleでは、アクションクエリを明示的にコミットする必要があります。そうしないと、データはmssqlではなくロールバックされます。
sQLサーバーでは、トランザクションのコンテキストで具体的に実行してそのトランザクションをロールバックしない限り、ロールバックされません。または、トランザクションがコミットされずに接続が閉じます。しかし、上記のクエリにはトランザクションコンテキストがありません。
クエリを再構築して削除を少し効率的にすることもできますが、基本的にボックスの仕様が十分でない場合は、待ち状態に陥る可能性があります。
今後は、テーブルに一意のインデックスを作成して、これを繰り返さないようにする必要があります。
クエリはトランザクションにラップされないため、個々の削除ステートメントによってすでに行われた変更はロールバックされません。
次のクエリを使用して自分の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行で1秒より大幅に短い場合を除いて、1700万行すべてで数日かかります。
重複する行を効率的に削除する方法について、新しい質問をする必要があります。
トランザクションについて明示的に何もしない場合、接続は autocommit Transactions モードになります。このモードでは、すべてのSQLステートメントがトランザクションと見なされます。
問題は、これが個々のSQLステートメントがトランザクションであり、したがってユーザーが進むにつれてコミットされることを意味するのか、または外側のWHILEループがトランザクションとしてカウントされるのかということです。
[〜#〜] msdn [〜#〜] のWHILE構文の説明では、これについての説明はないようです。ただし、WHILEステートメントはデータベースを直接変更できないため、自動コミットトランザクションを開始しないことは論理的に思えます。
「暗黙のトランザクション」が設定されていない場合、ループの各反復で変更がコミットされました。
SQL Serverに「暗黙的なトランザクション」を設定することが可能です。これはデータベース設定です(デフォルトではオフです)。 Management Studio内の特定のクエリのプロパティ(クエリペインで右クリック>オプション)、クライアントのデフォルト設定、またはSETステートメントで暗黙的なトランザクションを持つこともできます。
SET IMPLICIT_TRANSACTIONS ON;
どちらの方法でも、これが当てはまった場合でも、クエリの実行の中断に関係なく、明示的なCOMMIT/ROLLBACKを実行する必要があります。
暗黙のトランザクション参照:
私はあなたのようなロジックをSQLに実装したシステムを継承しました。私たちのケースでは、類似した名前/アドレスなどを持つファジーマッチングを使用して行をリンクしようとしましたが、そのロジックは純粋にSQLで行われました。私がそれを継承したとき、テーブルには約300,000行があり、タイミングに従って、それらすべてに一致するのに1年かかると計算しました。
SQLの外でどれだけ速く実行できるかを確認するための実験として、dbテーブルをフラットファイルにダンプし、フラットファイルをC++プログラムに読み込み、独自のインデックスを作成し、ファジーロジックを実行するプログラムを作成しました。次に、フラットファイルをデータベースに再インポートします。 SQLで1年かかるのに、C++アプリでは約30秒かかりました。
したがって、私のアドバイスは、SQLで行っていることを試してはいけないということです。エクスポート、処理、再インポート。
この時点までに実行されたDELETEはロールバックされません。
問題のコード の最初の作成者であり、パフォーマンスはインデックスに依存するという警告を出したので、これを高速化するために次の項目を提案します。
RecordIdはPRIMARY KEYの方が適切です。 IDENTITYではなく、PRIMARY KEYです。 sp_helpを使用してこれを確認します
このクエリの評価には、いくつかのインデックスを使用する必要があります。これらの4つの列のうち、繰り返しが最も少ないものを割り出し、インデックスを付けます...
SELECT *
FROM MyTable
WHERE @long = longitude
AND @lat = latitude
AND @businessname = BusinessName
AND @phoneNumber = Phone
このインデックスを追加する前と後に、クエリプランをチェックして、インデックススキャンが追加されているかどうかを確認してください。
マシンに非常に高度なハードウェアがない場合、SQLサーバーがそのコマンドを完了するのに非常に長い時間がかかることがあります。この操作が内部でどのように実行されるかはわかりませんが、私の経験に基づいて、重複ルールを削除したツリー構造を使用するプログラムのデータベースからレコードをメモリに取り込むことで、これをより効率的に行うことができます挿入用。 ODBCを使用して、チャンク内のテーブル全体(たとえば、一度に10000行)をC++プログラムに読み取ってみます。 C++プログラムに入ると、std :: mapを使用します。ここで、keyは一意のキーで、structは構造体で、残りのデータを変数に保持します。すべてのレコードをループして、マップへの挿入を実行します。マップ挿入関数は、重複の削除を処理します。マップ内の検索はlg(n)時間なので、whileループを使用するよりも、重複を見つけるのにはるかに短い時間です。次に、テーブル全体を削除し、挿入クエリを作成してそれらをodbc経由で実行するか、テキストファイルスクリプトを作成してManagement Studioで実行することにより、マップからタプルをデータベースに戻すことができます。
また、重複する行を削除する別の方法を考えてみてください。
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列があると思います。
このクエリは、カーソルを使用したシングルパスアルゴリズムを使用して書き換えた場合、はるかに効率的だと思います。カーソルテーブルを経度、緯度、ビジネス名、および@phoneNumberの順に並べます。行を1つずつ移動します。行の経度、緯度、会社名、電話番号が前の行と同じである場合は、削除します。
方法論を真剣に検討する必要があると思います。セットで考え始める必要があります(パフォーマンスのためにバッチ処理が必要になる場合がありますが、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
ループとして、適切なインデックスがあっても、クエリは適切にスケーリングするのに苦労します。これに関する 前の質問 の提案に従って、クエリを単一のステートメントに書き換える必要があります。
トランザクション内で明示的に実行していない場合は、実行中のステートメントのみがロールバックされます。