I.はじめに
まず、MySQLのバージョンを説明します。
MySQLの> を選択したバージョン(); + - --------- + | バージョン()| + - --------- + | 5.7。17 | + - --------- + 1行に セット(0.00秒)
表の構造:
mysqlの> DESC テスト。 + - ------ + --------------------- + ------ + ----- + ----- ---- + ---------------- + | フィールド | タイプ | ヌル | キー | デフォルト | エクストラ | + - ------ + --------------------- + ------ + ----- + ----- ---- + ---------------- + | ID | BIGINT(20)符号なし| NO | PRI | NULL | AUTO_INCREMENT | | ヴァル | INT(10)符号なし | NO | MUL | 0 | | | ソース| INT(10)符号なし | NO | | 0 | | + - ------ + --------------------- + ------ + ----- + ----- ---- + ---------------- + 3行に セット(0.00 秒)
主キーの増加へのid、valの非ユニークなインデックス。
データの注ぎ大量の500万ドルの合計:
MySQLの> を選択し 、カウント(*)からテスト。 + - -------- + | カウント(*)| + - -------- + | 5242882 | + - -------- + 1行に セット(4.25秒)
私たちは、オフセット限界が大の行をオフセットする場合、効率があることを知っています:
MySQLの> を選択 * からテストここヴァル= 4リミット300000、5 。 + - ------- + ----- + -------- + | ID | ヴァル| ソース| + - ------- + ----- + -------- + | 3327622 | 4 | 4 | | 3327632 | 4 | 4 | | 3327642 | 4 | 4 | | 3327652 | 4 | 4 | | 3327662 | 4 | 4 | + - ------- + ----- + -------- + 5行に セット(15.98秒)
同じ目的を達成するために、我々は一般的に次の文を書き換えます。
MySQLの> を選択 * からテスト内側に 参加(選択 IDをからテストここヴァル= 4リミット300000、5 B)に a.id = b.id。
+ - ------- + ----- + -------- + --------- + | ID | ヴァル| ソース| ID | + - ------- + ----- + -------- + --------- + | 3327622 | 4 | 4 | 3327622 | | 3327632 | 4 | 4 | 3327632 | | 3327642 | 4 | 4 | 3327642 | | 3327652 | 4 | 4 | 3327652 | | 3327662 | 4 | 4 | 3327662 | + - ------- + ----- + -------- + --------- + 5行に セット(0.38秒)
時間差は明らかです。
なぜ、上記の結果はありますか?私たちは、テストどこのval = 4 SELECT * FROM見リミット300000,5、 クエリプロセス:
索引リーフ・ノード・データへのクエリ。
インデックスフィールド値のすべてのクエリは、リーフノードクラスタへのマスターキーに応じて必要。
下の絵に似て
最後の5つを取り外し、前に必要、iノード300005回、300005回のクエリデータクラスタ化インデックス、上記のように、最終的な結果は300,000濾過しました。クエリに300000ランダムI / Oデータがセットの結果には表示されませんしながら、MySQLは、データクエリクラスタ化インデックスにランダムI / Oの多くの費用。
推奨:いくつかの広範な原則MySQLのインデックスのB +ツリーの原理と同様に、インデックスを構築します。
きっと誰かが尋ねてきます:インデックスの使用が始まって以来、なぜそのノードに索引リーフ・ノードに沿っていない最初のチェック最後の5つのニーズは、実際のデータを照会するためにクラスタ化インデックスを行きます。唯一の5ランダムI / Oように、プロセスは以下の画像のようになります。
実際に、私はこの質問をお願いしたいと思います。
確認
ここでは、上記の推論を確認するために、実際の動作を見てみましょう。
確認のため
select * from test where val=4 limit 300000,5
是扫描300005个索引节点和300005个聚簇索引上的数据节点,我们需要知道MySQL有没有办法统计在一个sql中通过索引节点查询数据节点的次数。我先试了Handler_read_*系列,很遗憾没有一个变量能满足条件。
我只能通过间接的方式来证实:
InnoDB中有buffer pool。里面存有最近访问过的数据页,包括数据页和索引页。所以我们需要运行两个sql,来比较buffer pool中的数据页的数量。预测结果是运行select * from test a inner join (select id from test where val=4 limit 300000,5) b>之后,buffer pool
中的数据页的数量远远少于
对应的数量,因为前一个sql只访问5次数据页,而后一个sql访问300005次数据页。select * from test where val=4 limit 300000,5;
select * from test where val=4 limit 300000,5 mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('val','primary') and TABLE_NAME like '%test%' group by index_name; Empty set (0.04 sec)
可以看出,目前buffer pool中没有关于test表的数据页。
mysql> select * from test where val=4 limit 300000,5; +---------+-----+--------+ | id | val | source | +---------+-----+--------+ | 3327622 | 4 | 4 | | 3327632 | 4 | 4 | | 3327642 | 4 | 4 | | 3327652 | 4 | 4 | | 3327662 | 4 | 4 | +---------+-----+--------+ 5 rows in set (26.19 sec)
mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('val','primary') and TABLE_NAME like '%test%' group by index_name; +------------+----------+ | index_name | count(*) | +------------+----------+ | PRIMARY | 4098 | | val | 208 | +------------+----------+ 2 rows in set (0.04 sec)
可以看出,此时buffer pool中关于test表有4098个数据页,208个索引页。
select * from test a inner join (select id from test where val=4 limit 300000,5) b>为了防止上次试验的影响,我们需要清空buffer pool,重启mysql。
mysqladmin shutdown /usr/local/bin/mysqld_safe & mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('val','primary') and TABLE_NAME like '%test%' group by index_name; Empty set (0.03 sec)
运行sql:
mysql> select * from test a inner join (select id from test where val=4 limit 300000,5) b on a.id=b.id; +---------+-----+--------+---------+ | id | val | source | id | +---------+-----+--------+---------+ | 3327622 | 4 | 4 | 3327622 | | 3327632 | 4 | 4 | 3327632 | | 3327642 | 4 | 4 | 3327642 | | 3327652 | 4 | 4 | 3327652 | | 3327662 | 4 | 4 | 3327662 | +---------+-----+--------+---------+ 5 rows in set (0.09 sec)
mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('val','primary') and TABLE_NAME like '%test%' group by index_name; +------------+----------+ | index_name | count(*) | +------------+----------+ | PRIMARY | 5 | | val | 390 | +------------+----------+ 2 rows in set (0.03 sec)
我们可以看明显的看出两者的差别:第一个sql加载了4098个数据页到buffer pool,而第二个sql只加载了5个数据页到buffer pool。符合我们的预测。也证实了为什么第一个sql会慢:读取大量的无用数据行(300000),最后却抛弃掉。
而且这会造成一个问题:加载了很多热点不是很高的数据页到buffer pool,会造成buffer pool的污染,占用buffer pool的空间。
遇到的问题
为了在每次重启时确保清空buffer pool,我们需要关闭innodb_buffer_pool_dump_at_shutdown和innodb_buffer_pool_load_at_startup,这两个选项能够控制数据库关闭时dump出buffer pool中的数据和在数据库开启时载入在磁盘上备份buffer pool的数据。
参考资料:
1.https://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/
2.https://dev.mysql.com/doc/refman/5.7/en/innodb-information-schema-buffer-pool-tables.html
作者:zhangyachen
来源:https://dwz.cn/K1Q1cePW