MySQL テーブルを削除する際の I/O エラーの原因分析と解決方法

MySQL テーブルを削除する際の I/O エラーの原因分析と解決方法

問題現象

最近、sysbench を使用して MySQL をテストしました。テストに長い時間がかかったため、準備 -> 実行 -> クリーンアップの順序でバックグラウンドで実行されるスクリプトを作成しました。実行後、ログを確認すると問題が見つかりました。MySQL サービスのエラー ログに次のようなエラーが複数ありました。

[エラー] InnoDB: 存在しないテーブルスペースへの I/O を試行しています。I/O タイプ: 読み取り、ページ: [ページ ID: スペース = 32、ページ番号 = 57890]、I/O 長さ: 16384 バイト。

I/O エラーのように見えますが、MySQL プロセスはクラッシュせず、sysbench クライアントはエラーを報告しませんでした。

問題発見プロセス

エラーの時間記録とスクリプト出力の各段階の時点の比較に基づいて、その時点でスクリプトによって実行されていたコマンドは次のとおりであると判定されました。

sysbench --tables=100 --table-size=4000000 --threads=50 --mysql-db=sbtest --time=300 oltp_delete クリーンアップ

再度手動でテストケースを実行しましたが、同じ状況は再び発生しませんでした。ただし、スクリプトを実行すると、このエラー メッセージが引き続き表示されます。最初に疑われたのは、この問題は実行とクリーンアップの間の短い間隔によって引き起こされたということです。 100G のデータを実行するには時間がかかり、繰り返しコストも大きいため、まずはユースケースのデータ量を減らすようにしてください。 --table-size=4000000 を 2000000 に変更します。スクリプトを実行すると、この問題はトリガーされなくなります。最後に、トリガーを安定させ、再現時間を短縮するために、--table-size=3000000 を変更します。長い間隔によって問題が再現されないかどうかを確認するために、スクリプトは実行段階とクリーンアップ段階の間で 10 秒間スリープするように変更されました。確かに、このエラー メッセージはトリガーされませんでした。 5 秒間スリープするように変更すると、引き続きトリガーされる可能性がありますが、報告されるエラーの数は減少します。

問題調査

対応するバージョンの mysql5.7.22 のコードを確認すると、このエラーは fil0fil.cc ファイルの 5578 行目の fil_io() 関数の 1 か所でのみ発生することがわかりました。 gdb デバッグを直接使用し、この場所にブレークポイントを追加し、再現可能なスクリプトを実行して次のスタックを取得します。

(gdb) うーん
#0 fil_io (type=...、sync=sync@entry=false、page_id=...、page_size=...、byte_offset=byte_offset@entry=0、len=16384、buf=0x7f9ead544000、message=message@entry=0x7f9ea8ce9c78)、mysql-5.7.22/storage/innobase/fil/fil0fil.cc:5580
#1 0x00000000010f99fa buf_read_page_low (err=0x7f9ddaffc72c、sync=<最適化出力>、type=0、mode=<最適化出力>、page_id=...、page_size=...、unzip=true)、mysql-5.7.22/storage/innobase/buf/buf0rea.cc:195
#2 0x00000000010fc5fa buf_read_ibuf_merge_pages (sync=sync@entry=false、space_ids=space_ids@entry=0x7f9ddaffc7e0、page_nos=page_nos@entry=0x7f9ddaffc7a0、n_stored=2)、mysql-5.7.22/storage/innobase/buf/buf0rea.cc:834
#3 0x0000000000f3a86c、ibuf_merge_pages (n_pages=n_pages@entry=0x7f9ddaffce30、sync=sync@entry=false)、mysql-5.7.22/storage/innobase/ibuf/ibuf0ibuf.cc:2552
#4 0x0000000000f3a94a、ibuf_merge (sync=false、sync=false、n_pages=0x7f9ddaffce30)、mysql-5.7.22/storage/innobase/ibuf/ibuf0ibuf.cc:2656
#5 ibuf_merge_in_background (full=full@entry=false)、mysql-5.7.22/storage/innobase/ibuf/ibuf0ibuf.cc:2721 で
#6 0x000000000102bcf4 srv_master_do_active_tasks () で、mysql-5.7.22/storage/innobase/srv/srv0srv.cc:2132
#7 srv_master_thread (arg=<最適化済み>)、mysql-5.7.22/storage/innobase/srv/srv0srv.cc:2383
#8 /lib64/libpthread.so.0 の start_thread() 内の 0x00007fa003eeddc5
#9 /lib64/libc.so.6 からの clone() で 0x00007fa002aab74d

明らかに、これは挿入バッファのマージ操作を実行しているバックグラウンド スレッドです。このとき、space->stop_new_ops が true であることがわかります。これは、処理対象のページが属するスペースが削除されていることを意味します。削除中のスペースに対して操作を行うのはなぜですか?これには、挿入バッファ機能、挿入バッファのマージ プロセス、およびテーブル削除プロセスを調査する必要があります。

挿入バッファの背景知識

挿入バッファは、補助インデックス ページがバッファ プールに存在しない場合に変更をキャッシュし、後で他の読み取り操作によってページがバッファ プールにロードされたときに変更をマージする特殊なデータ構造 (B+ ツリー) です。 MySQL がこの機能を導入した当初は、挿入操作のみをキャッシュできたため、挿入バッファと呼ばれていました。現在、これらの操作は INSERT、UPDATE、または DELETE (DML) であるため、変更バッファと呼ばれています (この記事では引き続き挿入バッファと説明しています)。ただし、ソース コードでは識別子として ibuf が引き続き使用されています。この機能は、同じページへの複数の更新をキャッシュし、それらを 1 回の更新操作にマージして、IO を削減し、ランダム IO をシーケンシャル IO に変換します。これにより、ランダム IO によるパフォーマンスの低下を回避し、データベースの書き込みパフォーマンスを向上させることができます。

関連する挿入バッファのマージロジック

バッファ ページがバッファ プールに読み込まれると、挿入バッファのマージが実行されます。マージ プロセスが発生する主なシナリオはいくつかあります。

  • ページがバッファ プールに読み込まれると、読み取りが完了した後に ibuf がマージされ、ページが使用可能になります。
  • マージ操作はバックグラウンド タスクとして実行されます。 innodb_io_capacity パラメータは、InnoDB バックグラウンド タスクの各マージ プロセスのページ数の上限を設定できます。
  • クラッシュ リカバリ中に、インデックス ページがバッファー プールに読み込まれると、対応するページの挿入バッファー マージが実行されます。
  • 挿入バッファは永続的であり、システムクラッシュによって無効になることはありません。再起動後、挿入バッファのマージ操作は通常の状態に戻ります。
  • --innodb-fast-shutdown=0 を使用すると、サーバーのシャットダウン時に ibufs の完全なマージを強制できます。

今回の問題は明らかに 2 番目のケースに属します。 InnoDB メイン スレッド (svr_master_thread) は、挿入バッファーに対して 1 秒ごとにマージ操作をアクティブに実行します。まず、過去 1 秒以内にサーバー上で何らかのアクティビティ (ページへのタプルの挿入、UNDO テーブルでの行操作など) があったかどうかを判断します。アクティビティがあった場合、マージするページの最大数は innodb_io_capacity 設定の 5% になります。そうでない場合、マージするページの最大数は innodb_io_capacity によって設定された値になります。

InnoDB メインスレッド (svr_master_thread) マージの主なプロセスは次のとおりです。

  • メイン スレッドは、ibuf ツリーのリーフ ノードからページ番号とスペース番号を読み取り、バイナリ配列 (ロック解除) に記録します。
  • メイン スレッドは、タプル内のスペースがテーブル スペース キャッシュ内にあるかどうかを確認します。ない場合は、削除されたことを意味し、対応する ibuf レコードを削除します。
  • メインスレッドは、削除中の領域に対して非同期読み取り操作を実行するかどうかを判断します。実行する場合はエラーが報告され、対応する ibuf レコードが削除され、プロセスはプロセス 2 に進み、次の配列要素の判断を続行します。
  • すべてが正常であれば、メイン スレッドは非同期 IO 要求を送信し、マージする必要があるインデックス ページを非同期で読み取ります。
  • I/O ハンドラー スレッドは、完了した非同期 I/O を受け取った後、マージ操作を実行します。
  • マージを実行するときは、fil_space_acquire を呼び出して space->n_pending_ops を増分します。同時削除操作を避けてください。
  • 実行が完了すると、fil_space_release が呼び出され、space->n_pending_ops が減算されます。

関連テーブルを削除するためのロジック

  • fil_system->mutex をロックし、sp->stop_new_ops = true を設定し、スペースを削除対象としてマークし、そのスペースに対する新しい操作を禁止してから、fil_system->mutex のロックを解除します。
  • fil_system->mutex をロックし、space->n_pending_ops をチェックし、fil_system->mutex のロックを解除します。検出された値が 0 より大きい場合、まだ完了していない依存操作が残っていることを意味します。20 ミリ秒間スリープしてから再試行してください。
  • fil_system->mutex をロックし、space->n_pending_flushes と (*node)->n_pending をチェックし、fil_system->mutex のロックを解除します。検出値が 0 より大きい場合、まだ完了していない依存 I/O があることを意味し、システムは 20 ミリ秒のスリープ後に再試行します。
  • この時点では、競合する操作はなく、すべてのダーティ ページがフラッシュされるか、指定された表領域のすべてのページが削除されているものと想定されます。
  • 指定されたスペースのレコードをテーブル スペース キャッシュから削除します。
  • 対応するデータ ファイルを削除します。

結論

状況は非常に明確です。メイン スレッドの ibuf (スペース、ページ) を取得するプロセスと削除操作を実行するプロセスの間には、相互排他性を保証するロックはありません。非同期 I/O が完了した後のマージ操作と削除操作のみが相互に排他的です。バックグラウンド スレッドが ibuf マージを開始し、ステップ 2 の検出を実行したが、ステップ 3 の検出をまだ実行しておらず、ユーザー スレッドがテーブルの削除を開始し、stop_new_ops フラグを設定したが、テーブルスペース キャッシュを削除するステップ 5 をまだ実行していない場合、このエラー メッセージが表示されます。 2 つのスレッド間の相互作用を次の図に示します。

予期しない事態が発生しない場合は、ブレークポイントに到達したときに、対応するテーブルの削除操作を実行しているスレッドが存在する必要があります。確かに、次のスタックが見つかります。

スレッド 118 (スレッド 0x7f9de0111700 (LWP 5234)):
#0 0x00007fa003ef1e8e、pthread_cond_broadcast@@GLIBC_2.3.2 ()、/lib64/libpthread.so.0 から
#1 0x0000000000f82f41 ブロードキャスト (this=0xd452ef8)、mysql-5.7.22/storage/innobase/os/os0event.cc:184
#2 が mysql-5.7.22/storage/innobase/os/os0event.cc:75 に設定されました (this=0xd452ef8)
#3 os_event_set (イベント=0xd452ef8)、mysql-5.7.22/storage/innobase/os/os0event.cc:483
#4 0x00000000010ec8a4 シグナル内 (this=<最適化済み>)、mysql-5.7.22/storage/innobase/include/ut0mutex.ic:105
#5 終了 (this=<最適化済み>)、mysql-5.7.22/storage/innobase/include/ib0mutex.h:690
#6 終了 (this=<最適化済み>)、mysql-5.7.22/storage/innobase/include/ib0mutex.h:961
#7 buf_flush_yield (bpage=<最適化済み出力>、buf_pool=<最適化済み出力>)、mysql-5.7.22/storage/innobase/buf/buf0lru.cc:405
#8 buf_flush_try_yield (処理済み = <最適化済み>、bpage = <最適化済み>、buf_pool = <最適化済み>)、mysql-5.7.22/storage/innobase/buf/buf0lru.cc:449
#9 buf_flush_or_remove_pages (trx=<最適化済み>、flush=<最適化済み>、observer=<最適化済み>、id=<最適化済み>、buf_pool=<最適化済み>)、mysql-5.7.22/storage/innobase/buf/buf0lru.cc:632
#10 buf_flush_dirty_pages (buf_pool=<最適化出力>、id=<最適化出力>、observer=<最適化出力>、flush=<最適化出力>、trx=<最適化出力>)、mysql-5.7.22/storage/innobase/buf/buf0lru.cc:693
#11 buf_LRU_remove_pages の 0x00000000010f6de7 (trx=0x0、buf_remove=BUF_REMOVE_FLUSH_NO_WRITE、id=55、buf_pool=0x31e55e8)、mysql-5.7.22/storage/innobase/buf/buf0lru.cc:893
#12 buf_LRU_flush_or_remove_pages (id=id@entry=55、buf_remove=buf_remove@entry=BUF_REMOVE_FLUSH_NO_WRITE、trx=trx@entry=0x0)、mysql-5.7.22/storage/innobase/buf/buf0lru.cc:951
#13 0x000000000114e488 が fil_delete_tablespace (id=id@entry=55、buf_remove=buf_remove@entry=BUF_REMOVE_FLUSH_NO_WRITE) にあり、mysql-5.7.22/storage/innobase/fil/fil0fil.cc:2800 にあります
#14 0x0000000000fe77bd、row_drop_single_table_tablespace (trx=0x0、is_encrypted=false、is_temp=false、filepath=0x7f9d7c209f38 "./sbtest/sbtest25.ibd"、tablename=0x7f9d7c209dc8 "sbtest/sbtest25"、space_id=55)、mysql-5.7.22/storage/innobase/row/row0mysql.cc:4189
#15 row_drop_table_for_mysql (name=name@entry=0x7f9de010e020 "sbtest/sbtest25"、trx=trx@entry=0x7f9ff9515750、drop_db=<最適化出力>、nonatomic=<最適化出力>、nonatomic@entry=true、handler=handler@entry=0x0)、mysql-5.7.22/storage/innobase/row/row0mysql.cc:4741
#16 0x0000000000f092f3、ha_innobase::delete_table (this=<最適化済み>、name=0x7f9de010f5e0 "./sbtest/sbtest25")、mysql-5.7.22/storage/innobase/handler/ha_innodb.cc:12539
#17 0x0000000000801a30 が ha_delete_table (thd=thd@entry=0x7f9d7c1f6910、table_type=table_type@entry=0x2ebd100、path=path@entry=0x7f9de010f5e0 "./sbtest/sbtest25"、db=db@entry=0x7f9d7c00e560 "sbtest"、alias=0x7f9d7c00df98 "sbtest25"、generate_warning=generate_warning@entry=true) にあります (mysql-5.7.22/sql/handler.cc:2586)
#18 0x0000000000d0a6af が mysql_rm_table_no_locks (thd=thd@entry=0x7f9d7c1f6910、tables=tables@entry=0x7f9d7c00dfe0、if_exists=true、drop_temporary=false、drop_view=drop_view@entry=false、dont_log_query=dont_log_query@entry=false) にあります (mysql-5.7.22/sql/sql_table.cc:2546)
#19 0x0000000000d0ba58、mysql_rm_table (thd=thd@entry=0x7f9d7c1f6910、tables=tables@entry=0x7f9d7c00dfe0、if_exists=<最適化されて出力>、drop_temporary=<最適化されて出力>)、mysql-5.7.22/sql/sql_table.cc:2196
#20 0x0000000000c9d90b、mysql_execute_command (thd=thd@entry=0x7f9d7c1f6910、first_level=first_level@entry=true)、mysql-5.7.22/sql/sql_parse.cc:3589
#21 0x0000000000ca1edd、mysql_parse (thd=thd@entry=0x7f9d7c1f6910、parser_state=parser_state@entry=0x7f9de01107a0)、mysql-5.7.22/sql/sql_parse.cc:5582
#22 0x0000000000ca2a20 が、dispatch_command (thd=thd@entry=0x7f9d7c1f6910、com_data=com_data@entry=0x7f9de0110e00、command=COM_QUERY) にあります (mysql-5.7.22/sql/sql_parse.cc:1458)
#23 0x0000000000ca4377、do_command (thd=thd@entry=0x7f9d7c1f6910)、mysql-5.7.22/sql/sql_parse.cc:999
#24 0x0000000000d5ed00 が handle_connection (arg=arg@entry=0x10b8e910) にあり、mysql-5.7.22/sql/conn_handler/connection_handler_per_thread.cc:300 にあります
#25 0x0000000001223d74、pfs_spawn_thread (arg=0x10c48f40)、mysql-5.7.22/storage/perfschema/pfs.cc:2190
#26 /lib64/libpthread.so.0 の start_thread() で 0x00007fa003eeddc5
#27 0x00007fa002aab74d は /lib64/libc.so.6 からの clone() にあります

解決

buf_read_ibuf_merge_pages、buf_read_page_low、fil_io に新しいパラメータ ignore_missing_space を追加します。削除されるスペースを無視することを示します。デフォルト値は false で、ibuf_merge_pages が呼び出されると true に設定されます。 fil_io エラー報告領域では、パラメータが true であるかどうかもさらに判断されます。 true の場合、エラーは報告されず、他のプロセスは続行されます。

または、buf_read_ibuf_merge_pages が buf_read_page_low を呼び出すときに、IORequest::IGNORE_MISSING パラメータを直接渡します。

具体的なコードについては、MariaDB コミットを参照してください: 8edbb1117a9e1fd81fbd08b8f1d06c72efe38f44

影響を受けるバージョン

関連情報を確認してください。この問題は、Bug#19710564 を変更するときにテーブルスペースのバージョンを削除したときに発生しました。

  • MySQL Community Server 5.7.6 で導入され、バージョン 5.7.22 では修正されず、バージョン 8.0.0 で修正されました。
  • MariaDB Server 10.2 が影響を受けます。 MariaDB Server 10.2.9、10.3.2 が修正されました

最適化の提案

次のようにしてパフォーマンスを最適化できます。buf_read_ibuf_merge_pages にエラーのあるスペース ID を記録し、ループ中に次のページのスペース ID を判別し、スペース ID が同じ場合は、対応する ibuf レコードを直接削除します (現在割り当てられている最大スペース ID はシステム テーブルスペースに記録され、スペース ID は 0xFFFFFFF0UL より小さい 4 バイトを占めます。割り当てるときに、システム テーブルスペースに保存されている値を読み取り、一意になるように 1 を追加します)。

要約する

上記はこの記事の全内容です。この記事の内容が皆さんの勉強や仕事に一定の参考学習価値を持つことを願っています。ご質問があれば、メッセージを残してコミュニケーションしてください。123WORDPRESS.COM を応援していただきありがとうございます。

以下もご興味があるかもしれません:
  • MySQL テーブル削除操作の実装 (delete、truncate、drop の違い)
  • MySQL テーブル内の重複データを検索して削除する方法の概要
  • MySql テーブル内の行を削除する実用的な方法
  • MySQL でのテーブルの作成と削除の詳細な例
  • MySQL テーブルを削除するときに外部キー制約を無視するシンプルな実装
  • MySQLテーブルを削除する方法

<<:  Nginx 急ぎ購入 電流制限構成 実装分析

>>:  Vueのシンプルな状態管理ストアモードを理解する方法

推薦する

Linux yum コマンドを使用して mysql8.0 をインストールする方法の詳細なチュートリアル

1. 設置前によく掃除する rpm -pa | grep mysql または rpm -qa | g...

Vue3のいくつかの利点についての簡単な説明

目次1. ソースコード1.1 モノレポ1.2 タイプスクリプト2. パフォーマンス2.1 ソースコー...

システム外のフォント参照とトランジション効果

コードをコピーコードは次のとおりです。 <span style="font-fami...

MySQL binlog の解析

目次1. binlogの紹介2. Binlog関連のパラメータ3. バイナリログの内容を分析するIV...

Vue3における非親子コンポーネント通信の詳細な説明

目次最初の方法アプリ.vueホーム.vueホームコンテンツ.vueデータの応答性レスポンシブプロパテ...

Bootstrap FileInputは画像アップロード機能を実装します

この記事の例では、Bootstrap FileInputの具体的なコードを共有して、画像アップロード...

MySQLデータのセキュリティを確保するための提案

データは企業の中核資産であり、企業にとって最も重要なタスクの 1 つです。注意しないと、データが意図...

VirtualBoxにOpenSuseをインストールする方法

仮想マシンはホストマシンにインストールされます。 CPU とメモリはホスト マシンと共有する必要があ...

Vue2.0の双方向データバインディング原則を手動で実装する

一言で言えば: データハイジャック (Object.defineProperty) + パブリッシュ...

Dockerイメージの作成、保存、読み込み方法

イメージを作成する方法は 3 つあります。既存のイメージに基づいてコンテナを作成する、ローカル テン...

レスポンシブウェブデザインを実現するためにIEでCSS3メディアクエリをサポートする

今日の画面解像度は、320 ピクセル (iPhone) ほど小さいものから、2560 ピクセル以上 ...

幅の比率に応じて高さを変えるCSSを実装するいくつかの方法

[解決策1: パディングの実装]原理:要素の padding の値がパーセンテージの場合、このパーセ...

Linux の netstat コマンドの詳細な紹介

目次1. はじめに2. 出力情報の説明3. netstatの共通パラメータ4. netstatネット...

入力ファイルの制御と美化について

一部のWebサイトでアップロードする場合、「参照」ボタンをクリックすると、[ファイルの選択]ダイアロ...

Docker を使用してフロントエンド アプリケーションをデプロイする方法

Dockerはますます普及しています。環境を軽量かつ柔軟に分離し、容量を拡張し、運用保守管理を容易に...