MySQL で大量のデータ (数千万) を素早く削除するためのいくつかの実用的なソリューションの詳細な説明

MySQL で大量のデータ (数千万) を素早く削除するためのいくつかの実用的なソリューションの詳細な説明

著者は最近、仕事でパフォーマンスのボトルネックの問題に遭遇しました。MySQL テーブルには毎日約 776 万件の新しいレコードが追加され、保存期間は 7 日間です。7 日以上経過したデータは、新しいレコードを追加する前に古くなる必要があります。 9日間連続で実行した後、1日分のデータを削除するのに約3.5時間かかります(環境:128G、32コア、4Tハードドライブ)。これは許容できません。もちろん、テーブル全体を削除したい場合は、

TRUNCATE TABLE を実行するだけです。

最初の解決策は(削除がそれほど遅くなることは予想されていなかったため)次のとおりです(最も単純で最も素朴な方法)。

cnt_date <= target_date の table_name から削除します

さらに研究を重ねた結果、ついに 770 万件以上のデータ レコードを超高速 (約 1 秒) で削除するという目標を達成しました。1 つのテーブルに含まれるデータの総量は約 4,600 万件でした。最適化プロセスはレイヤーごとに実行され、詳細は次のように記録されています。

  • バッチ削除(毎回一定数を制限)し、すべてのデータが削除されるまでループで削除します。同時に、key_buffer_size がデフォルトの 8M から 512M に増加します。

運用効果:削除時間が約3.5時間から3時間に増加

(1)limit(具体的なサイズは適宜設定)を使って一度に削除するデータの量を制限し、データが削除されたかどうかを判定します。ソースコードは以下のとおりです(Python実装)。

def delete_expired_data(mysqlconn, 日):
 mysqlcur = mysqlconn.cursor()
 delete_sql = "table_name から DELETE を実行、cnt_date<='%s' の制限は 50000" % 日
 query_sql = "table_name から srcip を選択 where cnt_date <= '%s' limit 1" % 日
 試す: 
  df = pd.read_sql(query_sql、mysqlconn)
  真の場合:
   df が None または df.empty の場合:
    壊す
   mysqlcur.execute(delete_sql)
   mysqlconn.コミット()

   df = pd.read_sql(query_sql、mysqlconn)
 を除外する:
  mysqlconn.ロールバック()

(2)key_buffer_sizeを増やす

mysqlcur.execute("グローバル key_buffer_size を 536870912 に設定")

key_buffer_size はグローバル変数です。詳細については、MySQL の公式ドキュメントを参照してください: https://dev.mysql.com/doc/refman/5.7/en/server-configuration.html

  • クイック削除 + 最適化可能

適用可能なシナリオ: MyISAM テーブル

理由: MyISAM は削除されたデータをリンク リストに保持し、スペースと行の位置は後でデータを挿入するときに再利用されます。 直接削除後、MySQL はインデックス ブロックをマージします。これには大量のメモリのコピーと移動が含まれますが、OPTIMIZE TABLE はインデックスを直接再構築します。つまり、データ ブロックの新しいコピーを直接作成します (JVM ガベージ コレクション アルゴリズムを考えてみてください)。

運用効果:削除時間が3.5時間から1時間40分に増加

具体的なコードは次のとおりです。

def delete_expired_data(mysqlconn, 日):
 mysqlcur = mysqlconn.cursor()
 delete_sql = "table_name から DELETE QUICK を実行、cnt_date<='%s' で制限 50000" % 日
 query_sql = "table_name から srcip を選択 where cnt_date <= '%s' limit 1" % 日
 optimize_sql = "テーブル g_visit_relation_asset を最適化"
 試す: 
  df = pd.read_sql(query_sql、mysqlconn)
  真の場合:
   df が None または df.empty の場合:
    壊す
   mysqlcur.execute(delete_sql)
   mysqlconn.コミット()

   df = pd.read_sql(query_sql、mysqlconn)
  mysqlcur.execute(optimize_sql)
  mysqlconn.コミット()
 を除外する:
  mysqlconn.ロールバック()
  • テーブルパーティション、有効期限のあるパーティションを直接削除する(最終ソリューション - フラッシュセール)

MySQL テーブルをパーティション分割する方法には、RANGE、KEY、LIST、HASH などいくつかあります。詳細については、公式ドキュメントを参照してください。ここでのアプリケーション シナリオの日付は変更されるため、固定パーティション名を設定するために RANGE を使用することは適切ではありません。このシナリオには HASH パーティションの方が適しています。

(1)パーティションテーブルの定義、SQL文は次のようになります。

ALTER TABLE table_name PARTITION BY HASH(TO_DAYS(cnt_date)) PARTITIONS 7;

TO_DAYS は、日付 (日付型である必要があり、そうでない場合はエラーが報告されます: (サブ) パーティション関数内の定数、ランダム、またはタイムゾーンに依存する式は許可されません) を日数 (年、月、日の合計日数) に変換し、HASH して 7 つのパーティションを作成します。実はMOD7日目です。

(2)エージングする日付が格納されているパーティションをクエリします。SQL文は次のようになります。

"explain パーティション select * from g_visit_relation_asset where cnt_date = '%s'" % expired_day

実行結果は次のとおりです (partitions 列はパーティションを示します)。

+----+-------------+------------------+------------+-------+----------------+-------+---------+----------+-----------+------------+
| id | select_type | テーブル | パーティション | タイプ | 可能なキー | キー | キー長 | ref | 行 | フィルター済み | 追加 |
+----+-------------+------------------+------------+-------+----------------+-------+---------+----------+-----------+------------+
| 1 | SIMPLE | table_name | p1 | ALL | cnt_date_index | NULL | NULL | NULL | 1325238 | 100.00 | where の使用 |
+----+-------------+------------------+------------+-------+----------------+-------+---------+----------+-----------+------------+
セットに 1 行、警告 2 件 (0.00 秒)

(3)パーティションをOPTIMIZEまたはREBUILDする場合、SQL文は次のようになります。

"ALTER TABLE g_visit_relation_asset OPTIMIZE PARTITION '%s'" % パーティション

完全なコードは次のとおりです [Python 実装]。これはループして、指定された日付より小さいデータを削除します。

def clear_partition_data(mysqlconn, 日):
 mysqlcur = mysqlconn.cursor()
 expired_day = 日
 query_partition_sql = "explain パーティション select * from table_name where cnt_date = '%s'" % expired_day
 # パーティションを切り捨てた後に OPTIMIZE または REBUILD を実行する
 試す: 
  真の場合:
   df = pd.read_sql(クエリパーティションsql、mysqlconn)
   df が None または df.empty の場合:
    壊す
   パーティション = df.loc[0, 'パーティション']
   パーティションがNoneでない場合:
    clear_partition_sql = "テーブル table_name を変更してパーティション %s を削除" % パーティション
    mysqlcur.execute(clear_partition_sql)
    mysqlconn.コミット()

    optimize_partition_sql = "ALTER TABLE table_name OPTIMIZE PARTITION %s" % パーティション
    mysqlcur.execute(optimize_partition_sql)
    mysqlconn.コミット()
   
   期限切れ日 = (期限切れ日 - timedelta(日数 = 1)).strftime("%Y-%m-%d")
   df = pd.read_sql(クエリパーティションsql、mysqlconn)
 を除外する:
  mysqlconn.ロールバック()
  • 他の

削除されたデータがテーブルデータの 50% を超える場合は、必要なデータを一時テーブルにコピーしてから元のテーブルを削除し、一時テーブルの名前を元のテーブルに変更することをお勧めします。MySQL の場合は次のとおりです。

 新規に挿入
  メインから*を選択
   WHERE ...; -- 保持したい行のみ
 テーブル main の名前を Old に、New の名前を Main に変更します。
 DROP TABLE Old; -- ここでスペースが解放されます

パーティションを削除するには、対応するデータを削除せずに、ALTER TABLE table_name REMOVE PARTITIONINGを実行します。

参照:

1) https://dev.mysql.com/doc/refman/5.7/en/alter-table-partition-operations.html 具体的なパーティション分割手順

2) http://mysql.rjweb.org/doc.php/deletebig#solutions 大容量データを削除するためのソリューション

この記事の著作権は著者と Blog Garden が共有しています。転載は歓迎しますが、著者の同意なしにこの声明を残し、元のリンクを記事ページの目立つ位置に表示する必要があります。そうでない場合、法的責任を追及する権利を留保します。

************************************************************************

エネルギーは限られており、アイデアは多すぎます。ただ 1 つのことをうまく行うことに集中してください。

私はただのプログラマーです。 5年以内に良いコードを書き、技術ブログのすべての単語を磨き、コピーゼロと独創性にこだわります。ブログの意義は、文章スタイルを磨き、論理と秩序を訓練し、知識の体系的な理解を深めることです。それが他の人に役立つとしたら、それは本当に幸せなことです。

これで、MySQL から大量のデータ (数千万) をすばやく削除するためのいくつかの実用的なソリューションに関するこの記事は終了です。MySQL から大量のデータをすばやく削除する方法の詳細については、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • インデックスを使用して数千万のデータを持つ MySQL のクエリ速度を最適化する
  • MySQLループは数千万のデータを挿入する
  • 数千万のMySQLデータ量を素早くページ分割する方法
  • MySQL 数千万のビッグデータに対するSQLクエリ最適化の知識ポイントのまとめ
  • MySql クイック挿入数千万の大規模データの例
  • MySQLで数千万のテストデータを素早く作成する方法
  • MySQL数千万の大規模データに対する30のSQLクエリ最適化テクニックの詳細な説明
  • 数千万のデータを扱うMySQLのページングクエリのパフォーマンスを最適化する
  • 数千万ページ分のMySQL高速ページングを最適化する方法
  • MySQLデータベースの数千万件のデータクエリとストレージの詳細な説明

<<:  ReactとReduxの配列処理の説明

>>:  Ubuntu 18.04 (物理マシン) で OpenWRT 開発環境を構成する方法

推薦する

負荷分散と動的・静的分離を実現するNginx+Tomcatの原理の分析

1. Nginx ロードバランシングの実装原理1. Nginxはリバースプロキシを通じて負荷分散を実...

検証コード干渉を実装する js (動的)

この記事の例では、検証コードの動的干渉を実装するためのjsの具体的なコードを共有しています。具体的な...

uni-appがNFC読み取り機能を実装

この記事では、参考までに、NFC読み取り機能を実装するためのuni-appの具体的なコードを紹介しま...

Linuxカーネルとデバイスツリーのコンパイルと書き込みを分析する

目次1. 材料を準備する2. Linuxカーネルファイルをダウンロードする3. コンパイル4. TF...

XHTMLタグは適切に使用する必要があります

<br />123WORDPRESS.COM の以前のチュートリアルでは、Web ページ...

LinuxカーネルとSVNバージョン間の競合の解決策

現象システムは Linux システムカーネルを正常にコンパイルできましたが、SVN をインストールし...

NodeJSとブラウザにおけるこのキーワードの違い

序文JavaScript を学習した人なら誰でも、さまざまな環境で this がどこを指すかという問...

Docker で Redis センチネル モードを構成する方法 (複数のサーバー上)

目次序文状態DockerをインストールするRedisのマスターノードとスレーブノードを構成する序文以...

IE6 の iframe の水平スクロール バーの解決策

状況は以下のとおりです: (PS: 赤いボックスは iframe 領域を表し、灰色の四角形は上記の ...

PDO を使用して SQL インジェクションを防ぐ原理の分析

序文この記事では、SQL インジェクションを回避するために pdo の前処理メソッドを使用します。詳...

ReactとReduxの配列処理の説明

この記事では、reduce()、filter()、map()、every()、some()、spre...

MySQL GTID の総合概要

目次01 GTIDの紹介02 GTIDの仕組み03 GTIDの利点と欠点04 テスト環境構築05 テ...

Reactベースのコンポーネントのカプセル化の実装手順

目次序文antd はどのようにしてコンポーネントをカプセル化するのでしょうか?ディバイダーコンポーネ...

Linux で MySQL のスケジュールバックアップを実装する方法

実際のプロジェクトでは、緊急事態を防ぐためにデータベースを頻繁にバックアップする必要があります。しば...

Centos7.3 で mysql5.7.18 をインストールして初期パスワードを変更する方法

この記事では、Centos7.3でのmysql5.7.18のインストールと初期パスワードの変更につい...