GA

2017/11/28

MySQLから大量のレコードをちまちま削除するメモ

想定ケースとしては1億行くらいのテーブルから7000万行くらい消すクエリーを、レプリケーションが遅れずバッファプールも食い切らない程度にちまちま消すようにする。
DELETE FROM t1 WHERE hoge = 1 AND last_update < '2017-11-28'
「ちまちま削除する」なので、トランザクションでAll or Nothingを保証したい場合は使えない。 id をプライマリーキー(ただしサロゲートキーかどうかは問わない)、 hoge, last_update が本来消し込みに使いたいカラムだとする。 プライマリーキー(またはユニークキー)がないテーブルのことは考えない。

KEY(hoge, last_update) がある場合

  • ターゲットのプライマリーキーを取り出して DELETE .. WHERE id IN .. の形に落とし込む
  • 行ロックに落とし込める
  • idの型を選ばない(varcharだろうと使える)
  • 自前でINリストを作るのが面倒ならGROUP_CONCATという手もあるけどその場合は group_concat_max_len に注意
  • DELETE の方でもとの条件をANDしておくのを忘れると事故ることがある。。
  • プライマリーキーに対するWHEREがあるので実行計画で暴発しにくい…?
    • けどあんまりINのリストを長くするとテーブルスキャン選びやがった on 5.7
SELECT id FROM t1 WHERE hoge = 1 AND last_update < '2017-11-28' ORDER BY id LIMIT 1000;
DELETE FROM t1 WHERE id IN (.., ..) AND hoge = 1 AND last_update < '2017-11-28';

SELECT id FROM t1 WHERE hoge = 1 AND last_update < '2017-11-28' ORDER BY id LIMIT 1000;
DELETE FROM t1 WHERE id IN (.., ..) AND hoge = 1 AND last_update < '2017-11-28';

..

(KEY(hoge) のみがある、または削除に使えるキーがない) && idが数値型の場合

  • idのレンジを細かく区切ってループさせる
  • 最終的には全レコードにアクセスしないといけないので一度にやるとバッファプールが荒れる。適度にsleepを入れること
  • ネクストキーロックなので、DELETEしてる範囲(+α)にINSERTやUPDATEが来るとブロックされる
DELETE FROM t1 WHERE hoge = 1 AND last_update < '2017-11-28' AND id BETWEEN 1 AND 1000;
DELETE FROM t1 WHERE hoge = 1 AND last_update < '2017-11-28' AND id BETWEEN 1001 AND 2000;
DELETE FROM t1 WHERE hoge = 1 AND last_update < '2017-11-28' AND id BETWEEN 2001 AND 3000;
..

KEY(hoge) のみがある && idが文字列型の場合

  • idの最小値を取ってORDER BY id LIMITで少しずつ行く
  • 最終的には全レコードにアクセスしないといけないので一度にやるとバッファプールが荒れる。適度にsleepを入れること
  • ネクストキーロックなので、DELETEしてる範囲(+α)にINSERTやUPDATEが来るとブロックされる
  • 実行計画が暴発すると地獄が見えるのでDELETEの方はUSE INDEXした方が良さげ。DELETEでUSE INDEXするには テーブルリファレンス構文 の方を使う
  • 正直この辺まで来るとtsvにでも吐き出させて WHERE id IN (..) の形にした方が良いような気がする
SELECT MIN(id) AS min_id FROM t1 WHERE hoge = 1 AND last_update < '2017-11-28' ;
DELETE t1 FROM t1 USE INDEX(PRIMARY) WHERE id >= $min_id AND hoge = 1 AND last_update < '2017-11-28' ORDER BY id LIMIT 1000;

SELECT MIN(id) AS min_id FROM t1 WHERE hoge = 1 AND last_update < '2017-11-28' ;
DELETE t1 FROM t1 USE INDEX(PRIMARY) WHERE id >= $min_id AND hoge = 1 AND last_update < '2017-11-28' ORDER BY id LIMIT 1000;
..

削除に使えるキーがなくてidが文字列型の場合

  • 悔い改めなさい
  • 一度tsvにでも吐き出させて WHERE id IN (..) の形に落とし込む
  • SELECTで1回テーブルスキャン、これでバッファプールが荒れそう。ぶん回しても良いバックアップとかバッチ用とかのMySQLがあればここが無視できるからこのやり方でも安定しそう
SELECT id FROM t1 WHERE hoge = 1 AND last_update < '2017-11-28'  INTO OUTFILE '/tmp/target.txt; /* これが時間かかるはずなのでたっぷり後悔できる */

DELETE FROM t1 WHERE id IN (.., ..) AND hoge = 1 AND last_update < '2017-11-28'; /* お好みの言語でファイルを処理してINリストを作る */
DELETE FROM t1 WHERE id IN (.., ..) AND hoge = 1 AND last_update < '2017-11-28';
..
どうでしょう。

【2021/05/21 18:31】
MySQL 8.0では取れる手が増えていてちょっと感動した。

5 件のコメント :

  1. はじめまして。こちらの記事で質問させていただけますでしょうか?

    mysql(5.6.10) の delete で force index を使いたく、いろいろと検索していたら、たどり着きました。

    「DELETEでUSE INDEXするには テーブルリファレンス構文 の方を使う」
    ということがこちらで書かれていますが、こちらに関する公式のマニュアル等があれば、
    教えていただけませんでしょうか?

    一応、
    delete from t1 where ~
    と書くところを、
    delete t1 from t1 force index(key1) where ~
    と書く、
    ということで理解はできましたが、
    公式のマニュアルも確認しておきたいな、という意図です。

    お手数ですが、
    よろしくお願いいたします。

    返信削除
  2. DELETEステートメントで「テーブルリファレンス記述が使える」のはDELETEステートメントのページに記述があります。

    MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.2.2 DELETE 構文 https://dev.mysql.com/doc/refman/5.6/ja/delete.html



    「テーブルリファレンス記述」がどういうものかについてはJOINの節に説明があります。

    MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.2.9.2 JOIN 構文 https://dev.mysql.com/doc/refman/5.6/ja/join.html

    これで求める情報になっているでしょうか?

    返信削除
  3. ご回答ありがとうございます。

    ・delete 文で table_references 句が使用できること
    ・テーブルリファレンス(テーブル参照)について

    については理解できました。

    もう一点、
    ・delete 文でインデックスヒントを使用するときに、テーブルリファレンス構文を使用する必要がある
    ということが記載された公式の文書を見たいのですが、ご存知ではないでしょうか?

    何度もお手数ですが、
    よろしくお願いいたします。

    返信削除
  4. 私の記憶にある限り、存在しなかったと思います。

    1-1. 「テーブルリファレンス」はインデックスヒントを解釈することができます。
    1-2. 「テーブル」はテーブルオブジェクトですので、「インデックスヒント」という概念はありません。


    2. DELETE構文のドキュメントには、単一テーブルを使った構文( "DELETE FROM tbl_name" ..) = 引数が「テーブル」のみである構文と、テーブルリファレンスを使った構文( "DELETE FROM tbl_name FROM table_references"ともう一つ) = 引数が「テーブル」および「テーブルリファレンス」である構文しかありません。

    3. 1-1., 1-2. および 2.より、DELETEステートメントは「テーブルリファレンス」を使う以外にインデックスヒントを使うことができません。


    という推移的な結論です。

    https://dev.mysql.com/doc/refman/5.6/ja/delete.html

    返信削除
  5. ご回答ありがとうございます。

    頂いた情報で理解でき、スッキリしました!

    また、
    delete でインデックスヒントを使う、という目的も果たせ、
    大変助かりました。

    ありがとうございます。

    返信削除