首页 > 解决方案 > 电子邮件集合的高效索引,用于按电子邮件域进行排序和过滤

问题描述

我正在使用 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 索引可以提高性能并使其更容易处理与我描述的那种查找?

我已经考虑过的替代方案是:

标签: mongodbmongoosedata-modeling

解决方案


第一个选项实际上应该工作得很好。根据$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 毫秒就足够了。

与往常一样,我强烈建议您对数据执行测试并查看其执行情况,因为数据特征可能会影响性能。


推荐阅读