mongodb - 电子邮件集合的高效索引,用于按电子邮件域进行排序和过滤
问题描述
我正在使用 Mongoose 来保存电子邮件地址的中央集合,并且我也有用户和组织的集合。在我的应用程序中,我通过用户(经过验证的)电子邮件域将用户与组织相关联。例如,Acme Ltd 拥有域 acme.com 和 acme.co.uk,通过从使用这些域的所有电子邮件中进行选择,我可以整理出一个唯一的关联用户列表。
用户可以拥有多个电子邮件地址(1 个主要电子邮件地址 + 多个辅助电子邮件地址)。用户不能共享电子邮件地址(因此“verifiedBy”字段强制用户和电子邮件之间的关系是一对一的)。
我的架构(当前)如下:
const emailSchema = new Schema({
_id: {
type: String,
get: function idReverse(_id) { if(_id) return _id.split("@").reverse().join("@"); },
set: (str) => { str.trim().toLowerCase().split("@").reverse().join("@") }
},
verifiedBy: { type: String, ref: 'User' }
}, options );
我的问题是是否值得在 setter 中反转电子邮件地址的域部分,并在 getter 中取消反转它们 - 正如我所展示的 - 以便 _id 上的基础 MongoDb 索引可以提高性能并使其更容易处理与我描述的那种查找?
我已经考虑过的替代方案是:
- 按原样存储电子邮件并使用正则表达式按域部分选择用户(在处理方面对我来说感觉很昂贵)
- 将域部分存储在单独的字段中并为其编制索引(感觉很昂贵,因为会有两个索引和重复的数据存储)
解决方案
第一个选项实际上应该工作得很好。根据$regex
文档:
[...] 如果正则表达式是“前缀表达式”,则可以进行进一步优化,这意味着所有潜在的匹配都以相同的字符串开头。[...]
如果正则表达式以插入符号 (^) 或左锚 (\A) 开头,后跟一串简单符号,则它是“前缀表达式”。[...]
实验
让我们看看它是如何在大约 800k 文档的集合中工作的,其中大约 25% 的文档有电子邮件。分析的示例查询是{email: /^gmail/}
.
没有索引:
db.users.find({email: /^gmail/}).explain('executionStats').executionStats
// ...
// "nReturned" : 2208,
// "executionTimeMillis" : 250,
// "totalKeysExamined" : 0,
// "totalDocsExamined" : 202720,
// ...
带{email: 1}
索引:
db.users.find({email: /^gmail/}).explain('executionStats').executionStats
// ...
// "nReturned" : 2208,
// "executionTimeMillis" : 5,
// "totalKeysExamined" : 2209,
// "totalDocsExamined" : 2208,
// ...
正如我们所看到的,它绝对有帮助——无论是在执行时间还是在检查文档方面(更多检查的文档可能意味着更多的 IO 工作)。如果我们忽略前缀并更直接地使用查询,让我们看看它是如何工作的:{email: /gmail/}
.
没有索引:
db.users.find({email: /gmail/}).explain('executionStats').executionStats
// ...
// "nReturned" : 2217,
// "executionTimeMillis" : 327,
// "totalKeysExamined" : 0,
// "totalDocsExamined" : 202720,
// ...
带{email: 1}
索引:
db.users.find({email: /gmail/}).explain('executionStats').executionStats
// ...
// "nReturned" : 2217,
// "executionTimeMillis" : 210,
// "totalKeysExamined" : 200616,
// "totalDocsExamined" : 2217,
// ...
最后,索引有很大帮助,尤其是在执行带前缀的查询时。看起来前缀查询足够快,可以在单个字段中保持原样。一个单独的字段可能会更好地利用索引(玩它!),但我认为 5 毫秒就足够了。
与往常一样,我强烈建议您对数据执行测试并查看其执行情况,因为数据特征可能会影响性能。
推荐阅读
- python - 将字节转换为字符串而不丢失任何字符
- node.js - 我可以向 HTTP API 服务器发出请求而不暴露自己的漏洞吗?
- python-3.x - 如何从 Python 中的旧列表构建新列表,一次添加两个新元素?
- reactjs - 无法对未安装的组件执行 React 状态更新。这是一个空操作,但它表示内存泄漏
- android - 在 API 级别 23+ 上准备的媒体播放器失败
- vue.js - 从数组数据初始化 for 循环中的组件
- sharepoint - 如何从 Sharepoint 在线下载文件
- azure - 我收到包含多个已处理数据的下游消息
- r - ggplot2按年叠加折线图?
- c# - 如何使用 Entity Framework 在 SQL Azure 中平均 DateTime 值?