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)),executionStats和allPlanExecution
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
-
從
MongoDB中刪除集合, 有2種方式:-
db.coll.remove({}): 刪除文件之后, 文件原本占用的物理空間不會被回收; 是刪除符合條件的數(shù)據(jù), 不會刪除其他結(jié)構(gòu)(如索引等); -
db.coll.drop(): 刪除文件之后, 文件原本占用的物理空間會進(jìn)行回收;
針對物理空間沒有進(jìn)行回收的情況, 可以使用
compact命令釋放這些空間, 具體命令為db.runCommand({"compact":"collection_name"}). -
-
主幣交易要求交易
hash(數(shù)據(jù)庫中的_id)和代幣交易的交易hash log_idxblock_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)化案例
- 在
time和value上創(chuàng)建組合索引,db.usdt_token_trans.createIndex({'time':1,'value':1}): - 分別在
time和value上創(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]"
]
},
- 兩者都是使用組合索引, 只是索引中字段的順序不一樣, 查詢速度竟然差別這么大. 不明白為什么? 可能的原因是:
-
time和value的索引空間不同,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格式
-
在
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類型.
-
-
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類型才可以. - 在
MongoDB的shell中, 可以使用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