一、簡介
NeDB 是使用 Nodejs 實現的一個 NoSQL 嵌入式數據庫操作模塊,可以充當內存數據庫,也可以用來實現本地存儲,甚至可以在瀏覽器中使用。查詢方式比較靈活,支持使用正則、比較運算符、邏輯運算符、索引以及 JSON 深度查詢等。
NeDB 嵌入到了應用程序進程中,消除了與客戶機服務器配置相關的開銷,在運行時,也只需要較少的內存開銷,使用精簡代碼編寫,速度更快。其 API 是 MongoDB 的一個子集,可以通過這些接口輕松管理應用程序數據,而不依靠原始的文檔文件。
具有簡單、輕量、速度快等特點,由于嵌入式數據庫存儲總數據量最好要控制在 1GB 以內,所以適合在不需要大量數據處理的應用系統中使用(比如使用 nw.js 等實現的桌面應用程序、并發量不大的系統等)。
二、Git 地址
https://github.com/louischatriot/nedb
三、快速上手
由于 NeDB 可以看作是精簡版的 MongoDB,這里和 MongoDB 的使用做一下對比,以便可以更直觀的感受 NeDB 的簡便。
MongoDB
1、下載安裝包;(http://www.mongodb.org/downloads)
2、解壓縮文件;
3、設置系統變量;
4、配置 mongodb 運行環境;
5、啟動 mongodb 服務;
6、連接 mongodb;
7、添加 mongodb 為 windows 服務;
8、啟動服務;
1 |
net start MongoDB |
9、安裝 mongoose 模塊(mongoose 官網 http://mongoosejs.com/)
1 |
npm install mongoose --save |
10、使用(以 express 為例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
var mongoose = require('mongoose'); exports.index = function(req, res){ var db = mongoose.createConnection('localhost', 'test'); var schema = mongoose.Schema({ name: 'string' }); var User = db.model('User', schema); var user = new User({ name: 'tom' }); user.save(function(err) { if(err) // ... res.end(); }); User.find({'name':'tom'}, function(err, docs) { res.render('index', { title: docs}); }); }; |
11、停止或刪除服務;
1 2 3 |
net stop MongoDB sc delete MongoDB |
NeDB
1、安裝模塊
1 |
npm install nedb --save |
2、使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
// 加載模塊 const nedb = require('nedb'); // 實例化連接對象(不帶參數默認為內存數據庫) const db = new nedb({ filename: '/data/save.db', autoload: true }); // 插入單項 db.insert({ name: 'tom' }, (err, ret) => {}); // 插入多項 db.insert( [ { name: 'tom' }, { name: 'jerry' } ] , (err, ret) => {}); // 查詢單項 db.findOne({ name: 'tom' }, (err, ret) => {}); // 查詢多項 db.find({ name: { $in: ['tom', 'jerry'] } }) .sort({ _id: -1 }) .exec((err, ret) => {}); // 更新單項 db.update({ _id: '1' }, { $set: { name: 'kitty' } }, (err, ret) => {}); // 更新多項 db.update({}, { $set: { name: 'kitty' } }, { multi: true }, (err, ret) => {}); // 刪除單項 db.remove({ _id: '1' }, (err, ret) => {}) // 刪除多項 db.remove({ name: 'kitty' }, { multi: true }, (err, ret) => {}); |
通過對比,嵌入式數據庫在使用上的優勢一目了然,無需任何數據庫服務器,也不用安裝、配置、啟動一個數據庫服務,而且 NeDB 的 API 抽取了 MongoDB 常用的一些接口,在用法上大同小異,性能也不錯。如果項目使用 Node 實現,并且存儲數據量不大,又熟悉 MongoDB 語法,那么,NeDB 就值得一用。詳細用法請參照官方文檔或下方中文翻譯文檔。
注:對于習慣了關系型數據庫的開發人員來說,有些術語以及坑需要重申一下:
1、“ 表” 對應“ 集合 (collection)”,“ 行” 對應“ 文檔(document)”,一個 database 中可以有多個 collection,一個 collection 中又可以有多個 document;
2、NeDB 默認 utf-8 編碼;
3、嚴格區分大小寫,比如查詢 db.find({"name":"tom"}) 和 db.find({"Name":"tom"}) 并不是用的同一字段做的條件;
如果您在使用過程中遇到其他問題,可以留言,我們一起補充。
四、選擇
之所以寫該節,是因為本文介紹 NeDB,但并不是推薦 NeDB。選取什么樣的數據庫主要取決于項目以及個人情感。由于涉及到 SQL 數據庫與 NoSQL 數據庫的概念,所以先從大的方面簡單說一下,然后再介紹 Node 嵌入式數據庫。
先簡單回顧下數據庫的分類。
數據庫通常分為層次式數據庫、網絡式數據庫和關系式數據庫三種。而不同的數據庫是按不同的數據結構來聯系和組織的。在當今的互聯網中,最常見的數據庫模型主要有兩種:關系型數據庫和非關系型數據庫。
關系型數據庫
關系型數據庫模型是把復雜的數據結構歸結為簡單的二元關系(即二維表格形式)。在關系型數據庫中,對數據的操作幾乎全部建立在一個或多個關系表格上,通過對這些關聯的表格分類、合并、連接或選取等運算來實現數據庫的管理。主流關系型數據庫有 Oracle、MySQL、MariaDB、SqlServer、Access、PostgreSQL、DB2 等。其中個人感覺 PostgreSQL 功能十分強大,雖是關系型數據庫,但支持 json 和 Hstore 字段,兼有事務和文檔特性,只是性能就差了點。
非關系型數據庫
NoSQL 意味著 Not only SQL。面對超大規模和高并發的 SNS(社交網絡服務) 類型的 web2.0 純動態網站,傳統的關系型數據庫顯得有些力不從心,比如表的橫向擴展等。NoSQL 數據庫作為傳統關系型數據庫的有效補充,在特定的場景下可以發揮出難以想象的高效率和高性能。主流的非關系型數據庫分為鍵值存儲數據庫 (Memcached、Redis 等),列存儲數據庫 (HBase 等),圖形數據庫 (Neo4j 等),面向文檔數據庫 (MongoDB、CouchDB 等)。
由于 NeDB 屬于面向文檔數據庫,這里提及一下該類數據庫,了解其它類型數據庫可以自行查詢官方文檔。面向文檔數據庫可以看做是鍵值數據庫的一個升級,不但允許鍵值嵌套,還提高了查詢效率。面向文檔數據庫會將數據以文檔形式存儲。每個文檔都是自包含的數據單元,是一系列數據項的集合。每個數據項都有一個名詞與對應值,值既可以是簡單的數據類型,如字符串、數字和日期等;也可以是復雜的類型,如有序列表和關聯對象。數據存儲的最小單位是文檔,同一個表中存儲的文檔屬性可以是不同的,數據可以使用 XML、JSON 或 JSONB 等多種形式存儲。
介紹完分類,接下來就簡單說一下各自的使用場景。
RDBMS
特點:
- 提供事務,使兩個或兩個以上的成功或失敗的數據更改作為一個原子單元;
- 高度組織化結構化數據;
- 數據和關系都存儲在單獨的表中;
- 需要預先定義表模式;
- 鼓勵標準化減少數據冗余;
- 支持多表查詢;
- 強制數據完整性;
- 嚴格的一致性;
- 支持擴展(橫向擴展有些痛苦);
- 結構化查詢語言(SQL);
- 誕生 40 年之多,十分成熟,有足夠的支持;
從其特點分析,可看出其適合有明確的定義,規范比較明確的項目。比如在線商城和銀行系統等。該類系統需要具備強制數據完整性以及事務支持的健壯存儲系統??梢栽囅胍幌氯绻?ATM 機取錢,ATM 機沒有吐錢,但是后臺數據庫已經把錢減掉了,會是一種什么樣的體驗呢?
NoSQL
特點:
- Not only SQL;
- 沒有聲明性查詢語言;
- 沒有預定義的模式;
- 鍵-值對存儲,列存儲,文檔存儲,圖形數據庫;
- 最終一致性,而非 ACID 屬性;
- 非結構化和不可預知的數據;
- CAP 定理 ;
- 高性能,高可用性和可伸縮性;
- 是一個新的、令人興奮的技術,并不是十分成熟;
從其特點分析,最適合無固定要求的組織數據。比如社交網絡、客戶管理和網絡監控系統等。
就客戶管理系統來說,假如剛開始使用關系型數據庫建一個聯系人的表,表字段有主鍵 id、姓名 name、電話 telephone、郵箱 email、地址 address。那么問題來了,現在有聯系人有三個電話號碼(住宅座機、移動電話、工作電話)需要輸入,這時就要考慮單獨創建一個 telephone 表,這樣就不受限制了,也讓我們的數據標準化了。新建 telephone 表結構:聯系人 contact_id、號碼類型 name、號碼 num。email 與 address 也存在同樣的問題,address 的情況更加復雜,這里不再展開。對關系型數據庫來說,Schema 是固定不變的,而我們事先是不能預測所有字段的,比如剛才的聯系人表,很快我們會發現當前字段不能滿足,比如要添加性別 gender、年齡 age、生日 birthday 等字段,那么最后就導致需要加一個 otherdata 表。數據又是碎片化的,當查詢一個聯系人時,如果該聯系人有 3 個電話號碼、2 個 email 地址和 5 個地址,那么 SQL 查詢需要檢查所有表,并將產生 3*2*5=30 條結果,使得全文搜索很困難。
面對這種情況,如果選擇 NoSQL 數據庫,聯系人列表將從中受益。數據庫將一個聯系人的所有數據存儲在一個單獨的文檔里的 contacts 集合里。
1 2 3 4 5 6 7 8 9 10 11 12 |
[ { name: "tom", telephone: { home: "123456", mobile: "123456789", work: "1234567890" }, ... }, ... ] |
如果這時需要添加一些數據,這些數據沒有必要應用到之前的聯系人,NoSQL 數據庫就可以隨意添加或移除字段。聯系人數據存儲在單獨的文檔中,也使得全文搜索變得簡單。
介紹完 SQL 與 NoSQL 數據庫的基本概念,就該回到正題啦,介紹下 Node 嵌入式數據庫,SQLite 同樣也有 SQL 與 NoSQL 之分。
Nodejs 可用的 SQLite 有 node-sqlite,node-sqlite3,NeDB,nStore 以及 final-db 等。其中 node-sqlite,node-sqlite3 屬于 SQL 數據庫,NeDB,nStore 以及 final-db 屬于 NoSQL 數據庫。如需詳細了解各個模塊,可以去看相應的官方文檔。
其中使用最多的應該算 node-sqlite3 和 NeDB 了,兩者的區別很明顯,前者是 SQL 數據庫,后者是 NoSQL 數據庫。另外,sqlite3 相對 NeDB 要重一些,性能也要差一點,使用 SQL 語句失去了 js 直接操作 json 的簡便,API 也相對復雜很多。而 NeDB 只提供了基本的 CURD 操作,只能用于小型應用,大場景并不適用,數據加載到內存中進行操作,不適合內存非常緊張的應用,目前作者也沒有給出具體的內存控制方法。。
五、源碼簡析
想要更深入的理解 NeDB,就需要了解它是如何實現的。我這里給出一些我閱讀源碼時記憶比較深刻的幾個點。
1、所有改變數據的操作 (indert,update,remove),都會觸發 persistence.persistNewState 方法,比如可以看一下 datastore.js 第 265-268 行的 Datastore.prototype._insert 方法。該方法決定數據的去處,如果是當作內存數據庫來用,該方法會提前返回,如果是本地文檔持久化存儲,則會將數據經過 utf-8 編碼序列化之后追加到備份數據庫的文檔中。
2、數據在 model.js 中通過 serialize 方法被序列化,該方法使用 JSON.stringfy 序列化 json 數據,在回調函數中將 undefined 類型值映射為 null,并且使用與 mongoDB 相同的規則 (不能以"$" 開頭,也不能包含".") 校驗屬性的有效性。
3、數據從硬盤上加載到內存時,使用了 async 模塊的 waterfall 方法。該方法參數是由方法組成的數組,并且先執行的方法會將執行結果傳入下一個方法,方法按順序執行,并且當其中一個方法報錯,就會導致后面的方法不再執行,直接在主方法回調拋出異常。
4、包括當持久層初始化時從磁盤上加載數據在內的所有的操作命令都會通過 Executor 類的實例,將方法傳入隊列,保證命令可以按序執行(包括同步與異步方法)。
源碼并不難理解,通過以上幾點,希望可以讓大家更容易解讀源碼。通讀 NeDB 的源碼,對 Node 異步 I/O 以及基于事件編程的思想會有進一步的認識。
為了方便大家理解,對官方文檔做了簡單翻譯,如有不當的地方希望大家指正,中文 API 文檔如下:
六、API
1、new Datastore(options)
作用:
初始化一個數據存儲,相當于 MongoDB 的一個集合、Mysql 的一張表。
options 對象配置參數:
① filename(可選): 數據存儲文件路徑。如果為空,數據將會自動存儲在內存中。注意路徑不能以“~” 結尾。
② inMemoryOnly(可選, 默認 false): 數據存儲方式。是否只存在于內存中。
③ loadDatabase: 將數據加載到內存中。
④ timestampData(可選, 默認 false): 自動生成時間戳,字段為 createdAt 和 updateAt,用來記錄文檔插入和更新操作的時間點。
⑤ autoload(可選, 默認 false): 如果使用 autoload,當數據存儲被創建時,數據將自動從文件中加載到內存,不必去調用 loadDatabase。注意所有命令操作只有在數據加載完成后才會被執行。
⑥ onload(可選): 在數據加載完成后被調用,也就是在 loadDatabase 方法調用后觸發。該方法有一個 error 參數,如果試用了 autoload,而且沒有定義該方法,在數據加載過程中出錯將默認會拋出該錯誤。
⑦ afterSerialization(可選): 在數據被序列化成字符串之后和被寫入磁盤前,可以使用該方法對數據進行轉換。比如可以做一些數據加密工作。該方法入參為一個字符串 (絕對不能含有字符“\n”,否則數據會丟失),返回轉換后的字符串。
⑧ beforeDeserialization(可選): 與 afterSerialization 相反。兩個必須成對出現,否則會引起數據丟失,可以理解為一個加密解密的過程。
⑨ corruptAlertThreshold(可選): 默認 10%, 取值在 0-1 之間。如果數據文件損壞率超過這個百分比,NeDB 將不會啟動。取 0,意味著不能容忍任何數據損壞;取 1,意味著忽略數據損壞問題。
⑩ compareStrings(可選): compareStrings(a, b) 比較兩個字符串,返回-1、0 或者 1。如果被定義,將會覆蓋默認的字符串比較方法,用來兼容默認方法不能比較非 US 字符的缺點。
注:如果使用本地存儲,而且沒有配置 autoload 參數,需要手動調用 loadDatabase 方法,所有操作 (insert, find, update, remove) 在該方法被調用前都不會執行。還有就是,如果 loadDatabase 失敗,所有命令也將不會執行。
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// 示例 1: 內存數據庫(沒有必要調用loadDatabase方法) var Datastore = require('nedb'), db = new Datastore(); // 示例 2: 本地存儲需要手動調用loadDatabase方法 var Datastore = require('nedb'), db = new Datastore({ filename: 'path/to/datafile' }); db.loadDatabase(function (err) { // 回調函數(可選) // Now commands will be executed }); // 示例 3: 帶有autoload配置項的本地存儲 var Datastore = require('nedb'), db = new Datastore({ filename: 'path/to/datafile', autoload: true }); // You can issue commands right away // 示例 4: 創建多個數據存儲 db = {}; db.users = new Datastore('path/to/users.db'); db.robots = new Datastore('path/to/robots.db'); // 如果不配置autoload,需要加載數據庫(該方法是異步的) db.users.loadDatabase(); db.robots.loadDatabase(); |
2、db.insert(doc, callback)
作用:
插入文檔數據 (文檔相當于 mysql 表中的一條記錄)。如果文檔不包含_id 字段,NeDB 會自動生成一個,該字段是 16 個字符長度的數字字符串。該字段一旦確定,就不能被更改。
參數:
doc: 支持 String, Number, Boolean, Date, null, array 以及 object 類型。如果該字段是 undefined 類型,將不會被保存,這里和 MongoDB 處理方式有點不同,MongoDB 會將 undefined 轉換為 null 進行存儲。字段名稱不能以"$" 開始,也不能包含"."。
callback(可選): 回調函數,包含參數 err 以及 newDoc,err 是報錯,newDoc 是新插入的文檔,包含它的_id 字段。
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
var doc = { hello: 'world' , n: 5 , today: new Date() , nedbIsAwesome: true , notthere: null , notToBeSaved: undefined // 該字段不會被保存 , fruits: [ 'apple', 'orange', 'pear' ] , infos: { name: 'nedb' } }; db.insert(doc, function (err, newDoc) { // Callback is optional // newDoc is the newly inserted document, including its _id // newDoc has no key called notToBeSaved since its value was undefined }); // 使用array,實現批量插入。一旦其中一個操作失敗,所有改變將會回滾。 db.insert([{ a: 5 }, { a: 42 }], function (err, newDocs) { // Two documents were inserted in the database // newDocs is an array with these documents, augmented with their _id }); // 如果a字段有唯一性約束,該操作將會執行失敗。 db.insert([{ a: 5 }, { a: 42 }, { a: 5 }], function (err) { // err is a 'uniqueViolated' error // The database was not modified }); |
3、db.find(query, callback)
作用:
查詢符合條件的文檔集。
參數:
query: object 類型,查詢條件。支持使用比較運算符 ($lt, $lte, $gt, $gte, $in, $nin, $ne), 邏輯運算符 ($or, $and, $not, $where), 正則表達式進行查詢。
callback(可選): 回調函數,包含參數 err 以及 docs,err 是報錯,docs 是查詢到的文檔集。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
// 數據存儲集合 // { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false, satellites: ['Phobos', 'Deimos'] } // { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true, humans: { genders: 2, eyes: true } } // { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false } // { _id: 'id4', planet: 'Omicron Persei 8', system: 'futurama', inhabited: true, humans: { genders: 7 } } // { _id: 'id5', completeData: { planets: [ { name: 'Earth', number: 3 }, { name: 'Mars', number: 2 }, { name: 'Pluton', number: 9 } ] } } // 示例1: 基本查詢??梢允褂谜齽t表達式匹配字符串。使用“.”匹配對象或者數組里面的元素。 // 單字段查詢 db.find({ system: 'solar' }, function (err, docs) { // docs is an array containing documents Mars, Earth, Jupiter // If no document is found, docs is equal to [] }); // 正則表達式查詢 db.find({ planet: /ar/ }, function (err, docs) { // docs contains Mars and Earth }); // 多條件查詢 db.find({ system: 'solar', inhabited: true }, function (err, docs) { // docs is an array containing document Earth only }); // 根據對象屬性查詢 db.find({ "humans.genders": 2 }, function (err, docs) { // docs contains Earth }); // 根據數組對象屬性查詢 db.find({ "completeData.planets.name": "Mars" }, function (err, docs) { // docs contains document 5 }); db.find({ "completeData.planets.name": "Jupiter" }, function (err, docs) { // docs is empty }); db.find({ "completeData.planets.0.name": "Earth" }, function (err, docs) { // docs contains document 5 // If we had tested against "Mars" docs would be empty because we are matching against a specific array element }); // 對象深度比較查詢,不要與"."使用混淆 db.find({ humans: { genders: 2 } }, function (err, docs) { // docs is empty, because { genders: 2 } is not equal to { genders: 2, eyes: true } }); // 查詢所有結果集 db.find({}, function (err, docs) { }); // 查詢某一個文檔 db.findOne({ _id: 'id1' }, function (err, doc) { // doc is the document Mars // If no document is found, doc is null }); // 示例2: {field: {$op: value}} ($op代表任意比較運算符) // $lt, $lte: 小于,小于等于 // $gt, $gte: 大于,大于等于 // $in: 屬于 // $ne, $nin: 不等于,不屬于 // $exists: 取值為true或者false,用于檢測文檔是否具有某一字段 // $regex: 檢測字符串是否與正則表達式相匹配 // $lt, $lte, $gt and $gte 只能用于數字和字符串類型 db.find({ "humans.genders": { $gt: 5 } }, function (err, docs) { // docs contains Omicron Persei 8, whose humans have more than 5 genders (7). }); // 當進行字符串比較的時候,將使用字典序。 db.find({ planet: { $gt: 'Mercury' }}, function (err, docs) { // docs contains Omicron Persei 8 }) // Using $in. $nin is used in the same way db.find({ planet: { $in: ['Earth', 'Jupiter'] }}, function (err, docs) { // docs contains Earth and Jupiter }); // Using $exists db.find({ satellites: { $exists: true } }, function (err, docs) { // docs contains only Mars }); // Using $regex with another operator db.find({ planet: { $regex: /ar/, $nin: ['Jupiter', 'Earth'] } }, function (err, docs) { // docs only contains Mars because Earth was excluded from the match by $nin }); // 示例3: 當文檔中有一個字段是數組,NeDB將首先判斷查詢值是否為數組,如果是數組的話將執行精確查找,然后再去判斷是否存在數組比較方法(現在只支持$size和$elemMatch)。如果都沒有,將會對所有元素進行查詢。 // $size: 匹配數組的大小 // $elemMatch: 匹配至少一個數組元素 // 精確查找 db.find({ satellites: ['Phobos', 'Deimos'] }, function (err, docs) { // docs contains Mars }) db.find({ satellites: ['Deimos', 'Phobos'] }, function (err, docs) { // docs is empty }) // 使用數組比較方法 // $elemMatch 運算符將匹配數組中滿足所有條件的元素 db.find({ completeData: { planets: { $elemMatch: { name: 'Earth', number: 3 } } } }, function (err, docs) { // docs contains documents with id 5 (completeData) }); db.find({ completeData: { planets: { $elemMatch: { name: 'Earth', number: 5 } } } }, function (err, docs) { // docs is empty }); // 在$elemMatch中使用比較運算符 db.find({ completeData: { planets: { $elemMatch: { name: 'Earth', number: { $gt: 2 } } } } }, function (err, docs) { // docs contains documents with id 5 (completeData) }); // 注意不能使用嵌套的運算符, e.g. { $size: { $lt: 5 } } 將會拋出異常 db.find({ satellites: { $size: 2 } }, function (err, docs) { // docs contains Mars }); db.find({ satellites: { $size: 1 } }, function (err, docs) { // docs is empty }); // If a document's field is an array, matching it means matching any element of the array db.find({ satellites: 'Phobos' }, function (err, docs) { // docs contains Mars. Result would have been the same if query had been { satellites: 'Deimos' } }); // This also works for queries that use comparison operators db.find({ satellites: { $lt: 'Amos' } }, function (err, docs) { // docs is empty since Phobos and Deimos are after Amos in lexicographical order }); // This also works with the $in and $nin operator db.find({ satellites: { $in: ['Moon', 'Deimos'] } }, function (err, docs) { // docs contains Mars (the Earth document is not complete!) }); // 示例4: 邏輯運算符 $or, $and, $not, $where // $or, $and: 并集,交集 { $op: [query1, query2, ...] } // $not: 取非 { $not: query } // $where: 條件 { $where: function () { /* object is "this", return a boolean */ } } db.find({ $or: [{ planet: 'Earth' }, { planet: 'Mars' }] }, function (err, docs) { // docs contains Earth and Mars }); db.find({ $not: { planet: 'Earth' } }, function (err, docs) { // docs contains Mars, Jupiter, Omicron Persei 8 }); db.find({ $where: function () { return Object.keys(this) > 6; } }, function (err, docs) { // docs with more than 6 properties }); // You can mix normal queries, comparison queries and logical operators db.find({ $or: [{ planet: 'Earth' }, { planet: 'Mars' }], inhabited: true }, function (err, docs) { // docs contains Earth }); // 示例5: Projections // 在第二個參數傳入projections對象,來規定返回字段。比如: {a:1, b:1}指定只返回a和b字段,{a:0, b:0}指定省略a和b這兩個字段。 // _id默認返回,不需要返回設置_id: 0 // Same database as above // Keeping only the given fields db.find({ planet: 'Mars' }, { planet: 1, system: 1 }, function (err, docs) { // docs is [{ planet: 'Mars', system: 'solar', _id: 'id1' }] }); // Keeping only the given fields but removing _id db.find({ planet: 'Mars' }, { planet: 1, system: 1, _id: 0 }, function (err, docs) { // docs is [{ planet: 'Mars', system: 'solar' }] }); // Omitting only the given fields and removing _id db.find({ planet: 'Mars' }, { planet: 0, system: 0, _id: 0 }, function (err, docs) { // docs is [{ inhabited: false, satellites: ['Phobos', 'Deimos'] }] }); // Failure: using both modes at the same time db.find({ planet: 'Mars' }, { planet: 0, system: 1 }, function (err, docs) { // err is the error message, docs is undefined }); // You can also use it in a Cursor way but this syntax is not compatible with MongoDB db.find({ planet: 'Mars' }).projection({ planet: 1, system: 1 }).exec(function (err, docs) { // docs is [{ planet: 'Mars', system: 'solar', _id: 'id1' }] }); // Project on a nested document db.findOne({ planet: 'Earth' }).projection({ planet: 1, 'humans.genders': 1 }).exec(function (err, doc) { // doc is { planet: 'Earth', _id: 'id2', humans: { genders: 2 } } }); // 示例6:排序和分頁 // 文檔集 // doc1 = { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false, satellites: ['Phobos', 'Deimos'] } // doc2 = { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true, humans: { genders: 2, eyes: true } } // doc3 = { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false } // doc4 = { _id: 'id4', planet: 'Omicron Persei 8', system: 'futurama', inhabited: true, humans: { genders: 7 } } // No query used means all results are returned (before the Cursor modifiers) db.find({}).sort({ planet: 1 }).skip(1).limit(2).exec(function (err, docs) { // docs is [doc3, doc1] }); // You can sort in reverse order like this db.find({ system: 'solar' }).sort({ planet: -1 }).exec(function (err, docs) { // docs is [doc1, doc3, doc2] }); // You can sort on one field, then another, and so on like this: db.find({}).sort({ firstField: 1, secondField: -1 }) ... // You understand how this works! |
4、db.findOne(query, callback)
作用:
查詢符合條件的一個文檔。與 db.find 使用相同。
5、db.update(query, update, options, callback)
作用:
根據 update 參數的規則,更新匹配到 query 的結果集。
參數:
query: 與 find 和 findOne 中 query 參數的用法一致
update: 指定文檔更改規則。該參數可以是一個新的文檔,也可以是一套修飾符,兩者不能同時使用。使用修飾符時,如果需要更改的字段不存在,將會自動創建??捎玫男揎椃?$set(改變字段值), $unset(刪除某一字段), $inc(增加某一字段), $min/$max(改變字段值,傳入值需要小于/大于當前值), 還有一些用在數組上的修飾符,$push, $pop, $addTopSet, $pull, $each, $slice,具體用法如下示例。
options: object 類型。muti(默認 false),是否允許修改多條文檔;upsert(默認為 false),如果 query 沒有匹配到結果集,有兩種情況需要考慮,一個是 update 是一個簡單的對象 (不包含任何修飾符),另一種情況是帶有修飾符,對第一種情況會直接將該文檔插入,對第二種情況會將通過修飾符更改后的文檔插入;
callback(可選): 參數 (err, numAffected, affectedDocuments, upsert)。numAffected:被影響的文檔個數;affectedDocuments:更新后的文檔。
注意:_id 不能被修改
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
// 文檔集 // { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false } // { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true } // { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false } // { _id: 'id4', planet: 'Omicron Persia 8', system: 'futurama', inhabited: true } // 用一個文檔替換另一個文檔 db.update({ planet: 'Jupiter' }, { planet: 'Pluton'}, {}, function (err, numReplaced) { // numReplaced = 1 // The doc #3 has been replaced by { _id: 'id3', planet: 'Pluton' } // Note that the _id is kept unchanged, and the document has been replaced // (the 'system' and inhabited fields are not here anymore) }); // 設定一個已存字段的值 db.update({ system: 'solar' }, { $set: { system: 'solar system' } }, { multi: true }, function (err, numReplaced) { // numReplaced = 3 // Field 'system' on Mars, Earth, Jupiter now has value 'solar system' }); // 設定一個不存在字段的值 db.update({ planet: 'Mars' }, { $set: { "data.satellites": 2, "data.red": true } }, {}, function () { // Mars document now is { _id: 'id1', system: 'solar', inhabited: false // , data: { satellites: 2, red: true } // } // Not that to set fields in subdocuments, you HAVE to use dot-notation // Using object-notation will just replace the top-level field db.update({ planet: 'Mars' }, { $set: { data: { satellites: 3 } } }, {}, function () { // Mars document now is { _id: 'id1', system: 'solar', inhabited: false // , data: { satellites: 3 } // } // You lost the "data.red" field which is probably not the intended behavior }); }); // 刪除一個字段 db.update({ planet: 'Mars' }, { $unset: { planet: true } }, {}, function () { // Now the document for Mars doesn't contain the planet field // You can unset nested fields with the dot notation of course }); // 設置upsert db.update({ planet: 'Pluton' }, { planet: 'Pluton', inhabited: false }, { upsert: true }, function (err, numReplaced, upsert) { // numReplaced = 1, upsert = { _id: 'id5', planet: 'Pluton', inhabited: false } // A new document { _id: 'id5', planet: 'Pluton', inhabited: false } has been added to the collection }); // If you upsert with a modifier, the upserted doc is the query modified by the modifier // This is simpler than it sounds :) db.update({ planet: 'Pluton' }, { $inc: { distance: 38 } }, { upsert: true }, function () { // A new document { _id: 'id5', planet: 'Pluton', distance: 38 } has been added to the collection }); // If we insert a new document { _id: 'id6', fruits: ['apple', 'orange', 'pear'] } in the collection, // let's see how we can modify the array field atomically // $push inserts new elements at the end of the array db.update({ _id: 'id6' }, { $push: { fruits: 'banana' } }, {}, function () { // Now the fruits array is ['apple', 'orange', 'pear', 'banana'] }); // $pop removes an element from the end (if used with 1) or the front (if used with -1) of the array db.update({ _id: 'id6' }, { $pop: { fruits: 1 } }, {}, function () { // Now the fruits array is ['apple', 'orange'] // With { $pop: { fruits: -1 } }, it would have been ['orange', 'pear'] }); // $addToSet adds an element to an array only if it isn't already in it // Equality is deep-checked (i.e. $addToSet will not insert an object in an array already containing the same object) // Note that it doesn't check whether the array contained duplicates before or not db.update({ _id: 'id6' }, { $addToSet: { fruits: 'apple' } }, {}, function () { // The fruits array didn't change // If we had used a fruit not in the array, e.g. 'banana', it would have been added to the array }); // $pull removes all values matching a value or even any NeDB query from the array db.update({ _id: 'id6' }, { $pull: { fruits: 'apple' } }, {}, function () { // Now the fruits array is ['orange', 'pear'] }); db.update({ _id: 'id6' }, { $pull: { fruits: $in: ['apple', 'pear'] } }, {}, function () { // Now the fruits array is ['orange'] }); // $each can be used to $push or $addToSet multiple values at once // This example works the same way with $addToSet db.update({ _id: 'id6' }, { $push: { fruits: { $each: ['banana', 'orange'] } } }, {}, function () { // Now the fruits array is ['apple', 'orange', 'pear', 'banana', 'orange'] }); // $slice can be used in cunjunction with $push and $each to limit the size of the resulting array. // A value of 0 will update the array to an empty array. A positive value n will keep only the n first elements // A negative value -n will keep only the last n elements. // If $slice is specified but not $each, $each is set to [] db.update({ _id: 'id6' }, { $push: { fruits: { $each: ['banana'], $slice: 2 } } }, {}, function () { // Now the fruits array is ['apple', 'orange'] }); // $min/$max to update only if provided value is less/greater than current value // Let's say the database contains this document // doc = { _id: 'id', name: 'Name', value: 5 } db.update({ _id: 'id1' }, { $min: { value: 2 } }, {}, function () { // The document will be updated to { _id: 'id', name: 'Name', value: 2 } }); db.update({ _id: 'id1' }, { $min: { value: 8 } }, {}, function () { // The document will not be modified }); |
6、db.remove(query, options, callback)
作用:
根據 options 配置刪除所有 query 匹配到的文檔集。
參數:
query: 與 find 和 findOne 中 query 參數的用法一致
options: 只有一個可用。muti(默認 false),允許刪除多個文檔。
callback: 可選,參數: err, numRemoved
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 文檔集 // { _id: 'id1', planet: 'Mars', system: 'solar', inhabited: false } // { _id: 'id2', planet: 'Earth', system: 'solar', inhabited: true } // { _id: 'id3', planet: 'Jupiter', system: 'solar', inhabited: false } // { _id: 'id4', planet: 'Omicron Persia 8', system: 'futurama', inhabited: true } // 刪除一條記錄 // options set to {} since the default for multi is false db.remove({ _id: 'id2' }, {}, function (err, numRemoved) { // numRemoved = 1 }); // 刪除多條記錄 db.remove({ system: 'solar' }, { multi: true }, function (err, numRemoved) { // numRemoved = 3 // All planets from the solar system were removed }); // 刪除所有記錄 db.remove({}, { multi: true }, function (err, numRemoved) { }); |
7、db.ensureIndex(options, callback)
作用:
NeDB 支持索引。索引可以提高查詢速度以及保證字段的唯一性。索引可以用在任何字段,包括嵌套很深的字段。目前,索引只能用來加速基本查詢以及使用 $in, $lt, $lte, $gt 和 $gte 運算符的查詢,如上 find 接口中示例所示。保證索引不為數組對象。方法可以在任何時候被調用,推薦在應用啟動時就調用 (該方法是同步的, 為 1000 個文檔添加索引僅需 35ms)。
參數:
fieldName(必須): 索引字段,使用“.” 給嵌套的字段加索引。
unique(可選,默認 false): 字段唯一性約束。注意:唯一性約束會增加為兩個文檔中沒有定義的字段添加索引的錯誤。
sparse(可選,默認 false): 不能為沒有定義的字段加索引。如果接受給多個文檔中沒有定義的字段添加索引,給需要該配置參數與 unique 一起使用。
expireAfterSeconds(可選,秒數): TTL 索引,設置自動過期時間。
刪除索引: db.removeIndex(fieldName, cb)
注意:_id 字段會自動加索引和唯一性約束,不必再為它使用 ensureIndex。如果使用本地存儲,索引也將保存在數據文件中,當第二次加載數據庫時,索引也將自動被添加。如果加載一個已經有索引的數據庫,刪除索引將不起任何作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
db.ensureIndex({ fieldName: 'somefield' }, function (err) { // If there was an error, err is not null }); // 對索引設置唯一性約束 db.ensureIndex({ fieldName: 'somefield', unique: true }, function (err) { }); // Using a sparse unique index db.ensureIndex({ fieldName: 'somefield', unique: true, sparse: true }, function (err) { }); // 使用唯一性約束制造錯誤,查看err的格式 db.insert({ somefield: 'nedb' }, function (err) { // err is null db.insert({ somefield: 'nedb' }, function (err) { // err is { errorType: 'uniqueViolated' // , key: 'name' // , message: 'Unique constraint violated for key name' } }); }); // 移除somefield字段的索引 db.removeIndex('somefield', function (err) { }); // Example of using expireAfterSeconds to remove documents 1 hour // after their creation (db's timestampData option is true here) db.ensureIndex({ fieldName: 'createdAt', expireAfterSeconds: 3600 }, function (err) { }); // You can also use the option to set an expiration date like so db.ensureIndex({ fieldName: 'expirationDate', expireAfterSeconds: 0 }, function (err) { // Now all documents will expire when system time reaches the date in their // expirationDate field }); |
8、db.count(query, callback)
作用:
計數。與 find 用法相同。
示例:
1 2 3 4 5 6 7 8 9 |
// Count all planets in the solar system db.count({ system: 'solar' }, function (err, count) { // count equals to 3 }); // Count all documents in the datastore db.count({}, function (err, count) { // count equals to 4 }); |
9、db.persistence.compactDatafile
作用:
為了性能考慮,NeDB 存儲使用 append-only 格式,意味著所有的更改和刪除操作其實都是被添加到了文件末尾。每次加載數據庫時,數據庫會自動被壓縮,才能拿到規范的文檔集。
也可以手動調用壓縮方法 db.persistence.compactDatafile(該方法沒有參數)。函數內部有隊列機制,保證命令按順序執行。執行完成后,會觸發 compaction.done 事件。
也可以設置自動壓縮方法 db.persistence.setAutocompactionInterval(interval) 來定時執行。interval 是毫秒級別 (大于 5000ms)。停止自動壓縮使用方法 db.persistence.stopAutocompaction()。
壓縮會花費一些時間 (在普通機器上,5w 條記錄花費 130ms 處理,并不會耗費太久)。在壓縮執行期間,其他操作將不能執行,所以大部分項目不需要使用它。
假設不受 corruptAlertThreshold 參數的限制,壓縮將會把損壞的記錄全部移除掉。
壓縮會強制系統將數據寫入磁盤,這就保證了服務崩潰不會引起數據的全部丟失。最壞的情況就是崩潰發生在兩個壓縮同步操作之間,會導致全部數據的丟失。
性能
在普通機器上,對于 1 萬條記錄
NeDB 吞吐量 (帶索引):
Insert: 5950 ops
Find: 25440 ops
Update: 4490 ops
Remove: 6620 ops
Terry 2016 年 11 月 30 日
用 NeDB 現在最蛋碎的問題是所有數據操作都是異步的,查詢無所謂,進行添加,修改,刪除這些操作,通常需要按照可控的順序執行或者會被封裝成 Function 供調用,異步的形式就比較麻煩了
yee 2018 年 4 月 23 日
Great
simon3000 2016 年 5 月 24 日
node 還有一個個人感覺不錯但是功能有點少的數據庫叫 level 不知道和這個 NeDB 比怎么樣那個好像速度快一點不過功能少了
simon3000 2016 年 5 月 24 日
我還用過一個用 lodash 控制的數據庫叫做 lowdb 不過他那個是 json 儲存所以數據庫一大就會消耗很多內存并且很慢但是他 APi 都是同步的所以數據少倒是可以當很簡單的數據庫用
XFEPeter 2019 年 6 月 11 日
level 可擴展性非常強,只是同樣存在瓶頸
美圖共賞 2016 年 4 月 14 日
美圖在這里:http://www.fydzv.com
碧青 2016 年 4 月 1 日
細細讀完,好文點贊 [good]