首頁>Club>
比如,select * from table order by age limit 300000,300020。
23
回覆列表
  • 1 # 軟體測試開發技術棧

    使用合理的分頁方式以提高分頁的效率

    正如樓主所說,分頁查詢在我們的實際應用中非常普遍,也是最容易出問題的查詢場景。比如對於下面簡單的語句,一般想到的辦法是在name,age,register_time欄位上建立複合索引。這樣條件排序都能有效的利用到索引,效能迅速提升。

    如上例子,當 LIMIT 子句變成 “LIMIT 100000, 50” 時,此時我們會發現,只取50條語句為何會變慢?

    原因很簡單,MySQL並不知道第 100000條記錄從什麼地方開始,即使有索引也需要從頭計算一次,因此會感覺非常的慢。

    通常,我們在做分頁查詢時,是可以獲取上一頁中的某個資料標誌來縮小查詢範圍的,比如時間,可以將上一頁的最大值時間作為查詢條件的一部分,SQL可以最佳化為這樣:

  • 2 # 年輕人不講捂得

    這個得明白mysql的儲存原理 講個例子 一個小區有很多棟 一棟有很多層 一層有很多室 你哪個小區哪一棟那一層都不告訴它 它能一下找到嗎 雖然它也想快點幫你找到

  • 3 # 網店教主

    個人實戰經驗分享一下,商品表,資料量還是相對較大的,有好幾百萬。當時最初架構也是遇到這種問題,因為最初設計的時候沒想到會有這麼大資料量,也就應對10萬以內的架構設計。那後來也是透過不斷找尋方案,最終採用了一種橋連線表的方案。主表是商品表,幾百萬或者上千萬商品。

    第一步,建立橋資料表,一個自增ID,一個商品ID,主要這倆欄位,額外排序條件也可以加進去,均int型別,不宜過大。這個表就是存商品ID用的。

    第二步,查詢分頁的時候,先在這個橋表做分頁查詢,表小,都是索引,速度非常快,然後取出商品ID後,再用已知商品ID對商品表做in查詢。查出具體資訊。這樣速度提升巨大。簡單實用,僅需對分頁部分做查詢修改即可完成。

    目前三五百萬商品表,都是毫秒級查詢,沒改造之前需要半分鐘。

    再說一句是MySQL資料庫,有的說什麼分表分庫,那都沒必要,用in條件查詢,那是最快的,直接告訴在哪,取數就行了。

  • 4 # 碼農的搬磚生涯

    題主給的這個sql其實想要的資料也就20條吧(你那個300020應該是打錯了,不可能是實際業務一頁顯示30多萬條記錄),單純查三十多萬資料其實很快,為什麼分頁後就很慢?

    變慢的原因,一方面是select *,另一方面是資料量較大,還有一個是帶有排序操作。本質是分頁查詢時,會先查詢出limit + offset條記錄,然後擷取後面的offset記錄。

    Mysql資料庫作為一款比較主流的開源關係型資料庫,市場上我覺得貌似開發者沒有一個沒用過吧。

    影響MySQL查詢效能的因素有很多,比如sql,表結構設計,磁碟io,網絡卡io,高併發,資料庫相關引數配置,還有伺服器硬等。

    這裡面涉及最多也是面試中最常問的就是有關sql的最佳化。

    因為很多效能上的問題來自sql的比較多,mysql資料庫在資料量級達到百萬以上效能是逐漸下降的。

    關於sql最佳化又有很多最佳化的方向和手段。比如對錶結構的欄位型別,預設值,索引等最基礎的做一些最佳化,然後編寫的sql最好要能完全命中索引。

    當然並不是說建索引就一定命中,不走索引就一定慢。這取決於mysql的執行計劃。

    還有建索引也並不是越多越好,單表索引最好不要超過6個,畢竟索引也佔空間,資料更新的同時,還牽扯到索引檔案的維護。

    OK說了這麼多,到底該怎麼對這個分頁又排序做最佳化呢?

    我的做法就是合理利用主鍵索引來處理

    select a.* from table a inner join (select id from table

    limit 300000,20)

    b on a.id=b.id;

    然後排序最好放到程式碼層面上去。

  • 5 # 愛可生雲資料庫

    很多應用往往只展示最新或最熱門的幾條記錄,但為了舊記錄仍然可訪問,所以就需要個分頁的導航欄。然而,如何透過MySQL更好的實現分頁,始終是比較令人頭疼的問題。雖然沒有拿來就能用的解決辦法,但瞭解資料庫的底層或多或少有助於最佳化分頁查詢。

    我們先從一個常用但效能很差的查詢來看一看。

    SELECT *

    FROM city

    ORDER BY id DESC

    LIMIT 0, 15

    這個查詢耗時0.00sec。So,這個查詢有什麼問題呢?實際上,這個查詢語句和引數都沒有問題,因為它用到了下面表的主鍵,而且只讀取15條記錄。

    CREATE TABLE city (

    id int(10) unsigned NOT NULL AUTO_INCREMENT,

    city varchar(128) NOT NULL,

    PRIMARY KEY (id)

    ) ENGINE=InnoDB;

    真正的問題在於offset(分頁偏移量)很大的時候,像下面這樣:

    SELECT *

    FROM city

    ORDER BY id DESC

    LIMIT 100000, 15;

    上面的查詢在有2M行記錄時需要0.22sec,透過EXPLAIN檢視SQL的執行計劃可以發現該SQL檢索了100015行,但最後只需要15行。大的分頁偏移量會增加使用的資料,MySQL會將大量最終不會使用的資料載入到記憶體中。就算我們假設大部分網站的使用者只訪問前幾頁資料,但少量的大的分頁偏移量的請求也會對整個系統造成危害。Facebook意識到了這一點,但Facebook並沒有為了每秒可以處理更多的請求而去最佳化資料庫,而是將重心放在將請求響應時間的方差變小。

    對於分頁請求,還有一個資訊也很重要,就是總共的記錄數。我們可以透過下面的查詢很容易的獲取總的記錄數。

    SELECT COUNT(*)

    FROM city;

    然而,上面的SQL在採用InnoDB為儲存引擎時需要耗費9.28sec。一個不正確的最佳化是採用 SQL_CALC_FOUND_ROWS,SQL_CALC_FOUND_ROWS 可以在能夠在分頁查詢時事先準備好符合條件的記錄數,隨後只要執行一句 select FOUND_ROWS(); 就能獲得總記錄數。但是在大多數情況下,查詢語句簡短並不意味著效能的提高。不幸的是,這種分頁查詢方式在許多主流框架中都有用到,下面看看這個語句的查詢效能。

    SELECT SQL_CALC_FOUND_ROWS *

    FROM city

    ORDER BY id DESC

    LIMIT 100000, 15;

    這個語句耗時20.02sec,是上一個的兩倍。事實證明使用 SQL_CALC_FOUND_ROWS 做分頁是很糟糕的想法。

    下面來看看到底如何最佳化。文章分為兩部分,第一部分是如何獲取記錄的總數目,第二部分是獲取真正的記錄。

    高效的計算行數

    如果採用的引擎是MyISAM,可以直接執行COUNT(*)去獲取行數即可。相似的,在堆表中也會將行數儲存到表的元資訊中。但如果引擎是InnoDB情況就會複雜一些,因為InnoDB不儲存表的具體行數。

    SELECT COUNT(*)

    FROM city

    USE INDEX(PRIMARY);

    獲取記錄

    下面進入這篇文章最重要的部分,獲取分頁要展示的記錄。上面已經說過了,大的偏移量會影響效能,所以我們要重寫查詢語句。為了演示,我們建立一個新的表“news”,按照時事性排序(最新發布的在最前面),實現一個高效能的分頁。為了簡單,我們就假設最新發布的新聞的Id也是最大的。

    CREATE TABLE news(

    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,

    title VARCHAR(128) NOT NULL

    ) ENGINE=InnoDB;

    一個比較高效的方式是基於使用者展示的最後一個新聞Id。查詢下一頁的語句如下,需要傳入當前頁面展示的最後一個Id。

    SELECT *

    FROM news WHERE id < $last_id

    ORDER BY id DESC

    LIMIT $perpage

    查詢上一頁的語句類似,只不過需要傳入當前頁的第一個Id,並且要逆序。

    SELECT *

    FROM news WHERE id > $last_id

    ORDER BY id ASC

    LIMIT $perpage

    SELECT id

    FROM (

    SELECT id, ((@cnt:= @cnt + 1) + $perpage - 1) % $perpage cnt

    FROM news

    JOIN (SELECT @cnt:= 0)T

    WHERE id < $last_id

    ORDER BY id DESC

    LIMIT $perpage * $buttons

    )C

    WHERE cnt = 0;

    透過上面的語句可以為每一個分頁的按鈕計算出一個offset對應的id。這種方法還有一個好處。假設,網站上正在釋出一片新的文章,那麼所有文章的位置都會往後移一位,所以如果使用者在釋出文章時換頁,那麼他會看見一篇文章兩次。如果固定了每個按鈕的offset Id,這個問題就迎刃而解了。Mark Callaghan發表過一篇類似的部落格,利用了組合索引和兩個位置變數,但是基本思想是一致的。

    SET p:= 0;

    UPDATE news SET page=CEIL((p:= p + 1) / $perpage) ORDER BY id DESC;

    當然,也可以新增一個專用於分頁的表,可以用個後臺程式來維護。

    UPDATE pagination T

    JOIN (

    SELECT id, CEIL((p:= p + 1) / $perpage) page

    FROM news

    ORDER BY id

    )C

    ON C.id = T.id

    SET T.page = C.page;

    現在想獲取任意一頁的元素就很簡單了:

    SELECT *

    FROM news A

    JOIN pagination B ON A.id=B.ID

    WHERE page=$offset;

    還有另外一種與上種方法比較相似的方法來做分頁,這種方式比較試用於資料集相對小,並且沒有可用的索引的情況下—比如處理搜尋結果時。在一個普通的伺服器上執行下面的查詢,當有2M條記錄時,要耗費2sec左右。這種方式比較簡單,建立一個用來儲存所有Id的臨時表即可(這也是最耗費效能的地方)。

    CREATE TEMPORARY TABLE _tmp (KEY SORT(random))

    SELECT id, FLOOR(RAND() * 0x8000000) random

    FROM city;

    ALTER TABLE _tmp ADD OFFSET INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, DROP INDEX SORT,ORDER BY random;

    接下來就可以向下面一樣執行分頁查詢了。

    SELECT *

    FROM _tmp

    WHERE OFFSET >= $offset

    ORDER BY OFFSET

    LIMIT $perpage;

    簡單來說,對於分頁的最佳化就是。。。避免資料量大時掃描過多的記錄。

  • 中秋節和大豐收的關聯?
  • 你覺得盜版書有沒有讀的價值?為什麼?