web-dev-qa-db-ja.com

MySQLで数百万の行を削除する

最近、作業中のサイトでバグを見つけて修正しました。これにより、テーブル内のデータの重複行が数百万になりますが、それらは行がなくても非常に大きくなります(まだ数百万)。これらの重複行を簡単に見つけることができ、単一の削除クエリを実行してそれらをすべて削除できます。問題は、この多数の行を1回のショットで削除しようとすると、テーブルが長時間ロックされることです。これは可能な限り避けたいと思います。 (テーブルをロックすることによって)サイトを削除することなく、これらの行を削除する唯一の方法は次のとおりです。

  1. ループ内で数千の小さな削除クエリを実行するスクリプトを記述します。これは、他のクエリがキューに入れて削除の合間に実行できるため、理論的にはロックされたテーブルの問題を回避します。ただし、それでもデータベースの負荷がかなり高くなり、実行に時間がかかります。
  2. テーブルの名前を変更し、既存のテーブルを再作成します(空になります)。次に、名前を変更したテーブルでクリーンアップを実行します。新しいテーブルの名前を変更し、古いテーブルに名前を付けて、名前を変更したテーブルに新しい行をマージします。これはかなり多くの手順を必要とする方法ですが、最小限の中断でジョブを完了する必要があります。ここで唯一注意が必要なのは、問題のテーブルがレポートテーブルであることです。そのため、名前が変更され、空のテーブルがその場所に配置されると、履歴レポートはすべて元に戻ります。さらに、保存されるデータの種類のため、マージプロセスは少し苦痛になる可能性があります。全体として、これは今のところ私の選択です。

他の誰かがこの問題を以前に経験したことがあるのか​​、もしそうなら、サイトを停止せずに、どうすればユーザーの中断を最小限に抑えて対処したのかと思いました。 2番目の方法、または別の同様の方法を選択した場合、夜遅くに実行するようにスケジュールを設定し、翌朝早くにマージを実行して、ユーザーに事前に知らせることができます。これはたいしたことではありません。クリーンアップを行うためのより良い、またはより簡単な方法について、誰かがアイデアを持っているかどうかを探しています。

67
Steven Surowiec
DELETE FROM `table`
WHERE (whatever criteria)
ORDER BY `id`
LIMIT 1000

洗浄、すすぎ、影響を受ける行がなくなるまで繰り返します。繰り返しの間に1〜3秒間スリープするスクリプトの場合があります。

132
chaos

また、テーブルにいくつかの制約を追加して、これが再び発生しないようにすることをお勧めします。 1ショットあたり1000の100万行では、スクリプトを1000回繰り返して完了する必要があります。スクリプトが3.6秒ごとに1回実行されると、1時間で完了します。心配ない。あなたのクライアントは気づかないでしょう。

8
duffymo

MySQLの25M +行のテーブルから1M +行を削除するユースケースがありました。バッチ削除のようなさまざまなアプローチを試みました(上記)。
最速の方法(必要なレコードを新しいテーブルにコピーする)がわかりました。

  1. IDのみを保持する一時テーブルを作成します。

CREATE TABLE id_temp_table(temp_id int);

  1. 削除する必要があるIDを挿入します。

id_temp_table(temp_id)select .....に挿入します.

  1. 新しいテーブルtable_newを作成

  2. Id_temp_tableにある不要な行なしで、テーブルからtable_newにすべてのレコードを挿入します

table_newに挿入.... table_id NOT IN(id_temp_tableからdistinct(temp_id)を選択);

  1. テーブルの名前を変更する

プロセス全体で約1時間かかりました。私の使用例では、100レコードのバッチの単純な削除には10分かかりました。

6
user1459144

以下は、1つずつ1,000,000レコードを削除します。

 for i in `seq 1 1000`; do 
     mysql  -e "select id from table_name where (condition) order by id desc limit 1000 " | sed 's;/|;;g' | awk '{if(NR>1)print "delete from table_name where id = ",$1,";" }' | mysql; 
 done

あなたはそれらを一緒にグループ化して、IN(id1、id2、.. idN)があまりにも多くの難しさを確信しているtable_nameを削除することができます

6
rich

同様の問題に直面しました。パーティションがなく、primary_key列にインデックスが1つしかない、サイズが約500 GBの非常に大きなテーブルがありました。マスターはマシンの塊、128コア、512ギガのRAMであり、複数のスレーブもありました。行の大規模な削除に取り組むためのいくつかの手法を試しました。私たちが見つけた最悪のものから最高のものまですべてここにあります

  1. 一度に1行をフェッチおよび削除します。これはあなたができる絶対的な最悪です。それで、私たちはこれを試しさえしませんでした。
  2. Primary_key列の制限クエリを使用してデータベースから最初の 'X'行をフェッチし、次にアプリケーションで削除する行IDを確認し、primary_key idのリストを使用して単一の削除クエリを起動します。したがって、「X」行ごとに2つのクエリ。現在、このアプローチは問題ありませんでしたが、バッチジョブを使用してこれを行うと、10分程度で約500万行が削除されたため、MySQL DBのスレーブが105秒遅れました。 10分間のアクティビティで105秒の遅れ。だから、止めなければなりませんでした。
  3. この手法では、後続のバッチフェッチとサイズ「X」の削除の間にそれぞれ50ミリ秒のラグを導入しました。これでラグの問題は解決しましたが、手法2の500万行と比較して、10分あたり120万から130万行を削除していました。
  4. データベーステーブルをパーティション分割し、不要な場合はパーティション全体を削除します。これは私たちが持っている最良のソリューションですが、事前にパーティション化されたテーブルが必要です。 primary_key列にのみインデックス付けされた、パーティション化されていない非常に古いテーブルがあるため、手順3を実行しました。パーティションの作成には時間がかかりすぎ、危機的状況に陥っていました。パーティション分割に関連するリンクをいくつか紹介します。 公式MySQLリファレンスOracle DBの毎日のパーティション分割

したがって、IMO、テーブルにパーティションを作成する余裕がある場合は、オプション#4を選択してください。そうでない場合は、オプション#3で停止します。

3
Mukul Bansal

mk-archiver 優れた Maatkit ユーティリティパッケージ(MySQL管理用のPerlスクリプトの束)を使用しますMaatkitはO'Reillyの作者であるBaron Schwartzからです「高性能MySQL」ブック。

目標は、OLTPクエリに多くの影響を与えずにテーブルから古いデータをニブルする、影響の少ないフォワード専用ジョブです。データを別のテーブルに挿入できます。同じサーバー。LOAD DATA INFILEに適した形式でファイルに書き込むこともできますが、どちらもできません。

不要な行を小さなバッチでアーカイブするために既に構築されており、ボーナスとして、削除する行を選択するクエリを台無しにした場合に削除された行をファイルに保存できます。

インストールは不要で、単に http://www.maatkit.org/get/mk-archiver を取得し、その上でperldocを実行(またはWebサイトを参照)してドキュメントを取得します。

3
casey

一度に2000行のバッチで実行します。中間でコミットします。 100万行はそれほど多くありません。テーブルに多くのインデックスがなければ、これは高速です。

1
cherouvim

私たちにとって、DELETE WHERE %s ORDER BY %s LIMIT %d回答はオプションではありませんでした。なぜなら、WHERE基準は遅く(インデックスのない列)、マスターにヒットするからです。

削除するプライマリキーのリストをリードレプリカから選択します。この種類の形式でエクスポートします。

00669163-4514-4B50-B6E9-50BA232CA5EB
00679DE5-7659-4CD4-A919-6426A2831F35

次のbashスクリプトを使用してこの入力を取得し、それをDELETEステートメントにチャンクします[mapfile built-inのためにbash≥4が必要]:

sql-chunker.shchmod +x meを忘れずに、bash 4実行可能ファイルを指すようにShebangを変更)

#!/usr/local/Cellar/bash/4.4.12/bin/bash

# Expected input format:
: <<!
00669163-4514-4B50-B6E9-50BA232CA5EB
00669DE5-7659-4CD4-A919-6426A2831F35
!

if [ -z "$1" ]
  then
    echo "No chunk size supplied. Invoke: ./sql-chunker.sh 1000 ids.txt"
fi

if [ -z "$2" ]
  then
    echo "No file supplied. Invoke: ./sql-chunker.sh 1000 ids.txt"
fi

function join_by {
    local d=$1
    shift
    echo -n "$1"
    shift
    printf "%s" "${@/#/$d}"
}

while mapfile -t -n "$1" ary && ((${#ary[@]})); do
    printf "DELETE FROM my_cool_table WHERE id IN ('%s');\n" `join_by "','" "${ary[@]}"`
done < "$2"

次のように呼び出します。

./sql-chunker.sh 1000 ids.txt > batch_1000.sql

これにより、出力が次のようにフォーマットされたファイルが得られます(バッチサイズ2を使用しました)。

DELETE FROM my_cool_table WHERE id IN ('006CC671-655A-432E-9164-D3C64191EDCE','006CD163-794A-4C3E-8206-D05D1A5EE01E');
DELETE FROM my_cool_table WHERE id IN ('006CD837-F1AD-4CCA-82A4-74356580CEBC','006CDA35-F132-4F2C-8054-0F1D6709388A');

次に、次のようにステートメントを実行します。

mysql --login-path=master billing < batch_1000.sql

login-pathに不慣れな人にとっては、コマンドラインにパスワードを入力せずにログインするためのショートカットにすぎません。

1
Birchlabs

mysql documentation によると、TRUNCATE TABLEDELETE FROMの高速な代替手段です。これを試して:

TRUNCATE TABLE table_name

5,000万行でこれを試しましたが、2分以内に完了しました。

注:切り捨て操作はトランザクションに対して安全ではありません。アクティブなトランザクションまたはアクティブなテーブルロックの過程で試行しようとするとエラーが発生する

1
by0

遅いのは、実際のレコードが主キーインデックス内に(主キーインデックスの順に)保存されるMySQlの「クラスター化インデックス」によるものだと思います。つまり、主キーを介したレコードへのアクセスは、インデックス内で正しい主キーを見つけたディスク上のレコードがすぐそこにあるため、1回のディスクフェッチで済むため、非常に高速です。

クラスター化インデックスを持たない他のデータベースでは、インデックス自体はレコードを保持せず、テーブルファイル内のレコードの場所を示す「オフセット」または「場所」を保持し、そのファイルで実際のデータを取得するために2回目のフェッチを行う必要があります。

クラスター化インデックスのレコードを削除するとき、テーブル内のそのレコードより上のすべてのレコードを下に移動して、インデックスに大量の穴が作成されないようにする必要があることを想像できます(それは少なくとも数年前のことです-後のバージョンこれを変更した可能性があります)。

上記のことを知って、MySQLで本当に高速な削除が行われることは、削除を逆の順序で実行することでした。これは、最初からレコードを削除するため、レコードの移動量が最小になります。つまり、後続の削除では再配置するオブジェクトが少なくなります。

0
Volksman

私はこれを行うためにスクリプトを作成していません。適切に実行するにはスクリプトが絶対に必要ですが、別のオプションは、新しい複製テーブルを作成し、保持するすべての行を選択することです。このプロセスが完了する間、トリガーを使用して最新の状態に保ちます。同期している場合(削除する行を除く)、トランザクション内の両方のテーブルの名前を変更して、新しいテーブルが古いテーブルの代わりになるようにします。古いテーブルを落として、出来上がり!

これには(明らかに)多くの追加のディスク領域が必要であり、I/Oリソースに負担がかかる場合がありますが、そうでない場合ははるかに高速になります。

データの性質に応じて、または緊急時に、古いテーブルの名前を変更し、その場所に新しい空のテーブルを作成し、暇なときに新しいテーブルに「キープ」行を選択することができます...

0
Tyler Hains