這是前幾天同事做的技術(shù)分析,講的挺不錯,現(xiàn)在整理一下
本文中數(shù)據(jù)庫操作與數(shù)據(jù)庫工作原理描述,都是經(jīng)過簡化的,實際應(yīng)用中,數(shù)據(jù)庫本身也會提供各種優(yōu)化措施。
數(shù)據(jù)庫線性搜索
假設(shè)現(xiàn)在又一個幾百萬條數(shù)據(jù)的購物車銷售記錄表,表結(jié)構(gòu)類似下表
| id | userid | productid | dealdate | promotions | dealstate |
|---|---|---|---|---|---|
| 1 | 10876 | 100003 | 14567776667 | -9.5 | 成交 |
| 2 | 93772 | 188293 | 14566636777 | -8 | 關(guān)閉 |
Q1: 查詢 userid = 100003 的用戶,所有購買記錄。
A1: 在沒有做任何優(yōu)化的情況下,數(shù)據(jù)庫需要從 id = 1 的記錄開始向下掃描,直到將整個?表掃描完畢,才可以檢索出所有?符合要求的記錄。?可想而知,當(dāng)表中記錄非常多的時候,需要?非常多的磁盤 I/O 才能完成此次查詢。在實際應(yīng)用中,例如 x 東用戶查詢自己的購買記錄這種需求,對服務(wù)器來說就是災(zāi)難啊。
使用索引提高查詢效率
使用?單個字段建立索引
回到 Q1 這個問題,如果我們在 userid 這個字段上建立索引,那么會產(chǎn)生一個 userid 到?記錄位置的映射。?這也是一個?表,在這個表中?查找某個 userid,就能夠找到所有符合該 userid ?的記錄在表中的位置,然后直接訪問這些位置讀取記錄就可以了。
使用多個字段建立索引
?Q2: 如果遇到一個購物達人,購物記錄有上萬條,通過 userid 這個索引查詢該用戶的所有搜索記錄,就?會產(chǎn)生上萬次磁盤 I/O,如果是機械硬盤,單單是尋道時間就?把這次查詢卡爆了,即使換 SSD,能夠服務(wù)的用戶?也是很有限的。
A2: ?遇到這種情況,我們可以將該用戶的購物記錄分頁,每次返回十幾條展示出來就可以了。向后翻頁的時候,可以從 offset 開始,再檢索出十幾條返回到前端展示。特別是移動端與桌面端?可能會跳到第10頁這種需求不同,向來都是上拉加載更多,是線性加載的,?對數(shù)據(jù)庫壓力就更小了。(PS. 跳轉(zhuǎn)到第幾頁這種需求,需要 count 數(shù)據(jù)的總數(shù),通常情況下,這個操作也需要將所有記錄讀取一遍,效率很低。)
這種需求下,一般會將購物記錄按照時間順序返回給用戶,我們只需要用 userid 和 dealdate 這兩個字段建立一個復(fù)合索引{userid,dealdate}就可以了。(建立索引時,指定 dealdate 按照降序排列)
檢索時,查找“近一月購物記錄”,只要按照時間檢索一些數(shù)據(jù)出來就可以了。
不應(yīng)該建立?什么樣的索引
如果?我們需要查詢成交狀態(tài),是不是可以?建立一個{dealstate}這樣的索引呢?
答案是,沒什么用。
如果檢索 dealstate = 成交 這樣的記錄,如果掃描整個表,需要檢索的記錄是 500?w 條。建立索引后,檢索的記錄也就減到 100w-200w 條。
這種優(yōu)化,并沒有數(shù)量級上的優(yōu)化效果,還需要占用大量空間,這種“優(yōu)化”一般認為是沒有意義的。
對于值為 bool 型或 enum? 型的字段,通常不建立索引。
建立索引的兩種原理
索引到數(shù)據(jù)映射一般有兩種實現(xiàn)方式,一種是 hash?,這個沒有排序的能力,另一種是? B 樹或 B+ 樹,這個有排序的能力。
如何快速從索引中檢索相應(yīng)的值
通常情況下,會選擇 B 樹或者 B+ 樹實現(xiàn)索引,以獲得排序的能力。
從 B+ 樹的數(shù)據(jù)結(jié)構(gòu)來看,?解決 ?Q2? 這個問題,例如要檢索上個月購物記錄,只要 logN 的時間就能查詢到相應(yīng)的記錄。
?并且從上圖可以看到,B+ 樹的各個節(jié)點之間,都通過指針進行連接,從當(dāng)前指向的數(shù)據(jù)出發(fā),向后查找數(shù)據(jù)是非常方便和快速的。能完美的實現(xiàn)上拉加載更多這種需求。
應(yīng)該如何建立復(fù)合索引
如果對本文開頭的表,建立一個復(fù)合索引,同時查詢購買時間、購物折扣等,可以?這樣建立復(fù)合索引。
{ productid(升序),promotions(升序), dealdate(降序)}
這里,雖然對 productid 進行排序沒有現(xiàn)實意義,但是在計算機看來,依然是可以排序的。
在建立? B+ 樹時,先比較 productid,再比較 promotions,最后比較? dealdate,并且按照比較結(jié)果?生成一顆 B+ 樹。
那么查找的時候,如果檢索的內(nèi)容是:{productid},索引是有效的。
如果?檢索的內(nèi)容是:{ productid(升序)、dealdate(降序)} 那么索引也是有效的,比較的時候,promotions 不比較(永遠==)就可以了。
如果檢索的內(nèi)容是:{ productid(升序)、 dealdate(降序)、promotions(升序)} 這時候,索引就無效了。因為此時的比較條件,與生存索引時的比較條件不一致(生成索引時,productid 相同的情況下,比較 promotions 的大小,此時 productid 相同的情況下,比較 dealdate 的大小。)
如果檢索的內(nèi)容是:{ productid(升序)、dealdate(升序)} 索引也是無效的。此時比較的條件,也與索引生成時不一致。
從上文就可以看出,建立數(shù)據(jù)庫索引時,就要考慮檢索條件才行