文檔數據庫服務屬于NoSQL數據庫,提供了可擴展的高性能數據解決方案,與關系型數據庫(例如MySQL、SQLServer、Oracle)一樣,在數據庫設計、語句優化、索引創建等方面都會影響數據庫的使用性能。
下面從不同維度,給出提升文檔數據庫服務實例使用性能的建議:
查詢操作的性能提升方式
- 使用索引:文檔數據庫服務中的索引和關系型數據庫中的索引類似,都可以加速數據的查詢和排序操作。在MongoDB中,我們可以通過 createIndex 方法來創建索引。
- 合理設計文檔結構:文檔數據庫服務中的數據結構應該遵循“嵌套”的原則,即盡量將關聯的數據放在同一個文檔中。這樣可以減少數據查詢時的I/O操作,提高查詢效率。
- 避免全局掃描:當查詢條件無法使用索引時,查詢操作可能會變得很慢。因此,我們應該盡可能避免全局掃描,可以通過分頁查詢、索引合并等方式來優化查詢。
- 增加緩存:通過使用緩存技術,可以減少查詢操作對數據庫的訪問,提高查詢效率。MongoDB中可以使用TTL索引來實現數據緩存。
- 使用聚合查詢:聚合查詢可以對文檔進行分組、統計、排序等操作,可以更方便地處理復雜的查詢需求。MongoDB中提供了聚合框架來實現這些操作。
常用的文檔數據庫服務索引
文檔數據庫服務支持豐富的索引機制,合理使用索引能夠加快數據檢索速度并降低資源消耗。
單字段索引
比如 coll表中有如下格式的文檔:
{ "_id" : ObjectId("64bde367d89fa22ccaa281e9"), "score" : 98, "name" : "zhang san" }
{ "_id" : ObjectId("64bde367d89fa22ccaa281ea"), "score" : 88, "name" : "li si" }
隨著業務的增長,文檔數逐漸變多后,如果想要按照 score 進行排序并提取 top 10 的文檔就顯得比較困難,因為涉及到全表掃描和內存排序。
此時可以按照 score 字段創建索引來加速查詢,使用 mongo shell 創建索引的命令如下:
db.coll.createIndex({score:1})
命令中包含的 score 字段表示索引的 key, 1 表示升序,-1 表示降序。文檔數據庫服務會按照 score 字段創建索引,底層實現為 b-tree 形式。用戶通過 score 字段進行等值查詢或者排序時,文檔數據庫服務會自動選擇索引進行加速。
用戶也可以在連接上實例后,通過下面的命令查看目前表中包含哪些索引:
db.coll.getIndexes()
多字段復合索引
假設 coll 表中有如下文檔:
{ "_id" : ObjectId("64bde367d89fa22ccaa281e9"), "score" : 98, "name" : "zhang san" }
{ "_id" : ObjectId("64bde367d89fa22ccaa281ea"), "score" : 88, "name" : "li si" }
{ "_id" : ObjectId("64bde73ad89fa22ccaa281eb"), "score" : 98, "name" : "wang wu" }
{ "_id" : ObjectId("64bde73ad89fa22ccaa281ec"), "score" : 98, "name" : "ma liu" }
如果希望查詢 score=98 的所有文檔,并按照 name 字段進行升序排序。
此時可以對 score 和 name 字段創建復合索引,使用 mongo shell 創建復合索引的命令如下:
db.coll.createIndex({score:1, name:1})
復合索引比單字段索引能夠處理的場景更多,比如上述復合索引既可以處理 score+name 聯合查詢的場景,也能處理只按照 score 等值查詢和排序的場景。
但是使用復合索引也有一些注意事項:
- 復合索引遵循前綴匹配原則。比如 {score: 1, name: 1} 復合索引能處理按照 score 查詢的場景,但是不能處理按照 name 查詢的場景。因此在建索引時需要特別注意字段順序。
- 字段越多,索引的存儲空間越大。比如用戶只有按照 score 字段進行查詢和排序的場景,就沒有必要創建復合索引了。
Hash 索引
如果只涉及到對單個字段的等值查詢,可以考慮創建 hash 索引,使用 mongo shell 創建 hash 索引的方式如下:
db.coll.createIndex({score: "hashed"})
Hash 索引底層也是使用 btree 存儲,但是區別在于 btree 的 key 是 hash 之后的值。
對于分片版本的文檔數據庫實例,如果按照 hash 方式創建分片表,則每個 shard server 上會自動創建基于分片鍵的 hash 索引。
Hash 索引有一些限制:
- 不能指定唯一屬性。
- 不能進行范圍掃描。
如果要創建索引的字段很長,由于 hash 索引是按照的 64bit 的hash 值創建索引,可以在一定程度上節省索引的存儲空間。
索引屬性
在創建索引時可以指定屬性完成更高級的功能,常用的配置屬性有:
- TTL 。實現數據自動過期刪除。比如有 coll 表中存放了系統運行日志,其中包含了 createTime 指定了日志生成時間,則可以按照這個字段創建 TTL 索引自動刪除 1 個小時以前的數據:db.coll.createIndex( { "createTime": 1 }, { expireAfterSeconds: 3600 } )。
- Unique。指定字段的唯一性。如果重復插入該字段值相同的數據會報錯失敗,指定方式為 db.coll.createIndex( { "name": 1 }, { unique: true } )。
- Partial。只對滿足條件的文檔創建索引。比如只對分數大于 80 的文檔創建索引:db.coll2.createIndex({score:1, name:1}, {partialFilterExpression: {score: {$gt: 80}} })。
慢查詢定位和處理
用戶可以在控制臺上單機實例名稱進入慢日志展示頁面,查看實例最近的慢請求。慢日志中一般會包含 docsExamined 等信息記錄了本次請求掃描的文檔數,可以結合日志信息和查詢語句來判斷是否缺少合適的索引。
用戶也可以使用 mongo shell 連接到文檔數據庫服務實例,然后執行 explain() 觀察查詢計劃是否復合預期。例如:
db.coll.find({xxx:xxx}).explain()
如果缺少索引,可以執行前文所述的命令進行索引創建,然后重新執行查詢命令觀察執行計劃是否正常。
清理不必要的索引
文檔數據庫服務中每個索引都使用獨立的文件存儲,如果數據庫中有太多不使用或者功能重復的索引,會在一定程度上浪費磁盤空間,并且影響數據庫的寫入性能。
對于功能重復的索引,比如前文提到的 {"score": 1, "name": 1} 復合索引就能覆蓋 {"score": 1} 索引的功能。
用戶可以在使用 mongo shell 連接上文檔數據庫實例之后,執行如下命令進行檢查和清理。
- 查看某個表的所有索引總大小:
db.coll.totalIndexSize()
- 查看每個索引的大小:
db.coll.stats().indexSizes
- 查看每個索引的字段和屬性信息:
db.coll.getIndexes()
- 查看索引的創建時間,使用次數等信息:
db.coll.aggregate([{$indexStats:{}}])
- 清理索引:
db.coll.dropIndex({a: 1})