欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

MongoDB增删改查

程序员文章站 2022-07-01 12:48:44
...

文档插入

插入是向MongoDB添加数据的基本方法,可使用 insert() 向目标集合插入一个文档,插入操作会给文档自动添加一个 _id 键。

db.doc.insert({bar:baz})

插入原理

当执行db.docname.insert()操作时,使用的驱动程序会将数据转换成BSON格式,然后将其送入MongoDB。数据库解析BSON,检验是否包含_id键,并且文档是否超过4MB,除此之外不做别的数据验证,只是简单地将文档原样存入数据库。此做法的缺陷是允许插入无效数据,优势是能让数据库更加安全原理注入式攻击。

主流语言的驱动在传送数据前会进行一些书有限性检查,例如文档是否超长,是否包含非UTF-8字符,是否使用未知类型等。要是对使用的驱动拿捏不准,可在启动数据库服务器的使用 --objcheck 选项,这样服务器会插入前先检查文档结构的有效性,但会牺牲些性能。

插入文档

db.docname.insert({})

db.persons.find({_id:'A01', name:'alice'});

插入校验

插入数据时MongoDB只对数据进行最基本的检查:检查文档基本结构,若无_id字段则自动增加一个。

检查大小就是其中一项基本结构检查:所有文档必须小于16MB(人为设定,不同版本不同。)。作为这样的限制主要是为了防止不良的模式设计,并保持性能一致。

若要查看 doc 文档的 BSON 大小(单位为字节),可在 shell 中执行 Object.bsonsize(doc)

Object.bsonsize(db.user)
398

批量插入

db.users.insert([
  {sn:'002', name:'ben'},
  {sn:'003', name:'carl'}
])

在Shell中可使用 batchInsert() 实现批量插入。

db.foo.batchInsert([{_id:0}, {_id:1}, {_id:2}])

批量插入多个文档即传递一个由文档构成的数组给MongoDB。批量插入可使用 mongoimport 导入。不同前版本MongoDB消息长度不同。

若执行批量插入过程中有一个文档插入失败,那么在这个文档之前的所有文档都会成功插入到集合中,而这个文档以及之后的所有文档全部插入失败。

db.docname.insert([{},{}...]) 此种方式是无法实现批量插入的,shell并不支持这种语法。若要完成批量插入可使用mongo的应用驱动或使用shell的for循环来完成。

在mongo3.4版本中已经支持此种批量插入的方式。

mongo --version
MongoDB shell version v3.4.10
db.version();
3.4.10
db.persons.drop()
db.persons.insert([{name:'alice'}, {name:'ben'}])
db.persons.find()
{
    "_id" : ObjectId("5a0b058a61bf6b35a74b0fb1"),
    "name" : "alice"
}
{
    "_id" : ObjectId("5a0b058a61bf6b35a74b0fb2"),
    "name" : "ben"
}

save 与 insert 操作有何区别呢

当遇到_id相同的情况下,save完成保存操作,insert则会报错。

db.persons.insert([
    {_id:'A001', name:'alice'},
    {_id:'A002', name:'ben'}
]);

db.persons.insert({_id:'A001', name:'carl'})
db.persons.save({_id:'A001', name:'carl'})
{
    "_id" : "A001",
    "name" : "carl"
}

插入多维数据

# 向goods集合中添加文档
db.goods.insert({
    name:'xiaomi5',
    price:1500,
    weight:159,
    number:100,
    area:{
            privince:"beijing",
            city:"beijing",
        }
    });
# 向goods集合中添加文档
db.goods.insert({
    name:"xiaoxi note",
    price:2000,
    weight:150,
    number:100,
    area:{
        privince:"beijing",
        city:"beijing"
    },
    color:["red","green","blue"]
})
# 查询goods集合中所有的文档
db.goods.find()
{
    "_id" : ObjectId("5a09b56d61bf6b35a74b0fa8"),
    "name" : "xiaomi5",
    "price" : 1500.0,
    "weight" : 159.0,
    "number" : 100.0,
    "area" : {
        "privince" : "beijing",
        "city" : "beijing"
    }
}
{
    "_id" : ObjectId("5a09b63561bf6b35a74b0fa9"),
    "name" : "xiaoxi note",
    "price" : 2000.0,
    "weight" : 150.0,
    "number" : 100.0,
    "area" : {
        "privince" : "beijing",
        "city" : "beijing"
    },
    "color" : [ 
        "red", 
        "green", 
        "blue"
    ]
}

插入并保存文档

db.users.insert({"username":"alice", "nickname":"alan", "realname":"lily"})
db.users.findOne()
{
    "_id" : ObjectId("5a04ca939486f0b25bd10143"),
    "username" : "alice",
    "nickname" : "alan",
    "realname" : "lily"
}

文档删除

db.doc.remove()
  • 查询表达式依然是json对象
  • 查询表达式匹配的行将被删除
  • 若无查询表达式则集合中所有文档将被删除
# 删除user文档中性别为男的一条集合
db.user.remove({gender:'m'}, true)

删除集合doc中所有文档,但不会删除集合本身和集合的元信息。remove()可接受一个查询文档作为可选参数,给定参数后仅符合条件的文档才被删除。删除数据是永久的,不能撤销也不能恢复。

db.doc.remove({optout: true})

删除速度

删除文档通常很快但要清空整个文档,使用 drop() 会更快,删除后在这个空集合上重建各项索引。

# 插入插入100w测试数据
for(var i=0; i<1000000; i++){
  db.test.insert({foo:bar, baz:i , z:10-i});
}

# 删除文档并记录时间
var timeRemove= function(){
  var start = (new Date()).getTime();
  //db.test.drop();//清空
  db.test.remove();//删除
  db.findOne();//确保删除完成
  var timeDiff = (new Date()).getTime() - start;
  print(' remove took : '+timeDiff);
}

timeRemove();

删除集合中所有数据

db.[docname].drop() 集合本身和索引不会受到影响。

db.persons.remove()
Failed to execute script.
Error: remove needs a query:...

# 查看系统索引
db.system.indexes.find()

根据条件删除

db.[docname].remove({})

删除集合persons中name等于alice的记录

db.persons.remove({name:'alice'})

若要清除一个数据量十分庞大的集合,直接删除并重建索引的办法比remove的效率高。

文档更新

文档存入数据库后可使用 update() 来更新,update 有两个参数,一个是查询器用于定位需更新的目标文档,一个是修改器(modifier)用于说明要对找到的文档进行那种修改。

db.users.find()

{"_id" : ObjectId("5a32af65f1ad61e2459d90aa"),"name" : "alice","sn" : "001"}
{"_id" : ObjectId("5a32afe4f1ad61e2459d90ab"),"sn" : "002","name" : "ben"}
{"_id" : ObjectId("5a32afe4f1ad61e2459d90ac"),"sn" : "003","name" : "carl"}

# 新文档直接替换了旧文档而非修改
db.users.update({name:'alice'}, {name:'junchow'})

{ "_id" : ObjectId("5a32af65f1ad61e2459d90aa"),"name" : "junchow" //更新后其它字段消失了}
{ "_id" : ObjectId("5a32afe4f1ad61e2459d90ab"),"sn" : "002","name" : "ben"}
{"_id" : ObjectId("5a32afe4f1ad61e2459d90ac"),"sn" : "003", "name" : "carl"}

# 若要修改文档中某字段应使用 $set 
db.users.update({name:'ben'}, {$set:{name:'avalon'}})


# 若要删除某个列应使用 $unset
# 若要重命名某个列应使用 $rename
# 若要自增某个列应使用 $inc
db.users.insert({name:'avalon', gender:1, age:30, nickname:'f4'})
db.users.update({name:'avalon'}, {$unset:{nickname:1}, $rename:{name:'username'}, $inc:{age:1}})

{"_id" : ObjectId("5a32b783f1ad61e2459d90ad"),"gender" : 1.0,"age" : 31.0,"username" : "avalon"}

更新可选参数
{upsert:true/false, multi:true/false}
upsert 表示若无匹配的行则直接插入,和mysql中的replace一样。
multi 修改多行时即使查询表达式命中多行也只会修改一行

db.users.update({name:'junchow'}, {$set:{name:'avatar'}}, {upsert:true})

$setOnInsert 当upsert为true时并发生了insert操作时,可以补充的字段。

db.users.update({name:'junchow'}, {$set:{name:'jay'}, $setOnInsert:{gender:1} }, {upsert:true})

更新操作是不可分割的:若是两个更新同时发生,先到达服务器的先执行,接着执行另一个。两个需要同时进行更新会迅速接连完成,此过程不会破坏文档。

文档查询

深入查询

如何将MySQL中的数据插入到MongoDB呢?将MySQL中内容查询出来后使用 json_encode 转换为JSON格式数据后插入MongoDB。

查看出商品编号为32的数据

db.goods.find({id:32})

查询出不属于栏目为3的所有商品

db.goods.find({category_id:{$ne:3}}, {category_id:1, id:1, title:1, _id:0})

查询价格高于3000的商品

db.goods.find({price:{$gt:3000}}, {category_id:1, id:1, title:1, _id:0})

查询价格低于或等于100元的商品

db.goods.find({price:{$lte:100}}, {category_id:1, id:1, title:1, _id:0})

查询栏目为4或栏目为11的商品

db.goods.find({category_id:{$in:[4, 11]}}, {category_id:1, id:1, title:1, _id:0})
db.goods.find({$or:[{$category_id:4}, {$category_id:1}]}, {category_id:1, id:1, title:1, _id:0})

查询价格大于等于100且小于等于500的商品

db.goods.find({$and:[{price:{$gte:100}}, {price:{$lte:500}}]}, {category_id:1, id:1, title:1, _id:0})

文档替换

最简单的更新就是用一个新文档完全替换匹配的文档,适用于进行大规模模式迁移的情况。

db.user.insert({name:'joe', friends:32, enemies:2});
db.user.find()
{
    "_id" : ObjectId("5a32829bd3d21dc2700cbc49"),
    "name" : "joe",
    "friends" : 32.0,
    "enemies" : 2.0
}

# 将friends和enemies移动到relationshiops子文档中
var joe = db.user.findOne({name:'joe'});
joe.relationships = {friends:joe.friends, enemies:joe.enemies};
joe.username = joe.name;
delete joe.friends;
delete joe.enemies;
delete joe.name;
db.user.update({name:'joe'}, joe);

{
    "_id" : ObjectId("5a32829bd3d21dc2700cbc49"),
    "relationships" : {
        "friends" : 32.0,
        "enemies" : 2.0
    },
    "username" : "joe"
}

常见的错误是查询条件匹配到多个文档,然后更新时由于第二个参数的存在产生重复的 _id,数据库会跑出错误,任何文档都不会更新。

db.user.insert({name:'joe', age:29});
db.user.insert({name:'joe', age:90});
db.user.insert({name:'joe', age:40});
db.user.find()
{
    "_id" : ObjectId("5a3284d9d3d21dc2700cbc4c"),
    "name" : "joe",
    "age" : 29.0
}
{
    "_id" : ObjectId("5a3284d9d3d21dc2700cbc4d"),
    "name" : "joe",
    "age" : 90.0
}
{
    "_id" : ObjectId("5a3284d9d3d21dc2700cbc4e"),
    "name" : "joe",
    "age" : 40.0
}

var joe = db.user.findOne({name:'joe', age:90});
joe.age++;
db.user.update({name:'joe'}, joe);

文档更新时的内存机制

当 document 被创建时,MongoDB为其分配内存和预留内存。当修改操作不超过预留内存时速度非常快,一旦超过预留内存MongoDB就会分配新内存,此时会消耗时间。

MongoDB增删改查
修改时的内存机制

db.collection.update(criteria, objNew, upsert, multi)

  • criteria update的查询条件,类似sql update查询内where后面的
  • objNew update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql - update查询内set后面的
  • upsert 可选,若不存在update的记录,是否插入objNew,为true则插入,默认false不插入。
  • multi 可选,默认是false只更新找到的第一条记录,为true就把按条件查出来多条记录全部更新。
  • witeConcern 可选,抛出异常的级别

强硬的文档替换时更新

db.[docname].update({查询器},{修改器})

错误的更新方式

db.persons.insert({name:'alice', age:27})
# 强硬的更新会用新文档替换老文档
db.persons.update({name:'alice'}, {age:30})
db.persons.find()
{
    "_id" : ObjectId("5a0b0a6b61bf6b35a74b0fb4"),
    "age" : 30.0
}

主键冲突时会报错并停止更新

强硬替换当替换的文档和已有文档id冲突时,系统会报错。

db.persons.insert([{_id:'A01', name:'alice'}, {_id:'A02', name:'ben'}])

插入或更新操作

db.docname.update({查询器}, {修改器}, true)

查询器查询出结果则更新,查询无结果则插入。

db.persons.find()
{
    "_id" : "A01",
    "name" : "alice"
}
{
    "_id" : "A02",
    "name" : "ben"
}
db.persons.update({_id:'A03'},{_id:'A03', name:'carl'},true);
db.persons.find();
{
    "_id" : "A01",
    "name" : "alice"
}
{
    "_id" : "A02",
    "name" : "ben"
}
{
    "_id" : "A03",
    "name" : "carl"
}

批量更新

db.docname.update({查询器}, {修改器}, false, true)

db.persons.drop();
db.persons.insert([
  {_id:'A01', name:'alice'},
  {_id:'A02', name:'alice'},
  {_id:'A03', name:'alice'}
]);
db.persons.update({name:'alice'},{name:'ben'});
db.persons.find();
{
    "_id" : "A01",
    "name" : "ben"
}
{
    "_id" : "A02",
    "name" : "alice"
}
{
    "_id" : "A03",
    "name" : "alice"
}
db.persons.update({name:'alice'},{$set:{name:'carl'}},false,true);
db.persons.find();
{
    "_id" : "A01",
    "name" : "carl"
}
{
    "_id" : "A02",
    "name" : "carl"
}
{
    "_id" : "A03",
    "name" : "carl"
}

使用修改器完成局部更新

MongoDB增删改查
使用修改器完成局部更新1
# $set multi为集合追加字段
db.persons.drop();
db.persons.insert({_id:'A01', name:'junchow'});
db.persons.update({_id:'A01'},{$set:{addr:[]}}, {multi:true});
db.persons.find();
{
    "_id" : "A01",
    "name" : "junchow",
    "addr" : []
}
# $push 向集合字段的数组内追加元素
db.persons.drop();
db.persons.insert({_id:'A01', name:'junchow'});
db.persons.update({_id:'A01'}, {$set:{fullname:[]}}, {multi:true});
db.persons.update({_id:'A01'}, {$push:{fullname:'jun'}});
db.persons.update({_id:'A01'}, {$push:{fullname:'chow'}});
db.persons.find();
{
    "_id" : "A01",
    "name" : "junchow",
    "fullname" : [ 
        "jun", 
        "chow"
    ]
}
# $push时若不存在指定键则创建数组类型的键值对
db.persons.update({_id:'A01'}, {$push:{skills:'mongodb'}});
db.persons.find();
{
    "_id" : "A01",
    "name" : "junchow",
    "fullname" : [ 
        "jun", 
        "chow"
    ],
    "skills" : [ 
        "mongodb",
    ]
}
# $addToSet 目标数组不存在元素则追加
db.persons.update({_id:'A01'}, {$addToSet:{fullname:'jun'}});
db.persons.find();
{
    "_id" : "A01",
    "name" : "junchow",
    "fullname" : [ 
        "jun", 
        "chow"
    ]
}
# $pushAll 批量添加数组元素
db.persons.update({_id:'A01'}, {$pushAll:{tags:['handsome','dream','happy']}});
db.persons.find();
/* 1 */
{
    "_id" : "A01",
    "name" : "junchow",
    "fullname" : [ 
        "jun", 
        "chow"
    ],
    "skills" : [ 
        "mongodb", 
        "mongodb"
    ],
    "tags" : [ 
        "handsome", 
        "dream", 
        "happy"
    ]
}
# $unset 删除集合中的字段
db.persons.update({name:'junchow'}, {$unset:{addr:[]}}, false, true);
db.persons.find();
{
    "_id" : "A01",
    "name" : "junchow"
}
MongoDB增删改查
使用修改器完成局部更新2
db.persons.find();
/* 1 */
{
    "_id" : "A01",
    "name" : "junchow",
    "fullname" : [ 
        "jun", 
        "chow"
    ],
    "skills" : [ 
        "mongodb", 
        "php", 
        "js", 
        "mysql", 
        "mongodb"
    ],
    "tags" : [ 
        "happy"
    ]
}
# $pop 为1时删除数组中末尾元素
db.persons.update({_id:'A01'}, {$pop:{skills:1}});
db.persons.find();
{
    "_id" : "A01",
    "name" : "junchow",
    "fullname" : [ 
        "jun", 
        "chow"
    ],
    "skills" : [ 
        "mongodb", 
        "php", 
        "js", 
        "mysql"
    ],
    "tags" : [ 
        "happy"
    ]
}
# $pop 为-1时删除数组中首位元素
db.persons.update({_id:'A01'}, {$pop:{skills:-1}});
db.persons.find();
{
    "_id" : "A01",
    "name" : "junchow",
    "fullname" : [ 
        "jun", 
        "chow"
    ],
    "skills" : [ 
        "php", 
        "js", 
        "mysql"
    ],
    "tags" : [ 
        "happy"
    ]
}
# $pull 删除数组中指定的单个元素
db.persons.update({_id:'A01'}, {$pull:{skills:'php'}});
db.persons.find();
{
    "_id" : "A01",
    "name" : "junchow",
    "fullname" : [ 
        "jun", 
        "chow"
    ],
    "skills" : [ 
        "js", 
        "mysql"
    ],
    "tags" : [ 
        "happy"
    ]
}
# $pullAll 删除指定的多个元素
db.persons.update({_id:'A01'}, {$pullAll:{skills:['js','mysql']}});
db.persons.find();
/* 1 */
{
    "_id" : "A01",
    "name" : "junchow",
    "fullname" : [ 
        "jun", 
        "chow"
    ],
    "skills" : [],
    "tags" : [ 
        "happy"
    ]
}
db.persons.update({_id:'A01'}, {$pushAll:{skills:[{type:'db', name:'mongodb'},{type:'js', name:'jquery'},{type:'php', name:'laravel'}]}});
db.persons.find();
{
    "_id" : "A01",
    "name" : "junchow",
    "fullname" : [
        "jun",
        "chow"
    ],
    "skills" : [
        {
            "type" : "js",
            "name" : "jquery"
        },
        {
            "type" : "db",
            "name" : "mongodb"
        },
        {
            "type" : "js",
            "name" : "jquery"
        },
        {
            "type" : "php",
            "name" : "laravel"
        }
    ],
    "tags" : [
        "happy"
    ]
}
# bug:使用对象点引用属性时需添加引号包裹,否则报错。
db.persons.update({skills.type:'db'}, {$set:{skills.$.author:'lily'}});
db.persons.find();
Error: Line 1: Unexpected token .

# 使用$数组定位器为数组追加属性
db.persons.update({'skills.type':'db'}, {$set:{'skills.$.author':'lily'}});
db.persons.find();
{
    "_id" : "A01",
    "name" : "junchow",
    "fullname" : [
        "jun",
        "chow"
    ],
    "skills" : [
        {
            "type" : "db",
            "name" : "mongodb",
            "author" : "lily"
        },
        {
            "type" : "php",
            "name" : "laravel"
        },
        {
            "type" : "js",
            "name" : "jquery"
        }
    ],
    "tags" : [
        "happy"
    ]
}

切记修改器是放在最外层,查询器都是放在内层。

批量数组更新

db.docname.update({key:val}, {$addToSet:{attr:{$each:[el1,el2,el3]}}})

$each 会循环数组,并对数组元素执行 $addToSet操作。$addToSet会判断若不存在则导入。

db.persons.update({_id:'A01'}, {$addToSet:{favorite:{$each:['reading','programing','swimming']}}});
db.persons.find();
{
    "_id" : "A01",
    "name" : "junchow",
    "fullname" : [ 
        "jun", 
        "chow"
    ],
    "skills" : [ 
        {
            "type" : "db",
            "name" : "mongodb",
            "author" : "lily"
        }, 
        {
            "type" : "php",
            "name" : "laravel"
        }, 
        {
            "type" : "js",
            "name" : "jquery"
        }
    ],
    "tags" : [ 
        "happy"
    ],
    "favorite" : [ 
        "reading", 
        "programing", 
        "swimming"
    ]
}