溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

MYSQL如何實現(xiàn)ORDER BY和LIMIT

發(fā)布時間:2021-10-29 17:31:47 來源:億速云 閱讀:190 作者:小新 欄目:MySQL數(shù)據(jù)庫

這篇文章主要為大家展示了“MYSQL如何實現(xiàn)ORDER BY和LIMIT”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“MYSQL如何實現(xiàn)ORDER BY和LIMIT”這篇文章吧。

一、MYSQL中的LIMIT和ORACLE中的分頁
在MYSQL官方文檔中描述limit是在結(jié)果集中返回你需要的數(shù)據(jù),它可以盡快的返回需要的行而不用管剩下的行,
在ORACLE中也有相關的語法比如 12C以前的rownun<n,也是達到同樣的效果,同時limit也能做到分頁查詢?nèi)?br/>limit n,m  則代表返回n開始的m行,ORACLE 12C以前也有分頁方式但是相對比較麻煩
那么如果涉及到排序呢?我們需要返回按照字段排序后的某幾行:
MYSQL:
select * from test order by id limit 51,100 
ORACLE 12C以前:
SELECT *
  FROM (SELECT tt.*, ROWNUM AS rowno
          FROM (SELECT t.*
                  FROM test t)
                 ORDER BY id desc) tt
         WHERE ROWNUM <= 100) table_alias
 WHERE table_alias.rowno > 50;


當然如上的語法如果id列有索引那么就簡單了,索引本生就是排序好的,使用索引結(jié)構(gòu)即可,但是如果id列沒有索引呢?
那該如何完成,難道把id列全部排序好在返回需要的行?顯然這樣代價過高,違背了limit中盡快返回需要的行的精神
這樣我們必須使用一種合適的算法來完成,那這里就引入的堆排序和優(yōu)先隊列(Priority Queue 簡稱PQ)。
在MYSQL中執(zhí)行計劃沒有完全的表現(xiàn),執(zhí)行計劃依然為filesort:
mysql> explain select * from testshared3 order by id limit 10,20;
+----+-------------+-------------+------------+------+---------------+------+---------+------+---------+----------+----------------+
| id | select_type | table       | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra          |
+----+-------------+-------------+------------+------+---------------+------+---------+------+---------+----------+----------------+
|  1 | SIMPLE      | testshared3 | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 1023820 |   100.00 | Using filesort |
+----+-------------+-------------+------------+------+---------------+------+---------+------+---------+----------+----------------+
1 row in set, 1 warning (0.02 sec)


但是根據(jù)源碼的提示
DBUG_PRINT("info", ("filesort PQ is applicable"));
DBUG_PRINT("info", ("filesort PQ is not applicable"));
注意這里PQ可能棄用,什么時候棄用看后面
可以看到是否啟用了PQ也就是優(yōu)先隊列的簡寫 
可以再trace中找到相關說明:
[root@testmy tmp]# cat pq.trace |grep "filesort PQ is applicable"
T@2: | | | | | | | | | | info: filesort PQ is applicable


在ORACLE中使用執(zhí)行計劃:
--------------------------------------------------------------------------------
Plan hash value: 1473139430
--------------------------------------------------------------------------------
| Id  | Operation                | Name   | Rows  | Bytes |TempSpc| Cost (%CPU)|
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |        |   100 | 77900 |       | 85431   (1)|
|*  1 |  VIEW                    |        |   100 | 77900 |       | 85431   (1)|
|*  2 |   COUNT STOPKEY          |        |       |       |       |            |
|   3 |    VIEW                  |        |   718K|   524M|       | 85431   (1)|
|*  4 |     SORT ORDER BY STOPKEY|        |   718K|   325M|   431M| 85431   (1)|
|   5 |      TABLE ACCESS FULL   | TEST10 |   718K|   325M|       | 13078   (1)|


這里SORT ORDER BY STOPKEY就代表了排序停止,但是ORACLE沒有源碼沒法確切的證據(jù)使用了
優(yōu)先隊列和堆排序,只能猜測他使用了優(yōu)先隊列和堆排序

二、堆排序和優(yōu)先隊列

--大頂堆特性(小頂堆相似見代碼)
1、必須滿足完全二叉樹
關于完全二叉樹參考
http://blog.itpub.net/7728585/viewspace-2125889/
2、很方便的根據(jù)父節(jié)點的位置計算出兩個葉子結(jié)點的位置
如果父節(jié)點的位置為i/2
左子節(jié)點為 i,右子節(jié)點為i+1
這是完全二叉樹的特性決定
3、所有子節(jié)點都可以看做一個子堆那么所有結(jié)點都有
父節(jié)點>=左子節(jié)點 && 父節(jié)點>=右節(jié)點
4、很明顯的可以找到最大的元素,就是整個堆的根結(jié)點

--堆需要完成操作
堆排序方法也是最優(yōu)隊列的實現(xiàn)方法,MYSQL源碼中明顯的使用了優(yōu)先隊列來優(yōu)化order by limit n ,估計max也是用的這種算法
當然前提是沒有使用到索引的情況下。
根據(jù)這些特性明顯又是一個遞歸的成堆的操作。
參考算法導論第六章,里面的插圖能夠加深理解,這里截取一張構(gòu)建好的大頂堆
MYSQL如何實現(xiàn)ORDER BY和LIMIT

構(gòu)建方法:自下而上的構(gòu)建自左向右構(gòu)建堆,其實就是不斷調(diào)用維護方法的過程
維護方法:使用遞歸的逐級下降的方法進行維護,是整個算法的核心內(nèi)容,搞清楚了維護方法其他任何操作都來自于它。
排序方法:最大元素放到最后,然后逐層下降的方法進行調(diào)整。
數(shù)據(jù)庫中的應用:
order by asc/desc limit n:簡化的排序而已,只是排序前面n就可以了,不用全部排序完成,性能優(yōu)越,數(shù)據(jù)庫分頁查詢大量使用這個算法。參考代碼
max/min :a[1]就是最大值,只能保證a[1]>=a[2]&&a[1]>=a[3]  不能保證a[3]>=a[4],堆建立完成后就可以找到MAX值,但是MYSQL max并沒有使用這個算法

我在代碼中完成了這些操作,代碼中有比較詳細的注釋,這里就不詳細說明了。
我使用了2個數(shù)組用于作為測試數(shù)據(jù)
        int i,a[11]={0,999,3,2,9,34,5,102,90,2222,1}; //測試數(shù)據(jù) a[0]不使用
        int b[11]={0,999,3,2,9,999,888888,102,90,2222,111};//測試數(shù)據(jù) b[0]不使用

分別求a素組的最大值和最小前3位數(shù)字,求b數(shù)組的MAX/MIN值,結(jié)果如下:
gaopeng@bogon:~/datas$ ./a.out 
大頂堆:
order by desc a array limit 3 result:2222 999 102 
max values b array reulst:888888 
小頂堆:
order by asc a array limit 3 result:1 2 3 
min values b array reulst:2 
可以看到?jīng)]問題。


--優(yōu)先隊列:優(yōu)先隊列不同于普通隊列先進先出的規(guī)則,而定義為以某種規(guī)定先出,比如最大先出或者最小先出,這個沒什么難度了,不就和數(shù)據(jù)庫的order
            by limit是一回事嗎?當然是用大頂堆或者小頂堆完成
 
三、MYSQL中優(yōu)先隊列的接口

MYSQL中的優(yōu)先隊列類在
priority_queue.h中的class Priority_queue : public Less
他實現(xiàn)了很多功能,不過其他功能都很簡單主要是堆的維護
下面是MYSQL源碼中的堆的維護代碼
  void heapify(size_type i, size_type last)
  {
    DBUG_ASSERT(i < size());
    size_type largest = i;

    do
    {
      i = largest;
      size_type l = left(i);
      size_type r = right(i);


      if (l < last && Base::operator()(m_container[i], m_container[l]))
      {
        largest = l;
      }


      if (r < last && Base::operator()(m_container[largest], m_container[r]))
      {
        largest = r;
      }


      if (largest != i)
      {
        std::swap(m_container[i], m_container[largest]);
      }
    } while (largest != i);
  }
可以看見實際和我寫的差不多。


四、MYSQL如何判斷是否啟用PQ
一般來說快速排序的效率高于堆排序,但是堆排序有著天生的特點可以實現(xiàn)優(yōu)先隊列,來實現(xiàn)
order by limit 

(關于快速排序參考:http://blog.itpub.net/7728585/viewspace-2130743/)

那么這里就涉及一個問題,那就是快速排序和最優(yōu)的隊列的臨界切換,比如
表A 100W行記錄 id列沒有索引
select * from a order by id limit 10;

select * from a order by id limit 900000,10;

肯定前者應該使用最優(yōu)隊列,而后者實際上要排序好至少900010行數(shù)據(jù)才能返回。
那么這個時候應該使用快速排序,那么trace信息應該為
filesort PQ is not applicable
[root@testmy tmp]# cat pqdis.trace |grep "filesort PQ "
T@2: | | | | | | | | | | info: filesort PQ is not applicable

那么MYSQL值確定是否使用PQ,其判定接口為check_if_pq_applicable函數(shù),
簡單的說MYSQL認為堆排序比快速排序慢3倍如下:
  /*
    How much Priority Queue sort is slower than qsort.
    Measurements (see unit test) indicate that PQ is roughly 3 times slower.
  */
  const double PQ_slowness= 3.0;


所以就要進行算法的切換,但是具體算法沒有仔細研究可以自行參考check_if_pq_applicable函數(shù)
至少和下面有關
1、是否能夠在內(nèi)存中完成
2、排序行數(shù)
3、字段數(shù)


最后需要說明一點PQ排序關閉了一次訪問排序的pack功能如下:
    /*
      For PQ queries (with limit) we know exactly how many pointers/records
      we have in the buffer, so to simplify things, we initialize
      all pointers here. (We cannot pack fields anyways, so there is no
      point in doing lazy initialization).
     */

五、實現(xiàn)代碼,維護方法列出了2種實現(xiàn),方法2是算法導論上的更容易理解

點擊(此處)折疊或打開

  1. /*************************************************************************

  2.   > File Name: heapsort.c

  3.   > Author: gaopeng QQ:22389860 all right reserved

  4.   > Mail: gaopp_200217@163.com

  5.   > Created Time: Sun 08 Jan 2017 11:22:14 PM CST

  6.  ************************************************************************/


  7. #include<stdio.h>

  8. #include<stdlib.h>


  9. #define LEFT(i) i<<1

  10. #define RIGTH(i) (i<<1)+1

  11. //堆排序的性能不及快速排序但是在某些情況下非常有用

  12. //數(shù)據(jù)庫的order by limit使用了優(yōu)先隊列,基于堆排序


  13. int swap(int k[],int i,int j)

  14. {

  15.         int temp;


  16.         temp = k[i];

  17.         k[i] = k[j];

  18.         k[j] = temp;

  19.         return 0;

  20. }



  21. int bigheapad(int k[],int s,int n) //s=4,n=9

  22. {

  23.         /*

  24.          * one:

  25.          int i;

  26.          int temp = k[s]; //temp=9=k[4] 父節(jié)點值保存到temp

  27.          for(i=2*s;i<=n;i=i*2)// i=8

  28.          {

  29.          if(i<n && k[i]<k[i+1])//如果左子節(jié)點小于右子節(jié)點

  30.          {

  31.          i++; //右節(jié)點

  32.          }


  33.          if(temp>=k[i])

  34.          {

  35.          break;

  36.          }


  37.          k[s] = k[i];

  38.          s = i;

  39.          }


  40.          k[s] = temp;

  41.          */

  42.         // two: 參考算法導論P155頁,整個方法更容易理解其原理,調(diào)整使用逐層下降的方法進行調(diào)整

  43.         int l; //s 左節(jié)點編號

  44.         int r; //s 右節(jié)點編號

  45.         int largest;


  46.         l=LEFT(s); //左節(jié)點編號

  47.         r=RIGTH(s);//右節(jié)點編號


  48.         if(s<=n/2) // n/2為最小節(jié)點編號的父親節(jié)點 如果s大于這個值說明這個節(jié)點不會有任何子節(jié)點不需要進行調(diào)整 ??!,這是整個算法的核心中的核心。搞了我老半天

  49.         {

  50.                 if (l<=n && k[l] > k[s])

  51.                 {

  52.                         largest = l;

  53.                 }

  54.                 else

  55.                 {

  56.                         largest = s;

  57.                 }


  58.                 if(r<=n && k[r] > k[largest])

  59.                 {

  60.                         largest = r;

  61.                 }


  62.                 if(largest != s)

  63.                 {

  64.                         swap(k,largest,s);

  65.                         bigheapad(k,largest,n); //對數(shù)據(jù)調(diào)整后可能的子節(jié)點樹繼續(xù)進行調(diào)整直到達到遞歸退出條件

  66.                 }

  67.         }

  68. return 0;

  69. }



  70. int bigheapbulid(int k[],int n)

  71. {

  72.         int i;

  73.         for(i=n/2;i>0;i--)//采用自底向上的方法構(gòu)建 算法導論P156 EXP 1:i= n/2 p:4 l:8 r:9  2: i = p:3 l:6 r:7  n/2剛好是最后一個節(jié)點的父親節(jié)點所以自下而上

  74.         {

  75.                 bigheapad(k,i,n);

  76.         }

  77. return 0;


  78. }


  79. int bigheapsort(int k[],int n) //sort的過程就是將最大元素放到最后,然后逐層下降的方法進行調(diào)整

  80. {

  81.         int i;

  82.         for(i=n;i>1;i--)

  83.         {

  84.                 swap(k,1,i);

  85.                 bigheapad(k,1,i-1);

  86.         }

  87. return 0;

  88. }


  89. int biglimitn(int k[],int n,int limitn)//limit 也是我關心的 這里明顯可以看到他的優(yōu)勢實際它不需要對整個數(shù)組排序,你要多少我排多少給你就好,也是最大元素放到最后,然后逐層下降的方法進行調(diào)整的原理

  90. {

  91.         int i;

  92.         for(i=n;i>n-limitn;i--)

  93.         {

  94.                 swap(k,1,i);

  95.                 bigheapad(k,1,i-1);

  96.         }

  97. return 0;

  98. }


  99. int smallheapad(int k[],int s,int n) //s=4,n=9

  100. {


  101.         int l; //s 左節(jié)點編號

  102.         int r; //s 右節(jié)點編號

  103.         int smallest;


  104.         l=LEFT(s); //左節(jié)點編號

  105.         r=RIGTH(s);//右節(jié)點編號


  106.         if(s<=n/2) // n/2為最小節(jié)點編號的父親節(jié)點 如果s大于這個值說明這個節(jié)點不會有任何子節(jié)點不需要進行調(diào)整 !!

  107.         {


  108.                 if (l<=n && k[l] < k[s])

  109.                 {


  110.                         smallest = l;

  111.                 }

  112.                 else

  113.                 {


  114.                         smallest = s;

  115.                 }


  116.                 if(r<=n && k[r] < k[smallest])

  117.                 {


  118.                         smallest = r;

  119.                 }


  120.                 if(smallest != s)

  121.                 {


  122.                         swap(k,smallest,s);

  123.                         smallheapad(k,smallest,n); //對數(shù)據(jù)調(diào)整后可能的子節(jié)點樹繼續(xù)進行調(diào)整直到達到遞歸退出條件

  124.                 }

  125.         } 

  126. return 0;

  127. }



  128. int smallheapbulid(int k[],int n)

  129. {


  130.         int i;

  131.         for(i=n/2;i>0;i--)

  132.         {


  133.                 smallheapad(k,i,n);

  134.         }

  135. return 0;

  136. }


  137. int smallheapsort(int k[],int n)

  138. {


  139.         int i;

  140.         for(i=n;i>1;i--)

  141.         {


  142.                 swap(k,1,i);

  143.                 smallheapad(k,1,i-1);

  144.         }

  145. return 0;

  146. }


  147. int smalllimitn(int k[],int n,int limitn)

  148. {


  149.         int i;

  150.         for(i=n;i>n-limitn;i--)

  151.         {


  152.                 swap(k,1,i);

  153.                 smallheapad(k,1,i-1);

  154.         }

  155. return 0;

  156. }



  157. int main()

  158. {


  159.         int i,a[11]={0,999,3,2,9,34,5,102,90,2222,1}; //測試數(shù)據(jù) a[0]不使用

  160.         int b[11]={0,999,3,2,9,999,888888,102,90,2222,111};//測試數(shù)據(jù) b[0]不使用

  161.         bigheapbulid(a,10);

  162.         biglimitn(a,10,3);


  163.         printf("大頂堆:\n");

  164.         printf("order by desc a array limit 3 result:");

  165.         for(i=10;i>10-3;i--)

  166.         {

  167.                 printf("%d ",a[i]);

  168.         }

  169.         printf("\n");

  170.         bigheapbulid(b,10);

  171.         printf("max values b array reulst:");

  172.         printf("%d \n",b[1]);


  173.         smallheapbulid(a,10);

  174.         smalllimitn(a,10,3);

  175.         printf("小頂堆:\n");

  176.         printf("order by asc a array limit 3 result:");

  177.         for(i=10;i>10-3;i--)

  178.         {

  179.                 printf("%d ",a[i]);

  180.         }

  181.         printf("\n");

  182.         smallheapbulid(b,10);

  183.         printf("min values b array reulst:");

  184.         printf("%d \n",b[1]);

  185. return 0;

  186. }

以上是“MYSQL如何實現(xiàn)ORDER BY和LIMIT”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業(yè)資訊頻道!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI