Mongoose,使用字符串与布尔值的文档状态

Com*_*zza 3 state document mongoose mongoose-schema

您好,我想知道在保存文档的“状态”方面有什么更好的方法?我能想到的两种方法是使用字符串结尾枚举:

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

或使用布尔值:

const proposal = new Schema({
    approved: {
        type: Boolean,
        default: false
    },
    denied: {
        type: Boolean,
        default: false
    }
});
Run Code Online (Sandbox Code Playgroud)

在性能和安全性方面,哪一个更好?在外面,搜索似乎booleansstring搜索更快。

Jas*_*ust 5

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

让我们回顾一下两个提议的模式并命名它们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
    }
});
Run Code Online (Sandbox Code Playgroud)

假设

proposalA表示文档状态只能是以下三个值之一:'pending'、'approved' 和 'denied'。 proposalB表示支持proposalAthen 对于“待定”状态的假设,“批准”和“拒绝”都是错误的。

顾虑

查询、索引和修改

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

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

假设没有其他查询参数,这将需要一个单独的索引state用于proposalA同时proposalB将需要两个索引,每一个用于approveddenied,并使用蒙戈的索引交集化合物指数approveddenied

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

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

说到内存大小,一个文档{ state: 'pending' }大约是{ approved: false, denied: false }. 虽然目前这看起来微不足道,但如前所述,如果添加更多状态或在其他字段中继续这种模式,那么很容易看出文档大小也会很快膨胀。

从编程的角度返回搜索查询表明这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
}
Run Code Online (Sandbox Code Playgroud)

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

function getDocsFromState(state) {
  const Foo = mongoose.Model('foo');
  const query = {
    approved: state === 'approved',
    denied: state === 'denied'
  };

  return Foo.find(query).exec(); // Promise
}
Run Code Online (Sandbox Code Playgroud)

除了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
}
Run Code Online (Sandbox Code Playgroud)

虽然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
}
Run Code Online (Sandbox Code Playgroud)

条件逻辑和验证

通过使用多个字段来表示每个枚举值来模拟枚举时,验证变得有点麻烦。根据定义,枚举可防止一次存储多个值,proposalB需要使用验证来防止两者approved同时denied为真。根据用于更新文档的验证工具(本机 mongo 与 mongoose 等 3rd 方库),强制执行此操作可能会限制更新方法(部分更新与在保存之前更新内存中的完整文档)。

最后,我们已经看到条件逻辑对于查询和更新文档是多么必要,但代码中可能还有其他地方可能需要这样做。任何时候proposalB需要检查内存中的文档的当前状态时,都需要使用类似的条件逻辑,而proposalA只需检查枚举值。

TL; 博士;

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

希望这可以帮助!