MongoDB系列教程-第三章:PyMongo操作MongoDB数据库(1)—— 连接、基本CRUD操作
📖 第三章:PyMongo操作MongoDB数据库(1)—— 连接、基本CRUD操作
文章目录
- 📖 第三章:PyMongo操作MongoDB数据库(1)—— 连接、基本CRUD操作
- 3.1 什么是 PyMongo
- 3.2 安装PyMongo
- 3.3 连接到MongoDB
- 3.3.1 基本连接
- 3.3.2 连接选项(关键参数与配置)
- 3.3.3 连接后的操作
- 3.3.4 连接验证与错误处理
- 3.4 基本CRUD操作
- 3.4.1 插入数据 (Create)
- 3.4.1.1 插入单个文档
- 3.4.1.2 插入多个文档
- 3.4.2 查询数据 (Read)
- 3.4.2.1 查询所有文档
- 3.4.2.2 条件查询
- 3.4.2.3 查询操作符
- 3.4.2.4 查询选项
- 3.4.3 更新数据 (Update)
- 3.4.3.1 更新单个文档
- 3.4.3.2 更新多个文档
- 3.4.3.3 更新操作符
- 3.4.4 删除数据 (Delete)
- 3.4.4.1 删除单个文档
- 3.4.4.2 删除多个文档
- 3.4.4.3 删除集合
通过前面两章节的学习(MongoDB系列教程-第一章:MongoDB简介、安装 、概念解析、用户管理、连接、实际应用示例、MongoDB系列教程-第二章:MongoDB数据库概念和特点、数据库操作、集合操作、文档操作、规范及常见问题解决、实际应用示例),我们对MongoDB的各种基础概念(数据库、文档、集合、数据类型)和Shell操作都有一定的熟悉。
基于以上基础,可以在实际项目工程中使用PyMongo进行数据库操作了。
本章围绕PyMongo的核心功能,结合代码示例进行讲解。关于PyMongo的官方文档,大家可以看这里:https://www.mongodb.com/zh-cn/docs/languages/python/pymongo-driver/current/
3.1 什么是 PyMongo
PyMongo是MongoDB官方提供的Python驱动程序,能够在Python程序中连接和操作MongoDB数据库。MongoDB 是目前最流行的 NoSQL 数据库之一。
3.2 安装PyMongo
# 创建项目目录
mkdir pymongo-project
cd pymongo-project# 安装PyMongo
pip install pymongo# 安装依赖
pip install dnspython # 用于DNS解析
3.3 连接到MongoDB
3.3.1 基本连接
from pymongo import MongoClient# 连接到本地MongoDB
client = MongoClient('mongodb://localhost:27017/')# 连接到远程MongoDB
client = MongoClient('mongodb://username:password@host:port/')# 连接到MongoDB Atlas
client = MongoClient('mongodb+srv://username:password@cluster.mongodb.net/')
pymongo 库通过 MongoClient 类创建与 MongoDB 服务的连接,核心语法为:
from pymongo import MongoClient# 实例化客户端(建立连接)
client = MongoClient(连接字符串)
- 连接字符串:描述 MongoDB 服务的地址、端口、认证信息等,是连接的核心参数。
- 客户端对象(client):后续所有数据库操作(如切换数据库、操作集合)都通过该对象实现。
基本连接可以按照使用场景的不同,分为三种,即:1、连接本地无认证的 MongoDB;2、连接需要认证的 MongoDB;3、连接 MongoDB Atlas(云服务)。
- 连接本地无认证的 MongoDB(开发环境常用)
# 连接本地默认服务(地址 127.0.0.1,端口 27017)
client = MongoClient('mongodb://localhost:27017/')# 简化写法(默认就是 localhost:27017)
client = MongoClient() # 等效于上面的写法
- 适用场景:本地开发、测试环境,MongoDB 未启用身份验证(
--auth
未开启)。 - 注意:需确保 mongod 服务已启动,且端口与连接字符串一致。
- 连接需要认证的 MongoDB(生产环境常用)
# 连接远程或本地带用户名密码的服务
client = MongoClient('mongodb://username:password@host:port/')
参数解析:
username/password
:数据库用户的账号密码(需提前在 MongoDB 中创建,且有对应权限)。host
:服务器 IP 或域名(如 192.168.1.100、mongodb.example.com)。port
:服务端口(默认 27017,若未修改可省略)。
示例:
连接 10.0.0.5 服务器上的 MongoDB(端口 27019),用户 admin,密码 123456:
client = MongoClient('mongodb://admin:123456@10.0.0.5:27019/')
- 连接 MongoDB Atlas(云服务)
# 连接 MongoDB Atlas 集群
client = MongoClient('mongodb+srv://username:password@cluster.mongodb.net/')
- 特点:
mongodb+srv
协议是 Atlas 专用,会自动解析集群节点信息(无需手动指定多个节点)。 - 获取连接字符串:在 Atlas 控制台的 “Connect”→“Connect your application” 中生成,需替换 username、password 和集群名。
- 注意:需将本地 IP 加入 Atlas 的 IP 白名单(或允许所有 IP:0.0.0.0/0,测试用),否则连接会被拒绝。
3.3.2 连接选项(关键参数与配置)
from pymongo import MongoClient# 带选项的连接
client = MongoClient('mongodb://localhost:27017/',maxPoolSize=50, # 连接池最大连接数minPoolSize=10, # 连接池最小连接数maxIdleTimeMS=30000, # 连接最大空闲时间serverSelectionTimeoutMS=5000, # 服务器选择超时connectTimeoutMS=2000, # 连接超时socketTimeoutMS=2000 # Socket超时
)
除了基础连接字符串,MongoClient
还支持通过参数配置连接细节(两种方式等效):
- 方式 1:通过连接字符串参数
通过参数拼接到连接字符串,如下:
# 超时设置、最大连接数等(参数拼接到连接字符串)
client = MongoClient('mongodb://localhost:27017/?connectTimeoutMS=5000&maxPoolSize=10'
)
- 方式 2:通过关键字参数
# 等效于上面的连接字符串配置
client = MongoClient('mongodb://localhost:27017/',connectTimeoutMS=5000, # 连接超时(毫秒),避免无限等待maxPoolSize=10, # 连接池最大连接数,控制资源占用serverSelectionTimeoutMS=3000 # 服务器选择超时(毫秒)
)
参数解释:
connectTimeoutMS
:连接超时(建议设为 3-5 秒,默认无限制)。serverSelectionTimeoutMS
:选择服务器超时(适用于集群,建议 3-5 秒)。maxPoolSize
:连接池大小(默认 100,根据并发量调整,避免资源浪费)。retryWrites
:是否自动重试写入操作(Atlas 等集群环境建议设为 True)。
3.3.3 连接后的操作
连接之后,通过 client
对象可访问数据库和集合:
# 获取数据库
db = client['myapp'] # 或者 client.myapp# 获取集合
collection = db['users'] # 或者 db.users# 完整的操作链
users = client['myapp']['users']
语法如下:
# 切换到目标数据库(不存在则自动创建)
db = client['数据库名'] # 或 client.数据库名(如 client.user_db)# 访问集合(不存在则在首次插入数据时自动创建)
collection = db['集合名'] # 或 db.集合名(如 db.users)
3.3.4 连接验证与错误处理
连接可能因网络、认证、服务未启动等原因失败,需添加验证和异常处理:
from pymongo.errors import ConnectionFailure, PyMongoErrortry:# 验证连接(发送 ping 命令)client.admin.command('ping')print("连接成功!")
except ConnectionFailure:print("连接失败:MongoDB 服务未启动或地址/端口错误")
except PyMongoError as e:print(f"其他错误:{str(e)}")
完整demo:
from pymongo import MongoClient
from pymongo.errors import ConnectionFailure, PyMongoErrordef connect_to_mongodb(uri):"""连接MongoDB并验证连接状态参数:uri: MongoDB连接字符串返回:成功返回客户端对象,失败返回None"""try:# 创建MongoDB客户端client = MongoClient(uri)# 验证连接(向admin数据库发送ping命令)client.admin.command('ping')print("成功连接到MongoDB服务")return clientexcept ConnectionFailure:print("连接失败:MongoDB服务未启动,或地址/端口不正确")return Noneexcept PyMongoError as e:# 捕获其他MongoDB相关错误(如认证失败、权限问题等)print(f"MongoDB操作错误:{str(e)}")return Noneexcept Exception as e:# 捕获其他意外错误(如网络问题等)print(f"发生未知错误:{str(e)}")return None# 示例使用
if __name__ == "__main__":# 根据实际环境修改连接字符串# 本地无认证示例mongo_uri = "mongodb://localhost:27017/"# 带认证的远程连接示例(替换为实际信息)# mongo_uri = "mongodb://username:password@host:port/"# MongoDB Atlas连接示例(替换为实际信息)# mongo_uri = "mongodb+srv://username:password@cluster.mongodb.net/"# 连接数据库client = connect_to_mongodb(mongo_uri)# 连接成功后可进行后续操作if client:try:# 示例:查看数据库列表databases = client.list_database_names()print(f"可用数据库列表:{databases}")# 此处可添加其他数据库操作(如访问集合、插入数据等)finally:# 关闭连接client.close()print("已关闭MongoDB连接")
3.4 基本CRUD操作
3.4.1 插入数据 (Create)
3.4.1.1 插入单个文档
from pymongo import MongoClient
from datetime import datetimeclient = MongoClient('mongodb://localhost:27017/')
db = client['myapp']
users = db['users']# 插入单个文档
user = {"name": "张三","email": "zhangsan@example.com","age": 25,"created_at": datetime.now()
}result = users.insert_one(user)
print(f"插入的文档ID: {result.inserted_id}")
3.4.1.2 插入多个文档
# 插入多个文档
users_data = [{"name": "李四","email": "lisi@example.com","age": 30,"created_at": datetime.now()},{"name": "王五","email": "wangwu@example.com","age": 28,"created_at": datetime.now()}
]result = users.insert_many(users_data)
print(f"插入的文档ID列表: {result.inserted_ids}")
在使用 pymongo 进行文档插入(insert_one 和 insert_many)时,有以下需要注意:
1、_id 字段的自动生成与手动指定
- 自动生成:若插入的文档未包含 _id 字段,MongoDB 会自动为其生成一个唯一的 ObjectId(如示例中
result.inserted_id
返回的值),作为文档的唯一标识。 - 手动指定:若显式在文档中添加 _id 字段(如
{"_id": 1, "name": "张三"}
),则需确保其在集合中唯一,否则会抛出DuplicateKeyError
(主键冲突)。
# 手动指定 _id(需保证唯一)
user = {"_id": "user_001", "name": "张三"}
users.insert_one(user)
2、insert_one
与 insert_many
的返回值
insert_one
:返回InsertOneResult
对象,通过inserted_id
属性获取插入文档的 _id。insert_many
:返回InsertManyResult
对象,通过inserted_ids
属性获取所有插入文档的 _id 列表(顺序与插入数据一致)。
这两个对象还包含 acknowledged
属性(布尔值),表示插入操作是否被服务器确认(默认 True)。
3、批量插入的 ordered 参数
insert_many
有一个可选参数 ordered
(默认 True),控制批量插入的行为:
ordered=True
(默认):按顺序插入文档,若某条文档出错(如 _id 重复),则后续文档不再插入。ordered=False
:并行插入文档,忽略单条错误,继续插入后续文档(适合允许部分成功的场景)。
# 忽略单条错误,继续插入其他文档
result = users.insert_many(users_data, ordered=False)
3.4.2 查询数据 (Read)
3.4.2.1 查询所有文档
# 查询所有文档
all_users = users.find()
for user in all_users:print(user)# 转换为列表
users_list = list(users.find())
解释:
find()
方法返回的是一个游标对象(而非直接返回所有数据),通过迭代游标获取文档(适合内存友好,适合大数据量)。- 游标只能遍历一次,遍历结束后需重新调用
find()
获取新游标。 list(users.find())
会将所有结果加载到内存,适合小数据量场景,大数据量可能导致内存溢出。
3.4.2.2 条件查询
# 查询特定条件的文档
# 查询年龄大于25的用户
adult_users = users.find({"age": {"$gt": 25}})# 查询特定邮箱的用户
user = users.find_one({"email": "zhangsan@example.com"})# 复合条件查询
users_filtered = users.find({"age": {"$gte": 25, "$lte": 35},"name": {"$regex": "^张"}
})
解释:
find_one()
返回第一条匹配的文档(字典类型),无匹配时返回None
;find()
返回所有匹配文档的游标。- 条件参数为字典格式,
{字段: {操作符: 值}}
表示“对指定字段应用操作符筛选”。 - 复合条件默认是“逻辑与”(
$and
)关系,即同时满足所有条件。
3.4.2.3 查询操作符
# 比较操作符
users.find({"age": {"$gt": 25}}) # 大于($gt: greater than)
users.find({"age": {"$gte": 25}}) # 大于等于($gte: greater than or equal)
users.find({"age": {"$lt": 30}}) # 小于($lt: less than)
users.find({"age": {"$lte": 30}}) # 小于等于($lte: less than or equal)
users.find({"age": {"$ne": 25}}) # 不等于($ne: not equal)
users.find({"age": {"$in": [25, 30]}}) # 在列表中(匹配25或30)# 逻辑操作符
users.find({"$and": [{"age": {"$gte": 25}},{"age": {"$lte": 35}}]
}) # 同时满足两个条件(等价于 {"age": {"$gte":25, "$lte":35}})users.find({"$or": [{"name": "张三"},{"name": "李四"}]
}) # 满足任一条件# 正则表达式
users.find({"name": {"$regex": "^张"}}) # 以"张"开头(正则匹配)
users.find({"email": {"$regex": "@gmail\\.com$"}}) # 以@gmail.com结尾(注意转义特殊字符)
解释:
- 操作符均以
$
开头,是 MongoDB 语法的标志性特征。 $and
通常可省略(多个字段条件默认是and
关系),但字段内多条件需显式使用(如{"age": {"$gt":25, "$lt":30}}
)。- 正则表达式查询适合字符串模糊匹配,性能取决于是否有对应索引(无索引时会全表扫描)。
3.4.2.4 查询选项
# 限制返回字段(投影)
users.find({}, {"name": 1, "email": 1, "_id": 0})
# 解释:第二个参数为投影字典,1表示返回该字段,0表示不返回;_id默认返回,需显式设为0隐藏# 排序
users.find().sort("age", 1) # 升序(1表示升序)
users.find().sort("age", -1) # 降序(-1表示降序)
# 解释:多字段排序:.sort([("age", 1), ("name", -1)]) 先按age升序,再按name降序# 限制结果数量
users.find().limit(10) # 只返回前10条结果# 跳过结果(分页常用)
users.find().skip(5) # 跳过前5条结果# 组合使用(分页场景:获取第6-15条,按年龄降序)
users.find().sort("age", -1).limit(10).skip(5)
解释:
- 投影设置中,不能同时混合 1 和 0(除
_id
外),例如{"name":1, "age":0}
是错误的。 skip()
不适合大数据量分页(如skip(100000)
会效率低下),建议用范围查询替代(如{"_id": {"$gt": 上一页最后一个_id}}
)。- 方法调用顺序不影响结果(MongoDB 会自动优化执行顺序),但建议按逻辑顺序编写(筛选→排序→分页)。
3.4.3 更新数据 (Update)
3.4.3.1 更新单个文档
# 更新单个文档
result = users.update_one({"email": "zhangsan@example.com"},{"$set": {"age": 26}}
)
print(f"匹配的文档数: {result.matched_count}")
print(f"修改的文档数: {result.modified_count}")# 使用$inc增加数值
users.update_one({"email": "zhangsan@example.com"},{"$inc": {"age": 1}}
)# 使用$push添加添加数组元素
users.update_one({"email": "zhangsan@example.com"},{"$push": {"hobbies": "编程"}}
)
解释:
update_one()
仅更新第一条匹配条件的文档,即使有多个符合条件的文档。- 第一个参数是筛选条件(同查询语法),第二个参数是更新操作(必须使用更新操作符,如
$set
)。 - 返回结果包含
matched_count
(匹配的文档数量)和modified_count
(实际被修改的文档数量,若字段值未变化则为0)。 $inc
用于对数值型字段进行增减(示例中age: 1
表示年龄+1,-1
则表示-1)。$push
用于向数组字段添加元素(若字段不存在会自动创建数组)。
3.4.3.2 更新多个文档
# 更新多个文档
result = users.update_many({"age": {"$lt": 30}},{"$set": {"status": "young"}}
)
print(f"匹配的文档数: {result.matched_count}")
print(f"修改的文档数: {result.modified_count}")
解释:
update_many()
会更新所有匹配条件的文档,适用于批量更新场景。- 筛选条件和更新操作的语法与
update_one()
一致,但作用范围不同。 - 需谨慎使用无筛选条件的
update_many()
(如users.update_many({}, {...})
),会更新集合中所有文档。
3.4.3.3 更新操作符
# $set: 设置字段值
users.update_one({"_id": user_id}, {"$set": {"name": "新名字"}})
# 解释:用于修改现有字段值或添加新字段(若字段不存在)# $unset: 删除字段
users.update_one({"_id": user_id}, {"$unset": {"temp_field": ""}})
# 解释:删除指定字段,值通常设为空字符串(不影响删除效果)# $inc: 增加数值
users.update_one({"_id": user_id}, {"$inc": {"score": 10}})
# 解释:对数值字段进行增减(正数增加,负数减少),仅适用于int/float类型# $push: 向数组添加元素
users.update_one({"_id": user_id}, {"$push": {"tags": "新标签"}})
# 解释:向数组字段添加元素,允许重复(如多次添加相同标签会出现多个)# $pull: 从数组删除元素
users.update_one({"_id": user_id}, {"$pull": {"tags": "旧标签"}})
# 解释:从数组中删除所有匹配的元素(示例中删除所有值为"旧标签"的元素)# $addToSet: 向数组添加唯一元素
users.update_one({"_id": user_id}, {"$addToSet": {"tags": "唯一标签"}})
# 解释:仅当元素不存在于数组中时才添加,避免重复(类似集合的去重添加)
解释:
- 所有更新操作必须通过操作符实现,直接写
{"name": "新名字"}
会报错(需用$set
包裹)。 - 可组合多个操作符一次性完成多字段更新,例如:
users.update_one({"_id": user_id},{"$set": {"name": "新名"}, "$inc": {"age": 1}, "$push": {"hobbies": "跑步"}} )
- 数组操作符(
$push
/$pull
/$addToSet
)仅适用于数组类型字段,对非数组字段使用会报错。
3.4.4 删除数据 (Delete)
3.4.4.1 删除单个文档
# 删除单个文档
result = users.delete_one({"email": "zhangsan@example.com"})
print(f"删除的文档数: {result.deleted_count}")
解释:
delete_one()
仅删除第一条匹配条件的文档,即使存在多个符合条件的文档。- 筛选条件语法与查询、更新操作一致(如
{"_id": ObjectId("...")}
或字段匹配)。 - 返回结果的
deleted_count
表示实际删除的文档数量(0 或 1)。 - 若需精确删除某条文档,建议使用
_id
作为筛选条件(唯一且高效)。
3.4.4.2 删除多个文档
# 删除多个文档
result = users.delete_many({"age": {"$lt": 18}})
print(f"删除的文档数: {result.deleted_count}")
解释:
delete_many()
会删除所有匹配条件的文档,适用于批量删除场景。- 若筛选条件为空(
delete_many({})
),会删除集合中所有文档(保留集合结构,仅清空数据)。 - 操作不可逆,执行前务必确认筛选条件的准确性,建议先通过
find()
验证匹配结果。 deleted_count
表示实际删除的文档总数,可用于验证删除效果。
3.4.4.3 删除集合
# 删除整个集合
db.drop_collection("users")
解释:
drop_collection()
会删除指定集合所有数据、索引及集合本身(彻底移除,非清空)。- 操作不可逆,删除后无法恢复集合结构及数据,需格外谨慎。
- 相比
delete_many({})
,删除集合的性能更高(直接移除物理文件),但无法保留集合元数据。 - 也可通过集合对象调用
drop()
方法:users.drop()
(效果与db.drop_collection("users")
一致)。