質問

私は最近、私が取り組んでいたサイトでバグを見つけて修正し、その結果、テーブル内の数百万の複製のデータが、それらがなくても非常に大きくなる(まだ数百万)になります。これらの複製の行を簡単に見つけることができ、1つの削除クエリを実行してすべてを殺すことができます。問題は、1発のこの多くの行を削除しようとすると、テーブルが長い間ロックされ、可能であれば避けたいということです。 (テーブルをロックすることで)サイトを削除せずにこれらの行を取り除くために私が見ることができる唯一の方法は次のとおりです。

  1. ループで数千の小さな削除クエリを実行するスクリプトを書きます。これは、他のクエリがキューに入り、削除の間に実行できるため、理論的にロックされたテーブルの問題を回避します。しかし、それはまだデータベースの負荷をかなり急上昇させ、実行に長い時間がかかります。
  2. テーブルの名前を変更して、既存のテーブルを再作成します(今では空になります)。次に、名前が変更されたテーブルでクリーンアップを行います。新しいテーブルの名前を変更し、古いものに名前を付けて、新しい行を名前を変更したテーブルにマージします。これはかなり多くのステップが必要ですが、最小限の中断で仕事を終わらせる必要があります。ここでの唯一のトリッキーな部分は、問題のテーブルがレポートテーブルであることです。そのため、邪魔にならないように改名され、空のものがその場所に置かれたら、すべての歴史的なレポートが元に戻るまで消えます。さらに、データの種類が保存されているため、マージプロセスは少し痛みになる可能性があります。全体として、これは今の私の選択です。

他の誰かが以前にこの問題を抱えているかどうか疑問に思っていましたが、もしそうなら、サイトを倒さずに、そしてうまくいけば、ユーザーへの中断が最小限に抑えられていますか? 2番、または異なる類似のアプローチで行くと、夜遅くに走るようにスケジュールを立てて、翌朝早くマージを行い、ユーザーに事前に知らせることができますので、それは大したことではありません。クリーンアップを行うためのより良い、またはより簡単な方法のためのアイデアがあるかどうかを確認したいと思っています。

役に立ちましたか?

解決

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

洗浄、すすぎ、ゼロ行が影響を受けるまで繰り返します。たぶん、反復の間に2秒または3つ眠るスクリプトで。

他のヒント

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

以下は、1つのレコードを一度に1つずつ削除します。

 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

それらを一緒にグループ化して、table_nameを削除することができます。

MySQLの25m+行テーブルで1m+行を削除するというユースケースがありました。バッチ削除(上記)などのさまざまなアプローチを試しました。
私は、最速の方法(新しいテーブルへの必要なレコードのコピー)であることがわかりました:

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

テーブルid_temp_table(temp_id int);

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

ID_TEMP_TABLE(TEMP_ID)にID_TEMP_TABLEに挿入.....

  1. 新しいテーブルTable_newを作成します

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

table_newに挿入....ここで、table_id in in(select distinct(temp_id)from id_temp_table);

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

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

私は使用します MKアーチバー 素晴らしいものから Maatkit ユーティリティパッケージ(MySQL管理用のPerlスクリプトの束)Maatkitは、O'Reilly「High Performance MySQL」ブックの著者であるBaron Schwartzからのものです。

目標は、OLTPクエリにあまり影響を与えることなく、テーブルから古いデータをかじるための影響力の低い、前進のみです。データを別のテーブルに挿入できます。これは、同じサーバー上にある必要はありません。また、ロードデータインフィルに適した形式でファイルに書き込むこともできます。または、どちらもできません。その場合、それは単なる増分削除です。

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

インストールは必要ありません。つかむだけです http://www.maatkit.org/get/mk-archiver ドキュメントのためにその上でperldocを実行します(またはWebサイトを読みます)。

私は同様の問題に直面しました。サイズが約500 GBの非常に大きなテーブルがあり、Partitioningのない1つのインデックスが1つだけありました。私たちのマスターは、マシンのハルク、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分あたり22〜130万行を削除していました。
  4. データベーステーブルをパーティション化し、必要なときにパーティション全体を削除します。これは私たちが持っている最良のソリューションですが、事前に保持されたテーブルが必要です。 Primary_Key列にのみインデックスが付いている非常に古いテーブルではない非常に古いテーブルがないため、ステップ3に従いました。パーティションを作成するには時間がかかりすぎて、危機モードになりました。ここに私が役立つと感じたパーティション化に関連するいくつかのリンクがあります - 公式MySQLリファレンス, Oracle DB Daily Partitioning.

したがって、IMO、テーブルにパーティションを作成する贅沢をする余裕がある場合は、オプション#4にアクセスしてください。そうしないと、オプション#3に貼り付けられています。

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

による MySQLドキュメント, TRUNCATE TABLE 高速な代替品です DELETE FROM. 。これを試して:

TRUNCATE TABLE table_name

私はこれを50m行で試しましたが、2分以内に行われました。

注:トランケート操作はトランザクションセーフではありません。アクティブトランザクションまたはアクティブテーブルロックの過程でエラーを試みるときにエラーが発生します

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

削除したい主要なキーのリストを読み取りreplicaから選択します。この種の形式でエクスポートします:

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

次のBashスクリプトを使用してこの入力をつかみ、削除ステートメントにチャンクします bash≥4が必要です mapfile 組み込み]:

sql-chunker.sh (覚えておいてください chmod +x 私、そしてあなたのbash4実行可能ファイルを指すためにシバンを変更します):

#!/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

これにより、SOのように出力がフォーマットされたファイルが得られます(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, 、コマンドラインでパスワードを入力せずにログインするための近道です。

遅さは、実際のレコードがプライマリキーインデックス内に保存されているMySQLの「クラスター化されたインデックス」によるものであると思います - プライマリキーインデックスの順序で。これは、プライマリキーを介したレコードへのアクセスが非常に高速であることを意味します。これは、インデックスに正しい主キーを見つけたディスク上のレコードがすぐそこにあるため、ディスクフェッチが1つだけ必要だからです。

クラスター化されたインデックスのない他のデータベースでは、インデックス自体がレコードを保持するのではなく、テーブルファイル内のレコードがどこにあるかを示す「オフセット」または「位置」だけを保持し、実際のデータを取得するためにそのファイルに2番目のフェッチを作成する必要があります。 。

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

MySQLで本当にsped削除されたことがわかったことを知ることは、削除を逆順序で実行することでした。これにより、最初の削除が再配置するオブジェクトが少ないことを意味することを意味する最後からレコードを削除するため、レコードの動きが最小になります。

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

これには(明らかに)多くの追加のディスクスペースが必要であり、I/Oリソースに課税される場合がありますが、それ以外の場合ははるかに高速になる可能性があります。

データの性質または緊急時には、古いテーブルの名前を変更して、その場所に新しい空のテーブルを作成し、余暇の新しいテーブルに「維持」行を選択できます...

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top