InnoDB は、成熟したクロスプラットフォーム データベース エンジンとして、同期、非同期 IO、IO マージなどを含む、効率的で使いやすい IO インターフェイスのセットを実装しています。この記事では、その内部実装について簡単に紹介します。主なコードは os0file.cc ファイルに集中しています。この記事の分析はデフォルトで MySQL 5.6、CentOS 6、および gcc 4.8 に基づいています。他のバージョンに関する情報は別途指摘されます。
WALテクノロジー: ログファーストテクノロジー、基本的にすべてのデータベースがこのテクノロジーを使用します。簡単に言うと、データ ブロックを書き込む必要がある場合、データベース フロントエンド スレッドはまず対応するログをディスクに書き込み (バッチ シーケンシャル書き込み)、その後クライアントに操作が成功したことを通知します。データ ブロックのデータ ブロック (離散ランダム書き込み) 次に、それをバックグラウンド IO スレッドに置きます。このテクノロジーを使用すると、ディスク書き込み操作が 1 回増えますが、ログがバッチで順次書き込まれるため、効率が非常に高く、クライアントは迅速に応答を得ることができます。さらに、実際のデータ ブロックがディスクに書き込まれる前にデータベースがクラッシュした場合、データベースは再起動時にデータを失うことなくクラッシュ リカバリにログを使用できます。
データの事前読み取り: データブロックAに「隣接」するデータブロックBとCが読み込まれると、BとCも読み込まれる可能性が高くなりますので、読み出すことができます Bの場合、読み出しますこれがデータ先読み技術です。ここでいう隣接とは、物理的な隣接性と論理的な隣接性の2つの意味があります。基礎となるデータ ファイル内の隣接関係は、物理的隣接と呼ばれます。データファイルが隣接していないが、論理的に隣接している場合(id=1のデータとid=2のデータは論理的に隣接しているが、物理的に隣接しているとは限らず、同じファイル内の異なる場所に存在する場合もあります)、論理的隣接と呼ばれます。
ファイルを開くモード: Open システム コールには、O_DIRECT、O_SYNC、およびデフォルト モードの 3 つの共通モードがあります。 O_DIRECT モードは、ファイルに対する後続の操作がファイル システム キャッシュを使用しないことを意味します。別の観点から見ると、ユーザー モードは、ファイルの書き込みに O_DIRECT モードを使用します。成功した場合、データは実際にディスクに書き込まれます (ディスクに付属のキャッシュに関係なく)。ファイルの読み取りには O_DIRECT モードが使用されます。各読み取り操作は実際にはディスクから読み取られます。ファイルシステムのキャッシュ。 O_SYNC はオペレーティング システムのキャッシュを使用することを意味し、ファイルの読み取りと書き込みはカーネルを経由しますが、このモードでは、データが書き込まれるたびにデータがディスクに書き込まれることも保証されます。デフォルト モードは O_SYNC モードと似ていますが、データを書き込んだ後、ホストがダウンしたときにデータがまだファイル システムに存在する可能性があるという点が異なります。失われます。さらに、書き込み操作では、変更または追加されたデータをディスクに書き込むだけでなく、ファイルのメタ情報もディスクに書き込む必要があります。両方の部分がディスクに書き込まれる場合にのみ、データが失われることはありません。 O_DIRECT モードでは、ファイルのメタ情報がディスクに書き込まれることが保証されないため (ただし、ほとんどのファイル システムでは保証されます、バグ #45892)、他の操作が実行されない場合、O_DIRECT でファイルを書き込んだ後に損失が発生するリスクがあります。 O_SYNC は、データとメタ情報の両方がディスク上に配置されることを保証します。どちらのデータもデフォルト モードでは保証されません。
関数 fsync を呼び出した後は、データとログがすべてディスクに書き込まれることが保証されます。そのため、O_DIRECT およびデフォルト モードを使用してファイルを開く場合は、データの書き込み後に fsync 関数を呼び出す必要があります。
同期 IO: (Linux 上で) 一般的に使用される読み取り/書き込み関数は、このタイプの IO です。その特徴は、関数の実行時に呼び出し元が関数の実行が完了するまで待機することです。メッセージ通知機構は関数が を返すため、後で戻り値を直接確認することで操作が成功したかどうかを知ることができます。このタイプの IO 操作はプログラミングが比較的簡単で、すべての操作を同じスレッドで完了できますが、データベース システムでは、たとえばログインするために特定のデータが緊急に必要な場合に呼び出すのが適しています。 WAL はクライアントに返される必要があります。ダウンロードする前に、同期 IO 操作が実行されます。
非同期 IO: データベースでは、バックグラウンドでデータ ブロックをブラッシングする IO スレッドは、基本的に非同期 IO を使用します。データベース フロントエンド スレッドは、他の作業に戻る前にブラシ ブロック リクエストを非同期 IO キューに送信するだけで済みます。一方、バックグラウンド スレッドの IO スレッドは、これらの送信されたリクエストが完了したかどうかを定期的にチェックし、完了している場合は、いくつかの処理を実行します。後続の処理作業。同時に、非同期 IO はリクエストのバッチで送信されることがよくあります。異なるリクエストが同じファイルにアクセスし、連続したオフセットを持つ場合、それらのリクエストは 1 つの IO リクエストにマージされる可能性があります。たとえば、最初のリクエストはファイル 1、オフセット 100 から始まる 200 バイトのデータを読み取り、2 番目のリクエストはファイル 1、オフセット 300 から始まる 100 バイトのデータを読み取ります。その後、2 つのリクエストを「ファイル 1 の読み取り、300 バイト」にマージできます。オフセット 100 から始まるデータ。データの事前読み取りにおける論理的な事前読み取りでも、多くの場合、非同期 IO テクノロジが使用されます。 Linux 上の現在の非同期 IO ライブラリでは、ファイルを O_DIRECT モードで開く必要があり、データ ブロックが保存されるメモリ アドレス、ファイルの読み取りと書き込みのオフセット、読み取りと書き込みのデータ量は整数倍である必要があります。ファイル システムの論理ブロック サイズ。ファイル システムの論理ブロック サイズは、
と同様のステートメントを使用してクエリできます。上記の 3 つがファイル システムの論理ブロック サイズの整数倍ではない場合、読み取り関数と書き込み関数を呼び出すときに EINVAL エラーが報告されます。ただし、ファイルが O_DIRECT を使用して開かれていない場合、プログラムは実行できますが、同期 IO に低下し、io_submit 関数呼び出しをブロックします。 sudo blockdev --getss /dev/sda5
InnoDB では、システムに pred/pwrite 関数 (os_file_read_func
および os_file_write_func
) がある場合は、それらを読み取りと書き込みに使用します。それ以外の場合は、 lseek+読み取り/書き込みソリューション。これは InnoDB 同期 IO です。 pread/pwrite のドキュメントを見ると、これら 2 つの関数はファイル ハンドルのオフセットを変更せず、スレッド セーフであるため、マルチスレッド環境で推奨されることがわかります。lseek+read/write ソリューションには独自の関数が必要です。ミューテックス保護。同時実行条件下では、カーネル状態の頻繁な障害がパフォーマンスに一定の影響を与えます。 os_file_read_func
和os_file_write_func
),则使用它们进行读写,否则使用lseek+read/write方案。这个就是InnoDB同步IO。查看pread/pwrite文档可知,这两个函数不会改变文件句柄的偏移量且线程安全,所以多线程环境下推荐使用,而lseek+read/write方案则需要自己使用互斥锁保护,在高并发情况下,频繁的陷入内核态,对性能有一定影响。
在InnoDB中,使用open系统调用打开文件(os_file_create_func
),模式方面除了O_RDONLY(只读),O_RDWR(读写),O_CREAT(创建文件)外,还使用了O_EXCL(保证是这个线程创建此文件)和O_TRUNC(清空文件)。默认情况下(数据库不设置为只读模式),所有文件都以O_RDWR模式打开。innodb_flush_method这个参数比较重要,重点介绍一下:
如果innodb_flush_method设置了O_DSYNC,日志文件(ib_logfileXXX)使用O_SYNC打开,因此写完数据不需要调用函数fsync刷盘,数据文件(ibd)使用default模式打开,因此写完数据需要调用fsync刷盘。
如果innodb_flush_method设置了O_DIRECT,日志文件(ib_logfileXXX)使用default模式打开,写完数据需要调用fsync函数刷盘,数据文件(ibd)使用O_DIRECT模式打开,写完数据需要调用fsync函数刷盘。
如果innodb_flush_method设置了fsync或者不设置,数据文件和日志文件都使用default模式打开,写完数据都需要使用fsync来刷盘。
如果innodb_flush_method设置为O_DIRECT_NO_FSYNC,文件打开方式与O_DIRECT模式类似,区别是,数据文件写完后,不调用fsync函数来刷盘,主要针对O_DIRECT能保证文件的元数据也落盘的文件系统。
InnoDB目前还不支持使用O_DIRECT模式打开日志文件,也不支持使用O_SYNC模式打开数据文件。
注意,如果使用linux native aio(详见下一节),innodb_flush_method一定要配置成O_DIRECT,否则会退化成同步IO(错误日志中不会有任务提示)。
InnoDB使用了文件系统的文件锁来保证只有一个进程对某个文件进行读写操作(os_file_lock
),使用了建议锁(Advisory locking),而不是强制锁(Mandatory locking),因为强制锁在不少系统上有bug,包括linux。在非只读模式下,所有文件打开后,都用文件锁锁住。
InnoDB中目录的创建使用递归的方式(os_file_create_subdirs_if_needed
和os_file_create_directory
)。例如,需要创建/a/b/c/这个目录,先创建c,然后b,然后a,创建目录调用mkdir函数。此外,创建目录上层需要调用os_file_create_simple_func
函数,而不是os_file_create_func
,需要注意一下。
InnoDB也需要临时文件,临时文件的创建逻辑比较简单(os_file_create_tmpfile
),就是在tmp目录下成功创建一个文件后直接使用unlink函数释放掉句柄,这样当进程结束后(不管是正常结束还是异常结束),这个文件都会自动释放。InnoDB创建临时文件,首先复用了server层函数mysql_tmpfile的逻辑,后续由于需要调用server层的函数来释放资源,其又调用dup函数拷贝了一份句柄。
如果需要获取某个文件的大小,InnoDB并不是去查文件的元数据(stat
函数),而是使用lseek(file, 0, SEEK_END)
的方式获取文件大小,这样做的原因是防止元信息更新延迟导致获取的文件大小有误。
InnoDB会预分配一个大小给所有新建的文件(包括数据和日志文件),预分配的文件内容全部置为零(os_file_set_size
os_file_create_func
)。ファイル)、O_EXCL (このスレッドがこのファイルを作成したことを確認します)、および O_TRUNC (ファイルをクリアします)。デフォルトでは (データベースは読み取り専用モードに設定されていません)、すべてのファイルは O_RDWR モードで開かれます。 innodb_flush_method のパラメータの方が重要です。
os_file_lock
)、代わりに勧告ロック (勧告ロック) を使用します。強制ロック (強制ロック)。強制ロックには Linux を含む多くのシステムにバグがあるためです。非読み取り専用モードでは、すべてのファイルは、開かれた後にファイル ロックでロックされます。 🎜🎜 InnoDB でのディレクトリの作成には再帰 (os_file_create_subdirs_if_needed
および os_file_create_directory
) が使用されます。たとえば、ディレクトリ /a/b/c/ を作成する必要がある場合、最初に c、次に b、次に a を作成し、ディレクトリを作成して mkdir 関数を呼び出します。また、ディレクトリの上位層を作成するには、os_file_create_func
の代わりに os_file_create_simple_func
関数を呼び出す必要があることに注意してください。 🎜🎜 InnoDB には一時ファイルも必要です。一時ファイルの作成ロジックは比較的単純です (os_file_create_tmpfile
)。tmp ディレクトリにファイルを作成した後、unlink 関数を直接使用してハンドルを解放します。プロセスが終了すると(正常終了、異常終了に関わらず)、このファイルは自動的に解放されます。 InnoDB は一時ファイルを作成するときに、最初にサーバー層関数 mysql_tmpfile のロジックを再利用します。その後、サーバー層関数を呼び出してリソースを解放する必要があるため、dup 関数を呼び出してハンドルをコピーします。 🎜🎜特定のファイルのサイズを取得する必要がある場合、InnoDB はファイルのメタデータをチェック (stat
関数) せず、lseek(file, 0, SEEK_END) を使用します。 code> ファイルサイズを取得する理由は、メタ情報の更新遅延により不正なファイルサイズが取得されることを防ぐためです。 🎜🎜 InnoDB は、新しく作成されたすべてのファイル (データ ファイルとログ ファイルを含む) にサイズを事前に割り当て、現在のファイルがいっぱいになると、事前に割り当てられたすべてのファイルの内容をゼロに設定します (<code>os_file_set_size
)。もう一度延長してください。さらに、ログ ファイルが作成されるとき、つまり install_db フェーズ中に、割り当ての進行状況が 100MB 間隔でエラー ログに出力されます。 🎜🎜一般的に従来のIO操作や同期IOは比較的シンプルですが、InnoDBでは基本的にデータファイルの書き込みには非同期IOが使用されます。 🎜MySQL は Linux ネイティブ AIO よりも前に誕生したため、MySQL 非同期 IO コードで非同期 IO を実装するには 2 つのソリューションがあります。
1 つ目は、オリジナルのシミュレートされた aio です。InnoDB は、Linux ネイティブの air がインポートされる前、および air をサポートしていない一部のシステムで aio メカニズムをシミュレートしました。非同期の読み取りおよび書き込みリクエストが送信されると、そのリクエストはキューに入れられて返されるだけで、プログラムは他のことを行うことができます。バックグラウンドにはいくつかの非同期 IO 処理スレッドがあり (innobase_read_io_threads および innobase_write_io_threads の 2 つのパラメータによって制御されます)、これらのスレッドは継続的にこのキューからリクエストを取り出し、同期 IO を使用して読み取りおよび書き込みリクエストと読み取りおよび書き込み後の作業を完了します。完成しました。
もう1つはネイティブアイオです。現在、Linux 上の io_submit、io_getevents などの関数を使用して完了します (glibc aio は使用されません。これもシミュレートされています)。 io_submit を使用してリクエストを送信し、io_getevents を使用してリクエストを待ちます。さらに、ウィンドウ プラットフォームには、対応する独自の aio もありますが、ここでは紹介しません。ウィンドウ テクノロジ スタックを使用する場合、データベースは sqlserver を使用する必要があります。現在、他のプラットフォーム (Linux と Windows を除く) は Simulate aio のみを使用できます。
まず、いくつかの一般的な関数と構造を紹介し、次に Linux 上の Simulate alo と Native aio を詳しく紹介します。
グローバル配列は os0file.cc で定義され、タイプは os_aio_array_t
です。これらの配列は、読み取りおよび書き込み要求をキャッシュするために Simulate aio によって使用されるキューです。配列の各要素は os_aio_slot_t code> Type。各 IO リクエストのタイプ、ファイルの fd、オフセット、読み取られるデータの量、IO リクエストが開始された時刻、IO リクエストが完了したかどうかなどが記録されます。さらに、Linux ネイティブ io の struct iocb も <code>os_aio_slot_t
にあります。配列構造 os_aio_slot_t
は、使用されたデータ要素 (os_aio_slot_t
) の数、空かどうか、いっぱいかどうかなどの統計情報を記録します。このようなグローバル配列は合計 5 つあり、非同期データ ファイル読み取りリクエスト (os_aio_read_array
)、データ ファイル書き込み非同期リクエスト (os_aio_write_array
)、およびログ ファイルを保存するために使用されます。非同期書き込みリクエスト (os_aio_log_array
)、バッファ書き込み非同期リクエスト (os_aio_ibuf_array
)、データ ファイルの同期読み取りおよび書き込みリクエスト (os_aio_sync_array
)。ログ ファイルのデータ ブロックの書き込みは同期 IO ですが、ここでログの書き込みに非同期リクエスト キュー (os_aio_log_array
) を割り当てるのはなぜですか?その理由は、チェックポイント情報を InnoDB ログ ファイルのログ ヘッダーに記録する必要があるためです。現在、チェックポイント情報の読み取りと書き込みは、それほど緊急ではないため、依然として非同期 IO を使用して実装されています。ウィンドウ プラットフォームでは、特定のファイルに非同期 IO が使用されている場合、そのファイルは同期 IO を使用できないため、データ ファイルの同期読み取りおよび書き込み要求キュー (os_aio_sync_array
) が導入されます。ログ ファイルを非同期リクエスト キューから読み取る必要はありません。ログを読み取る必要があるのはクラッシュ リカバリ中にのみであり、クラッシュ リカバリを実行するときはデータベースがまだ使用できないため、非同期読み取りモードに入る必要はありません。 。ここで注意すべき点は、変数 innobase_read_io_threads と innobase_write_io_threads の 2 つのパラメーターが何であっても、os_aio_read_array
と os_aio_write_array
は 1 つだけであるということです。ただし、os_aio_slot_tLinux では、それに応じて要素が増加し、変数が 1 増加し、要素の数が 256 増加します。たとえば、innobase_read_io_threads=4 の場合、os_aio_read_array 配列は 4 つの部分に分割され、各部分には 256 個の要素があり、各部分には独自の独立したロック、セマフォ、統計変数があり、4 つのスレッドをシミュレートするために使用されます。innobase_write_io_threads も同様です。ここから、各非同期読み取り/書き込みスレッドがキャッシュできる読み取りおよび書き込みリクエストの上限が 256 であることもわかります。この数を超えると、後続の非同期リクエストは待機する必要があります。 256 は、非同期 IO 同時実行数に対する InnoDB レイヤーの制御として理解できます。また、ファイル システム レイヤーとディスク レベルでの長さ制限もあります。<code>cat /sys/block/sda/queue/nr_requests
を使用します。 > と はそれぞれ cat /sys/block/sdb/queue/nr_requests
クエリです。 os_aio_array_t
,这些数组就是Simulate aio用来缓存读写请求的队列,数组的每一个元素是os_aio_slot_t
类型,里面记录了每个IO请求的类型,文件的fd,偏移量,需要读取的数据量,IO请求发起的时间,IO请求是否已经完成等。另外,Linux native io中的struct iocb也在os_aio_slot_t
中。数组结构os_aio_slot_t
中,记录了一些统计信息,例如有多少数据元素(os_aio_slot_t
)已经被使用了,是否为空,是否为满等。这样的全局数组一共有5个,分别用来保存数据文件读异步请求(os_aio_read_array
),数据文件写异步请求(os_aio_write_array
),日志文件写异步请求(os_aio_log_array
),insert buffer写异步请求(os_aio_ibuf_array
),数据文件同步读写请求(os_aio_sync_array
)。日志文件的数据块写入是同步IO,但是这里为什么还要给日志写分配一个异步请求队列(os_aio_log_array
)呢?原因是,InnoDB日志文件的日志头中,需要记录checkpoint的信息,目前checkpoint信息的读写还是用异步IO来实现的,因为不是很紧急。在window平台中,如果对特定文件使用了异步IO,就这个文件就不能使用同步IO了,所以引入了数据文件同步读写请求队列(os_aio_sync_array
)。日志文件不需要读异步请求队列,因为只有在做奔溃恢复的时候日志才需要被读取,而做崩溃恢复的时候,数据库还不可用,因此完全没必要搞成异步读取模式。这里有一点需要注意,不管变量innobase_read_io_threads和innobase_write_io_threads两个参数是多少,os_aio_read_array
和os_aio_write_array
都只有一个,只不过数据中的os_aio_slot_t
元素会相应增加,在linux中,变量加1,元素数量增加256。例如,innobase_read_io_threads=4,则os_aio_read_array数组被分成了四部分,每一个部分256个元素,每个部分都有自己独立的锁、信号量以及统计变量,用来模拟4个线程,innobase_write_io_threads类似。从这里我们也可以看出,每个异步read/write线程能缓存的读写请求是有上限的,即为256,如果超过这个数,后续的异步请求需要等待。256可以理解为InnoDB层对异步IO并发数的控制,而在文件系统层和磁盘层面也有长度限制,分别使用cat /sys/block/sda/queue/nr_requests
和cat /sys/block/sdb/queue/nr_requests
查询。os_aio_init
在InnoDB启动的时候调用,用来初始化各种结构,包括上述的全局数组,还有Simulate aio中用的锁和互斥量。os_aio_free
则释放相应的结构。os_aio_print_XXX
系列的函数用来输出aio子系统的状态,主要用在show engine innodb status
语句中。
Simulate aio相对Native aio来说,由于InnoDB自己实现了一套模拟机制,相对比较复杂。
入口函数为os_aio_func
os_aio_init
は、InnoDB が開始され、前述のグローバル配列や Simulate aio で使用されるロックやミューテックスなどのさまざまな構造を初期化するときに呼び出されます。 os_aio_free
は、対応する構造を解放します。 os_aio_print_XXX
一連の関数は、aio サブシステムのステータスを出力するために使用され、主に show Engine innodb status
ステートメントで使用されます。
os_aio_func
です。デバッグ モードでは、データ ブロックが保存されているメモリ アドレスなどのパラメータがチェックされます。 、およびファイル読み取り 書き込まれたオフセットと読み書きされたデータ量が OS_FILE_LOG_BLOCK_SIZE の整数倍であるかどうか。ただし、Simulate aio は最終的に同期 IO を使用し、ファイルを開くモードで O_DIRECT が使用されているかどうかはチェックされません。ファイルを開くには O_DIRECT を使用する必要があります。 🎜検証に合格した後、os_aio_array_reserve_slot
が呼び出され、この IO リクエストが特定のバックグラウンド IO 処理スレッド (innobase_xxxx_io_threads によって割り当てられますが、実際には同じグローバル配列内にあります) に割り当てられます。バックグラウンドの IO スレッド処理を容易にするための IO リクエストの関連情報。 IO リクエスト タイプが同じで、同じファイルがリクエストされ、オフセットが比較的近い場合 (デフォルトでは、オフセットの差は 1M 以内)、InnoDB は後続の手順を容易にするために 2 つのリクエストを同じ IO スレッドに割り当てます。マージ。 os_aio_array_reserve_slot
,作用是把这个IO请求分配到某一个后台io处理线程(innobase_xxxx_io_threads分配的,但其实是在同一个全局数组中)中,并把io请求的相关信息记录下来,方便后台io线程处理。如果IO请求类型相同,请求同一个文件且偏移量比较接近(默认情况下,偏移量差别在1M内),则InnoDB会把这两个请求分配到同一个io线程中,方便在后续步骤中IO合并。
提交IO请求后,需要唤醒后台io处理线程,因为如果后台线程检测到没有IO请求,会进入等待状态(os_event_wait
)。
至此,函数返回,程序可以去干其他事情了,后续的IO处理交给后台线程了。
介绍一下后台IO线程怎么处理的。
InnoDB启动时,后台IO线程会被启动(io_handler_thread
)。其会调用os_aio_simulated_handle
从全局数组中取出IO请求,然后用同步IO处理,结束后,需要做收尾工作,例如,如果是写请求的话,则需要在buffer pool中把对应的数据页从脏页列表中移除。
os_aio_simulated_handle
首先需要从数组中挑选出某个IO请求来执行,挑选算法并不是简单的先进先出,其挑选所有请求中offset最小的请求先处理,这样做是为了后续的IO合并比较方便计算。但是这也容易导致某些offset特别大的孤立请求长时间没有被执行到,也就是饿死,为了解决这个问题,在挑选IO请求之前,InnoDB会先做一次遍历,如果发现有请求是2s前推送过来的(也就是等待了2s),但是还没有被执行,就优先执行最老的请求,防止这些请求被饿死,如果有两个请求等待时间相同,则选择offset小的请求。
os_aio_simulated_handle
接下来要做的工作就是进行IO合并,例如,读请求1请求的是file1,offset100开始的200字节,读请求2请求的是file1,offset300开始的100字节,则这两个请求可以合并为一个请求:file1,offset100开始的300字节,IO返回后,再把数据拷贝到原始请求的buffer中就可以了。写请求也类似,在写操作之前先把需要写的数据拷贝到一个临时空间,然后一次写完。注意,只有在offset连续的情况下IO才会合并,有间断或者重叠都不会合并,一模一样的IO请求也不会合并,所以这里可以算是一个可优化的点。
os_aio_simulated_handle
如果发现现在没有IO请求,就会进入等待状态,等待被唤醒
综上所述,可以看出IO请求是一个一个的push的对立面,每push进一个后台线程就拿去处理,如果后台线程优先级比较高的话,IO合并效果可能比较差,为了解决这个问题,Simulate aio提供类似组提交的功能,即一组IO请求提交后,才唤醒后台线程,让其统一进行处理,这样IO合并的效果会比较好。但这个依然有点小问题,如果后台线程比较繁忙的话,其就不会进入等待状态,也就是说只要请求进入了队列,就会被处理。这个问题在下面的Native aio中可以解决。
总体来说,InnoDB实现的这一套模拟机制还是比较安全可靠的,如果平台不支持Native aio则使用这套机制来读写数据文件。
如果系统安装了libaio库且在配置文件里面设置了innodb_use_native_aio=on则启动时候会使用Native aio。
入口函数依然为os_aio_func
,在debug模式下,依然会检查传入的参数,同样不会检查文件是否以O_DIRECT模式打开,这算是一个有点风险的点,如果用户不知道linux native aio需要使用O_DIRECT模式打开文件才能发挥出aio的优势,那么性能就不会达到预期。建议在此处做一下检查,有问题输出到错误日志。
检查通过之后,与Simulated aio一样,调用os_aio_array_reserve_slot
,把IO请求分配给后台线程,分配算法也考虑了后续的IO合并,与Simulated aio一样。不同之处,主要是需要用IO请求的参数初始化iocb这个结构。IO请求的相关信息除了需要初始化iocb外,也需要在全局数组的slot中记录一份,主要是为了在os_aio_print_XXX
os_event_wait
) に入るからです。 。
バックグラウンド IO スレッドがどのように処理されるかを紹介します。
io_handler_thread
)。 os_aio_simulated_handle
を呼び出してグローバル配列から IO リクエストを取り出し、その後、書き込みリクエストの場合、同期 IO を使用して処理を行う必要があります。の場合、対応するリクエストをバッファ プールに入れる必要があります。データ ページはダーティ ページ リストから削除されます。 🎜🎜🎜🎜os_aio_simulated_handle
まず、実行する IO リクエストを配列から選択する必要があります。選択アルゴリズムは単純な先入れ先出しではなく、最小のオフセットを持つリクエストを選択します。これは、その後の IO マージの計算を容易にするためです。ただし、この問題を解決するために、InnoDB は、IO リクエストを選択する前に、最初にトラバーサルを実行します。リクエストが 2 秒前に見つかった場合、プッシュされた (つまり 2 秒間待機した) がまだ実行されていない場合、これらのリクエストが枯渇するのを防ぐために、最も古いリクエストが最初に実行されます (リクエストが 2 つある場合)。待ち時間が同じリクエストの場合、オフセットが小さいリクエストが選択されます。 🎜🎜🎜🎜os_aio_simulated_handle
次に行うことは、IO マージを実行することです。たとえば、読み取りリクエスト 1 は、オフセット 100 から始まる 200 バイトのファイル 1 をリクエストし、読み取りリクエスト 2 は、オフセット 300 から始まる 100 バイトのファイル 1 をリクエストします。次に、これら 2 つのリクエストを 1 つのリクエスト (file1、offset100 から始まる 300 バイト) にマージできます。IO が戻ったら、データを元のリクエストのバッファにコピーするだけです。書き込みリクエストも同様で、書き込み操作の前に、書き込まれるデータが一時領域にコピーされてから、一度に書き込まれます。 IO はオフセットが連続している場合にのみマージされることに注意してください。中断または重複がある場合、それらはマージされないため、これは最適化ポイントと見なすことができます。 🎜🎜🎜🎜os_aio_simulated_handle
現在IOリクエストが無いことが分かると待機状態に入り覚醒を待ちます🎜🎜os_aio_func
であり、ファイルが正しいかどうかもチェックされません。 Linux ネイティブの aio を利用するには、O_DIRECT モードを使用してファイルを開く必要があることをユーザーが認識していない場合、これは少し危険です。パフォーマンスは期待どおりではありません。ここを確認して問題があればエラーログに出力することをお勧めします。 🎜🎜🎜🎜シミュレートされた aio と同様に、チェックに合格した後、os_aio_array_reserve_slot
を呼び出して、IO リクエストをバックグラウンド スレッドに割り当てます。割り当てアルゴリズムでは、シミュレートされた aio と同様に、後続の IO マージも考慮されます。主な違いは、iocb 構造体を IO リクエストのパラメータで初期化する必要があることです。 iocb の初期化に加えて、IO リクエストの関連情報もグローバル配列のスロットに記録する必要があります。これは、主に os_aio_print_XXX
一連の関数での統計の便宜のためです。 🎜🎜🎜🎜 io_submit を呼び出してリクエストを送信します。 🎜🎜🎜🎜この時点で、関数は戻り、プログラムは他のことを実行できるようになり、後続の IO 処理はバックグラウンド スレッドに渡されます。 🎜次はバックグラウンド IO スレッドです。 🎜Simulate aio と同様に、InnoDB の開始時にバックグラウンド IO スレッドも開始されます。 Linux ネイティブの aio の場合、後続の呼び出しは同様ですが、基本的な実装は io_getevents 関数を呼び出して IO リクエストが完了するのを待つだけです。タイムアウトは 0.5 秒です。これは、0.5 秒以内に IO リクエストが完了しない場合、関数は戻り、待機するために io_getevents を呼び出し続けます。もちろん、待機する前に、サーバーが閉じているかどうかを判断し、閉じている場合は、出口。 os_aio_linux_handle
这个函数。这个函数的作用与os_aio_simulated_handle
もう 1 つの違いは、IO リクエストがない場合、Simulate aio は待機状態になるのに対し、Native aio は 0.5 秒ごとに起動し、いくつかのチェックを行ってから待機し続けることです。したがって、新しいリクエストが来たとき、シミュレートされた aio はユーザー スレッドを起動する必要がありますが、ネイティブ aio は起動する必要がありません。さらに、サーバーのシャットダウン時に Simulate aio も起動する必要がありますが、Native aio は起動しません。