大數(shù)據(jù)分頁(yè)方案

軟件開(kāi)發(fā)中队伟,常用要用到分頁(yè)、計(jì)算總數(shù)幽勒,數(shù)據(jù)量超過(guò)千萬(wàn)嗜侮、上億的時(shí)候,往往count的需要超過(guò) 1s 的執(zhí)行時(shí)間啥容,甚至 3-5s锈颗,對(duì)于一個(gè)追求性能的前沿團(tuán)隊(duì)來(lái)說(shuō),這個(gè)不能忍斑浠荨击吱!

為什么會(huì)慢?

mysql 會(huì)對(duì)所有符合的條件做一次掃描遥昧。

select count(*) from table_a where a = '%d' ...

如果 a=%d 的數(shù)據(jù)有 1000W 條姨拥,那么數(shù)據(jù)庫(kù)就會(huì)掃描一次 1000W 條數(shù)據(jù)庫(kù)。如果不帶查詢條件渠鸽,那這種全表掃描將更可怕叫乌。

count(*) 和 count(1)、count(0)

count(expr) 為統(tǒng)計(jì) expr 不為空的記錄

count(*) 它會(huì)計(jì)算總行數(shù)徽缚,不管你字段是否有值都會(huì)列入計(jì)算范圍憨奸。

coount(0),count(1) 沒(méi)有差別,它會(huì)計(jì)算總行數(shù)

Example 1:

mysql> explain extended select count(*) from user;

...

1 row in set, 1 warning (0.34 sec)

mysql> show warnings;

+-------+------+--------------------------------------------------+

| Level | Code | Message |

+-------+------+--------------------------------------------------+

| Note | 1003 | select count(0) AS `count(*)` from `user` |

Example 2:

mysql> select count(*) from login_log

-> ;

+----------+

| count(*) |

+----------+

| 2513 |

+----------+

1 rows in set (0.00 sec)

mysql> select count(logoutTime) from login_log;

+-------------------+

| count(logoutTime) |

+-------------------+

| 308 |

+-------------------+

1 rows in set (0.00 sec)

怎么解決凿试?

MyISAM DB

MyISAM 引擎很容易獲得總行數(shù)的統(tǒng)計(jì)排宰,查詢速度變得更快。因?yàn)?MyISAM 存儲(chǔ)引擎已經(jīng)存儲(chǔ)了表的總行數(shù)那婉。

MyISAM 會(huì)為每張表維護(hù)一個(gè) row count 的計(jì)數(shù)器板甘,每次新增加一行,這個(gè)計(jì)數(shù)器就加 1详炬。但是如果有查詢條件盐类,那么 MyISAM 也 game over 了,MyISAM 引擎不支持條件緩存呛谜。

On MyISAM, doing a query that does SELECT COUNT(*) FROM {some_table}, is very fast, since MyISAM keeps the information in the index

其他 DB 引擎

受到 MySIAM DB 的啟發(fā)在跳,我們可以手動(dòng)維護(hù)總數(shù)緩存在表的索引中了。

1隐岛、如果 ID 連續(xù)猫妙,且基本不會(huì)斷開(kāi)。直接取最大值 ID

2聚凹、如果表中存在連續(xù)的數(shù)字列并設(shè)為索引割坠,那么通過(guò)頁(yè)碼即可計(jì)算出此字段的范圍齐帚,直接作范圍查詢即可:

start = (page-1)*pagesize+1

end = page*pagesize

select * from table where id >start and id <=end

1、涉及到總數(shù)操作彼哼,專(zhuān)門(mén)維護(hù)一個(gè)總數(shù)对妄。新增一個(gè)用戶,總數(shù)值加 1, 需要總數(shù)的時(shí)候直接拿這個(gè)總數(shù), 比如分頁(yè)時(shí)沪羔。如果有多個(gè)條件,那么就需要維護(hù)多個(gè)總數(shù)列象浑。該方案的擴(kuò)展性更好蔫饰,隨著用戶表數(shù)量增大, 水平切分用戶表,要獲取用戶總數(shù)愉豺,直接查詢這個(gè)總數(shù)表即可篓吁。

分頁(yè)正反偏移

數(shù)據(jù)庫(kù)自帶的 skip 和 limit 的限制條件為我們創(chuàng)建了分頁(yè)的查詢方式,但是如果利用不對(duì)蚪拦,性能會(huì)出現(xiàn)千倍萬(wàn)倍差異杖剪。

簡(jiǎn)單一點(diǎn)描述:limit 100000,20 的意思掃描滿足條件的 100020 行,扔掉前面的 100000 行驰贷,返回最后的 20 行盛嘿,問(wèn)題就在這里。如果我反向查詢 oder by xx desc limit 0,20括袒,那么我只要索引 20 條數(shù)據(jù)次兆。

Example 3

mysql> select count(*) from elastic_task_log_copy;

+----------+

| count(*) |

+----------+

| 1705162 |

+----------+

1 rows in set (2.31 sec)

正向偏移查詢。超級(jí)浪費(fèi)的查詢锹锰,需要先 skip 大量的符合條件的查詢芥炭。

mysql> select id from elastic_task_log_copy order by id asc limit 1705152,10;

+---------+

| id |

+---------+

| 1705157 |

| 1705158 |

| 1705159 |

| 1705160 |

| 1705161 |

| 1705162 |

| 1705163 |

| 1705164 |

| 1705165 |

| 1705166 |

+---------+

10 rows in set (2.97 sec)

反向偏移查詢。同樣的查詢結(jié)果恃慧,千差萬(wàn)別的結(jié)果园蝠。

mysql> select id from elastic_task_log_copy order by id desc limit 0,10;

+---------+

| id |

+---------+

| 1705166 |

| 1705165 |

| 1705164 |

| 1705163 |

| 1705162 |

| 1705161 |

| 1705160 |

| 1705159 |

| 1705158 |

| 1705157 |

+---------+

10 rows in set (0.01 sec)

這兩條 sql 是為查詢最后一頁(yè)的翻頁(yè) sql 查詢用的。由于一次翻頁(yè)往往只需要查詢較小的數(shù)據(jù)痢士,如 10 條彪薛,但需要向后掃描大量的數(shù)據(jù),也就是越往后的翻頁(yè)查詢怠蹂,掃描的數(shù)據(jù)量會(huì)越多陪汽,查詢的速度也就越來(lái)越慢。

由于查詢的數(shù)據(jù)量大小是固定的褥蚯,如果查詢速度不受翻頁(yè)的頁(yè)數(shù)影響挚冤,或者影響最低,那么這樣是最佳的效果了(查詢最后最幾頁(yè)的速度和開(kāi)始幾頁(yè)的速度一致)赞庶。

在翻頁(yè)的時(shí)候训挡,往往需要對(duì)其中的某個(gè)字段做排序(這個(gè)字段在索引中)澳骤,升序排序。那么可不可以利用索引的有序性來(lái)解決上面遇到的問(wèn)題澜薄。

比如有 10000 條數(shù)據(jù)需要做分頁(yè)为肮,那么前 5000 條做 asc 排序,后 5000 條 desc 排序肤京,在 limit startnum颊艳,pagesize 參數(shù)中作出相應(yīng)的調(diào)整。

但是這無(wú)疑給應(yīng)用程序帶來(lái)復(fù)雜忘分,這條 sql 是用于論壇回復(fù)帖子的 sql棋枕,往往用戶在看帖子的時(shí)候,一般都是查看前幾頁(yè)和最后幾頁(yè)妒峦,那么在翻頁(yè)的時(shí)候最后幾頁(yè)的翻頁(yè)查詢采用 desc 的方式來(lái)實(shí)現(xiàn)翻頁(yè)重斑,這樣就可以較好的提高性能。

游標(biāo):上一頁(yè)的最大值或者最小值

如果你知道上一頁(yè)和下一頁(yè)的臨界值肯骇,那么翻頁(yè)查詢也是信手拈來(lái)了窥浪,直接就告訴了數(shù)據(jù)庫(kù)我的起始查詢?cè)谀模簿蜎](méi)有什么性能問(wèn)題了笛丙。我更愿意稱(chēng)這個(gè)東西為游標(biāo) (Cursor)漾脂。

如果做下拉刷新,那么就直接避免掉分頁(yè)的問(wèn)題了胚鸯。根據(jù)上一頁(yè)的最后一個(gè)值去請(qǐng)求新數(shù)據(jù)符相。

mysql> select id from elastic_task_log_copy where id >= 1699999 limit 10;

+---------+

| id |

+---------+

| 1699999 |

| 1700000 |

| 1700001 |

| 1700002 |

| 1700003 |

| 1700004 |

| 1700005 |

| 1700006 |

| 1700007 |

| 1700008 |

+---------+

10 rows in set (0.01 sec)

緩存和不精準(zhǔn)

數(shù)據(jù)量達(dá)到一定程度的時(shí)候,用戶根本就不關(guān)心精準(zhǔn)的總數(shù), 沒(méi)人關(guān)心差幾個(gè)蠢琳“≈眨看看知乎、微博傲须、微信訂閱號(hào)蓝牲,不精準(zhǔn)的統(tǒng)計(jì)到處都是。

如果每次點(diǎn)擊分頁(yè)的時(shí)候都進(jìn)行一次 count 操作泰讽,那速度肯定不會(huì)快到哪里去例衍。他們一般也是采用計(jì)數(shù)器的辦法。每次新增加一個(gè)粉絲已卸,就把值加 1佛玄,直接在用戶信息存儲(chǔ)一個(gè)總數(shù),一段時(shí)間后重新查詢一次累澡,更新該緩存梦抢。這樣分頁(yè)的時(shí)候直接拿這個(gè)總數(shù)進(jìn)行分頁(yè),顯示的時(shí)候直接顯示模糊之就行愧哟。

那為什么微信公眾號(hào)的閱讀量只有 10W+ 這個(gè)量級(jí)呢奥吩?100W+ 級(jí)去哪了哼蛆!

其他大神的建議

1、mysql 的數(shù)據(jù)查詢, 大小字段要分開(kāi), 這個(gè)還是有必要的, 除非一點(diǎn)就是你查詢的都是索引內(nèi)容而不是表內(nèi)容, 比如只查詢 id 等等

2霞赫、查詢速度和索引有很大關(guān)系也就是索引的大小直接影響你的查詢效果, 但是查詢條件一定要建立索引, 這點(diǎn)上注意的是索引字段不能太多腮介,太多索引文件就會(huì)很大那樣搜索只能變慢,

3、查詢指定的記錄最好通過(guò) Id 進(jìn)行 in 查詢來(lái)獲得真實(shí)的數(shù)據(jù). 其實(shí)不是最好而是必須端衰,也就是你應(yīng)該先查詢出復(fù)合的 ID 列表, 通過(guò) in 查詢來(lái)獲得數(shù)據(jù)

4叠洗、mysql 千萬(wàn)級(jí)別數(shù)據(jù)肯定是沒(méi)問(wèn)題的, 畢竟現(xiàn)在的流向 web2.0 網(wǎng)站大部分是 mysql 的

5、合理分表也是必須的, 主要涉及橫向分表與縱向分表, 如把大小字段分開(kāi), 或者每 100 萬(wàn)條記錄在一張表中等等, 像上面的這個(gè)表可以考慮通過(guò) uid 的范圍分表, 或者通過(guò)只建立索引表, 去掉相對(duì)大的字段來(lái)處理.

6旅东、count() 時(shí)間比較長(zhǎng), 但是本身是可以緩存在數(shù)據(jù)庫(kù)中或者緩存在程序中的, 因?yàn)槲覀儺?dāng)時(shí)使用在后臺(tái)所以第一頁(yè)比較慢但是后面比較理想

7灭抑、SELECT id 相對(duì) SELECT差距還是比較大的, 可以通過(guò)上面的方法來(lái)使用 SELECT id + SELECT… IN 查詢來(lái)提高性能

8、必要的索引是必須的, 還是要盡量返回 5%-20% 的結(jié)果級(jí)別其中小于 5% 最理想;

9玉锌、mysql 分頁(yè)的前面幾頁(yè)速度很快, 越向后性能越差, 可以考慮只帶上一頁(yè), 下一頁(yè)不帶頁(yè)面跳轉(zhuǎn)的方法, 呵呵這個(gè)比較垃圾但是也算是個(gè)方案, 只要在前后多查一條就能解決了. 比如 100,10 你就差 99,12 呵呵名挥,這樣看看前后是否有結(jié)果.

10疟羹、前臺(tái)還是要通過(guò)其他手段來(lái)處理, 比如 lucene/Solr+mysql 結(jié)合返回翻頁(yè)結(jié)果集, 或者上面的分表

11主守、總數(shù)可能是存在內(nèi)存中, 這樣分頁(yè)計(jì)算的時(shí)候速度很快。累加操作的時(shí)候?qū)?nèi)存中的值加 1榄融〔我總數(shù)這個(gè)值要持久化,還是要存到磁盤(pán)上的愧杯,也就是數(shù)據(jù)庫(kù)中 (可以是關(guān)系型數(shù)據(jù)庫(kù)涎才,也可以是 mongdb 這樣的數(shù)據(jù)庫(kù)很適合存儲(chǔ)計(jì)數(shù))。把總數(shù)放在內(nèi)存中力九,只是避免頻繁的磁盤(pán) i/0 操作 (操作數(shù)據(jù)庫(kù)就要涉及到磁盤(pán)讀寫(xiě))耍铜。

來(lái)源

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市跌前,隨后出現(xiàn)的幾起案子棕兼,更是在濱河造成了極大的恐慌,老刑警劉巖抵乓,帶你破解...
    沈念sama閱讀 222,464評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伴挚,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡灾炭,警方通過(guò)查閱死者的電腦和手機(jī)茎芋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蜈出,“玉大人田弥,你說(shuō)我怎么就攤上這事≌≡” “怎么了皱蹦?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,078評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵煤杀,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我沪哺,道長(zhǎng)沈自,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,979評(píng)論 1 299
  • 正文 為了忘掉前任辜妓,我火速辦了婚禮枯途,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘籍滴。我一直安慰自己酪夷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布孽惰。 她就那樣靜靜地躺著晚岭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪勋功。 梳的紋絲不亂的頭發(fā)上坦报,一...
    開(kāi)封第一講書(shū)人閱讀 52,584評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音狂鞋,去河邊找鬼片择。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锣咒,決...
    沈念sama閱讀 41,085評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼将硝,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,023評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎硫戈,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體酌壕,經(jīng)...
    沈念sama閱讀 46,555評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掏愁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了卵牍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片果港。...
    茶點(diǎn)故事閱讀 40,769評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖糊昙,靈堂內(nèi)的尸體忽然破棺而出辛掠,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,439評(píng)論 5 351
  • 正文 年R本政府宣布萝衩,位于F島的核電站回挽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏猩谊。R本人自食惡果不足惜千劈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望牌捷。 院中可真熱鬧墙牌,春花似錦、人聲如沸暗甥。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,601評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)撤防。三九已至虽风,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間寄月,已是汗流浹背辜膝。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,702評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留剥懒,地道東北人内舟。 一個(gè)月前我還...
    沈念sama閱讀 49,191評(píng)論 3 378
  • 正文 我出身青樓合敦,卻偏偏與公主長(zhǎng)得像初橘,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子充岛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容