【推薦】利用延遲關(guān)聯(lián)或者子查詢優(yōu)化超多分頁場景。說明:MySQL并不是挑過offeset行,而是取offset+N行,然后返回放棄前offset行,返回N行,那當(dāng)offset特別大的時候,效率就非常的底下,要么控制返回的總頁數(shù),要么對超過特定閾值的頁數(shù)進行SQL改寫。正例:先快速定位需要獲取的id段,然后再關(guān)聯(lián):SELECT a.* FROM 表1 a,(select id from 表1 where 條件 LIMIT 100000,20) b where a.id = b.id;
-- 優(yōu)化前SQL SELECT 各種字段 FROM`table_name` WHERE 各種條件 LIMIT0,10;
-- 優(yōu)化后SQL SELECT 各種字段 FROM`table_name` main_tale RIGHTJOIN ( SELECT 子查詢只查主鍵 FROM`table_name` WHERE 各種條件 LIMIT0,10; ) temp_table ON temp_table.主鍵 = main_table.主鍵
mysql> select * from test a inner join (select id from testwhere 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 inset (0.38 sec)
時間相差很明顯。為什么會出現(xiàn)上面的結(jié)果?我們看一下select * from test where val=4 limit 300000,5;的查詢過程:查詢到索引葉子節(jié)點數(shù)據(jù)。根據(jù)葉子節(jié)點上的主鍵值去聚簇索引上查詢需要的全部字段值。類似于下面這張圖:像上面這樣,需要查詢300005次索引節(jié)點,查詢300005次聚簇索引的數(shù)據(jù),最后再將結(jié)果過濾掉前300000條,取出最后5條。MySQL耗費了大量隨機I/O在查詢聚簇索引的數(shù)據(jù)上,而有300000次隨機I/O查詢到的數(shù)據(jù)是不會出現(xiàn)在結(jié)果集當(dāng)中的。另外,搜索公眾號互聯(lián)網(wǎng)架構(gòu)師后臺回復(fù)“2T”,獲取一份驚喜禮包。
下面我們實際操作一下來證實上述的推論:為了證實select * from test where val=4 limit 300000,5是掃描300005個索引節(jié)點和300005個聚簇索引上的數(shù)據(jù)節(jié)點,我們需要知道MySQL有沒有辦法統(tǒng)計在一個sql中通過索引節(jié)點查詢數(shù)據(jù)節(jié)點的次數(shù)。我先試了Handler_read_*系列,很遺憾沒有一個變量能滿足條件。我只能通過間接的方式來證實:InnoDB中有buffer pool。里面存有最近訪問過的數(shù)據(jù)頁,包括數(shù)據(jù)頁和索引頁。所以我們需要運行兩個sql,來比較buffer pool中的數(shù)據(jù)頁的數(shù)量。預(yù)測結(jié)果是運行select * from test a inner join (select id from test where val=4 limit 300000,5); 之后,buffer pool中的數(shù)據(jù)頁的數(shù)量遠遠少于select * from test where val=4 limit 300000,5;對應(yīng)的數(shù)量,因為前一個sql只訪問5次數(shù)據(jù)頁,而后一個sql訪問300005次數(shù)據(jù)頁。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%' groupby index_name;Empty set (0.04 sec)
可以看出,目前buffer pool中沒有關(guān)于test表的數(shù)據(jù)頁。
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中關(guān)于test表有4098個數(shù)據(jù)頁,208個索引頁。select * from test a inner join (select id from test where val=4 limit 300000,5) ;為了防止上次試驗的影響,我們需要清空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 testwhere 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 inset (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 inset (0.03 sec)