ページを読み取るときは、最初にページをディスクからメモリに読み取り、次に CPU がデータを処理するのを待つ必要があることは誰もが知っています。ディスクからメモリにデータを読み取るプロセスは非常に遅いため、読み取ったページをキャッシュする必要があるため、MySQL にはページをキャッシュするためのバッファ プールがあります。
まず、MySQL は起動時にオペレーティング システムから連続メモリ領域を申請し、この領域をバッファ プールとして使用します。キャッシュされたページをバッファプールに入れて管理します。
mysql> show variables like 'innodb_buffer_pool_size'; +-------------------------+-----------+ | Variable_name | Value | +-------------------------+-----------+ | innodb_buffer_pool_size | 134217728 | +-------------------------+-----------+ 1 row in set, 1 warning (0.00 sec)
デフォルトは 134217728 バイト、つまり 128MB であることがわかります。申請するキャッシュ サイズが 16 KB の倍数である場合、各ページ サイズは 16 KB であるため、断片化の問題は発生しません。
各ページには、バッファ プールに格納される、対応する制御ブロック情報が含まれています。各制御ブロックは各ページを管理します (各ページを参照するためにアドレスを使用します)。制御ブロックはページに関する情報を保存するために使用されます。制御ブロックの占有サイズは innodb_buffer_pool_size には含まれません。 MySQL は起動時に追加のスペースを適用します。
スペースを完全に利用できないため、制御ブロックとキャッシュ ページの間に不規則な断片化が発生します。 MySQL がオペレーティング システムに適用するメモリ領域には、一定サイズの制御ブロック領域が必要ですが、具体的なサイズは決定できないため、使用できない領域が生じることは避けられません。
フリーリンクリストはその名の通り、フリーキャッシュページを管理するリンクリストで、キャッシュページを使用しない場合はそのコントロールブロックが接続されます。無料のリンクリスト。
#コントロール ブロックをベース ノードを介して接続してフリー リンク リストを形成し、フリー ページ数などの基本情報を保存します。
ディスクからバッファ プールにページを読み取るとき、空き制御ブロックを取得し、対応するキャッシュ ページの基本情報を書き込みます。
MySQL はどのようにしてバッファ プール内のページに素早くアクセスし、対応するページがバッファ プールにキャッシュされているかどうかを確認するのでしょうか?
これは、Java のハッシュマップであるハッシュ テーブルを使用しています。テーブル スペースのページ番号が処理されてハッシュ キー値が形成され、その値がバッファ プール内のキャッシュ ページのアドレスになります。 。
この章を知ったときは、まず自分の理解とかなり違っていて、その後の MVCC で本当に衝撃を受けました。 eye-opener. 一度勉強してからまとめたものなので、比較的簡潔にまとまっています。
SQL ステートメントを使用して特定のレコードを変更する場合、特定のページまたは複数のページを変更します。ページを変更する場合、対応する変更をディスクに直接行うことはありません。 IO が遅すぎるため、最初に変更されたページ (略してダーティ ページ) をリンクします。これはフリー リンク リストに似ています。つまり、ベース ノードがダーティ ページに対応する制御ブロックをまとめて接続します。
このフラッシュ リンク リストは、まだページをディスクに更新していないリンク リストを表します。
#LRU リンク リストバッファ プールのサイズが制限されているため、キャッシュ ページのサイズも制限されているため、未使用のページを分離する必要があります消去法を実行します。 MySQL は、削除に LRU メソッドを使用します。 LRU は、最長未使用の削除戦略です。キャッシュされたページをリンクするには、リンク リストを使用します。最近アクセスされたページが先頭に表示され、最もアクセスの少ないページがリンク リストの最後に表示されます。 LRU がいっぱいです。新しいページが挿入されます。リンクされたリストの末尾のページを削除します。 LRU を直接使用します。MySQL が事前読み取りまたはテーブル全体のスキャンを実行すると、大量の低頻度ページが LRU リンク リストに読み込まれます。これにより、高頻度ページが直接削除され、使用頻度の低いページに置き換えられます。MySQL オプティマイザは、クエリによってアクセスされることが予想されるページをメモリ バッファ プールにプリロードして、クエリのパフォーマンスを向上させます。そのため、リンク リストを 2 つの部分に分割する、改良されたパーティション ベースの LRU リンク リストが存在します。 1 つは非常に頻繁に使用される若い領域、もう 1 つはあまり使用されない古い領域です。領域内のページの読み取りがシステム変数 innodb_read_ahead_threshold の値を超える場合、デフォルトは 56 です。つまり、ある領域で 56 ページを超えるページを読み取ると、MySQL は次の領域のすべてのページをメモリに非同期的に読み取ります。
- 線形先読み
バッファ プールがシーケンシャルであるかどうかに関係なく、特定の領域に 13 ページをキャッシュした場合、ページがキャッシュされると、MySQL がトリガーされ、この領域内のすべてのページが MySQL に非同期的に読み込まれます。システム変数 innodb_random_read_ahead を設定すると、ランダムな先読みをオフにすることができます。デフォルトはオフです。
- ランダム先読み
正常来说old区占比是37%,所以young区就占63%,我们可以通过innodb_old_blocks_pct来修改,默认就是37。
我们来讲讲这个基于分区的LRU链表。
首先buffer pool初始化,会将读取的页面直接放进old区。
但是如果我们对于同一个页面的多条记录进行访问的话,我们就会多次访问同一页多次。但是如果我们是全表扫描的话,是可能会将所有页面缓存进缓存池中的,所以MySQL对于其进行优化。
所以MySQL对于当页面第一次读入old区并在一定时间间隔(innodb_old_blocks_pct)内的多次访问来说是不会将其放入young区进行缓存的。innodb_old_blocks_pct的值默认为1000,就是刚来的来一秒内的多次访问是不会将其转移到young区的。
如果多次访问就会将old区的页升级到young区。当young区的页面被访问,只有young链表后1/4的页面被访问时才会将其转置到young区链表头,不然就不会改动,减少一些调整链表的性能损失。
MySQL会启动后台线程进行脏页,也就是修改的页面进行刷新到磁盘。
以下有两种方式刷新脏页:
从LRU的尾部扫描一些页面,刷新其中的脏页到磁盘中。
在LRU链表的old区域尾部,即不经常使用的页面中,后台线程会查找是否存在脏页,如果有,则将其更新至磁盘。控制扫描区域尾部数量的方法是更改系统变量innodb_lru_scan_depth。
从flush链表中更新到磁盘。
我们上面说了flush连接这脏页的控制块,我们就可以将连接这flush链表的脏页进行更新。
疑问:为什么要两种方式更新呢?我刚开始不懂这是我回过头来看的时候就懂了
首先我们脏页是缓存在buffer pool中的,但是我们buffer pool空间是有限的,又因为我们使用的是LRU的方式,又因为从flush链表将脏页同步到磁盘效率实在不高,所以不会很经常去更新脏页。如果我们不更新直接将其从LRU的链表抛弃也就是从缓存池中直接扔了,但是它是脏页就无法同步到磁盘了,同时flush链表链接的也会出现问题。
所以在LRU淘汰很久未使用的页有个前提就是它不是一个脏页。为了淘汰这些页面,我们需要检查LRU链表的末尾是否存在脏页并进行更新。
flush链表更新那就是它的本职工作了,它存这个也是干这个的,应该没有什么问题。
当系统十分繁忙,buffer pool使用量不足的时候,因为磁盘IO太慢了,所以会出现一种情况,就是大量的用户线程也在进行这个同步脏页的活。如果未进行脏页同步并淘汰缓冲池的页面,则无法读取该页面。
我们可以设置多个buffer pool来实现多实例提高性能。
mysql> show variables like 'innodb_buffer_pool_instances'; +------------------------------+-------+ | Variable_name | Value | +------------------------------+-------+ | innodb_buffer_pool_instances | 1 | +------------------------------+-------+ 1 row in set, 1 warning (0.00 sec)
我们可以设置innodb_buffer_pool_instances系统变量来控制实例变量。
但是当buffer pool的大小小于1G的时候,设置2个实例也是没有用的(会被恢复成1个),多实例的情况是建立在大内存的情况下的。
在MySQL5.7.5后,MySQL中的buffer pool的大小是以chunk来分配了,如下图。
一个buffer pool是由多个chunk组成的,所以MySQL向操作系统申请连续的内存空间,就是以chunk的方式来申请的,这样我们可以在MySQL运行时调整buffer pool的大小。在运行时更改chunk大小不可行,并且会造成性能浪费。?
innodb_buffer_pool_size / innodb_buffer_pool_instances = 每个实例buffer pool的大小。
每个实例的大小 / innodb_buffer_pool_chunk_size = 每个实例由多少个chunk构成。
不是弄很明白,怎么动态调整大小,我调整了但是mysqld占用内存大小还是只能重启才能生效,我不会。
show engine innodb status;
以上がMySQL でのページ バッファ プールの読み取りに関する知識ポイントは何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。