本文已参与「新人创作礼」活动,一起开启掘金创作之路。
MongoDb的shell就是javascript实现的,这意味着我们可以使用js脚本进行复杂的管理。例如对数据库进行统一操作、对其中数据进行统计计算等。这也是MongoDB同其他数据库相比非常大的一个优势。
mongo客户端有两种方式与mongodb服务进行交互。一种是交互式的,另一种是脚本化的(对于脚本化的方式把脚本贴shell执行也是可以的)。 参考 这里 官方说明 对应中文
关于mongo shell的这些方法在官网上都是可以找到手册的。例如: docs.mongodb.com/manual/refe… 就可以找到全量的方法。
一、运行脚本的几种方式:
(1)在os命令行下运行一个js文件,如下:
./mongo --host 11.186.6.180 --port 27017 collNumSize.js
注:上述命令不需要进入shell即可执行
(2)在mongo shell交互模式下运行一个js脚本——load方法
load("/usr/local/mongodb4/bin/jsstudy.js")
注:进入mongo shell然后执行上述指令
(3)还有一种mongodb eval的执行脚本的方式。
db.eval("function(){return 'refactor';}")
感觉有点鸡肋,不研究这个东西了
(4)mongo shell中直接贴js脚本
注:作为工具使用的话还是不推荐这种方式了
二、使用体验
1、直接在shell执行js代码
#直接将如下js脚本贴到shell即可执行
注:for循环后面的"{"好像必须要在for语句所在的那一行。
var batchSize = 5;
var eventName = ["report", "meeting", "advantage"]
var docs = [];
for (var i = 0; i < batchSize; i++) {
docs.push( {
event_name : eventName[i % 3], //字符串
type : i % 5, //数值类型
duration : i % 1000 //区分度较大的数值类型
} );
}
#这样我们就把数据写进去了!!!!
mongos> db.collection_2355128746.save(docs)
BulkWriteResult({
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 2,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})
mongos> db.collection_2355128746.find()
{ "_id" : ObjectId("60195b7a7caf296f546b41b8"), "event_name" : "report", "type" : 0, "duration" : 0 }
{ "_id" : ObjectId("60195b7a7caf296f546b41b9"), "event_name" : "meeting", "type" : 1, "duration" : 1 }
2、执行js脚本文件
1、连接mongdb数据库,并向其中插入记录。
1)创建脚本文件 test.js。如下:
/*
向db_shuozhuo这个数据库的 collection_0/1/2/3/4……集合中依次插入两条记录
注:其中的重点是 集合名称 为变量的情况下如何操作。
*/
var url = "mongodb://127.0.0.1:27017/db_shuozhuo";
var db = connect(url);
var collection_num = 3;
for (var i = 0; i < collection_num; i++)
{
var coll_name = "collection_" + i.toString();
db[coll_name].insert([{
"name": "Sally",
"age": 28,
"gender": "female",
"friend": ObjectId("5c9b18e87c2a03b843f51d1a")
},{
"name": "Jenny",
"age": 29,
"gender": "female",
"friend": ObjectId("5c9b18e87c2a03b843f51d1a")
}]);
}
2)执行此js文件
mongo test.js
然后就可以看到数据库中有对应库名、表名、数据记录了。
其他使用实例
实例一:获取某db的集合名称列表并输出到文档中。
(1)js脚本如下:
var url = "mongodb://127.0.0.1:27017/db_shuozhuo";
var db = connect(url);
var collection_num = 3;
for (var i = 0; i < collection_num; i++)
{
var coll_name = "collection_" + i.toString();
db[coll_name].insert([{
"name": "Sally",
"age": 28,
"gender": "female",
"friend": ObjectId("5c9b18e87c2a03b843f51d1a")
},{
"name": "Jenny",
"age": 29,
"gender": "female",
"friend": ObjectId("5c9b18e87c2a03b843f51d1a")
}]);
}
//getCollectionNames方法可用于获取db中的集合名称
collections=db.getCollectionNames()
//遍历集合列表,挨个输出
for (var i=0; i < collections.length; i++){
print(collections[i])
// print("\n") //换行符,这里不需要再换行
}
(2)按如下方式执行:
(3)file.txt文档如下
实例二、连接远程mongodb并执行js脚本
/*
(1)如果连接语句中没有指定用户名、密码则这个地方必要要指定(即一定要有如下两句)
(2)如果不指定db_name就ok(此时会连到默认的db,如test),指定db_name就不ok(报鉴权出错)此时优先检查这个db的users中是不是有我们用的这个用户名/密码。没有的话个这个db加一个就好了。
*/
var url = "mongodb://mongouser:qq%40mongo@11.186.2.222:27017/db_shuozhuo"
var db = connect(url);
var collection_num = 3;
for (var i = 0; i < collection_num; i++)
{
var coll_name = "collection_" + i.toString();
db[coll_name].insert([{
"name": "Sally",
"age": 28,
"gender": "female",
"friend": ObjectId("5c9b18e87c2a03b843f51d1a")
},{
"name": "Jenny",
"age": 29,
"gender": "female",
"friend": ObjectId("5c9b18e87c2a03b843f51d1a")
}]);
}
//getCollectionNames�~V��~U�~O��~T��~N�~N��~O~Vdb中�~Z~D�~[~F�~P~H�~P~M称
collections=db.getCollectionNames()
//�~A~M�~N~F�~[~F�~P~H�~H~W表,�~L�个�~S�~G�
for (var i=0; i < collections.length; i++){
print(collections[i])
// print("\n") //�~M��~L符,�~Y�~G~L�~M�~~@�~A�~F~M�~M��~L
}
1、在js脚本中指定用户名、密码、目标db
(1)可选参数的方式:
①./mongo --host 11.186.2.222 --port 27017 collNumSize.js
#当然你非要在链接语句中也带上用户名/密码也是没有问题的
②./mongo --host 11.186.2.222 --port 27017 -u "mongouser" -p "qq@mongo" collNumSize.js
(2)通过uri的方式当然也是可以的,参见 ./mongo -h
③./mongo 11.186.2.222:27017 collNumSize.js
#同理你非要带上用户名、密码也是没问题的
④./mongo mongodb://mongouser:qq%40mongo@11.186.2.222:27017/db_shuozhuo collNumSize.js
2、但是如果js脚本中没有指定用户名、密码,链接的时候就一定要指定,效果如下。
此时只有②④好用了,①③都会报鉴权不通过的错误。如下:
3、注意事项。对于上面指定连接哪个db的时候,要确保用户名、密码确实是这个db的user之一,否则会报鉴权不通过的错误。
举个例子:当我们不指定采用默认db的时候就是ok的,一旦指定我们自己的db就鉴权错误,大概率就是指定的这个db并没有mongouser这个用户。
(1)查看某db有哪些users的方法:
use db_name1
show users;
(2)给这个db添加用户名/密码的方法:
use db_name1
db.createUser({user: "mongouser", pwd: "qq@mongo", roles: [{ role: "dbOwner", db: "db_shuozhuo" }]})
实例三、统计某db下各集合的数据count
//列出每个集合数据的数量
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
var num = db.getCollection(collections[i]).count()
print(collections[i],num)
}
实例四、集合批量创建/删除的脚本
1、集合名称有规律时候的创建
/*
文件名:批量创建集合(prefix+index)
作用:在指定db下通过指定前缀和集合数批量创建集合
调用:./mongo 11.186.6.10:27017 createCollections.js > createCollections.txt
*/
var url = "mongodb://mongouser:qiyeqq%40mongo@11.186.6.10:27017/db_shuozhuo"
var db = connect(url);
var prefix = "collection_event_track_"
var collection_num = 1000;
for (var i = 0; i < collection_num; i++){
var coll_name = prefix + i.toString();
db.createCollection(coll_name)
}
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
print("create:" + collections[i])
}
2、集合名称无规律时候的创建
var url = "mongodb://mongouser:qiyeqq%40mongo@11.186.6.10:27017/db_shuozhuo"
var db = connect(url);
var collections=["collection1","collection2","collection3","collection4"]
for (var i=0; i < collections.length; i++){
db.createCollection(collections[i])
print("create:" + collections[i])
}
3、删除db下所有集合的js脚本
/*
文件名: removeCollections.js
作用:删除某db下的所有集合
调用:load没法将输出重定向到文件。
load("./removeCollections.js")
./mongo 11.186.6.10:27017 removeCollections.js > removeCollections.txt
*/
var url = "mongodb://mongouser:qiyeqq%40mongo@11.186.6.10:27017/db_shuozhuo"
var db = connect(url);
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
db.getCollection(collections[i]).drop()
print("remove:" + collections[i])
}
4、删除db下所有集合数据(不删除索引)
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
db.getCollection(collections[i]).remove({systime:{"$gt":1}})
print("remove:" + collections[i])
}
实例四、批量创建/删除索引
1、对db下以"test"开头的集合统一创建索引
注:这里完全可以采用字符串匹配的方式来搞,就是匹配集合名。
use db_shuozhuo
//获取db下的集合数
var t=db.getCollectionNames()
/*
这里面用到了javascript语言的一些知识:
(1)indexOf: 返回某个指定子字符串在字符串中首次出现的位置,https://www.w3school.com.cn/jsref/jsref_indexOf.asp
(2)forEach: https://blog.csdn.net/mijichui2153/article/details/114311867
*/
t.forEach(function(item){
print(item.indexOf("test"))
//集合名称"以test作为开头"的集合.
if(item.indexOf("test") == 0) {
//当数据量大,线上有性能要求,且访问量大的情况下切记要使用background方式(否则会造成数据库阻塞)
db.getCollection(item).createIndex({ index_name: 1}, { name: "index_name",unique: false, background:true});
//db.getCollection(item).createIndex({ index_name: 1}, { name: "index_name",unique: false});
print("create index success!")
}
}
)
print("end!")
2、对db下的集合添加索引(排除不存在的集合)
注:主要就是exists()方法;如果${str}集合存在就是一坨东西,如果不存在就是null(自测一下看就知道了)
use db_shuozhuo;
for(var i= 0 ;i<10;i++) {
var str = 'collection_'+ i;
print(str)
var temp = db[str].exists();
print(temp)
if (temp != null) {
db.getCollection(str).createIndex({ index_name: 1}, { name: "index_name",unique: false, background:true});
print("create index success!")
}
}
print("end !")
3、筛选出没有创建index_name列索引的集合名
对没有创建index_name索引的集合创建此索引,此处假设如果有创建的话index_name一定是紧随默认"_id"索引之后第一个创建的。
use db_shuozhuo;
var t=db.getCollectionNames()
var NoneIndexCollections =[]
t.forEach(function(item){
var index = db.getCollection(item).getIndexes();
if(index.length >= 2){
var temp = index[1]
if(temp.name != null && temp.name != 'index_name'){
print(item + " have no index[index_name]!")
NoneIndexCollections.push(item)
}
} else {
print(item + " have no index[index_name]!")
NoneIndexCollections.push(item)
}
}
)
print(NoneIndexCollections)
4、为没创建index_name列索引的集合创建此索引
use db_shuozhuo;
var t=db.getCollectionNames()
t.forEach(function(item){
var index = db.getCollection(item).getIndexes();
if(index.length >= 2){
var temp = index[1]
if(temp.name != null && temp.name != 'index_name'){
db.getCollection(item).createIndex({ index_name: 1}, { name: "index_name",unique: false, background:true});
print(item," createIndex[index_name] success!")
}
} else {
db.getCollection(item).createIndex({ index_name: 1}, { name: "index_name",unique: false, background:true});
print(item," createIndex[index_name] success!")
}
}
)
print("end !")
5、删除索引的js脚本
/*
文件名: dropIndex.js
作用:为某db下所有集合创建索引
调用:./mongo 11.186.6.10:27017 dropIndex.js
注:https://docs.mongodb.com/manual/reference/method/js-collection/ #都在集合方法里面
*/
var url = "mongodb://mongouser:qiyeqq%40mongo@11.186.6.10:27017/db_shuozhuo"
var db = connect(url);
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
//这里是drop掉所有用户自己创建的index(注:默认生成的_id是不会被drop掉的)
db.getCollection(collections[i]).dropIndexes()
print("drop index for:" + collections[i])
}
注:如果想drop掉指定index的话就用dropIndex删除指定index。
db.getCollection(collections[i]).dropIndex("First_1")
实例五、“偷梁换柱”
这里“偷梁换柱”对将原集合全部替换掉。这里的替换是指集合名、数据和原来一致,但是索引分片等属性清空。主要应用场景是原集合被错误的设置了分片,因为mongodb版本较低不支持unshard的补救方案。注:分片策略是集合维度设置的,把集合偷偷地换掉就可以了。
/*
"偷梁换柱"步骤如下:
第一步:把当前db的每个集合的数据都拷贝到一个对应的临时集合中.例如,临时集合的集合名是"老集合_tmp";
第二步:确定数据都迁移过来后,把原来的老集合(分片设置不对的集合)都删掉了;
第三步:删除后重新建立与老集合同名的集合,并给这些集合设置正确的索引/分片策略;
第四步:然后在把临时集合中的数据导入到对应名称的索引/分片都ok的新集合。
第五步:最后再把临时集合们删除,就大功告成了。
注:这个仅仅是迁数据不会把索引、分片等属性带过来。我们也通过这种方式验证"偷梁换柱"是否成功。
db.collection_event_track_100.getIndexes()
db.collection_event_track_100.stats().sharded
*/
//1.将集合数据拷贝到"集合_tmp"备份集合
//注:这个速度比想象中的要慢,速度大概为2~3w条/min;100w条就将近半个多小时;1亿条超不多要1d,擦!!
var temp_coll_postfix = "_tmp"
var collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
var target_collection = collections[i] + temp_coll_postfix
db.getCollection(collections[i]).find().forEach(function(x){db.getCollection(target_collection).insert(x);})
print("copy data from", collections[i] ," to ", target_collection)
}
//2.清空(删除)老集合
for (var i=0; i < collections.length; i++){
db.getCollection(collections[i]).drop()
print("drop:" + collections[i])
}
//3.重新创建一把与原老集合同名的集合(这时他们都是全新的集合了),并设置想要的索引/分片;
//4.把数据从临时存储集合存放会原名称的集合(注:这里是只是复用原集合名称而已,实际是全新的集合了)
var collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
var target_collection = collections[i].substring(0, collections[i].length-4)
db.getCollection(collections[i]).find().forEach(function(x){db.getCollection(target_collection).insert(x);})
print("copy data from", collections[i] ," to ", target_collection)
}
//5.再把临时集合删掉删除
for (var i=0; i < collections.length; i++){
db.getCollection(collections[i]).drop()
print("drop:" + collections[i])
}
print("success toulianghuanzhu!")
实例六、对db的每条数据统一做某种操作(forEach语句)——非常有用
举例一。 如下脚本作用:遍历一个db里面的所有数据然后给他加上一个数值为随机生成字串的uniqueid字段。
/*
forEach语句的功能非常强大;其实不是只遍历集合,本质上他是一个遍历数组的通用方法。
当然我们这里用来遍历集合中的数据(function回调有三个参数:第一个参数是遍历的数组内容,第二个参数是对应的数组索引,第三个参数是数组本身)。
这里的遍历可以做很多事情:打印、删除数据、插入数据、更新数据等都是可以的。
db.collection_2852199203.find()
db.collection_2852199265.find()
*/
//生成随机字串的函数
function randomString(len) {
len = len || 32;
var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
var maxPos = $chars.length;
var pwd = '';
for (i = 0; i < len; i++) {
pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
}
//这里主要是的解决i的问题,function内部实现不认识这个i;也就是说这个i传不进来。
var collections=db.getCollectionNames()
var index = 0;
for (var i=0; i < collections.length; i++){
index = i;
print("index:",index)
var operation = function(doc){
print("systime:",doc.systime);
doc.uniqueid = randomString(40)+"_byshuozhuo";
print("uniqueid:",doc.uniqueid);
print("collections[index]:",collections[index]);
db.getCollection(collections[index]).remove({"_id":doc._id});
db.getCollection(collections[index]).insert(doc);
} //ok的
db.getCollection(collections[index]).find().forEach(operation)
print(collections[index] ,"done!")
i=index
}
举例二:将如下的实例数据统一修改成后面的形式。改动点有 ①_id字段的前缀字串去掉换成appid字段的内容拼在前面 ②index_name字段同上一点 ③把index_name字段名改成key。
作用前数据如下:
db.test.insert({
"_id" : "msg_roaming_session_3610_1029610_8",
"systime" : 1637137563596249,
"index_name" : "msg_roaming_session_3610_1029610",
"appid" : 202002086,
})
作用后数据如下:
mongos> db.test.find().pretty()
{
"_id" : "202002086_3610_1029610_8",
"systime" : 1637137563596249,
"key" : "202002086_3610_1029610"
"appid" : 202002086,
}
###作用脚本如下
var collections=db.getCollectionNames()
var index = 0;
for (var i=0; i < collections.length; i++){
index = i;
print("index:",index)
//这里设定仅仅作用于test集合.if条件删除后就作用与db下全部集合了
if (collections[i] == "test") {
var operation = function(doc){
print("systime:",doc.systime);
raw_id = doc._id;
print("raw_id:",doc._id);
new_id = doc.appid.toString() + raw_id.slice(19); //不要前缀字串
doc._id = new_id;
print("new_id:",new_id)
new_key = doc.appid.toString() + doc.index_name.slice(19);
doc.index_name = new_key;
doc.key = new_key;
//删除老数据
db.getCollection(collections[index]).remove({"_id":raw_id});
//插入新的doc
db.getCollection(collections[index]).insert(doc);
//对新的doc取出想要删除的inde_name字段(此时已经有key字段了)
db.getCollection(collections[index]).update({"_id":new_id},{"$unset":{"index_name":0}});
}
db.getCollection(collections[index]).find().forEach(operation)
print(collections[index] ,"done!")
i=index
}
}
实例七、基于forEach语句的刷数据案例
(0)就是刷数据,如下分别为原始数据和目标数据对比。具体来说变化如下:
/*
步骤如下:一方面对数据进行统一的增减/修改字段的操作;另一方面"偷梁换柱"实现重新设置分片规则
第一步:把当前db的每个集合的数据都拷贝到一个对应的临时集合中.例如,临时集合命名规则为"老集合_tmp";
第二步:对数据进行你想要的调整,这里主要用forEach进行遍历操作,细节见脚本注释;
第三步:创建要承载数据的新集合,并设置想要的索引/分片;
注:这里相当于是把集合名称统一改了,如果想保持一致的话就把原承载数据集合删掉这里重建就行了
第四步:对新建的集合设置key字段的hash索引
第五步:对新建的集合设置运行分片,分片策略为key字段的hash分片
第六步:创建用于提升搜索性能的复合索引{key:1,seq:1}
第七步:把数据从临时存储集合存放挪到“新集合”
第八步:确认无误后把不用的集合删除即可,就大功告成了。
注:每个步骤后都验证下是否符合预期
*/
#原始数据
db.collection_86.find({"_id":"msg_roaming_session_10005522_10005534_1"}).pretty()
{
"_id" : "msg_roaming_session_10005522_10005534_1",
"random" : 2137766695,
"roaming_seq" : 1,
"systime" : 1635411532243647,
"appid" : 202038486,
"msg_body" : "EjMIABCn9q77ByABKL/Fw9ve7PMCQgwKCgiS2OIEEJ7Y4gRKEAgAEAAaCAgBENuf/M8KIAEaMgowEi4KLAoq5a6i5pyN54us6KeS5YW95bCP5Yqp5omL5Y2z5bCG5Li65oKo5pyN5Yqh",
"pkg_num" : 1,
"recv_uin" : 10005534,
"relation_type" : 0,
"seq" : 1,
"client_ip" : 0,
"ext1" : "",
"send_uin" : 10005522,
"div_seq" : 0,
"group_id" : 0,
"index_name" : "msg_roaming_session_10005522_10005534",
"from_term" : 0,
"pkg_index" : 0,
"send_time" : 1635411532243647
}
#目标数据
db.coll_86.find({"_id":"cc_202038486_10005522_10005534_1"}).pretty()
{
"_id" : "cc_202038486_10005522_10005534_1",
"random" : 2137766695,
"appid" : 202038486,
"msg_body" : "EjMIABCn9q77ByABKL/Fw9ve7PMCQgwKCgiS2OIEEJ7Y4gRKEAgAEAAaCAgBENuf/M8KIAEaMgowEi4KLAoq5a6i5pyN54us6KeS5YW95bCP5Yqp5omL5Y2z5bCG5Li65oKo5pyN5Yqh",
"pkg_num" : 1,
"recv_uin" : 10005534,
"seq" : 1,
"client_ip" : 0,
"ext1" : "",
"send_uin" : 10005522,
"div_seq" : 0,
"group_id" : 0,
"from_term" : 0,
"pkg_index" : 0,
"key" : "cc_202038486_10005522_10005534",
"time" : 1635411532243647,
"type" : 0
}
(1)把当前db的每个集合的数据都拷贝到一个对应的临时集合中.例如,临时集合命名规则为"老集合_tmp";
var temp_coll_postfix = "_tmp"
var collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
//定制逻辑结合实际看要不要
if (collections[i].substring(0,5) == "coll_"){
continue;
}
var target_collection = collections[i] + temp_coll_postfix
db.getCollection(collections[i]).find().forEach(function(x){db.getCollection(target_collection).insert(x);})
print("copy data from", collections[i] ," to ", target_collection)
}
(2)对_tmp中的数据进行遍历调整
var collections=db.getCollectionNames()
var index = 0;
for (var i=0; i < collections.length; i++){
var msg_num =0;
index = i;
//if (collections[i] == "collection_1_tmp"){
// continue;
//}
var coll_msg_num = db.getCollection(collections[index]).count();
//if (collections[i].substring(collections[i].length-4, collections[i].length) == "_tmp" && collections[i]=="collection_99_tmp"){
if (collections[i].substring(collections[i].length-4, collections[i].length) == "_tmp"){
var operation = function(doc){
msg_num = msg_num + 1;
print("msg_num:", msg_num)
if (msg_num > coll_msg_num){
print("msg_num > coll_msg_num", msg_num, coll_msg_num)
return;
}
raw_id = doc._id;
//print("raw_id:",doc._id);
if (raw_id <= 20){
new_id = raw_id;
}else{
//①按照如下规则给_id赋新值
new_id = "cc_" + doc.appid.toString() + "_" + raw_id.slice(20); //不要前缀字串
}
doc._id = new_id;
//print("new_id:",new_id)
//②新增了一个key字段,字段值如下;用以替代原来的index_name字段
new_key = "cc_" + doc.appid.toString() + doc.index_name.slice(19);
doc.key = new_key;
//③新增了一个time字段,用以替换原来的systime字段
doc.time = doc.systime;
//④新增了一个type字段,用以替换原来的relation_type字段
doc.type = doc.relation_type;
//删除老数据
db.getCollection(collections[index]).remove({"_id":raw_id});
//插入新的doc
db.getCollection(collections[index]).insert(doc);
//⑤对新插入的目标数据删除被替换或想要删除的index_name、systime、roaming_seq、send_time、relation_type等字段
db.getCollection(collections[index]).update({"_id":new_id},{"$unset":{"index_name":1,"systime":1,"roaming_seq":1,"send_time":1,"relation_type":1}});
}
msg_num = 0;
db.getCollection(collections[index]).find().forEach(operation)
print(collections[index], " done! msg_num[", msg_num, "],count[",coll_msg_num,"]")
i=index
}
}
#判断是否执行完了
db.collection_99_tmp.find({index_name:{"$exists":true}})
db.collection_1_tmp.find({index_name:{"$exists":true}})
db.collection_1_tmp.find({"key":{"$exists":false}})
(3)创建要承载数据的新集合,并设置想要的索引/分片;
注:这里相当于是把集合名称统一改了,如果想保持一致的话就把原承载数据集合删掉这里重建就行了。
var prefix = "coll_"
var collection_num = 100;
for (var i = 0; i < collection_num; i++){
var coll_name = prefix + i.toString();
db.createCollection(coll_name)
print("create collection:", coll_name)
}
(4)对新建的集合设置key字段的hash索引
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
if (collections[i].substring(0,5) == "coll_"){
db.getCollection(collections[i]).createIndex({ key: "hashed" })
print("create index for:" + collections[i])
}
}
(5)对新建的集合设置运行分片,分片策略为key字段的hash分片
target_db = "db_imsdk_roaming"
db.runCommand({enableSharding: target_db})
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
if (collections[i].substring(0,5) == "coll_"){
var collection = target_db + "." + collections[i]
db.adminCommand( { shardCollection: collection, key: {key: "hashed"}, numInitialChunks: 300})
print("sharding for:", collection)
}
}
#验证分片是否成功
db.coll_1.stats().sharded
(6)创建用于提升搜索性能的复合索引{key:1,seq:1}
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
if (collections[i].substring(0,5) == "coll_"){
db.getCollection(collections[i]).createIndex({key:1,seq:1})
print("create index for:" + collections[i])
}
}
(7)把数据从临时存储集合存放挪到“新集合”
var collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
if (collections[i].substring(collections[i].length-4,collections[i].length) == "_tmp"){ //ok
var target_collection = "coll_" + collections[i].substring(11, collections[i].length-4)
print("collections[i]:",collections[i],"target_collection:",target_collection)
db.getCollection(collections[i]).find().forEach(function(x){db.getCollection(target_collection).insert(x);})
print("copy data from", collections[i] ," to ", target_collection)
}
}
(8)确认无误后再把原集合和临时集合删掉
五、shell脚本调用js脚本
经过验证没有任何问题。例如创建createIndex.sh脚本文件。就可以执行我们为mongo写的js脚本。
这样定时的轮询检查db中哪些集合没有目标索引并创建之是完全可行的了。
#! /bin/bash
a="hello world"
echo $a
./mongo --host 11.186.6.180 --port 27017 collNumSize.js > file.txt
下面列出一些常用的函数方法:
/*
本文档列出常用的js方法,同时也会列出对应的shell helpers指令(如果有的话)
*/
1、列出db
show dbs; show databases;
db.adminCommand('listDatabases')
2、选用某个db作为当前db
use <db_name>
db = db.getSiblingDB('<db_name>')
3、获取集合名
show collections
db.getCollectionNames()
4、列出users
show users
db.getUsers()
5、列出角色
show roles
db.getRoles({showBuiltinRoles: true})
6、
一个使用案例:
1、要求。以key(账号对拼接)字段进行hash分片;对uniqueid字段设置唯一索引;同时考虑到主流查询方法为 {kfuin:2852119999,key:"event_detail_2852199351_wx58b4690f0ab8193f"} + 基于{"systime":1620455139591001}比较或者比对, 故创建关于这三个字段的复合索引以提升性能。目标db为 db_event_track ; 其下集合命名 collection_event_track_{kfuin%1000}。
2、参照几个链接的分析
MongoDB之分片集群_mijichui2153的博客-CSDN博客
MongoDB之explain(执行计划分析)_mijichui2153的博客-CSDN博客
3、首先设置测试环境。
测试环境和线上环境的区别是,测试换环境没必要一次性建立1000个集合,因为测试账号相对较少,建议直接对已有集合设置分片建立索引即可。(注:可以在适当的时候对新增的集合查漏补缺)
(1)首先对db_event_track开启允许分片(需切换至admin)。
转到admin数据库,然后执行以下语句允许db分片。
sh.enableSharding("db_event_track")
(2)集合分片——批量创建集合
注:对于oa环境不用批量创建集合;发现即使不创建集合,在尝试创建索引的时候也会自动创建对应集合(但是这里还是推荐正常创建集合)。线上业务如下:
var prefix = "collection_event_track_"
var collection_num = 10;
for (var i = 0; i < collection_num; i++){
var coll_name = prefix + i.toString();
db.createCollection(coll_name)
}
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
print("create:" + collections[i])
}
(3)集合分片——创建分片索引
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
db.getCollection(collections[i]).createIndex({ key: "hashed" })
print("create index for:" + collections[i])
}
db.collection2.createIndex({ key: "hashed" }
(4)创建唯一索引(注意:和下一步集合设置分片反过来的话会有问题!!!)
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
db.getCollection(collections[i]).createIndex({ key: 1, uniqueid: 1}, { unique: true })
print("create index for:" + collections[i])
}
db.collection_event_track_26.createIndex( { key: 1, uniqueid: 1}, { unique: true } )
注:本次操作顺序反调导致没法继续进行。考虑到历史数据不能丢,这里“偷梁换柱”对将原集合全部替换掉。这里的替换是指集合名、属于和原来一致,但是索引分片等属性被清空。参见 实例五 “偷梁换柱”。
注意:如果集合中已经有数据的话必须要满足你去设置的唯一性,否则设置失败。同理如果历史数据没有uniqueid字段,会统一视为uniqueid字段为null,这样也设置不了这个唯一索引。
解决办法:历史数据还要保留,那只能人为的补充uniqueid字段值了。参见 实例六、对db的每条数据统一做某种操作
注:如果是两个字段的唯一索引,这个索引也是能加速搜索性能的!!!!!
例如{key:1,subkey:1},{unique:true}即能用于保证唯一性,也能用于db.coll_0.find({key:"aaa",subkey:"bbb"})
(5)集合分片——对集合开启分片(当前数据库下执行即可)
target_db = "db_event_track"
db.runCommand({enableSharding: target_db})
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
var collection = target_db + "." + collections[i]
db.adminCommand( { shardCollection: collection, key: {key: "hashed"}})
print("sharding for:", collection)
}
(6)为了让搜索性能更好——设置{kfuin:1,key:1,systime:1}复合索引
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
db.getCollection(collections[i]).createIndex({key:1,systime:1})
print("create index for:" + collections[i])
}
(7)为了让包含ses_id字段搜索的性能更好——设置{kfuin:1,key:1,ses_id:1,systime:1}复合索引
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
db.getCollection(collections[i]).createIndex({key:1,ses_id:1,systime:1})
print("create index for:" + collections[i])
}
完毕后索引情况下如下(外加上面的四字段索引):
另一个使用案例:
要求如下:
库名:db_imsdk_roaming
表名:"collection_" + {appid%100}
唯一性保证:利用_id这个主键字段赋值md5(index_name + msg_time + "_" + seq)
分片:分片键是appid,分片方式采用hash分片;即需要对appid新建一个hash索引
查询语句:标准查询语句是 {appid:1,index_name:1} + 基于{seq:1}字段的比较与比对
查询索引:建立一个{appid:1,index_name:1,seq:1}的三字段复合索引
(1)首先是允许db分片 db_imsdk_roaming 注:在admin数据库下执行
sh.enableSharding("db_imsdk_roaming")
(2)批量创建集合
var prefix = "collection_"
var collection_num = 100;
for (var i = 0; i < collection_num; i++){
var coll_name = prefix + i.toString();
db.createCollection(coll_name)
}
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
print("create:" + collections[i])
}
(3)对分片键设置hash索引。
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
db.getCollection(collections[i]).createIndex({ appid: "hashed" })
print("create index for:" + collections[i])
}
(4)对集合开启分片(当前数据库执行即可)
target_db = "db_imsdk_roaming"
db.runCommand({enableSharding: target_db})
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
var collection = target_db + "." + collections[i]
db.adminCommand( { shardCollection: collection, key: {appid: "hashed"}})
print("sharding for:", collection)
}
(5)为提升查询性能设置{appid:1,index_name:1,seq:1}三字段复合索引
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
db.getCollection(collections[i]).createIndex({appid:1,index_name:1,seq:1})
print("create index for:" + collections[i])
}
修改分片策略同时对数据进行统一的字段改名、字段值修改、删除冗余字段等操作:
整体步骤如下:
1.把当前db的每个集合的数据都拷贝到一个对应的临时集合中.例如,临时集合命名规则为"老集合_tmp";
var temp_coll_postfix = "_tmp"
var collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
//if(collections[i] == "collection_99"){
//定制逻辑结合实际看要不要
if (collections[i].substring(0,5) == "coll_"){
continue;
}
var target_collection = collections[i] + temp_coll_postfix
db.getCollection(collections[i]).find().forEach(function(x){db.getCollection(target_collection).insert(x);})
print("copy data from", collections[i] ," to ", target_collection)
//}
}
2、如果要对数据进行遍历调整的话参照这里,调整有:
var collections=db.getCollectionNames()
var index = 0;
for (var i=0; i < collections.length; i++){
var msg_num =0;
index = i;
//if (collections[i] == "collection_1_tmp"){
// continue;
//}
var coll_msg_num = db.getCollection(collections[index]).count();
//if (collections[i].substring(collections[i].length-4, collections[i].length) == "_tmp" && collections[i]=="collection_99_tmp"){
if (collections[i].substring(collections[i].length-4, collections[i].length) == "_tmp"){
var operation = function(doc){
msg_num = msg_num + 1;
print("msg_num:", msg_num)
if (msg_num > coll_msg_num){
print("msg_num > coll_msg_num", msg_num, coll_msg_num)
return;
}
raw_id = doc._id;
//print("raw_id:",doc._id);
if (raw_id <= 20){
new_id = raw_id;
}else{
//①按照如下规则给_id赋新值
new_id = "cc_" + doc.appid.toString() + "_" + raw_id.slice(20); //不要前缀字串
}
doc._id = new_id;
//print("new_id:",new_id)
//②新增了一个key字段,字段值如下;用以替代原来的index_name字段
new_key = "cc_" + doc.appid.toString() + doc.index_name.slice(19);
doc.key = new_key;
//③新增了一个time字段,用以替换原来的systime字段
doc.time = doc.systime;
//④新增了一个type字段,用以替换原来的relation_type字段
doc.type = doc.relation_type;
//删除老数据
db.getCollection(collections[index]).remove({"_id":raw_id});
//插入新的doc
db.getCollection(collections[index]).insert(doc);
//⑤对新插入的目标数据删除被替换或想要删除的index_name、systime、roaming_seq、send_time、relation_type等字段
db.getCollection(collections[index]).update({"_id":new_id},{"$unset":{"index_name":1,"systime":1,"roaming_seq":1,"send_time":1,"relation_type":1}});
}
msg_num = 0;
db.getCollection(collections[index]).find().forEach(operation)
print(collections[index], " done! msg_num[", msg_num, "],count[",coll_msg_num,"]")
i=index
}
}
判断是否执行完了
db.collection_99_tmp.find({index_name:{"$exists":true}})
db.collection_1_tmp.find({"key":{"$exists":false}})
2.1.清空(删除)老集合
注:如果复用老集合名的话需要,否则可以放到最后做
3.创建要承载数据的新集合,并设置想要的索引/分片;
注:这里相当于是把集合名称统一改了,如果想保持一致的话就把原承载数据集合删掉这里重建就行了
var prefix = "coll_"
var collection_num = 100;
for (var i = 0; i < collection_num; i++){
var coll_name = prefix + i.toString();
db.createCollection(coll_name)
}
4.对新建的集合设置key字段的hash索引
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
if (collections[i].substring(0,5) == "coll_"){
db.getCollection(collections[i]).createIndex({ key: "hashed" })
print("create index for:" + collections[i])
}
}
5.对新建的集合设置运行分片,分片策略为key字段的hash分片
target_db = "db_imsdk_roaming"
db.runCommand({enableSharding: target_db})
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
if (collections[i].substring(0,5) == "coll_"){
var collection = target_db + "." + collections[i]
db.adminCommand( { shardCollection: collection, key: {key: "hashed"}})
print("sharding for:", collection)
}
}
db.coll_1.stats().sharded //验证分片是否成功
6.创建用于提升搜索性能的复合索引{key:1,seq:1}
collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
if (collections[i].substring(0,5) == "coll_"){
db.getCollection(collections[i]).createIndex({key:1,seq:1})
print("create index for:" + collections[i])
}
}
7.把数据从临时存储集合存放挪到“新集合”
var collections=db.getCollectionNames()
for (var i=0; i < collections.length; i++){
if (collections[i].substring(collections[i].length-4,collections[i].length) == "_tmp"){ //ok
var target_collection = "coll_" + collections[i].substring(11, collections[i].length-4)
print("collections[i]:",collections[i],"target_collection:",target_collection)
db.getCollection(collections[i]).find().forEach(function(x){db.getCollection(target_collection).insert(x);})
print("copy data from", collections[i] ," to ", target_collection)
}
}
8.确认无误后再把原集合和临时集合删掉
for (var i=0; i < collections.length; i++){
if (collections[i].substring(0,11) == "collection_"){
db.getCollection(collections[i]).drop()
print("drop:" + collections[i])
}
}
print("success toulianghuanzhu!")
\