1. 程式人生 > >MYSQL limt隨著offset增大效率變低

MYSQL limt隨著offset增大效率變低

最近在做一些大資料庫的操作維護的過程中,發現limt隨著offset也就是偏移量增大效率越來越慢,以前也發現過這個問題,但是資料量不大,所以就放那了,但是這次資料量有幾個表是5000到一億的資料量,查一次卡爆了有沒有,好了廢話不多說,讓我們一起來探討下。

 limt 是MySQL 提供的一個方便的分頁查詢的語句,使用起來也很方便

select * from A limt 5       #取A表中的前5條記錄

select * from A limt 5,2    #從A表的第5條之後開始取兩條記錄

我們前面說的偏移量就是limit 的第一個引數,這個引數越大,執行效率越低,我們來試驗下,我這邊有一個表大概有5萬多記錄

SELECT *from `fa_user_prod_account_detail使用者產品賬戶明細` LIMIT 10000,5 #受影響的行: 0時間: 0.005s
SELECT *from `fa_user_prod_account_detail使用者產品賬戶明細` LIMIT 20000,5 #受影響的行: 0 時間: 0.013s
SELECT *from `fa_user_prod_account_detail使用者產品賬戶明細` LIMIT 30000,5 #受影響的行: 0 時間: 0.017s
SELECT *from `fa_user_prod_account_detail使用者產品賬戶明細` LIMIT 40000,5 #受影響的行: 0 時間: 0.024s
SELECT *from `fa_user_prod_account_detail使用者產品賬戶明細` LIMIT 50000,5  #受影響的行: 0 時間: 0.035s

可見確實隨著偏移量的增大查詢的時間也逐步增大。這是因為MySQL並不是跳過offset行,而是取offset+N行,然後返回放棄前offset行,返回N行,當offset特別大,然後單條資料也很大的時候,每次查詢需要獲取的資料就越多,自然就會很慢。那我們怎麼優化這條SQL語句呢,說到查詢,我們肯定會第一時間,想到索引,可是我們要怎麼樣使用索引呢?何況索引也不是越多越好,越多的話在修改和插入的時候會給sql帶來更大的消耗這個時候我們會想到主鍵,因為每個表都會有一個主鍵,並且主鍵是預設的唯一索引我們是不是可以先把要取的這五條的ID查出來,然後再根據ID去取它的值呢?有了思路,那樣這就好辦了。我們可以這樣寫:

select * from `fa_user_prod_account_detail使用者產品賬戶明細` a  inner join (select b.ID from `fa_user_prod_account_detail使用者產品賬戶明細` as b limit 30000,5) c on a.ID=c.ID;
或者
SELECT * FROM `fa_user_prod_account_detail使用者產品賬戶明細`  WHERE ID >=(select  b.ID  from `fa_user_prod_account_detail使用者產品賬戶明細` as b limit 30000, 1) limit 5

第一個是使用了內連結查詢,第二個是子查詢,其實也是變相的內連結查詢,

不同之處是:

第一個先查出要找的五條資料的ID,然後根據5條ID,去查所有的的資訊,

第二個是先查出第一條的ID,然後根據where 條件刪選掉前面的偏移量,取5條資料。

第二條語句可以這麼寫:

SELECT * FROM `fa_user_prod_account_detail使用者產品賬戶明細`  WHERE ID in (  SELECT c.ID from ( (select  b.ID  from `fa_user_prod_account_detail使用者產品賬戶明細` as b limit 30000, 5))as c)
SELECT*FROM `fa_user_prod_account_detail使用者產品賬戶明細` as a  WHERE EXISTS(SELECT c.ID from (select  b.ID  from `fa_user_prod_account_detail使用者產品賬戶明細` as b limit 30000, 5 )as c WHERE a.ID=c.ID)

這樣寫的效率並不高因為in(裡面的sql並不會用到索引),還有這個SQL語句子查詢之所以嵌套了一個是因為limit不能使用在IN/ALL/ANY/SOME中

所以推薦內連結查詢

select * from `fa_user_prod_account_detail使用者產品賬戶明細` a  inner join (select b.ID from `fa_user_prod_account_detail使用者產品賬戶明細` as b limit 30000,5) c on a.ID=c.ID;
或者
SELECT * FROM `fa_user_prod_account_detail使用者產品賬戶明細`  WHERE ID >=(select  b.ID  from `fa_user_prod_account_detail使用者產品賬戶明細` as b limit 30000, 1) limit 5

這兩個查詢都差不多,但是更推薦第一個

下面讓我們看下執行的結果

select * from `fa_user_prod_account_detail使用者產品賬戶明細` a  inner join (select b.ID from `fa_user_prod_account_detail使用者產品賬戶明細` as b limit 10000,5) c on a.ID=c.ID;#時間: 0.004s
select * from `fa_user_prod_account_detail使用者產品賬戶明細` a  inner join (select b.ID from `fa_user_prod_account_detail使用者產品賬戶明細` as b limit 20000,5) c on a.ID=c.ID;#受影響的行: 0時間: 0.004s
select * from `fa_user_prod_account_detail使用者產品賬戶明細` a  inner join (select b.ID from `fa_user_prod_account_detail使用者產品賬戶明細` as b limit 30000,5) c on a.ID=c.ID;#受影響的行: 0時間: 0.006s
select * from `fa_user_prod_account_detail使用者產品賬戶明細` a  inner join (select b.ID from `fa_user_prod_account_detail使用者產品賬戶明細` as b limit 40000,5) c on a.ID=c.ID;#受影響的行: 0時間: 0.008s
select * from `fa_user_prod_account_detail使用者產品賬戶明細` a  inner join (select b.ID from `fa_user_prod_account_detail使用者產品賬戶明細` as b limit 50000,5) c on a.ID=c.ID;#受影響的行: 0時間: 0.007s

可以看到效率確實提升了不止一點點