摘抄自:https://mp.weixin.qq.com/s?__biz=MzkxMDI2NTc2OQ==&mid=2247485241&idx=1&sn=3330bf2abc82a857692aaee316824d90
limit偏移量不變,隨着查詢記錄量越來越大,所花費的時間也會越來越多。
limit查詢記錄數不變,隨着查詢偏移的增大,尤其查詢偏移大於10萬以后,查詢時間急劇增加。
原因分析
select * from user where sex = 1 limit 100,10
由於 sex 列是索引列,MySQL會走 sex 這棵索引樹,命中 sex=1 的數據。
然后又由於非聚簇索引中存儲的是主鍵 id 的值,且查詢語句要求查詢所有列,所以這里會發生一個回表的情況,在命中 sex 索引樹中值為1的數據后,拿着它葉子節點上的值也就是主鍵 id 的值去主鍵索引樹上查詢這一行其他列(name、sex)的值,最后返回到結果集中,這樣第一行數據就查詢成功了。
最后這句 SQL 要求limit 100, 10,也就是查詢第101到110個數據,但是 MySQL 會查詢前110行,然后將前100行拋棄,最后結果集中就只剩下了第101到110行,執行結束。
小結一下,在上述的執行過程中,造成 limit 大偏移量執行時間變久的原因有:
-
limit a, b會查詢前a+b條數據,然后丟棄前a條數據
MySQL數據庫的查詢優化器是采用了基於代價的方式,而查詢代價的估算是基於CPU代價和IO代價。如果MySQL在查詢代價估算中,認為全表掃描方式比走索引掃描的方式效率更高的話,就會放棄索引,直接全表掃描。
優化方式
t5表有200萬數據,id為主鍵,text為普通索引
使用覆蓋索引
如果一條SQL語句,通過索引可以直接獲取查詢的結果,不再需要回表查詢,就稱這個索引為覆蓋索引。
在MySQL數據庫中使用explain關鍵字查看執行計划,如果extra這一列顯示Using index,就表示這條SQL語句使用了覆蓋索引。
讓我們來對比一下使用了覆蓋索引,性能會提升多少吧。
沒有使用覆蓋索引
select * from t5 order by text limit 1000000, 10;
這次查詢花了3.690秒,讓我們看一下使用了覆蓋索引優化會提升多少性能吧。
使用了覆蓋索引
select id, `text` from t5 order by text limit 1000000, 10;
從上面的對比中,超大分頁查詢中,使用了覆蓋索引之后,花了0.201秒,而沒有使用覆蓋索引花了3.690秒,提高了18倍多,這在實際開發中,就是一個大的性能優化了。
子查詢優化
因為實際開發中,用SELECT查詢一兩列操作是非常少的,因此上述的覆蓋索引的適用范圍就比較有限。
所以我們可以通過把分頁的SQL語句改寫成子查詢的方法獲得性能上的提升。
select * from t5 where id>=(select id from t5 order by text limit 1000000, 1) limit 10;
其實使用這種方法,提升的效率和上面使用了覆蓋索引基本一致。
但是這種優化方法也有局限性:
-
這種寫法要求主鍵ID必須是連續的
-
Where子句不允許再添加其他條件
延遲關聯
和上述的子查詢做法類似,我們可以使用JOIN,先在索引列上完成分頁操作,然后再回表獲取所需要的列。
select a.* from t5 a inner join (select id from t5 order by text limit 1000000, 10) b on a.id=b.id;