高效进行不区分大小写的MongoDB查询(通过pymongo)

29 投票
3 回答
22030 浏览
提问于 2025-04-16 19:06

我现在正在用Python(Pyramid框架)创建一个网站,这个网站需要用户注册和登录。系统允许用户选择一个用户名,这个用户名可以包含大写字母、小写字母和数字的组合。

问题在于,如何确保两个用户不会不小心使用相同的用户名,也就是说在我的系统中,'randomUser' 应该和 'RandomUser' 或 'randomuser' 被视为相同的用户名。

不幸的是(在这种情况下),因为Mongo数据库是区分大小写的,所以可能会有多个用户使用“相同”的用户名。

我知道有一种方法可以查询Mongo数据库中的不区分大小写的字符串:

db.stuff.find_one({"foo": /bar/i});

但是,这在我使用pymongo进行查询时似乎不起作用:

username = '/' + str(username) + '/i'
response = request.db['user'].find_one({"username":username},{"username":1})

这样构建pymongo的查询方式是正确的吗(我觉得可能不对)?

这个查询将在每次创建用户账户或登录时使用(因为它需要检查用户名是否已经存在于系统中)。我知道这不是最有效的查询方式,那么如果它只在登录或账户创建时使用,这样是否重要?是否更好强制用户只能选择小写的用户名(这样就完全不需要不区分大小写的查询)?

3 个回答

1

区分大小写:

db.stuff.find_one({'name': {'$regex': f'^{username}$'}})

不区分大小写:

db.stuff.find_one({'name': {'$regex': f'^{username}$', "$options": 'i'}})
11

被接受的答案是有风险的,因为它会匹配任何包含用户名的字符串!更安全的做法是匹配完全相同的字符串:

import re
db.stuff.find_one({'name': re.compile('^' + username + '$', re.IGNORECASE)})

更安全的方式是对变量中的特殊字符进行转义,这样可以避免影响正则表达式的匹配:

import re
db.stuff.find_one({'name': re.compile('^' + re.escape(username) + '$', re.IGNORECASE)}) 
58

PyMongo使用的是原生的Python正则表达式,就像Mongo shell使用的是原生的JavaScript正则表达式一样。如果你想写出和上面在shell中写的查询相同的代码,你可以这样做:

db.stuff.find_one({'name': re.compile(username, re.IGNORECASE)})

不过要注意,这样做会导致无法使用在name字段上可能存在的任何索引。为了实现不区分大小写的搜索或排序,常见的做法是你的文档中再添加一个字段,比如name_lower,每当name发生变化时,这个字段就会被设置为name的小写版本。然后你可以这样查询这样的文档:

db.stuff.find_one({'name_lower': username.lower()})

撰写回答