其他平台安装教程可参考官网:https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-ubuntu/
cat /etc/lsb-release
sudo apt-get install libcurl4 libgssapi-krb5-2 libldap-2.5-0 libwrap0 libsasl2-2 libsasl2-modules libsasl2-modules-gssapi-mit openssl liblzma5 gnupg curl
curl -fsSL https://pgp.mongodb.com/server-7.0.asc | \
sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg \
--dearmor
/etc/apt/sources.list.d/mongodb-org-7.0.list
列表文件echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
配置 /etc/mongod.conf
:
bindIp: 0.0.0.0
fork: true
# mongod.conf
# for documentation of all options, see:
# http://docs.mongodb.org/manual/reference/configuration-options/
# Where and how to store data.
storage:
dbPath: /var/lib/mongodb
# engine:
# wiredTiger:
# where to write logging data.
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log
# network interfaces
net:
port: 27017
bindIp: 0.0.0.0
# how the process runs
processManagement:
fork: true
timeZoneInfo: /usr/share/zoneinfo
#security:
#operationProfiling:
#replication:
#sharding:
## Enterprise-Only Options:
#auditLog:
sudo mongod -f /etc/mongod.conf
执行后结果如下:
about to fork child process, waiting until server is ready for connections.
forked process: 361
child process started successfully, parent exiting
ps -ef | grep mongod
:可以看到有mongod进程在后台运行mongosh
:mongodb 客户端连接工具(安装时自带)成功连入使用:
在 MongoDB 中,root 账号是具有最高权限的账号,可以执行所有操作。
use admin
db.createUser({user:'root', pwd:'root',roles:['root']})
我们需要开启 MongoDB 的认证功能,以确保只有经过认证的用户才能访问数据库。
/etc/mongod.conf
在启动配置文件中,添加以下配置:
security:
authorization: enabled
重启 MongoDB 服务,认证功能才会生效。
官方描述:Sending a KILL signal kill -9 will probably cause damage as mongod will not be able to cleanly exit. (In such a scenario, run the repairDatabase command.)
可以采用在 mongosh 连入数据库后,执行下述指令来友好关闭服务进程。
use admin
db.shutdownServer()
在MongoDB中,每个数据库都有自己的权限系统,可以为每个数据库创建不同的账号并赋予不同的角色。
db.createUser({user: 'cauchy', pwd: 'root', roles: [{ role: 'readWrite', db: 'test'}]})
readWrite
: https://www.mongodb.com/docs/manual/reference/built-in-roles/#mongodb-authrole-readWrite
roles 可参考:https://www.mongodb.com/docs/manual/reference/built-in-roles/
执行下述指令,查看当前数据库系统中的所有用户信息:
use admin
db.system.users.find()
在 test 数据库中,验证当前 cauchy
用户权限
use test
db.auth('cauchy', 'root')
use test
db.dropUser('cauchy')
本节主要讲解在 Skynet 框架中一些常用的 API,以及如何使用相应的 API 来执行 MongoDB 的 CRUD。
前置变量、方法:
host = "127.0.0.1"
port = 27017
username = "cauchy"
password = "root"
authdb = "test"
db_name = "test"
function create_client()
return mongo.client({
host = host,
port = port,
username = username,
password = password,
authdb = authdb
})
end
数据库连接认证
db:auth(user, pwd)
测试代码:
function test_auth()
local ok, err, ret
local c = mongo.client({
host = host,
port = port,
}
)
local db = c[db_name]
db:auth(username, password)
db.testcol:dropIndex("*")
db.testcol:drop()
ok, err, ret = db.testcol:safe_insert({test_key = 1});
assert(ok and ret and ret.n == 1, err)
end
如果注释掉认证:-- db:auth(username, password)
,则会报错提示需要权限认证。
创建索引
db.collection:ensureIndex({ key1 }, { option })
源码 mongo.lua 中,这个 API 实际上就是创建索引:
mongo_collection.ensureIndex = mongo_collection.createIndex
测试代码:
function test_insert_with_index()
local ok, err, ret
local c = create_client()
local db = c[db_name]
db.testcol:dropIndex("*")
db.testcol:drop()
db.testcol:ensureIndex({test_key = 1}, {unique = true, name = "test_key_index"})
--[[ mongosh
db.testcol.getIndexes()
]]
ok, err, ret = db.testcol:safe_insert({test_key = 1})
assert(ok and ret and ret.n == 1, err)
ok, err, ret = db.testcol:safe_insert({test_key = 1})
assert(ok == false and string.find(err, "duplicate key error"))
end
查找符合条件的文档,find 查找所有,findOne 查找第一条
db.collection:find(query, projection)
、db.collection:findOne(query, projection)
projection
:查询结果的投影源码:(next
、find
、findOne
)
local mongo_cursor = {}
local cursor_meta = {
__index = mongo_cursor,
}
------------------------------------------------------------------------------------------------------
function mongo_cursor:next()
if self.__ptr == nil then
error "Call hasNext first"
end
local r = self.__document[self.__ptr]
self.__ptr = self.__ptr + 1
if self.__ptr > #self.__document then
self.__ptr = nil
end
return r
end
------------------------------------------------------------------------------------------------------
function mongo_collection:findOne(query, projection)
local cursor = self:find(query, projection)
if cursor:hasNext() then
return cursor:next()
end
return nil
end
------------------------------------------------------------------------------------------------------
function mongo_collection:find(query, projection)
return setmetatable( {
__collection = self,
__query = query and bson_encode(query) or empty_bson,
__projection = projection and bson_encode(projection) or empty_bson,
__ptr = nil,
__data = nil,
__cursor = nil,
__document = {},
__flags = 0,
__skip = 0,
__limit = 0,
__sort = empty_bson,
} , cursor_meta)
end
简单查看上述源码,可以发现 cursor:next
返回 __document
中的内容,即为实际找到的文档内容。
find
返回一张表,表中有很多字段(__collection
、__cursor
、__document
等),这张表的元表是 cursor_meta
,而 cursor_meta
的属性 __index
是表 mongo_cursor
。所以在用 find
查找符合条件的文档时,返回的值应该使用 next
方法去一个个遍历获取所有的返回结果,即为 __document
中的内容。
findOne
直接就是返回查找到的第一个文档,如上述 return cursor:next()
。
插入一条文档
db.collection:insert(doc)
、db.collection:safe_insert(doc)
源码:
function mongo_collection:insert(doc)
if doc._id == nil then
doc._id = bson.objectid()
end
self.database:send_command("insert", self.name, "documents", {bson_encode(doc)})
end
------------------------------------------------------------------------------------------------------
function mongo_collection:safe_insert(doc)
local r = self.database:runCommand("insert", self.name, "documents", {bson_encode(doc)})
return werror(r)
end
如上述源码,safe_insert
会返回一些相关信息(由 werror
返回),而 insert
没有任何返回值。
删除符合条件的一条或多条文档
db.collection:delete(query, single)
、db.collection:safe_delete(query, single)
single
:删除条数(即limit
限制)源码:
function mongo_collection:delete(query, single)
self.database:runCommand("delete", self.name, "deletes", {bson_encode({
q = query,
limit = single and 1 or 0,
})})
end
------------------------------------------------------------------------------------------------------
function mongo_collection:safe_delete(query, single)
local r = self.database:runCommand("delete", self.name, "deletes", {bson_encode({
q = query,
limit = single and 1 or 0,
})})
return werror(r)
end
如上述源码,safe_delete
会返回一些相关信息(由 werror
返回),而 delete
没有任何返回值。
测试代码:
function test_find_and_remove()
local ok, err, ret
local c = create_client()
local db = c[db_name]
db.testcol:dropIndex("*")
db.testcol:drop()
local cursor = db.testcol:find()
assert(cursor:hasNext() == false)
db.testcol:ensureIndex({test_key = 1}, {test_key2 = -1}, {unique = true, name = "test_index"})
ok, err, ret = db.testcol:safe_insert({test_key = 1, test_key2 = 1})
assert(ok and ret and ret.n == 1, err)
cursor = db.testcol:find()
assert(cursor:hasNext() == true)
local v = cursor:next()
assert(v)
assert(v.test_key == 1)
ok, err, ret = db.testcol:safe_insert({test_key = 1, test_key2 = 2})
assert(ok and ret and ret.n == 1, err)
ok, err, ret = db.testcol:safe_insert({test_key = 2, test_key2 = 3})
assert(ok and ret and ret.n == 1, err)
ret = db.testcol:findOne({test_key2 = 1})
assert(ret and ret.test_key2 == 1, err)
ret = db.testcol:find({test_key2 = {['$gt'] = 0}}):sort({test_key = 1}, {test_key2 = -1}):skip(1):limit(1)
--[[ mongosh
db.testcol.find({test_key2: {$gt: 0}}).sort({test_key: 1}, {test_key2: -1}).skip(1).limit(1)
]]
assert(ret:count() == 3)
assert(ret:count(true) == 1)
if ret:hasNext() then
ret = ret:next()
end
assert(ret and ret.test_key2 == 1)
db.testcol:delete({test_key = 1})
db.testcol:delete({test_key = 2})
ret = db.testcol:findOne({test_key = 1})
assert(ret == nil)
end
上述代码中有调用了sort
、 skip
、limit
,如源码所示,即为 find
返回的表中,__sort
、__skip
、__limit
字段附上了值,而不是直接对数据执行排序,跳转、约束等操作。
count
比较特殊,会实际执行一次指令 runCommand
,需要参数 with_limit_and_skip
。如果参数为 nil
或 false
,则执行会忽略 skip
和 limit
,反之,会加上。源码(sort
、 skip
、limit
、count
):
-- cursor:sort { key = 1 } or cursor:sort( {key1 = 1}, {key2 = -1})
function mongo_cursor:sort(key, key_v, ...)
if key_v then
local key_list = unfold({}, key, key_v , ...)
key = bson_encode_order(table.unpack(key_list))
end
self.__sort = key
return self
end
function mongo_cursor:skip(amount)
self.__skip = amount
return self
end
function mongo_cursor:limit(amount)
self.__limit = amount
return self
end
function mongo_cursor:count(with_limit_and_skip)
local cmd = {
'count', self.__collection.name,
'query', self.__query,
}
if with_limit_and_skip then
local len = #cmd
cmd[len+1] = 'limit'
cmd[len+2] = self.__limit
cmd[len+3] = 'skip'
cmd[len+4] = self.__skip
end
local ret = self.__collection.database:runCommand(table.unpack(cmd))
assert(ret and ret.ok == 1)
return ret.n
end
更新一条文档
db.collection:update(query,update,upsert,multi)
、db.collection:safe_update(query,update,upsert,multi)
upsert
: 默认是false。如果不存在 query 对应条件的文档,是 true 则插入一条新文档,false 则不插入。multi
: 默认是 false,只更新找到的第一条记录。是 true,就把按条件查出来多条记录全部更新。示例代码:
function test_update()
local ok, err, r
local c = create_client()
local db = c[db_name]
db.testcol:dropIndex("*")
db.testcol:drop()
db.testcol:safe_insert({test_key = 1, test_key2 = 1})
db.testcol:safe_insert({test_key = 1, test_key2 = 2})
-- ok, err, r = db.testcol:safe_update({test_key2 = 2}, { ['$set'] = {test_key = 2} }, true, false)
-- assert(ok and r and r.n == 1)
ok, err, r = db.testcol:safe_update({test_key = 1}, { ['$set'] = {test_key2 = 3} }, true, true)
assert(ok and r and r.n == 2)
end
聚合,将来自多个 doc 的 value 组合在一起,并通过对分组数据进行各种操作处理,返回计算后的数据结果,主要用于处理数据(例如统计平均值,求和等)。
db.collection:aggregate({ { ["$project"] = {tags = 1} } }, {cursor={}})
@param pipeline: array
@param options: map
测试代码:
function test_runcommand()
local ok, err, ret
local c = create_client()
local db = c[db_name]
db.testcol:dropIndex("*")
db.testcol:drop()
ok, err, ret = db.testcol:safe_insert({test_key = 1, test_key2 = 1})
assert(ok and ret and ret.n == 1, err)
ok, err, ret = db.testcol:safe_insert({test_key = 1, test_key2 = 2})
assert(ok and ret and ret.n == 1, err)
ok, err, ret = db.testcol:safe_insert({test_key = 2, test_key2 = 3})
assert(ok and ret and ret.n == 1, err)
local pipeline = {
{
["$group"] = {
_id = mongo.null,
test_key_total = { ["$sum"] = "$test_key"},
test_key2_total = { ["$sum"] = "$test_key2" },
}
}
}
ret = db:runCommand("aggregate", "testcol", "pipeline", pipeline, "cursor", {})
assert(ret and ret.cursor.firstBatch[1].test_key_total == 4)
assert(ret and ret.cursor.firstBatch[1].test_key2_total == 6)
local res = db.testcol:aggregate(pipeline, {cursor={}})
assert(res and res.cursor.firstBatch[1].test_key_total == 4)
assert(res and res.cursor.firstBatch[1].test_key2_total == 6)
end
官方的 aggregate
接口手册:https://www.mongodb.com/docs/manual/reference/command/aggregate/
批量插入和删除
db.collection:safe_batch_insert(docs)
、db.collection:safe_batch_delete(docs)
测试代码:
function test_batch_insert_delete()
local ok, err, ret
local c = create_client()
local db = c[db_name]
db.testcol:dropIndex("*")
db.testcol:drop()
local insert_docs = {}
local insert_length = 10
for i = 1, insert_length do
table.insert(insert_docs, {test_key = i})
end
db.testcol:safe_batch_insert(insert_docs)
ret = db.testcol:find()
assert(insert_length == ret:count(), "test safe batch insert failed")
local delete_docs = {}
local delete_length = 5
for i = 1, delete_length do
table.insert(delete_docs, {test_key = i})
end
db.testcol:safe_batch_delete(delete_docs)
assert(ret:count() == insert_length - delete_length, "test safe batch delete failed")
end
更多的 skynet.db.mongo 模块用法可以去源码阅读查看。
附上 MongoDB 7.0 Manual