MongoDB 學(xué)習(xí)筆記

MongoDB, MySQL和Redis的區(qū)別和使用場景

  • MySQL是關(guān)系型數(shù)據(jù)庫,支持事件
  • MongoDB和Redis是非關(guān)系型數(shù)據(jù)庫,不支持事件
  • 如何快速選擇
    • 希望速度快的時候,使用MongoDB或者Redis
    • 數(shù)據(jù)量過大的時候,可以將頻繁使用的數(shù)據(jù)存入Redis,其他的存入MongoDB
    • MongoDB不用提前建表建數(shù)據(jù)庫,使用方便,字段數(shù)量不確定的時候使用MongoDB
    • 后續(xù)需要用到數(shù)據(jù)之間的關(guān)系,可以考慮MySQL

數(shù)據(jù)庫常用操作

查詢所有的數(shù)據(jù)庫
show dbs

切換到某個數(shù)據(jù)庫
use databaseName
Notice:

  • 當(dāng)該數(shù)據(jù)庫存在時,會切換到該數(shù)據(jù)庫
  • 當(dāng)數(shù)據(jù)庫不存在時,會自動創(chuàng)建該數(shù)據(jù)庫,并切換到該數(shù)據(jù)庫

查看當(dāng)前所處的數(shù)據(jù)庫
db

集合常用操作

創(chuàng)建一個新的集合
db.createCollection(name,{capped,autoIndexId,size,max,...})

查詢數(shù)據(jù)
db.collection.find({cretiria}):
返回所有滿足過濾條件的文檔

db.collection.findOne({cretiria}):
返回滿足過濾條件的第一個文檔

pretty():
將輸出文檔進(jìn)行格式化

索引文檔嵌套字段

對于文檔:

{
   "address": {
      "city": "Los Angeles",
      "state": "California",
      "pincode": "123"
   },
   "tags": [
      "music",
      "cricket",
      "blogs"
   ],
   "name": "Tom Benzamin"
}

假設(shè)我們需要通過city,state,pincode字段來檢索文檔
db.users.find({'address.city':'Los Angeles'})

邏輯運(yùn)算符

and :{key1:value1,key2:value2}
or :{$or:[{key1:value1},{key2:value2}]}
and ,or: {key1:value1,$or:[{key2:value2},{key3:value3}]}

范圍運(yùn)算符

$in: 等于數(shù)組內(nèi)某個值,{age:{$in:[18,20]}}
$nin:不等于數(shù)組內(nèi)任何一個值,{age:{$nin:[18,20]}}

投影

db.collection.find({},{field1:value,field2:value}):

  • 0:表示不顯示
  • 1:表示顯示
    Notice:
  • _id字段默認(rèn)顯示;
  • 括號內(nèi)除_id字段,包括項(1)和排除項(0)不能同時使用

MongoDB 的更新操作

  • 以下語句會將 name 為 xiaowang的整個文檔替換為 {'name':'xiaozhao'}
    db.collection.updateOne({'name':'xiaowang'},{'name':'xiaozhao'})
    db.collection.replaceOne({'name':'xiaowang'},{'name':'xiaozhao'})

  • 以下語句會將 name 為 xiaowang 的這個文檔中的 name 字段更新為 xiaozhao
    db.collection.updateOne({'name':'xiaowang'},{$set:{'name':'xiaozhao'}})

  • 如果希望所有滿足條件的數(shù)據(jù)都得到更新,可以使用
    db.collection.updateMany({'name':'xiaowang'},{$set:{'name':'xiaozhao'}})

  • 如果將參數(shù) upsert設(shè)置為 true,那么當(dāng)沒有找到滿足條件的文檔時,插入的文檔將作為新文檔插入
    db.collection.updateOne({'name':'xiaowang'},{'name':'xiaozhao'},{'upsert':true})

MongoDB 刪除操作

  • 刪除滿足條件的第一條數(shù)據(jù)
    db.collection.deleteOne({criterion})

  • 刪除滿足條件的所有數(shù)據(jù)
    db.collection.deleteMany({critetion})

統(tǒng)計個數(shù)

方法count()用于統(tǒng)計結(jié)果集合中文檔的數(shù)量
db.collectionName.find({criteria}).count()

Notice:在新版本4.0中建議使用countDocuments(criteria,limit,skip,...)代替,上面的語句等價于:
db.collectionName.countDocuments({criteria})

相當(dāng)于:

db.collection.aggregate([
   {$match:<query>},
   {$group:{_id:null,n:{$sum:1}}},
])

消除重復(fù)

distinct()返回文檔特定字段的不同值
db.collectionName.distinct('去重字段',{條件})
db.stu.distinct(hometown,{age:{$gt:18}})

skip(), limit(), sort() 三個函數(shù)在一天語句中的時候,執(zhí)行的順序是 sort(), skip(), 最后是limit().
sort({field1:num1, field2:num2}): 1表示升序排列,-1表示降序排列

建立索引

MongoDB使用createIndex()函數(shù)來創(chuàng)建索引,3.0版本之前使用db.collection.ensureIndex() 函數(shù)創(chuàng)建索引。

  • 默認(rèn)情況下,索引字段是可以有重復(fù)的

  • 如果想要創(chuàng)建唯一值索引,可以使用:

    • db.collection.createIndex({field:1},{unique:true})
  • 創(chuàng)建聯(lián)合索引(對多個字段同時創(chuàng)建索引)

    • db.collection.createIndex({field1:1,field2:-1})
  • 查看集合內(nèi)文檔的所有索引

    • db.collection.getIndexes()
  • 刪除索引:

    • db.collection.dropIndexes() ## 刪除所有索引
    • db.collection.dropIndex(index_name) ## 刪除指定索引

數(shù)據(jù)庫的備份和恢復(fù)

數(shù)據(jù)庫備份語句:
mongodump -h dbhost -d dbname -o dbdirectory

  • dbhost:服務(wù)器地址,
  • dbname:要備份的數(shù)據(jù)庫
  • dbdirectory:存放備份數(shù)據(jù)庫的路徑

Notice:如果要備份本地數(shù)據(jù)庫,可直接使用:
mongodump -o dbdirectory ## 會把所有的數(shù)據(jù)庫文檔進(jìn)行備份
mongodump -d databaseName -o dbdirectory ## 會把指定的數(shù)據(jù)庫進(jìn)行備份

數(shù)據(jù)庫恢復(fù)語句:
mongorestore -h dbhost -d dbname --dir directory

  • dbhost:服務(wù)器地址,
  • dbname:用于存放數(shù)據(jù)的數(shù)據(jù)庫名稱
  • directory:存放備份數(shù)據(jù)庫集合的路徑

Notice:如果要恢復(fù)到本地數(shù)據(jù)庫,可直接使用:
mongorestore -d dbname --dir directory
Notice:這里的directory指的是集合所在的路徑,是備份數(shù)據(jù)庫時所指定的路徑dbdirectory的下一級文件夾

數(shù)據(jù)聚合操作

聚合(aggregate)是基于數(shù)據(jù)處理的聚合管道,每個文檔通過一個由多個階段(stage)組成的管道,可以對每個階段的管道進(jìn)行分組、過濾等功能,然后經(jīng)過一系列的處理,輸出相應(yīng)的結(jié)果。

常用的管道:

  • $match:過濾數(shù)據(jù),只輸出滿足條件的文檔
  • $group:將集合中的文檔分組,用于統(tǒng)計結(jié)果
  • $project:修改輸入文檔的結(jié)構(gòu),如重命名、增加、刪除字段、創(chuàng)建計算結(jié)果
  • $sort:將輸入文檔排序后輸出
  • $skip:跳過指定數(shù)量的文檔,并返回余下的文檔
  • $limit:限制通過該階段的文檔數(shù)
  • $unwind:將數(shù)組類型的字段拆分

$group會根據(jù)_id后面的字段進(jìn)行分組,取字段的值的時候需要用$運(yùn)算符

db.collection.aggregate([
   {`$match`:{`field`:value}},
   {`$group`:{'_id':field,count:{`$sum`:1},'avg_age':{'$avg':'$age'}}},
   {`$project`:{'cate':'_id','counter':'$count','_id':0,'avg_age':1,}},
])

$group還可以根據(jù)多個字段進(jìn)行分組

db.collection.aggregate([
   {$group:{'_id':{'country':'$country','city':'$city'}}},
   ## 分組之后的結(jié)果是{_id:{country:value,city:value}}
   ## 取字典嵌套的字段的值,可以用`_id.country`,`_id.city`
   {$group:{'_id':{'country':'$_id.country','city':'$_id.city'},'count':{$sum:1}}},
   {$project:{'country':'$_id.country','city':'$_id.city','_id':0}}
])

$group的另一種用法:用于統(tǒng)計所有文檔

db.collection.aggregate([
   {`$match`:{`field`:value}},
   {`$group`:{'_id':null,count:{`$sum`:1},'avg_age':{'$avg':'$age'}}},
])

$sort可對多個字段進(jìn)行排序

db.collection.aggregate([
   {$sort:{field1:1,field2:-1}}
])

$unwind將文檔中某數(shù)組類型的字段拆分成多條,每條包含數(shù)組中的一個值

db.t.insertOne({'_id':1,'item':'t-shirt',`size`:['S','M','L']})
db.t.aggregate([
   {$unwind:'$size'},
])

結(jié)果為:

{'_id':1,'item':'t-shirt','size':'S'}
{'_id':1,'item':'t-shirt','size':'M'}
{'_id':1,'item':'t-shirt','size':'L'}

Notice:在默認(rèn)情況下,使用$unwind對數(shù)組字段展開時,會自動忽略null和空數(shù)組,
如果想要保留null和空數(shù)組,可以設(shè)置參數(shù)preserveNullAndEmptyArrays為true。
db.collection.aggregate({$unwind:{path:'$field',preserveNullAndEmptyArrays:true}})

explain():可用于查看命令執(zhí)行的詳細(xì)情況,輸出的信息詳細(xì)級別依次為:queryPlanner(默認(rèn)),executionStatsallPlanExecution
db.collection.explain('executionStats').find()

MongoDB 原子操作常用命令

$set: 用來指定一個鍵并更新鍵值,若該鍵不存在,就創(chuàng)建該鍵
{$set:{field:value}}

$inc:對文檔的某個數(shù)字類的鍵進(jìn)行增減操作
{$inc:{field:value}}

$push:把value追加到field里面,field一定要是數(shù)組類型才行,如果field不存在,會新增加一個數(shù)組類型進(jìn)去。
{$push:{field:value}}

$pushAll:同$push,只是一次可以追加多個值到一個數(shù)組字段內(nèi)。
{$pushAll:{field:value_array}}

$pull:從數(shù)組field內(nèi)刪除一個等于value的值
{$pull:{field:_value}}

$pop:刪除數(shù)組的第一個或最后一個元素
{$pop:{field:1}}

$rename:修改字段名稱
{$rename:{old_field_name:new_field_name}}

正則表達(dá)式

MongoDB使用$regex操作符來設(shè)置匹配字符串的正則表達(dá)式。
使用正則表達(dá)式之前不需要做任何配置。
考慮以下文檔:

{
   "post_text": "enjoy the mongodb articles on runoob",
   "tags": [
      "mongodb",
      "runoob"
   ]
}

使用正則表達(dá)式查找 post_text 包含 Runoob 字符串的文章

db.posts.find({post_text:{$regex:'Runoob'}})
db.posts.find({post_text:/Runoob/})
## 查找以enjoy開頭
db.posts.find({post_text:/^enjoy/})
## 查找以runoob結(jié)尾
db.posts.find({post_text:/Runoob$/})

如果需要檢索不區(qū)分大小寫,可以設(shè)置$options為$i

以下命令將查找不區(qū)分大小寫的字符串runoob
db.posts.find({post_text:{$regex:"runoob",$options:"$i"}})

優(yōu)化正則表達(dá)式查詢

  • 如果文檔中設(shè)置了索引,那么使用索引相比于正則表達(dá)式匹配查找所有的數(shù)據(jù)查詢速度更快。
  • 如果正則表達(dá)式是前綴表達(dá)式,所有匹配的數(shù)據(jù)將以指定的前綴字符串開始;

MongoDB 固定集合 (Capped Collection)

MongoDB 固定集合是性能出色,且有著固定大小的集合,就像一個環(huán)形隊列,當(dāng)集合空間用完之后,再插入的元素就會覆蓋最初始的元素。

Notice: 固定集合無法使用remove()函數(shù)刪除部分文檔,但是可以刪除全部文檔

創(chuàng)建固定集合
db.createCollection('cappedCollection',{capped:true,size:maxSize,max:maxNum})
其中:size是整個集合空間的大小,單位是KB;
max是集合文檔個數(shù)的上限,單位是'個';
如果空間大小達(dá)到上限,插入新文檔的時候,會覆蓋第一個文檔;
如果文檔個數(shù)到達(dá)上限,插入新文檔的時候,同樣會覆蓋第一個文檔。

判斷一個集合是否為固定集合:
db.collection.isCapped()

如果需要將一個已經(jīng)存在的集合轉(zhuǎn)換為固定集合,可以使用:
db.runCommand({'convertToCapped':collection,size:value})

固定集合的屬性

  • 對固定集合進(jìn)行插入數(shù)據(jù),速度極快
  • 按照插入順序的查詢,輸出速度極快
  • 能夠在插入最新數(shù)據(jù)時,淘汰最早的數(shù)據(jù)

常用用途

  • 存儲日志信息
  • 緩存一些少量的文檔

啟動 MongoDB 容器:

docker run -d --network host --name mongodb -e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=admin mongo

  1. MongoDB中刪除集合, 有2種方式:

    1. db.coll.remove({}): 刪除文件之后, 文件原本占用的物理空間不會被回收; 是刪除符合條件的數(shù)據(jù), 不會刪除其他結(jié)構(gòu)(如索引等);
    2. db.coll.drop(): 刪除文件之后, 文件原本占用的物理空間會進(jìn)行回收;

    針對物理空間沒有進(jìn)行回收的情況, 可以使用compact命令釋放這些空間, 具體命令為db.runCommand({"compact":"collection_name"}).

  2. 主幣交易要求交易hash(數(shù)據(jù)庫中的_id)和代幣交易的交易hash log_idx block_number(transaction_hash,block_number,log_index)的組合是唯一值. 在使用 pymongo批量插入時, 需要:

    try:
        eth_trans_coll.insert_many(
            trans, ordered=False, bypass_document_validation=True)
    except pymongo.errors.BulkWriteError as e:
        logging.debug(e)
    

    備注: orderd=False時, 會并行插入數(shù)據(jù)庫, 可以大幅提高插入效率; 同時, 即便部分?jǐn)?shù)據(jù)插入時出錯, 也不會影響其他數(shù)據(jù)的插入.bypass_document_validation=True可以繞過文件驗證環(huán)節(jié), 提高插入效率.

索引

刪除索引

  • 批量刪除索引: db.coll_name.dropIndexes([idx_name_1,idx_name_2])

explain 支持 queryPlanner(僅給出執(zhí)行計劃)、executionStats(給出執(zhí)行計劃并執(zhí)行)和 allPlansExecution(前兩種的結(jié)合)三種分析模式,默認(rèn)使用的是 queryPlanner

Mongo 會通過優(yōu)化分析選擇其中一種更好的方案放置到 winningPlan,最終的執(zhí)行計劃是 winningPlan 所描述的方式。其它稍次的方案則會被放置到 rejectedPlans 中,僅供參考。

所以 queryPlanner 的關(guān)注點(diǎn)是 winningPlan,如果希望排除其它雜項的干擾,可以直接只返回 winningPlan 即可:

db.coll_name.find().explain().queryPlanner.winningPlan

winningPlan 中,總執(zhí)行流程分為若干個 stage(階段),一個 stage 的分析基礎(chǔ)可以是其它 stage 的輸出結(jié)果。從這個案例來說,首先是通過 IXSCAN(索引掃描)的方式獲取到初步結(jié)果(索引得到的結(jié)果是所有符合查詢條件的文檔在磁盤中的位置信息),再通過 FETCH 的方式提取到磁盤上各個位置所對應(yīng)的完整文檔。這是一種很常見的索引查詢計劃(explain 返回的是一個樹形結(jié)構(gòu),實際先執(zhí)行的是子 stage,再往上逐級執(zhí)行父 stage

除了 queryPlanner 之外,還有一種非常有用的 executionStats 模式:

db.coll_name.find().explain('executionStats').executionStats

總體上大同小異,一些關(guān)鍵字段可以了解一下:

  • nReturned:執(zhí)行返回的文檔數(shù)
  • executionTimeMillis: 執(zhí)行時間(ms)
  • totalKeysExamined:索引掃描條數(shù)
  • totalDocsExamined:文檔掃描條數(shù)
  • executionStages:執(zhí)行步驟

MongoBD的索引采用B樹結(jié)構(gòu), 相較于時間復(fù)雜度為O(1)的哈希結(jié)構(gòu), B樹雖然效率為O(logN), 但它允許范圍查詢, 這是哈希樹無法做到的. 但是以下語句無法使用索引:

  • 正則表達(dá)式,及'非'操作符, 如$nin,$not等;
  • 算術(shù)運(yùn)算符, 如$mod等;
  • $where子句;

覆蓋索引

常見的執(zhí)行步驟為:

  • 在內(nèi)存中掃描索引(IXSCAN), 得到需要的結(jié)果(完整文檔在磁盤上的位置);
  • 從磁盤上根據(jù)索引結(jié)果, 拉取特定位置的文檔(FETCH);
  • 執(zhí)行字段映射, 得到需要的字段;

當(dāng)查詢的字段和要返回的字段都在一個組合索引中, 數(shù)據(jù)庫就不需要取磁盤抓取完整的文檔了. _id這個字段默認(rèn)會返回, 但是如果不需要的化, 可以不讓其返回.

文件排序

索引的2個主要用途就是分組和排序, 使用索引篩選, 也是建立在排序的基礎(chǔ)上. 所以可以在排序字段和篩選字段上建立組合索引,

MongoDB返回的文檔順序和查詢時的掃描順序一致, 掃描順序又分為集合掃描(COLLSCAN)和索引掃描(IXSCAN);

  • 集合中沒有索引, 或者索引失效時, MongoDB會掃描全部文檔, 掃描的順序和該集合中的文檔在磁盤上的存儲順序相同;
  • 當(dāng)掃描索引時, 會在內(nèi)存中對索引值進(jìn)行匹配, 得到所有等匹配到的文檔后, 再根據(jù)情況決定是否到磁盤拉取相應(yīng)的文檔, 或進(jìn)行下一階段. 因為索引本身就是排好序的, 所以掃描順序就取決于索引本身的順序;

常見的幾個階段:

  • IXSCAN:通過索引, 掃描出相關(guān)文檔的位置;
  • FETCH: 根據(jù)前面掃描到的位置到磁盤上抓取相應(yīng)的完整文檔;
  • SORT_KEY_GENERATOR: 獲取每一個文檔排序所用的健值;
  • SORT: 進(jìn)行內(nèi)存排序, 返回最終結(jié)果;(在內(nèi)存中排序, 效率非常低, 當(dāng)結(jié)果集大于32MB時, 還會返回錯誤).

批量插入/更新

BTC 的主幣交易和代幣交易都是使用 transaction_hash 作為 _id, 插入重復(fù)數(shù)據(jù)時, 可以直接使用 updateone 和 bulk_write;

其他幣種的代幣交易在transaction_hash, block_number, log_index上建有唯一值索引, 需要根據(jù)這三個字段篩選數(shù)據(jù), 有兩種方式

# 逐個指定需要更新字段
tasks = [UpdateOne({'transaction_hash': token_transfer['transaction_hash'],
                    'block_number': token_transfer['block_number'],
                    'log_index': token_transfer['log_index']},
                   {'$set':{'from_address':token_transfer['from_address'],
                            'to_address':token_transfer['to_address']}}, upsert=True)
            for token_transfer in token_trans]
trans_coll.bulk_write(
    tasks, ordered=False, bypass_document_validation=True)

# 把重復(fù)數(shù)據(jù)刪除 再批量插入
tasks = [DeleteOne({'transaction_hash': token_transfer['transaction_hash'],
                    'block_number': token_transfer['block_number'],
                    'log_index': token_transfer['log_index'], })
            for token_transfer in token_trans]
trans_coll.bulk_write(
    tasks, ordered=False, bypass_document_validation=True)
token_transfer_coll.insert_many(
    token_trans, ordered=False, bypass_document_validation=True)

# 錯誤的方式 整體更新會更變_id, 而MongoDB中 _id 不能更改,所以會報錯
tasks = [UpdateOne({'transaction_hash': token_transfer['transaction_hash'],
                    'block_number': token_transfer['block_number'],
                    'log_index': token_transfer['log_index']},
                   {'$set':token_transfer, upsert=True)
            for token_transfer in token_trans]
trans_coll.bulk_write(
    tasks, ordered=False, bypass_document_validation=True)

TIPS

  • $OR: 很可能會導(dǎo)致索引失效, 最好能用$IN代替, 如:

    # 存在索引 "province_1_city_1"
    db.collection.find({
        $or: [{
            province: "廣東省"
        }, {
            province: "福建省"
        }],
        city: "深圳市",
    })
    # 建議使用
    db.collection.find({
        province: {
            $in: ["廣東省", "福建省"]
        },
        city: "深圳市",
    })
    
  • 在數(shù)組中遍歷:

    數(shù)據(jù)樣例:{ "_id" : "1d8149eb8d8475b98113b5011cf70e0b7a4dccff71286d28b8b4b641f94f1e46", "block_number" : 700000, "block_timestamp" : ISODate("2021-09-11T04:14:32Z"), "value" : NumberDecimal("640388640"), "is_coinbase" : true, "inputs" : [ [ "0000000000000000000000000000000000000000000000000000000000000000", NumberLong("4294967295") ] ], "outputs" : [ [ "bc1q7wedv4zdu5smt3shljpu5mgns48jn299mukymc", 640388640 ], [ ], [ ] ] }
    
    # 需要查詢outputs中包含指定地址的交易
    db.trans_test.find({'outputs':{'$elemMatch':{'0':{'$eq':'bc1q7wedv4zdu5smt3shljpu5mgns48jn299mukymc'}}}})
    db.trans_test.find({'outputs':{'$elemMatch':{'0':'bc1q7wedv4zdu5smt3shljpu5mgns48jn299mukymc'}}})
    

優(yōu)化案例

  1. timevalue上創(chuàng)建組合索引,db.usdt_token_trans.createIndex({'time':1,'value':1}):
  2. 分別在timevalue上創(chuàng)建索引:
> db.usdt_token_trans.find({$and:[{time:{$gte:ISODate("2021-05-01T18:11:00Z")}},
                                {time:{$lte:ISODate("2021-05-01T19:11:00Z")}},
                                {value:{$gte:NumberLong("2998999900")}},
                                {value:{$lte:NumberLong("2999000100")}}]}).explain("executionStats")
# 創(chuàng)建組合索引時:
 "executionStats" : {
  "executionSuccess" : true,
  "nReturned" : 520,
  "executionTimeMillis" : 39,
  "totalKeysExamined" : 5411,
  "totalDocsExamined" : 520,
  "indexBounds" : {
     "time" : [
      "[new Date(1619892660000), new Date(1619896260000)]"
     ],
     "value" : [
      "[49999999900, inf.0]"
     ]
# 在 time 和 value 上單獨(dú)創(chuàng)建索引時
"executionStats" : {
  "executionSuccess" : true,
  "nReturned" : 520,
  "executionTimeMillis" : 85,
  "totalKeysExamined" : 33226,
  "totalDocsExamined" : 33226,
  "indexBounds" : {
     "time" : [
      "[new Date(1619892660000), new Date(1619896260000)]"
     ]
    },
  • 由上面的例子可以看出, 創(chuàng)建組合索引時, 查詢時需要遵守最佳左前綴法則(先使用前面的字段, 然后再使用后面的字段, 否則就是全文掃描);

    • 組合索引前面的字段是范圍查詢時, 后面字段的索引依然可以使用;
  • 在多個子段上分別建立索引時, 如果查詢條件只有該字段時, 會使用索引掃描, 速度比組合索引稍快;

    但如果查詢條件是多個字段時, 也只會選擇一個索引, 速度比組合索引慢很多.

# 創(chuàng)建組合索引"value_1_time_1"
"executionStages" : {
   "stage" : "FETCH",
   "nReturned" : 13,
   "executionTimeMillisEstimate" : 5,
    "indexName" : "value_1_time_1",
    "indexBounds" : {
     "value" : [
      "[2998999900, 2999000100]"
     ],
     "time" : [
      "[new Date(1619892660000), new Date(1619896260000)]"
     ]
    },
# 創(chuàng)建組合索引 "time_1_value_1"
"executionStats" : {
  "executionSuccess" : true,
  "nReturned" : 13,
  "executionTimeMillis" : 58,
    "indexName" : "time_1_value_1",
    "indexBounds" : {
     "time" : [
      "[new Date(1619892660000), new Date(1619896260000)]"
     ],
     "value" : [
      "[2998999900, 2999000100]"
     ]
    },

  • 兩者都是使用組合索引, 只是索引中字段的順序不一樣, 查詢速度竟然差別這么大. 不明白為什么? 可能的原因是:
    1. timevalue的索引空間不同, value的差異性比較小, 索引占用的空間也比較小, 所以更快;

查詢非空集合

  • MongoDB查詢非空集合,并比較幾種用法的效率
db.testcollection.find({"field1.0":{$exists: true}})
db.testcollection.find({"field1":{'$elemMatch':{'$ne':null}}})
db.testcollection.find({$where:"this.field1.length>0"})
db.testcollection.find({"field1":{$gt: []}})

# 在管道中可以這樣使用,效率依次下降
{'$match':{'outputs':{'$ne':[]}}},
{'$match':{'outputs':{'$elemMatch':{'ne':null}}}},
{'$match':{'outputs.0':{'$exists':true}}},
{'$match':{'outputs':{'$gt':['$size',0]}}},

allowDiskUse

當(dāng)數(shù)據(jù)集過大時, 使用 MongoDB進(jìn)行查詢會突破內(nèi)存上線,報錯信息為‘'QueryExceededMemoryLimitNoDiskUseAllowed'’, 這時可以在 aggregate()加上一個參數(shù) {allowDiskUse:true}以便可以使用磁盤進(jìn)行緩存, 即:db.coll.aggregate([{}...],{'allowDiskUse':true}). 調(diào)用pymongo模塊時,需要寫成addr_tag_testdata.aggregate([{}...], allowDiskUse=True)的形式.

Decimal格式

  1. Python中使用decimal格式可以使用decimal模塊:

    import decimal
    # 可以查看 decimal 的一些設(shè)置參數(shù)
    decimal.getcontext()
    # 以下為輸出:
    Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999,
            capitals=1, clamp=0, flags=[], traps=[Overflow, DivisionByZero,
            InvalidOperation])
    # 修改確定精度最后一位的方式
    decimal.getcontext().rounding = ROUND_UP # 'ROUND_HALF_EVEN':5以下舍棄,6以上加1;'ROUND_UP':向上取;'ROUND_DOWN':向下取
    # 指定精度為6位(指總位數(shù) 包括整數(shù)位), 默認(rèn)為28位;
    decimal.getcontext().prec=6
    decimal.Decimal(arg)
    
    # 把科學(xué)計算法表示的數(shù)字轉(zhuǎn)換成普通方法表示的數(shù)字, 可以用以下方法
    decimal.Decimal(eval(sci_number))
    
    
    • Decimal()的參數(shù)可以是整型, 也可以是字符串, 但不能是浮點(diǎn)型.
    • 該類型可以進(jìn)行四則運(yùn)算;
    • 無法直接存入MongoDB中;
    • 建議: 在平常使用時, 通過Decimal()to_decimal()轉(zhuǎn)化為Decimal類型; 需要存到MongoDB中時, 再轉(zhuǎn)化成bson.decimal128.Decimal128()類型, 從MongoDB取出之后, 再通過to_decimal()轉(zhuǎn)化為Decimal類型.
  2. bson.decimal128.Decima128()可以直接存到MongoDB中, 具體方法為:

    from bson.decimal128 import Decimal128
    from decimal import Decimal
    
    Decimal128('1')
    Decimal128(Decimal(1))
    # 在MongoDB中顯示為: NumberDecimal("1")
    
    • Decimal128()只能接受decimal.Decimal()類型和字符串類型作為參數(shù), 不接受整型和浮點(diǎn)型;
    • Decimal128()類型無法進(jìn)行四則運(yùn)算, 需要通過Decimal128().to_decimal()轉(zhuǎn)換成Python自帶的Decimal類型才可以.
    • MongoDBshell中, 可以使用NumberDecimal('111')直接創(chuàng)建Decimal類型; toDecimal()可以將其他類型轉(zhuǎn)化為Decimal格式; 最大可以容納34位的十進(jìn)制數(shù)值.
    • 注意: 任何類型轉(zhuǎn)化為浮點(diǎn)型后, 都可能會發(fā)生變化, 如字符串或整型1轉(zhuǎn)化為浮點(diǎn)型后可能變成1.00000000000000456; 如果需要非常精確地表示, 需要使用decimal.Decimal('1') , 這樣不論結(jié)果擴(kuò)大多少倍, 后面依然是0.

Session

如果客戶端和MongoDB服務(wù)器在30分鐘內(nèi)沒有互動, 服務(wù)器會自動釋放客戶端連接, 解決方案有2個:

  • 創(chuàng)建 session 時, 指定 session_id, 并且每5分鐘刷新一次該 session_id (官方推薦方案);

    # 可以使用該方法獲取 session_id
    with client.start_session() as session:
        session_id = session.session_id['id']
    # 但是沒有在 pymongo api 中找到刷新 session 的方法
    

    在 mongo shell 中可通過以下方式實現(xiàn): 參考文檔

    # 創(chuàng)建新的 session, 并獲取對應(yīng)的 session_id
    var session = db.getMongo().startSession()
    var sessionId = session.getSessionId().id
    var cursor = session.getDatabase("examples").getCollection("data").find().noCursorTimeout()
    
    # 每隔5分鐘刷新一次該 session 的連接
    var refreshTimestamp = new Date() // take note of time at operation start
    while (cursor.hasNext()) {
      // Check if more than 5 minutes have passed since the last refresh
      if ( (new Date()-refreshTimestamp)/1000 > 300 ) {
        print("refreshing session")
        db.adminCommand({"refreshSessions" : [sessionId]})
        refreshTimestamp = new Date()
      }
      // process cursor normally
    }
    
  • 對集合操作時, 指定 session, 參考文檔

with client.start_session() as session:
    db.coll.find({}, session=session)
  • 查詢數(shù)據(jù)時, 通過 batch_size指定每個 batch 的大小, 保證該 batch 在30分鐘內(nèi)肯定能執(zhí)行完;

    # 依然會返回全部數(shù)據(jù), 但是每個 batch 只返回30條 迭代的每個 document 也一樣
    for document in db.collection.find().batch_size(30)
     print(document)
    
    import time
    import pymongo
    # 當(dāng) batch_size = 2 時, 耗時:4.8 S
    # 當(dāng) batch_size = 100 時, 耗時:0.42 S
    # 當(dāng) batch_size = 10000 時, 耗時:0.25 S
    # 不指定 batch_size 時, 耗時: 0.255 S
    # counter 最終都是 13929
    with pymongo.MongoClient('192.168.1.7', 27017)as client:
        db = client['btc']
        block_info_coll = db['block_info']
        result = block_info_coll.find({}).batch_size(10)
    
        t0 = time.time()
        counter = 0
        for block_info in result:
            counter += 1
    
        print(counter)
        t1 = time.time()
        print(f'共耗時:{t1-t0} S')
    

Date

Date()會返回當(dāng)前時間 (字符串類型)

new Date() 會返回當(dāng)前時間 (ISODate 類型)

也可以通過傳遞參數(shù)生成指定日期的時間. 可接受的參數(shù)類型有以下幾種:

  • new Date('2022-01-01'): 返回 ISODate("2022-01-01T00:00:00Z")(UTC 時區(qū))
  • new Date('2022-01-01T08:00:00'): 返回 ISODate("2022-01-01T00:00:00Z") (會自動轉(zhuǎn)換成UTC時區(qū)), 如果MongoDB的服務(wù)器時區(qū)就是UTC時區(qū), 會返回 ISODate("2022-01-01T08:00:00Z")
  • new Date('2022-01-01T08:00:00Z'): 返回 ISODate("2022-01-01T08:00:00Z")
  • new Date(<integer>): 以毫秒為單位的 UNIX 時間戳;

數(shù)據(jù)導(dǎo)出/導(dǎo)入

將 MongoDB 中指定的集合導(dǎo)出為文件的命令:

mongoexport -d btc_b -c transaction_60w --type csv -o transaction_60w.csv --limit 10 --sort '{block_number:1}' -f "_id,block_number,block_timestamp,is_coinbase,value,inputs,outputs,new_inputs"

遇到的問題: 怎么在導(dǎo)入時指定字段的類型, 如 block_number.int32(), 對應(yīng)的命令為:

mongoimport -d btc -c transaction_60w -f 'block_number.int32()' --type cs
v --columnsHaveTypes --drop --mode upsert transaction_60w.csv

使用該命令的難點(diǎn)在于: 導(dǎo)入時, 沒有找到指定各個字段的類型.

可以導(dǎo)出為 json格式, 會自帶類型, 但是會將二進(jìn)制導(dǎo)出為字符串, 在導(dǎo)入時沒有找到將其類型指定為二進(jìn)制的方法.

# 導(dǎo)出命令
mongoexport -d btc_b -c transaction_60w --type json -o transaction_60w.json --limit 10 -f "_id,block_number,block_timestamp,is_coinbase,value,inputs,outputs,new_inputs"

# 導(dǎo)入命令
mongoimport -d btc -c transaction_60w --type json --jsonArray --drop --mode upsert transaction_60w.json

使用 mongodump導(dǎo)出指定的集合, 具體命令為:

# 導(dǎo)出指定集合
mongodump -d btc_b -c transaction_70w -o transaction_70w.bson

# 導(dǎo)入指定集合
mongorestore -d btc -c transaction_70w --drop transaction_70w.bson
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • MongoDB 基本概念 數(shù)據(jù)庫 MongoDB一個數(shù)據(jù)庫包括多個集合,類似于MySQL中一個數(shù)據(jù)庫包含多個表;一...
    唔代閱讀 1,003評論 0 0
  • MongoDB常用操作 一、查詢 find方法 查詢所有的結(jié)果: select * from users;===d...
    止風(fēng)者閱讀 651評論 1 3
  • mongoDB簡介 MongoDB是用C++語言編寫的非關(guān)系型數(shù)據(jù)庫。特點(diǎn)是高性能、易部署、易使用,存儲數(shù)據(jù)十分方...
    AubreyXue閱讀 715評論 2 3
  • MongoDB MongoDB 是由C++語言編寫的,是一個基于分布式文件存儲的開源數(shù)據(jù)庫系統(tǒng)。MongoDB 將...
    hxx閱讀 539評論 0 0
  • Windows上的安裝方法: 下載,安裝,可以從customer自定義安裝路徑后,例如我設(shè)置的安裝路徑為"F:\M...
    cccshuang閱讀 272評論 0 1

友情鏈接更多精彩內(nèi)容