長時間実行されているクエリを停止した場合、ロールバックしますか?
-
03-07-2019 - |
質問
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
解決
いいえ、クエリの実行を停止しても、SQLサーバーは既に実行した削除をロールバックしません。オラクルは、アクションクエリの明示的なコミットを必要とするか、データがロールバックされますが、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時間をはるかに超える可能性があります。 whileループの各反復は、1700万行では2000行よりもはるかに長くかかるため、6秒ごとに2000レコードを処理できるという見積もりは間違っています。したがって、クエリが2000行で1秒よりも大幅に短い時間をかけない限り、1700万行すべてで数日かかります。
重複した行を効率的に削除する方法について、新しい質問をする必要があります。
トランザクションについて何も明示的に行わない場合、接続はトランザクションの自動コミットモード。このモードでは、すべてのSQLステートメントはトランザクションと見なされます。
問題は、これが個々のSQLステートメントがトランザクションであり、したがって進行中にコミットされていることを意味するのか、外側のWHILEループがトランザクションとしてカウントされるのかということです。
MSDN 。ただし、WHILEステートメントはデータベースを直接変更できないため、自動コミットトランザクションを実行しないというのは論理的に思えます。
暗黙的なトランザクション
「暗黙的なトランザクション」が設定されていない場合、ループの各反復で変更がコミットされました。
任意のSQL Serverに「暗黙的なトランザクション」を設定することは可能です。これはデータベース設定です(デフォルトではオフです)。 Management Studio内の特定のクエリのプロパティ(クエリペインの右クリック&gt;オプション)、クライアントのデフォルト設定、またはSETステートメントで暗黙的なトランザクションを設定することもできます。
SET IMPLICIT_TRANSACTIONS ON;
どちらの場合でも、この場合、クエリ実行の中断に関係なく、明示的なCOMMIT / ROLLBACKを実行する必要があります。
暗黙的なトランザクションの参照:
私は、SQLで実装されているようなロジックを持つシステムを継承しました。私たちの場合、類似した名前/アドレスなどを持つファジーマッチングを使用して行をリンクしようとしており、そのロジックは純粋にSQLで実行されていました。継承した時点で、テーブルには約300,000行あり、タイミングに従って、すべてを一致させるには1年かかると計算しました。
SQLの外でどれだけ速くできるかを確認する実験として、dbテーブルをフラットファイルにダンプし、フラットファイルをC ++プログラムに読み込み、独自のインデックスを作成し、ファジーを実行するプログラムを作成しましたそこでロジックを作成し、フラットファイルをデータベースに再インポートします。 SQLで1年かかったことは、C ++アプリで約30秒かかりました。
だから、私のアドバイスは、SQLでやっていることを試してはいけないということです。エクスポート、処理、再インポート。
この時点までに実行されたDELETEはロールバックされません。
問題のコードの元の作成者、およびパフォーマンスはインデックスに依存するという警告を発行したので、これを高速化するために次の項目を提案します。
RecordIdはプライマリキーの方が適切です。 IDENTITYを意味するのではなく、PRIMARY KEYを意味します。 sp_helpを使用してこれを確認します
このクエリの評価には、いくつかのインデックスを使用する必要があります。これらの4つの列のうち、繰り返し回数とインデックスが最も少ない列を特定します...
SELECT *
FROM MyTable
WHERE @long = longitude
AND @lat = latitude
AND @businessname = BusinessName
AND @phoneNumber = Phone
このインデックスを追加する前と後、クエリプランをチェックして、インデックススキャンが追加されているかどうかを確認します。
ループとして、適切なインデックスを使用しても、クエリのスケーリングはうまくいきません。 前の質問の提案に従って、クエリを単一のステートメントに書き換える必要がありますa>これ。
トランザクション内で明示的に実行していない場合、実行中のステートメントのみがロールバックされます。
このクエリは、カーソルを使用したシングルパスアルゴリズムを使用して書き換えられた場合、はるかに効率的になると思います。カーソルテーブルは、経度、緯度、ビジネス名、@ 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
また、重複する行を削除する別の方法を考えてみてください:
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列があると仮定します。
マシンに非常に高度なハードウェアが搭載されていない場合、SQLサーバーがそのコマンドを完了するのに非常に長い時間がかかる場合があります。この操作が内部でどのように実行されるかはわかりませんが、私の経験に基づいて、重複ルールを削除するツリー構造を使用するプログラムのデータベースからレコードをメモリに取り込むことにより、これをより効率的に行うことができます挿入用。 ODBCを使用して、Chuncksのテーブル全体(一度に10000行)をC ++プログラムに読み込んでみてください。 C ++プログラムでは、std :: mapを使用します。keyは一意のキーであり、structは変数に残りのデータを保持する構造体です。すべてのレコードをループし、マップへの挿入を実行します。マップ挿入関数は、重複の削除を処理します。マップ内での検索はlg(n)の時間なので、whileループを使用するよりも重複を見つける時間ははるかに短くなります。その後、挿入クエリを作成してodbcで実行するか、テキストファイルスクリプトを作成して管理スタジオで実行することにより、テーブル全体を削除し、マップからデータベースにタプルを追加し直すことができます。
これは否定的だと確信しています。そうでなければ、トランザクションのポイントは何になりますか?