Mongo gem是MongoDB官方为Ruby语言提供的驱动程序,它封装了与数据库通信的底层协议,让开发者可以用纯Ruby代码完成文档的增删改查。相比通过ORM中间层操作数据库,直接使用驱动程序能获得更细粒度的控制能力,也更适合脚本任务和性能敏感的场景。这篇文章从安装开始,逐步讲解mongo gem的核心用法。

安装与建立连接
mongo gem通过RubyGems发布,安装非常简单。在终端执行gem install mongo即可,如果是在Rails项目中使用,则在Gemfile中加入gem 'mongo', '~> 2.20'后运行bundle安装。建议锁定2.x主版本,因为1.x版本的API与2.x完全不兼容,网上不少旧教程基于1.x编写,直接照搬会报错,这是初学者最常踩的坑。
安装完成后,建立连接只需要几行代码。驱动程序的核心入口是Mongo::Client,它管理着连接池并充当数据库操作的起点:
require 'mongo'
# 连接本地默认端口27017的test数据库
client = Mongo::Client.new(['127.0.0.1:27017'], database: 'test')
# 也可以通过URI方式连接
client = Mongo::Client.new('mongodb://127.0.0.1:27017/test')
# 获取集合对象
collection = client[:users]
puts collection.count_documents
client.close连接参数中比较重要的有database指定数据库名,max_pool_size控制连接池上限(默认5),connect_timeout设置连接超时时间。如果数据库开启了认证,需要在URI中带上用户名密码,例如mongodb://user:pass@127.0.0.1:27017/test,密码中的特殊字符记得做URL编码,否则解析会失败。
文档的增删改查操作
mongo gem的CRUD接口设计得非常Ruby化,方法名与MongoDB本身的操作语义一一对应。插入文档使用insert_one或insert_many,传入的参数就是普通的Ruby哈希:
collection.insert_one(name: '张三', age: 28, tags: ['ruby', 'mongodb'])
# 批量插入,返回结果包含生成的_id列表
result = collection.insert_many([
{ name: '李四', age: 35 },
{ name: '王五', age: 22 }
])
puts result.inserted_ids查询方面,find方法接受一个条件哈希,支持MongoDB所有的查询操作符,写法是把操作符作为字符串键传入。常用操作符包括$gt(大于)、$lt(小于)、$in(包含于集合)、$regex(正则匹配)等。查询结果是一个惰性的游标对象,只有真正迭代时才会从服务器取数据,因此在结果集上链式调用sort、limit、skip都不会立即产生网络请求。
# 查询年龄大于25的用户,按年龄倒序,取前10条
collection.find(age: { '$gt' => 25 })
.sort(age: -1)
.limit(10)
.each { |doc| puts doc['name'] }
# 使用$in和$regex组合查询
collection.find('tags' => { '$in' => ['ruby'] },
'name' => { '$regex' => /张/ }).to_a
# 更新文档,$set只修改指定字段
collection.update_one({ name: '张三' },
{ '$set' => { age: 29 } })
# 删除操作
collection.delete_one(name: '王五')
collection.delete_many(age: { '$lt' => 18 })需要注意一点,更新时如果漏写$set操作符,整个文档会被替换成新的哈希,原有字段会丢失,这是实际开发中出现过很多次的数据事故来源。另外find返回的文档键是字符串类型,访问时用doc['name']而不是doc[:name]。
索引管理与生产环境配置技巧
数据量上来之后,索引就成了绕不开的话题。mongo gem提供了完整的索引管理接口,可以在代码里完成索引的创建、查看和删除:
# 为name字段创建升序索引
collection.indexes.create_one({ name: 1 }, unique: true)
# 创建复合索引
collection.indexes.create_one({ age: -1, created_at: 1 })
# 查看现有索引
collection.indexes.each { |idx| puts idx['name'] }
# 删除索引
collection.indexes.drop_one('age_-1_created_at_1')创建索引时加上unique: true可以保证字段唯一性,用它实现业务上的去重约束比在应用层判断更可靠。background选项在大数据集上也有价值,可以让索引创建过程不阻塞其他读写操作。
生产环境连接MongoDB通常面临的是副本集或分片集群。连接副本集时,只需在URI中列出多个节点并加上replicaSet参数,驱动会自动发现所有成员并在主节点故障时完成切换:
client = Mongo::Client.new( ['node1.ipipp.com:27017', 'node2.ipipp.com:27017'], database: 'prod', replica_set: 'rs0', max_pool_size: 20, min_pool_size: 5, wait_queue_timeout: 5, server_selection_timeout: 10 )
连接池的配置直接影响并发能力。Rails或Sidekiq这类多线程环境下,max_pool_size建议根据线程数调整,默认的5在高并发下会成为瓶颈。读偏好read参数可以设置为:secondary_preferred,让读请求优先分散到从节点,减轻主库压力。写关注write参数则决定写入确认级别,对一致性要求高的场景用wmode: :majority,追求吞吐量可以用默认值。
最后提醒两个实用细节:一是Client对象是线程安全的,应该在应用启动时创建一次全局复用,而不是每次请求都新建连接,反复建连的开销相当可观;二是调试查询问题时可以用Mongo::Logger.logger.level = ::Logger::DEBUG打开驱动日志,能看到实际发送到服务器的命令,排查问题效率会高很多。掌握这些用法之后,mongo gem基本能覆盖日常开发中的全部MongoDB操作需求。