百萬級數據,分頁如何處理?

(點擊便可跳轉閱讀)java

1. SpringBoot內容聚合面試

2. 面試題內容聚合算法

3. 設計模式內容聚合sql

4. 排序算法內容聚合數據庫

5. 多線程內容聚合設計模式

正文:bash

最近遇到了這麼一個狀況,數據庫裏面的數據因爲長期的堆積,致使數據量不斷的上升,然後臺的系統每次進行分頁查詢的時候,效率都會下降不少。後來查看了一下以後,發現此時的分頁原理主要是採用了傳統的物理分頁 limit n,m 的方式。多線程

爲了方便演示,我特地建立了如下幾張表進行實例演練:框架

表分別是商品表,用戶表,用戶選購商品記錄表:函數

goods user g_u

三張表的關係比較簡單,user的id和goods裏面的id合併生成關聯數據,存儲在了g_u裏面。三張數據庫表的設計以下所示:

CREATE TABLE `goods` (
  `id` int(11) NOT NULL,
  `name` varchar(60) COLLATE utf8_unicode_ci NOT NULL,
  `price` decimal(6,1) NOT NULL,
  `des` varchar(40) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
  `age` tinyint(3) NOT NULL,
  `sex` tinyint(1) NOT NULL COMMENT '年齡',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100001 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

CREATE TABLE `g_u` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `g_id` int(11) NOT NULL COMMENT '商品id',
  `u_id` int(11) NOT NULL COMMENT '用戶id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2800001 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;複製代碼

這個模擬的應用場景很是簡單,用戶和商品之間的關係維持在了一對多的關聯中。爲了方便進行後續的測試,我用jmeter批量建立了1900000條測試數據,模擬一次百萬級的數據查詢場景。

相應的數據腳本也已經存在百度雲中了,須要的同窗能夠前往下載:
地址:

連接: https://pan.baidu.com/s/1BfddJ8MBtnpeiV84gNmClA

提取碼: 4kmp

假設如今需求裏面有這樣的一個業務場景,須要咱們對購買記錄表裏面的數據進行分頁查詢,那麼對於常規的分頁查詢操做,常人會想到的方式多是經過下述的語句:

SELECT * from g_u as gu ORDER BY id limit 1850000,100複製代碼

測試一下發現,查詢的時間爲:

百萬級數據,分頁如何處理?


當咱們搜索的數據越靠後邊的時候,搜索的速度就會越低下,所以這個時候,適當的建立索引就顯得比較重要了。

首先咱們來作一次explain的sql檢測,檢測結果爲以下所示:

百萬級數據,分頁如何處理?


因爲咱們查詢的時候,使用的是根據主鍵索引id進行排序,所以查詢的時候key一項爲PRIMARY。


SELECT * FROM g_u WHERE id >=(SELECT id FROM g_u LIMIT 1850000,1) ORDER BY id  LIMIT 100複製代碼

此時查詢有了一些許的提高,可是依舊查詢緩慢

百萬級數據,分頁如何處理?


經過explain執行計劃分析結果可見:


百萬級數據,分頁如何處理?


子查詢用到了索引,外部查詢用到了where的輔助索引

這個時候咱們不妨能夠試下經過利用主鍵id來提高咱們的查詢效率:

SELECT * FROM g_u as gu WHERE gu.id>($firstId+$pageSize*$pageSize)  limit 100複製代碼

查詢的時間一會兒大大縮短了許多:

百萬級數據,分頁如何處理?


經過explain分析一下該sql:


百萬級數據,分頁如何處理?


這裏面,sql在運行的時候藉助了主鍵索引的幫助,所以效率大大提高了。


可是這個時候,可能你會有這麼一個疑惑。若是說數據的索引不是連續的該如何處理分頁時候每頁數據的完整性和一致性?


這裏不妨能夠試試另外的一種思路,經過創建一張第三方的表g_u_index表,將本來亂序的id存儲在g_u_index中,在g_u_index一表中,咱們能夠經過該表有序的g_u_index.id來對應本來相應的無序的g_u.id。建表的sql語句以下所示:

CREATE TABLE `g_u_index` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `index` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_id_index` (`id`,`index`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1900024 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;複製代碼

ps: 能夠爲id和index二者創建一套複合索引,提高查詢的效率。

這裏咱們須要保證一點就是,g_u表中插入的數據順序須要和g_u_index表中插入的順序是一致的。而後查詢分頁指定的index時候能夠這麼來查:

SELECT g_u_index.index FROM g_u_index  WHERE id=($firstId+$pageSize*$pageSize)  limit 1複製代碼

經過執行explain分析後,結果變成以下所示:

百萬級數據,分頁如何處理?


查詢時間爲:0.001s


百萬級數據,分頁如何處理?

有了第三方表的幫助下,此時分頁的sql優化能夠調整爲如下這種方式:

SELECT * FROM g_u as gu where gu.id>(
SELECT g_u_index.index FROM g_u_index  WHERE id=($firstId+$pageSize*$pageSize) limit 1
) limit 100複製代碼

經過構建了第三方表以後,數據的查詢時間一會兒大大縮減了:

百萬級數據,分頁如何處理?

查詢的時候爲了更加人性化,一般不須要顯示這些無心義的id,須要的是商品名稱和用戶姓名,假設咱們仍是隻採用最原始的無第三方表的方式進行查詢的話,效率會比較底下:

SELECT gu.id,goods.`name`,`user`.username FROM g_u as gu ,goods ,`user` 
where goods.id=gu.g_id AND `user`.id=gu.u_id 
ORDER BY id limit 1500000,1000複製代碼

結果:

百萬級數據,分頁如何處理?

所以若是藉助了第三方表查詢的話,sql能夠調整成下方這種類型:

SELECT goods.`name`,`user`.username FROM g_u as gu ,goods ,`user` 
where goods.id=gu.g_id AND `user`.id=gu.u_id 
and 
gu.id>=(
SELECT g_u_index.index FROM g_u_index  WHERE id=(9+1000*1900) limit 1
) limit 100複製代碼

查詢的時間會大大減小:

百萬級數據,分頁如何處理?

經過explain執行計劃分析以後,結果以下:

百萬級數據,分頁如何處理?

在實際的業務場景中,一張原來就有上百萬數據的表要作出這樣的id拆分,而且同步到第三方表的確實不太容易,這裏推薦一種思路,能夠藉助阿里的中間件canal來實現對於數據庫日誌的訂閱,而後自定義進行數據的同步操做。

對於canal的講解在個人這篇文章中也有講述: 阿里Canal框架(數據同步中間件)初步實踐

對於sql的優化須要結合實際的業務需求來開展,總的來講,這部分仍是須要有必定的實戰演練才能變強。

經常使用的sql優化技巧小結:

1.數據量大的時候,應儘可能避免全表掃描,應考慮在 where及 order by 涉及的列上創建索引,建索引能夠大大加快數據的檢索速度。

2.適當的使用Explain能夠對sql進行相應的深刻分析。

3.當只要一行數據時使用LIMIT 1。

4.在使用索引字段做爲條件時,若是該索引是複合索引,那麼必須使用到該索引中的第一個字段做爲條件時才能保證系統使用該索引,不然該索引將不會被使用,而且應儘量的讓字段順序與索引順序相一致。

5.不要在 where子句中的「=」左邊進行函數、算術運算或其餘表達式運算,不然系統將可能沒法正確使用索引。

6.適當的時候採用覆蓋索引能夠提升查詢的效率。

相關文章
相關標籤/搜索