6

cats42,795,120 行のテーブルがあります。

どうやらこれは行数が多いようです。だから私がするとき:

/* owner_cats is a many-to-many join table */
DELETE FROM cats
WHERE cats.id_cat IN (
SELECT owner_cats.id_cat FROM owner_cats
WHERE owner_cats.id_owner = 1)

クエリがタイムアウトします:(

(編集: 値を増やす必要があり CommandTimeout ます。デフォルトはわずか 30 秒です)

TRUNCATE TABLE cats他の飼い主から猫を吹き飛ばしたくないので使えません。

「リカバリ モデル」を「シンプル」に設定して SQL Server 2005 を使用しています。

だから、私はこのようなことをすることを考えました(ところでアプリケーションからこのSQLを実行します):

DELETE TOP (25) PERCENT FROM cats
WHERE cats.id_cat IN (
SELECT owner_cats.id_cat FROM owner_cats
WHERE owner_cats.id_owner = 1)

DELETE TOP(50) PERCENT FROM cats
WHERE cats.id_cat IN (
SELECT owner_cats.id_cat FROM owner_cats
WHERE owner_cats.id_owner = 1)

DELETE FROM cats
WHERE cats.id_cat IN (
SELECT owner_cats.id_cat FROM owner_cats
WHERE owner_cats.id_owner = 1)

私の質問はDELETE、SQL Server 2005 で使用できる行数のしきい値はどれくらいですか?

または、私のアプローチが最適でない場合は、より良いアプローチを提案してください。ありがとう。

この投稿は私を十分に助けませんでした:

編集 (2010 年 8 月 6 日):

さて、上記のリンクをもう一度読んだ後、これらのテーブルにインデックスがないことに気付きました。また、以下のコメントですでにその問題を指摘している人もいます。これは架空のスキーマでid_catあり、PK ではないことに注意してください。実際のスキーマでは、一意のフィールドではないためです。

インデックスを配置します:

  1. cats.id_cat
  2. owner_cats.id_cat
  3. owner_cats.id_owner

私はまだこのデータ ウェアハウジングのコツをつかんでいると思います。明らかに、すべてのJOINフィールドにインデックスが必要ですよね?

ただし、このバッチ ロード プロセスを実行するには数時間かかります。私はすでにそれをSqlBulkCopy(一度に42ミルではなく、チャンクで)やっています。いくつかのインデックスと PK があります。次の投稿を読んで、インデックスが一括コピーでも遅くなるという私の理論を確認しました。

そのためDROP、コピーの前にインデックスに移動し、コピーが完了したら再度インデックスを作成しますCREATE

ロード時間が長いため、これらの提案をテストするにはしばらく時間がかかります。結果とともにまた報告します。

更新 (2010 年 8 月 7 日):

トムは次のように提案しました。

DELETE
FROM cats c
WHERE EXISTS (SELECT 1
FROM owner_cats o
WHERE o.id_cat = c.id_cat
AND o.id_owner = 1)

それでもインデックスがない場合、4,200 万行の場合、上記の方法では 22:08 に対して 13:21 分: 秒かかりました。ただし、1,300 万行の場合、私の古い方法では 2:10 に対して 2:13 かかりました。これは素晴らしいアイデアですが、それでもインデックスを使用する必要があります。

更新 (2010 年 8 月 8 日):

何かがひどく間違っています!インデックスをオンにすると、上記の最初の削除クエリは 1:9 時間:分(はい、1 時間です!)対 22:08 分:秒および 13:21 分:秒対 2:10 分:秒で 4,200 万行とそれぞれ13ミリ行。インデックスを使って Tom のクエリを試してみますが、これは間違った方向に向かっています。助けてください。

更新 (2010 年 8 月 9 日):

Tom の削除には、42 万行の場合は 1:06 時間:分、1,300 万行の場合は 10:50 分:秒かかりましたが、それぞれ 13:21 分:秒と 2:13 分:秒でした。 桁違いにインデックスを使用すると、データベースの削除に時間がかかります! 私のデータベースの .mdf と .ldf が、最初の (42 ミル) の削除中に 3.5 GB から 40.6 GB に増加した理由はわかっていると思います。 私は何を間違っていますか?

更新 (2010 年 8 月 10 日):

他のオプションがないため、私は、うまくいかない解決策であると感じているものを思いつきました(できれば一時的なものです)

  1. データベース接続のタイムアウトを 1 時間に増やします (CommandTimeout=60000;デフォルトは 30 秒) 。
  2. トムのクエリを使用:DELETE FROM WHERE EXISTS (SELECT 1 ...)少し高速に実行されたため
  3. DROP削除ステートメントを実行する前のすべてのインデックスと PK (???)
  4. 実行DELETEステートメント
  5. CREATEすべてのインデックスと PK

クレイジーに思えますが、少なくとも最初のロードを使用してTRUNCATE最初からやり直すよりも高速です. 4200万行。(注: ロード プロセスが例外をスローした場合、最初からやり直しますが、以前の を吹き飛ばしたくないので、テーブルを使用したくないため、 を使用しようとしています。)owner_idowner_idowner_idowner_idTRUNCATEowner_catsDELETE

もう助けていただければ幸いです:)

4

9 に答える 9

6

サブクエリを使用せず、代わりに結合を使用しましたか?

DELETE cats 
FROM
 cats c
 INNER JOIN owner_cats oc
 on c.id_cat = oc.id_cat
WHERE
   id_owner =1

そして、もしあなたが別の結合ヒントを試したことがあるなら、例えば

DELETE cats 
FROM
 cats c
 INNER HASH JOIN owner_cats oc
 on c.id_cat = oc.id_cat
WHERE
   id_owner =1
于 2010-08-10T21:06:29.097 に答える
6

実際のしきい値はありません。これは、接続でのコマンド タイムアウトの設定によって異なります。

これらの行をすべて削除するのにかかる時間は、次の条件に左右されることに注意してください。

  • 対象の行を見つけるのにかかる時間
  • トランザクション ログにトランザクションを記録するのにかかる時間
  • 対象のインデックス エントリを削除するのにかかる時間
  • 対象の実際の行を削除するのにかかる時間
  • 他のプロセスがテーブルの使用を停止するのを待機するのにかかる時間。これにより、この場合は排他テーブル ロックとなる可能性が最も高いものを取得できます。

最後のポイントは、多くの場合、最も重要です。別のクエリ ウィンドウで sp_who2 コマンドを実行して、ロックの競合が発生していないことを確認し、コマンドの実行を妨げます。

不適切に構成された SQL Server は、このタイプのクエリでうまく機能しません。小さすぎるトランザクション ログや、データ ファイルと同じディスクを共有するトランザクション ログは、大きな行を操作するときにパフォーマンスに深刻な影響を与えることがよくあります。

解決策については、すべてのことと同様に、状況によって異なります。これはあなたが頻繁に行うつもりですか?残っている行の数によっては、テーブルを別の名前で再構築し、名前を変更してその制約をすべてトランザクション内で再作成するのが最も速い方法かもしれません。これがその場限りのものである場合は、ADO CommandTimeout が十分に高く設定されていることを確認してください。そうすれば、この大きな削除のコストを負担できます。

于 2010-08-06T23:01:22.227 に答える
6

削除によってテーブルから「かなりの数」の行が削除される場合、これは DELETE の代わりになる可能性があります。レコードを別の場所に保持し、元のテーブルを切り捨て、「キーパー」を元に戻します。何かのようなもの:

SELECT *
INTO #cats_to_keep
FROM cats
WHERE cats.id_cat NOT IN (    -- note the NOT
SELECT owner_cats.id_cat FROM owner_cats
WHERE owner_cats.id_owner = 1)

TRUNCATE TABLE cats

INSERT INTO cats
SELECT * FROM #cats_to_keep
于 2010-08-06T23:29:30.840 に答える
4

EXISTSではなくを使用するとIN、パフォーマンスが大幅に向上します。これを試して:

DELETE
  FROM cats c
 WHERE EXISTS (SELECT 1
                 FROM owner_cats o
                WHERE o.id_cat = c.id_cat
                  AND o.id_owner = 1)
于 2010-08-07T10:06:18.577 に答える
3

試してみる価値があるかもしれませんMERGE例えば

MERGE INTO cats 
   USING owner_cats
      ON cats.id_cat = owner_cats.id_cat
         AND owner_cats.id_owner = 1
WHEN MATCHED THEN DELETE;
于 2011-09-30T07:42:21.693 に答える
3

他の人が述べたように、4200 万行を削除すると、データベースはデータベースに対して 4200 万の削除をログに記録する必要があります。したがって、トランザクション ログは大幅に増加する必要があります。あなたが試みるかもしれないことは、削除をチャンクに分割することです。次のクエリでは、NTile ランキング関数を使用して、行を 100 個のバケットに分割します。それが遅すぎる場合は、各削除が小さくなるようにバケットの数を拡張できます。owner_cats.id_ownerowner_cats.id_catsおよびcats.id_cat(主キーと数値であると仮定しました)にインデックスがあると、非常に役立ちます。

Declare @Cats Cursor
Declare @CatId int  --assuming an integer PK here
Declare @Start int
Declare @End int
Declare @GroupCount int

Set @GroupCount = 100

Set @Cats = Cursor Fast_Forward For
    With CatHerd As
        (
        Select cats.id_cat
            , NTile(@GroupCount) Over ( Order By cats.id_cat ) As Grp
        From cats
            Join owner_cats
                On owner_cats.id_cat = cats.id_cat
        Where owner_cats.id_owner = 1
        )
        Select Grp, Min(id_cat) As MinCat, Max(id_cat) As MaxCat
        From CatHerd
        Group By Grp
Open @Cats
Fetch Next From @Cats Into @CatId, @Start, @End

While @@Fetch_Status = 0
Begin
    Delete cats
    Where id_cat Between @Start And @End

    Fetch Next From @Cats Into @CatId, @Start, @End
End 

Close @Cats
Deallocate @Cats

上記のアプローチの注目すべき点は、トランザクションに対応していないことです。したがって、40 番目のチャンクで失敗した場合、行の 40% が削除され、残りの 60% はまだ存在します。

于 2010-08-06T23:15:53.630 に答える
3

そのようなしきい値はありません.十分なトランザクションログスペースがあれば、任意のテーブルからすべての行を削除できます.これは、クエリが失敗する可能性が最も高い場所です. DELETE TOP (n) PERCENT FROM cats WHERE ... からいくつかの結果を取得している場合は、次のようにループでラップできます。

SELECT 1
WHILE @@ROWCOUNT <> 0
BEGIN
 DELETE TOP (somevalue) PERCENT FROM cats
 WHERE cats.id_cat IN (
 SELECT owner_cats.id_cat FROM owner_cats
 WHERE owner_cats.id_owner = 1)
END
于 2010-08-06T23:02:12.287 に答える
1

<編集> (2011 年 9 月 28 日)
私の回答は基本的にトーマスのソリューション (10 年 8 月 6 日) と同じように実行されます。彼が実際のCURSORを使用しているため、回答を投稿したときにそれを見逃したので、関連するレコードの数のために「悪い」と思いました。しかし、今彼の答えを読み直すと、彼がカーソルを使用する方法は実際には「良い」ことに気づきました。非常に賢い。私は彼の答えに賛成票を投じたばかりで、将来的には彼のアプローチを使用するでしょう。理由が分からない場合は、もう一度見てください。それでも表示されない場合は、この回答にコメントを投稿してください。詳細を説明するために戻ってきます。誰かが実際の CURSOR の使用を拒否する DBA を持っている可能性があるため、回答を残すことにしました。:-)
</編集>

この質問は 1 年前のものですが、最近同様の状況が発生しました。私は、別のテーブルへの結合を使用して大きなテーブルを「一括」更新しようとしていましたが、これもかなり大きなものでした。問題は、結合によって非常に多くの「結合されたレコード」が生成され、処理に時間がかかりすぎて、競合の問題が発生する可能性があることでした。これは1回限りの更新だったので、次の「ハック」を思いつきました。更新するテーブルを通過する WHILE LOOP を作成し、一度に更新する 50,000 レコードを選択しました。それは次のように見えました:

DECLARE @RecId bigint
DECLARE @NumRecs bigint
SET @NumRecs = (SELECT MAX(Id) FROM [TableToUpdate])
SET @RecId = 1
WHILE @RecId < @NumRecs
BEGIN
    UPDATE [TableToUpdate]
    SET UpdatedOn = GETDATE(),
        SomeColumn = t2.[ColumnInTable2]
    FROM    [TableToUpdate] t
    INNER JOIN [Table2] t2 ON t2.Name = t.DBAName 
        AND ISNULL(t.PhoneNumber,'') = t2.PhoneNumber 
        AND ISNULL(t.FaxNumber, '') = t2.FaxNumber
    LEFT JOIN [Address] d ON d.AddressId = t.DbaAddressId 
        AND ISNULL(d.Address1,'') = t2.DBAAddress1
        AND ISNULL(d.[State],'') = t2.DBAState
        AND ISNULL(d.PostalCode,'') = t2.DBAPostalCode
    WHERE t.Id BETWEEN @RecId AND (@RecId + 49999)
    SET @RecId = @RecId + 50000
END

派手なことは何もありませんが、それは仕事を成し遂げました。一度に 50,000 レコードしか処理していなかったため、作成されたロックは短命でした。また、オプティマイザは、テーブル全体を処理する必要がないことを認識したため、実行計画をより適切に選択できました。

<編集> (2011 年 9 月 28 日)
ここで何度も言及され、「適切な」レコードを別のテーブルにコピーすることに関して Web のいたるところに投稿されている提案には、大きな警告があります。 TRUNCATE (または DROP と reCREATE、または DROP と名前の変更) を行ってから、テーブルを再作成します。

テーブルが PK-FK 関係 (または他の CONSTRAINT) の PK テーブルである場合、これを行うことはできません。確かに、関係を削除し、クリーンアップを行い、関係を再確立することはできますが、FK テーブルもクリーンアップする必要があります。関係を再確立する前にそれを行うことができます。これは、より多くの「ダウンタイム」を意味します。または、作成時に制約を適用せず、後でクリーンアップすることを選択することもできます。PK テーブルをクリーンアップする前に、FK テーブルをクリーンアップすることもできると思います。要するに、何らかの方法で FK テーブルを明示的にクリーンアップする必要があるということです。

私の答えは、ハイブリッド SET ベース/準 CURSOR プロセスです。この方法のもう 1 つの利点は、PK-FK 関係が CASCADE DELETES に設定されている場合、サーバーが処理してくれるため、上記のクリーンアップを行う必要がないことです。会社/DBA がカスケード削除を思いとどまらせる場合は、このプロセスの実行中にのみ有効にし、終了したら無効にするように依頼できます。クリーンアップを実行するアカウントの権限レベルに応じて、カスケード削除を有効/無効にする ALTER ステートメントを SQL ステートメントの最初と最後に追加できます。 </編集>

于 2011-08-04T22:49:21.120 に答える
0

別の質問に対するビル・カーウィンの答えは、私の状況にも当てはまります。

DELETEそのテーブルの行の大部分を削除することを目的としている場合、人々がよく行うことの1つは、保持したい行だけを複製テーブルにコピーしてから、元のテーブルをより迅速に使用DROP TABLEまたは消去することです。 TRUNCATE。」

この回答のマットは、次のように述べています。

「オフラインで大きな%を削除する場合は、保持するデータを使用して新しいテーブルを作成し、古いテーブルを削除して、名前を変更するのが理にかなっている場合があります。」

この回答のammoQ(同じ質問から)は次のことを推奨しています(言い換え):

  • 大量の行を削除するときにテーブルロックを発行する
  • 外部キー列にインデックスを付ける
于 2010-08-11T23:18:39.537 に答える