六、API
初始化一个数据存储,相当于 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 失败,所有命令也将不会执行。
示例
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 字段。
示例
,n:5
,today:newDate()
,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 是查询到的文档集。
示例:
作用:
查询符合条件的一个文档。与 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 不能被修改
示例:
// 文档集
// { _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
// 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
示例:
作用:
参数:
fieldName(必须): 索引字段,使用“.” 给嵌套的字段加索引。
unique(可选,默认 false): 字段唯一性约束。注意:唯一性约束会增加为两个文档中没有定义的字段添加索引的错误。
sparse(可选,默认 false): 不能为没有定义的字段加索引。如果接受给多个文档中没有定义的字段添加索引,给需要该配置参数与 unique 一起使用。
expireAfterSeconds(可选,秒数): TTL 索引,设置自动过期时间。
删除索引: db.removeIndex(fieldName, cb)
注意:_id 字段会自动加索引和唯一性约束,不必再为它使用 ensureIndex。如果使用本地存储,索引也将保存在数据文件中,当第二次加载数据库时,索引也将自动被添加。如果加载一个已经有索引的数据库,删除索引将不起任何作用。
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 用法相同。
示例:
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