在mongodb服务器上存储和执行 js 函数 - 存储过程

虽然官方不推荐使用将业务逻辑存储在数据库中,并且提示在 mongodb 中执行 javascript 存在性能限制。

但实际上,将 javascript 函数存储在 mongodb 中执行,还是非常有必要的,更方便,许多场景下性能会更好(在执行大量查询处理时不需要将数据传回客户端引擎)。

在目前的版本中,我们任然可以将 javascript 函数存储在 mongodb 内置的一个特殊集合 db.system.js 中,然后这些变量就可以在任何 mongodb 的 javascript 上下文中调用,包括:db.eval()、$where子句、mapReduce。

自从 mongodb 3.0 版本及之后,执行 db.eval() 会得到一个 Warning: db.eval is deprecated,意味着该 api 在未来即将被删除。但目前社区还没有提供类似的替代方案,并且该 api 在最新的 mongodb 4.0 版本中任然可以正常使用。

相关讨论可以参考: SERVER-17453 warn that db.eval() / eval command is deprecated - MongoDB

如何使用
首先登陆 mongo shell

$ mongo

use testdb
新增一个函数
db.system.js.save({
_id: 'doSomeThing', // 存储过程名称
value(args) { // 过程体
return db.courses.count();

},
description: 'just a test.', // 描述,非必须
});
或者

db.system.js.insert({
_id: 'doSomeThing', // 存储过程名称
value(args) { // 过程体

return db.courses.count();

},
description: 'just a test.', // 描述,非必须
});
修改已经存在的函数
db.system.js.update(
{

_id: 'doSomeThing', // 存储过程名称

},
{

value() { // 过程体
  return db.courses.count();
},

}
);
注意,新增时的参数是一个 object,而修改(update)时的参数类似 db.collect.update(, ),两个参数均为 object。
执行
// 使用 eval 命令
db.runCommand({

eval:"doSomeThing(args)",
args: null,
nolock: true

});

// 或者使用 helper 方法 db.eval
db.eval("doSomeThing(args)");
// 也可以立即执行一个函数
db.eval(function(arg1,arg2){}, arg1, arg2);
查看已经存储的 javascript 函数
db.system.js.find();
注意事项
写锁定
需要注意的是,执行 db.eval() 时,mongodb 采用了全局写锁定,因此,执行 db.eval() 时将阻止对数据库的所有其他读写操作,意味着将阻塞其他任务,直到 db.eval() 执行结束,这在执行长时间任务时有些麻烦。

使用 eval 命令也同样存在全局写锁定,但可以设置 nolock 选项避免。db.eval() 和 eval 命令的不同之处只是不支持 nolock 选项。

javascript 解释器
一般来说,我们使用 mongo 命令登陆 shell,此时使用的是 mongoshell 的 javascript 解释器,不同版本的 shell 所支持的语法并不同。 而论坛使用 eval 命令或者 db.eval() 时,所使用的 javascript 解释器为服务器的解释器,这和 mongoshell 有所区别。

此外,在 mongodb 3.2 中,mongoshell 和服务器端的 javascript 引擎由 V8 更改为 SpiderMonkey 。你可以在 shell 中执行 db.serverBuildInfo() ,查看结果中的 javascriptEngine 字段确认当前使用的 javascript 引擎。

SpiderMonkey 增加了对 ES6 的支持,包括箭头函数、解构等。但仅此而已,实测 mongodb 4.0 版本中仍然不支持这样的语法: const obj={...obj1}; 。

所以,在编写 javascript 函数时,需要注意 mongodb 的版本差异和内置的 javascript 引擎类型,避免语法错误。

访问限制
自 mongodb 2.6 版本之后,如果启用了密码登陆,则必须有权访问所有资源上的所有操作才能运行 eval。

注:这样会导致安全风险。
如果你确定要这么做,可以创建一个拥有 anyAction 和 anyResource 的角色,并添加一个对应的用户。

use admin
db.createRole({
role: 'sysadmin',
roles: [],
privileges: [

{
  resource: { anyResource: true },
  actions: [ 'anyAction' ],
},

],
});

use dbtest
db.createUser({
user: 'root',
pwd: 'root',
roles: [

{ role: 'sysadmin', db: 'admin' },

],
});
最后
除了以上介绍的方法以外,mongodb 还支持其他直接执行 javascript 的方案,例如 mongoshell 中所支持的 load() 方法可以直接加载一个 js 文件并执行。

鉴于篇幅所限,更多介绍可以参考官方文档:Server-side JavaScript — MongoDB Manual

上一篇:可以通过培训“速成速转”


下一篇:kotlin极简教程 高阶函数 函数式编程章节节选