Mongoose,使用String vs Booleans记录状态

时间:2018-04-06 07:47:14

标签: mongoose state document mongoose-schema

你好我想知道什么是更好的方法来保存"状态"该文件?我能想到的两种方法是使用字符串结束枚举:

const proposal = new Schema({
    state: {
        type: String,
        enum: ['pending', 'approved', 'denied'],
        default: 'pending'
    }
});

或使用布尔值:

const proposal = new Schema({
    approved: {
        type: Boolean,
        default: false
    },
    denied: {
        type: Boolean,
        default: false
    }
});

哪个在性能和安全性方面会更好?在外部,搜索booleans似乎比string搜索更快。

1 个答案:

答案 0 :(得分:2)

除了搜索之外,还有一些其他的事情需要考虑,例如:条件逻辑;设定值;验证文件,文件大小;和索引。

让我们回顾一下两个提议的模式,并使用枚举命名proposalA,并使用多个字段模拟枚举proposalB

const proposalA = new Schema({
    state: {
        type: String,
        enum: ['pending', 'approved', 'denied'],
        default: 'pending'
    }
});

const proposalB = new Schema({
    approved: {
        type: Boolean,
        default: false
    },
    denied: {
        type: Boolean,
        default: false
    }
});

假设

proposalA表示文档状态只能是三个值中的一个:“待定”,“已批准”和“已拒绝”。 proposalB表示支持proposalA假设为“待定”状态,“已批准”和“已拒绝”均为假。

关注

查询,索引和修改

虽然proposalA确实使用字符串值,但任一提案的匹配都是针对搜索查询的等同性检查{ state : 'approved' }{ approved: true }。最大的区别在于pending

  • proposalA{ state: 'pending' }
  • proposalB{ approved: false, denied: false }

假设没有其他查询参数,这需要state proposalA proposalB approved denied需要两个索引,approved各一个}和denied,并使用mongo的single indexproposalA { state: 'pending' }{ approved: false, denied: false }

将其留给索引交叉点的问题是,如果查询变得更加复杂,那么由于各种原因,预测将使用哪个交叉点的能力变得非常棘手。例如,目前只有3个状态,如果添加新状态,则需要创建更多索引以确保查询有效。

这导致每个索引的另一个问题占用了mongo服务器的内存空间。虽然复合索引会将此查询的索引数减少为1,但内存中的索引可能仍然比proposalA的单个索引更大。

说到内存大小,function getDocsFromState(state) { const Foo = mongoose.Model('foo'); const query = { state }; // assuming state is a string of 'pending', 'approved', or 'denied' return Foo.find(query).exec(); // Promise } 的文档大小只有proposalB的一半。虽然目前这似乎微不足道,如前所述,如果添加更多状态或者其他字段继续使用此模式,那么很容易看出文档大小如何也会很快膨胀。

从程序化角度返回搜索查询显示function getDocsFromState(state) { const Foo = mongoose.Model('foo'); const query = { approved: state === 'approved', denied: state === 'denied' }; return Foo.find(query).exec(); // Promise } 非常简单:

proposalA

虽然需要使用一些条件代码来构造proposalB的查询(这种逻辑的一种可能变体):

proposalA

除了function updateDocState(_id, state) { const Foo = mongoose.Model('foo'); const update = { state }; // assuming state is a string of 'pending', 'approved', or 'denied' return Foo.update({ _id }, update).exec(); // Promise } 具有更简洁的代码之外,它不需要实现更新来支持新状态,而proposalB则需要它们。

同样的问题适用于更新州的价值。 function updateDocState(_id, state) { const Foo = mongoose.Model('foo'); const update = { approved: state === 'approved', denied: state === 'denied' }; return Foo.update({ _id }, update).exec(); // Promise } 仍然简明扼要:

proposalB

虽然approved仍然需要更多额外的逻辑:

denied

条件逻辑&验证

通过使用多个字段来表示每个枚举值来模拟枚举时,验证变得有点麻烦。根据定义,枚举会阻止一次存储多个值,proposalB需要使用验证来防止proposalA和{{1}}同时成立。执行此操作可能会限制更新方法(部分更新与保存前更新内存中的完整文档),具体取决于所使用的验证工具(本机mongo与第三方库,如mongoose)更新文档。

最后,我们已经看到了查询和更新文档所需的条件逻辑,但代码中可能还有其他区域需要这样做。每当需要检查{{1}}内存中的文档的当前状态时,都需要使用类似的条件逻辑,而{{1}}只需检查枚举值。

TL; DR;

我们已经看到枚举如何提供内置文档验证,减少文档和索引大小,简化索引策略,简化当前和可能的未来实现代码,最后在查询时几乎不会引起性能问题,因为这两种方法都使用了相等性检查。

希望这有帮助!