云開發(fā)的數(shù)據(jù)庫雖然是高性能、支持彈性擴(kuò)容,但是很多用戶在使用的過程中,更加注重功能的實(shí)現(xiàn),而忽視了數(shù)據(jù)庫的設(shè)計(jì)、索引的創(chuàng)建以及語句的優(yōu)化等對性能的影響,因此會遇到很多影響數(shù)據(jù)庫性能的問題,因此這里特意總結(jié)一下云開發(fā)數(shù)據(jù)庫性能優(yōu)化的注意事項(xiàng)。
以下是一些影響數(shù)據(jù)庫性能的優(yōu)化建議,當(dāng)然要結(jié)合具體的業(yè)務(wù)情況來處理,不能一概而論。尤其是一些請求量比較大、比較頻繁,比如小程序首頁的數(shù)據(jù)請求,數(shù)據(jù)庫的優(yōu)化要格外重視。
1、要合理使用索引
使用索引可以提高文檔的查詢、更新、刪除、排序操作,所以要結(jié)合查詢的情況,適當(dāng)創(chuàng)建索引。要盡量避免全表掃描,首先應(yīng)考慮在 where 及 order by 涉及的列上建立索引。更多索引的細(xì)節(jié)在索引的章節(jié)里有介紹。
2、擅于結(jié)合查詢情況創(chuàng)建組合索引
對于包含多個(gè)字段(鍵)條件的查詢,創(chuàng)建包含這些字段的組合索引是個(gè)不錯(cuò)的解決方案。組合索引遵循最左前綴原則,因此創(chuàng)建順序很重要,如果對組合索引不了解,可以結(jié)合索引的命中情況來判斷組合索引是否生效。要善于使用組合索引做到用最少的索引覆蓋最多的查詢。
3、查詢時(shí)要盡可能通過條件和limit限制數(shù)據(jù)
在查詢里where可以限制處理文檔的數(shù)量,而在聚合運(yùn)算中match要放在group前面,減少group操作要處理的文檔數(shù)量。無論是普通查詢還是聚合查詢都應(yīng)該使用limit限制返回的數(shù)據(jù)數(shù)量。
其實(shí)云開發(fā)針對普通查詢db.collection('dbName').get()默認(rèn)都有l(wèi)imit限制,在小程序端的限制為20條(自定義上限也是20條),在云函數(shù)端的限制為100條(自定義上限可以設(shè)置為1000條),聚合則在小程序端和云函數(shù)端默認(rèn)都為20條(自定義沒有上限,幾萬條都可以,前提是取出來的數(shù)據(jù)不能大于16M),也就是云開發(fā)數(shù)據(jù)庫已經(jīng)自帶了一些性能優(yōu)化,我們不應(yīng)該把這些默認(rèn)的限制當(dāng)成是一種束縛,而去隨意突破這些限制。
4、推薦在小程序端增刪改查數(shù)據(jù)庫
可以結(jié)合數(shù)據(jù)庫的安全規(guī)則,讓數(shù)據(jù)庫的增刪改查在小程序端進(jìn)行,這樣速度會更快,而且還可以節(jié)省云函數(shù)的資源。
云開發(fā)數(shù)據(jù)庫的增刪改查可以在小程序端進(jìn)行,也可以在云函數(shù)端進(jìn)行,那到底應(yīng)該把數(shù)據(jù)庫的增刪改查放在小程序端還是云函數(shù)端呢?一般情況下建議放在小程序端,這樣就只會消耗數(shù)據(jù)庫請求的次數(shù),而不會額外增加消耗云函數(shù)的資源使用量GBs、外網(wǎng)出流量。而云函數(shù)雖然有數(shù)據(jù)庫操作的更高的權(quán)限,但是小程序端結(jié)合安全規(guī)則也是可以讓數(shù)據(jù)庫的權(quán)限粒度更細(xì),也能滿足大部分權(quán)限要求。
5、盡可能限制返回的字段等數(shù)據(jù)量
如果查詢無需返回整個(gè)文檔或只是用來判斷鍵值是否存在,普通查詢可以通過filed、聚合查詢可以通過project來限制返回的字段,減少網(wǎng)絡(luò)流量和客戶端的內(nèi)存使用。
{
"title": "為什么要學(xué)習(xí)云開發(fā)",
"content": "云開發(fā)是騰訊云為移動開發(fā)者提供的一站式后端云服務(wù)",
"comments": [{
"name": "李東bbsky",
"created_on": "2020-03-21T10:01:22Z",
"comment": "云開發(fā)是微信生態(tài)下的最推薦的后臺技術(shù)解決方案"
}, {
"name": "小明",
"created_on": "2020-03-21T11:01:22Z",
"comment": "云開發(fā)學(xué)起來太簡單啦"
}]
}
云數(shù)據(jù)庫是關(guān)系型數(shù)據(jù)庫,一個(gè)記錄里可以嵌套非常多的數(shù)組和對象,如果取出整個(gè)記錄里的所有嵌套內(nèi)容就太耗性能流量了,比如上面的嵌套數(shù)組,有時(shí)候業(yè)務(wù)上并不需要顯示comments里的某些字段,是可以通過field的點(diǎn)表示法來限制返回的字段的。
//不顯示comments里的created_on
.field({
"comments.created_on":0
})
//只顯示comments里的comment,comments里的其他字段都不顯示
.field({
"comments.comment":1
})
6、查詢量大時(shí)建議不要用正則查詢
正則表達(dá)式查詢不能使用索引,執(zhí)行的時(shí)間比大多數(shù)選擇器更長,所以業(yè)務(wù)量比較大的地方,能不用正則查詢就不用正則查詢(盡量用其他方式來代替正則查詢),即使使用正則查詢也一定要盡可能的縮寫模糊匹配的范圍,比如使用開始匹配符 ^ 或結(jié)束匹配符 $ 。
比如有人是這樣用正則查詢的,他想根據(jù)省市來篩選客戶來源數(shù)據(jù),但是客戶來源的地址address填寫的是”廣東省深圳市“或”廣東深圳“,省市數(shù)據(jù)并不規(guī)范一致,于是使用正則進(jìn)行模糊查詢,但是如果你需要經(jīng)常根據(jù)地址來篩選客戶來源,那你應(yīng)該在數(shù)據(jù)庫對數(shù)據(jù)進(jìn)行處理,比如province和city來清洗重組數(shù)據(jù)從而替代模糊查詢。
7、盡可能使用更新指令
通過更新指令對文檔進(jìn)行修改,通??梢垣@得更好的性能,因?yàn)楦轮噶畈恍枰樵兊接涗浘涂梢灾苯訉ξ臋n進(jìn)行字段級的更新,尤其是不需要更新整個(gè)文檔只需要更新部分字段的場景。
還是上面的那個(gè)記錄為例,比如我們需要給文章添加評論,也就是往comments數(shù)組里添加值,我們可以使用 _.push
來給數(shù)組字段進(jìn)行字段級別的操作,而不是取出整個(gè)記錄,然后把評論用數(shù)組的concat或push的方法添加到記錄里,再更新整個(gè)記錄:
.update({
data:{
comments:_.push([{
"name": "小明",
"created_on": "2020-03-21T11:01:22Z",
"comment": "云開發(fā)學(xué)起來太簡單啦"
}])
}
})
云開發(fā)數(shù)據(jù)庫一個(gè)記錄可能會嵌套很多層,因此也會很大,使用更新指令進(jìn)行字段級別的微操比直接使用update這種記錄級別的更新性能要更好。
8、不要對太多數(shù)據(jù)進(jìn)行排序
不要一次性取出太多的數(shù)據(jù)并對數(shù)據(jù)進(jìn)行排序,如果需要排序,請盡量限制結(jié)果集中的數(shù)據(jù)量,比如我們可以先用where、match等操作限制數(shù)據(jù)量,也就是通常要把orderBy放在普通查詢或聚合查詢的最后面。
這里尤其強(qiáng)調(diào)的是,發(fā)現(xiàn)有不少人由于對數(shù)據(jù)庫的排序orderBy與翻頁skip沒有理解,竟然把數(shù)據(jù)庫所有數(shù)據(jù)使用遍歷取出來之后再來排序,哪怕是數(shù)據(jù)量只有百千條,這也是不正確的處理方式,應(yīng)該禁止這么干。排序使用數(shù)據(jù)庫的普通查詢或聚合查詢的orderBy就可以做到了,云開發(fā)默認(rèn)的limit數(shù)據(jù)限制不會影響排序的結(jié)果,禁止遍歷取出所有數(shù)據(jù)再來排序的愚蠢行為。
當(dāng)然如果業(yè)務(wù)會需要經(jīng)常對同一數(shù)據(jù)的多個(gè)字段來排序,比如商品經(jīng)常會按最新上架、價(jià)格高低、產(chǎn)地、折扣力度等進(jìn)行排序,則建議一次性取出這些數(shù)據(jù),存儲在緩存中,使用JavaScript的數(shù)組來進(jìn)行排序,而不是用數(shù)據(jù)庫查詢。
9、盡量少在業(yè)務(wù)量大的地方用以下查詢指令
查詢中的某些查詢指令可能會導(dǎo)致性能低下,如判斷字段是否存在的exists
,要求值不在給定的數(shù)組內(nèi)的nin
,表示需滿足任意多個(gè)查詢篩選條件or
,表示需不滿足指定的條件not
,盡量少在業(yè)務(wù)使用量比較大的地方用這些查詢指令。
這里所說的盡量少用不代表不用,而是能夠用最直接的方式就用最直接的方式代替,讓數(shù)據(jù)庫查詢盡可能的簡單而不是搞的過于復(fù)雜,盡可能少讓查詢指令做這些復(fù)雜的事情。
10、集合中文檔的數(shù)量可以定期歸檔
集合中文檔的數(shù)據(jù)量會影響查詢性能,對不用的數(shù)據(jù)或過期的數(shù)據(jù)可以進(jìn)行定期歸檔并刪除。比如我們也可以借助于定時(shí)觸發(fā)器周期性的對數(shù)據(jù)庫里的數(shù)據(jù)進(jìn)行備份、刪除。
11、不要讓數(shù)據(jù)庫請求干多余的事情,盡量少干事
能夠使用JavaScript替代的計(jì)算、數(shù)組、對象操作等,就盡量用JavaScript處理;能通過數(shù)據(jù)庫設(shè)計(jì)讓數(shù)據(jù)庫查詢少計(jì)算的就盡量合理設(shè)計(jì)數(shù)據(jù)庫,要盡可能的讓數(shù)據(jù)庫少干活,不能一次查詢多個(gè)指令、正則查詢套來套去的。
12、在數(shù)據(jù)庫設(shè)計(jì)時(shí)可以用內(nèi)嵌文檔來取代lookup
云開發(fā)數(shù)據(jù)庫是非關(guān)系型數(shù)據(jù)庫,可以對經(jīng)常要使用lookup跨表查詢的情況做反范式化的內(nèi)嵌文檔設(shè)計(jì),通過這種方式取代聯(lián)表查詢lookup可以提升不少性能。
減少使用聯(lián)表查詢lookup的使用的方式要注意兩點(diǎn),一是通過內(nèi)嵌文檔的方式是可以減少關(guān)系型數(shù)據(jù)庫那種表與表之間的關(guān)聯(lián)關(guān)系的,比如要聯(lián)表取出博客里最新的10篇文章以及文章里相應(yīng)的評論,這在關(guān)系型數(shù)據(jù)庫里原本是需要聯(lián)表查詢的,但是當(dāng)把評論內(nèi)嵌到文章的集合里時(shí),就不需要聯(lián)表了;二是有的時(shí)候我們只是需要跨表而不是聯(lián)表,可以通過多次查詢來取代聯(lián)表。
13、推薦使用短字段名
和關(guān)系型數(shù)據(jù)庫不同的是,云開發(fā)數(shù)據(jù)庫是文檔型數(shù)據(jù)庫,集合中的每一個(gè)文檔都需要存儲字段名,因此字段名的長度相比關(guān)系型數(shù)據(jù)庫來說會需要更多的存儲空間。
"comments": [{
"name": "李東bbsky",
"created_on": "2020-03-21T10:01:22Z",
"comment": "云開發(fā)是微信生態(tài)下的最推薦的后臺技術(shù)解決方案"
}, {
"name": "小明",
"created_on": "2020-03-21T11:01:22Z",
"comment": "云開發(fā)學(xué)起來太簡單啦"
}]
這里的字段名name、created_on、comment有多少個(gè)記錄,有多少個(gè)嵌套的對象就會被寫多少次,有時(shí)候比字段的值還要長,是比較占空間的。
在業(yè)務(wù)上有些關(guān)鍵的數(shù)據(jù)可以通過間接的方式查詢獲取到,但是由于查詢時(shí)會存在計(jì)算、跨表等問題,這個(gè)時(shí)候建議新增一些冗余字段。
比如我們要統(tǒng)計(jì)文章下面的評論數(shù),可能你將文章的評論獨(dú)立建了一個(gè)集合如comments,這時(shí)候要獲取每篇文章的評論數(shù)是可以根據(jù)文章的id條件來count該文章有多少條評論的?;蛘吣阋部梢园衙科恼碌脑u論數(shù)組作為子文檔內(nèi)嵌到每個(gè)文章記錄的comments字段,這個(gè)時(shí)候可以通過數(shù)組的長度來算出該文章的評論數(shù)。類似于評論數(shù)的還有點(diǎn)贊量、收藏量等,這些雖然都是可以通過count或數(shù)組length的方式來間接獲取到的,但是在評論數(shù)很多的情況下,count和數(shù)組的length是非常耗性能的,而且count還需要獨(dú)立占據(jù)一個(gè)請求。
遇到這種情況,建議在數(shù)據(jù)庫設(shè)計(jì)時(shí),要用所謂的冗余字段來記錄每篇文章的點(diǎn)贊量、評論數(shù)、收藏量,在小程序端直接用inc原子自增的方式更新該字段的值。
{
"title": "為什么要學(xué)習(xí)云開發(fā)",
"content": "云開發(fā)是騰訊云為移動開發(fā)者提供的一站式后端云服務(wù)",
"commentNum":2, //新增一個(gè)評論數(shù)的字段
"comments": [{
"name": "李東bbsky",
"created_on": "2020-03-21T10:01:22Z",
"comment": "云開發(fā)是微信生態(tài)下的最推薦的后臺技術(shù)解決方案"
}, {
"name": "小明",
"created_on": "2020-03-21T11:01:22Z",
"comment": "云開發(fā)學(xué)起來太簡單啦"
}]
}
比如我們希望在博客的首頁展示文章列表,而每篇文章要顯示評論總數(shù)。雖然我們可以通過comments的數(shù)組長度以及如果存在二級三級評論(尤其是這種情況),也是可以通過數(shù)組方法獲取到評論數(shù),但是不如直接查詢新增的冗余字段commentNum
來得直接。
有時(shí)候我們的業(yè)務(wù)會需要用戶經(jīng)常刪除數(shù)據(jù)庫里面的記錄或記錄里的數(shù)組的情況,但是刪除數(shù)據(jù)是非常耗費(fèi)性能的一件事,碰到業(yè)務(wù)高峰期,數(shù)據(jù)庫就會出現(xiàn)性能問題。這個(gè)時(shí)候,建議新增冗余字段做虛假刪除,比如給記錄添加delete的字段,默認(rèn)值為false,當(dāng)執(zhí)行刪除的時(shí)候,可以將字段的值設(shè)置true,查詢時(shí)只顯示delete為false的記錄,這樣數(shù)據(jù)在前端就不顯示了,做到了虛假刪除,在業(yè)務(wù)低谷時(shí)比如凌晨可以結(jié)合定時(shí)觸發(fā)器每天這個(gè)時(shí)候清理一遍。
我們經(jīng)常會有查詢數(shù)據(jù)庫里的數(shù)據(jù),并對數(shù)據(jù)進(jìn)行處理之后再寫回?cái)?shù)據(jù)庫的需求,如果查詢到的數(shù)據(jù)有很多條時(shí),就會需要我們進(jìn)行循環(huán)處理,不過這個(gè)時(shí)候一定要注意,不要把數(shù)據(jù)庫請求放到循環(huán)體內(nèi),而是先一次性查詢多條數(shù)據(jù),在循環(huán)體內(nèi)對數(shù)據(jù)進(jìn)行處理之后再一次性寫回?cái)?shù)據(jù)庫。
當(dāng)然小程序有些接口不能進(jìn)行數(shù)組操作,只能一條一條執(zhí)行,比如發(fā)送訂閱消息、上傳文件等操作等,這個(gè)避免的不了的例外。但是有些是可以通過數(shù)據(jù)庫的設(shè)計(jì)來規(guī)避這個(gè)問題的,比如把經(jīng)常要新增大量記錄的數(shù)據(jù)庫設(shè)計(jì)為只需要新增內(nèi)嵌文檔的數(shù)組數(shù)據(jù)等。
在數(shù)據(jù)庫的設(shè)計(jì)上以及在數(shù)據(jù)庫請求的代碼上,盡可能用一個(gè)數(shù)據(jù)庫請求來代替多個(gè)數(shù)據(jù)庫請求,尤其是用戶最常訪問的首頁,如果一個(gè)頁面的數(shù)據(jù)庫請求太多,會導(dǎo)致數(shù)據(jù)庫的并發(fā)問題。有些數(shù)據(jù)能夠緩存到小程序端就緩存到小程序度,不必過分強(qiáng)調(diào)數(shù)據(jù)的一致性。
我們有這樣一個(gè)集合user,最終會用來存儲用戶的個(gè)人信息,比如當(dāng)我們在用戶點(diǎn)擊登錄時(shí)會獲取用戶的昵稱和頭像,于是一般的邏輯是我們會在數(shù)據(jù)庫創(chuàng)建一個(gè)記錄,如下所示:
_id:"",
userInfo:{
"name":"李東bbsky",
"avatarUrl":"頭像地址"
}
但是更好的方式是,我們應(yīng)該創(chuàng)建一個(gè)完整的記錄(按照最終的字段設(shè)計(jì)),哪怕現(xiàn)在還沒有數(shù)據(jù),也要一致性建好這些空字段,方便以后直接使用update的方式來往里面填充數(shù)據(jù)。
_id:"",
userInfo:{
"name":"李東bbsky",
"avatarUrl":"頭像地址",
"email":"",
"address":""
...
},
stars:[],//存儲點(diǎn)贊的文章
collect:[] //存儲收藏的文章
目前我們沒法直接查看數(shù)據(jù)庫請求所花費(fèi)的時(shí)間,但是有一些其他數(shù)據(jù)作為佐證,在云函數(shù)端進(jìn)行數(shù)據(jù)庫請求,如果云函數(shù)的執(zhí)行時(shí)間超過100ms甚至更多,則基本可以判定為慢查詢,數(shù)據(jù)庫需要優(yōu)化。這時(shí),慢查詢不僅會影響數(shù)據(jù)庫的性能,還會影響云函數(shù)的性能。
我們知道云函數(shù)和云數(shù)據(jù)庫的并發(fā)都是非常依賴他們的耗時(shí)的,如果數(shù)據(jù)庫查詢速度變慢,查詢一次耗時(shí)由幾十毫秒增加到幾百毫秒,甚至以秒計(jì)算,都是十分耗費(fèi)資源和影響并發(fā)的:
Connection num overrun
的報(bào)錯(cuò)。我們可以在云開發(fā)控制臺設(shè)置-告警設(shè)置來給指定的云函數(shù)尤其是業(yè)務(wù)調(diào)用最頻繁的云函數(shù)設(shè)置運(yùn)行時(shí)間以及云函數(shù)運(yùn)行錯(cuò)誤的告警,以便隨時(shí)了解云開發(fā)環(huán)境的運(yùn)行狀況。
更多建議: