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 開発環境を構成する方法

推薦する

Docker で Kong API Gateway をインストールして使用する詳細なチュートリアル

1 はじめにKong は単純な製品ではありません。この記事で言及されている Kong は主に Kon...

CentOS7 は yum を使用して mysql 8.0.12 をインストールします

この記事では、centos7にyumを使用してMySQL 8.0.12をインストールする詳細な手順を...

Tomcat での jar のロードに関する異常な問題の分析と解決

現象の説明:プロジェクトでは、Springboot を使用して Web プロジェクトを開始します。起...

Linux で FastDFS ファイル サーバーを構築するための実装手順

目次1. ソフトウェアパッケージ2. gccをインストールする3. libfastcommonをイン...

MySQLデータベースのQPSとTPSの意味と計算方法

DB ベンチマーク テストを実行する場合、qps と tps はデータベースのパフォーマンスを測定す...

Nginxでネットワーク分離を解決した実践記録を詳しく解説

必要最近、Node オンライン サービスを移行する必要があったため、2 つの新しいオンライン サーバ...

DockerがElasticsearch7.xを起動してエラーを報告する問題を解決する

Docker実行コマンドの使用docker run -d -p 9200:9200 -p 9300:...

ネイティブ js はカスタム スクロール バー コンポーネントを実装します

この記事の例では、カスタムスクロールバーコンポーネントを実装するためのjsの具体的なコードを参考まで...

Vueコンポーネント間の通信の非常に詳細な要約

目次序文1. Props、$emit一方向データフロー2. $親、$子3. $attrs、$list...

Linux におけるシステム入出力管理の詳細な説明

システムの入力と出力の管理1. システムの入力と出力を理解するLinuxシステムでは、1は正しい出力...

JSはGMTとUTCのタイムゾーンを完全に理解しています

目次序文1. GMT GMTとはGMTの歴史2. UTC UTCとはUTC は次の 2 つの部分で構...

HTMLハイパーリンクタグAのTARGET属性の詳細な説明

ハイパーリンク <a> タグはリンク ポイントを表します。これは英語の単語「anchor...

クリックして認証コードと認証を切り替えるJavaScript

この記事では、クリックして切り替える認証コードと認証を実装するためのJavaScriptの具体的なコ...

ポップアップ効果を実現するにはjsを使用します

この記事の例では、ポップアップ効果を実現するためのjsの具体的なコードを参考までに共有しています。具...

MySQL 5.7.18 MSI インストール グラフィック チュートリアル

この記事では、参考までにMySQL 5.7.18 MSIインストールチュートリアルを紹介します。具体...