【问题标题】:When is it better to store flags as a bitmask rather than using an associative table?什么时候将标志存储为位掩码而不是使用关联表更好?
【发布时间】:2011-08-08 04:07:07
【问题描述】:

我正在开发一个应用程序,其中用户拥有使用不同功能(例如读取、创建、下载、打印、批准等)的不同权限。权限列表预计不会经常更改。对于如何将这些权限存储在数据库中,我有几种选择。

在什么情况下选项 2 会更好?

选项 1

使用关联表。

用户 ---- 用户 ID (PK) 姓名 部
权限
----
权限 ID (PK)
名称
用户权限
----
用户 ID (FK)
PermissionId (FK)

选项 2

为每个用户存储一个位掩码。

用户
----
用户 ID (PK)
姓名
部
权限
[Flags]
enum Permissions {
    Read = 1,
    Create = 2,
    Download = 4,
    Print = 8,
    Approve = 16
}

【问题讨论】:

    标签: c# sql-server database-design bitmask


    【解决方案1】:

    好问题!

    首先,让我们对“更好”做一些假设。

    我假设您不太关心磁盘空间 - 从空间的角度来看,位掩码是有效的,但如果您使用 SQL Server,我不确定这是否重要。

    我假设您确实关心速度。使用计算时位掩码可以非常快 - 但在查询位掩码时您将无法使用索引。这应该不是那么重要,但是如果您想知道哪些用户具有创建访问权限,您的查询将类似于

    select * from user where permsission & CREATE = TRUE
    

    (今天无法访问 SQL Server,在路上)。由于数学运算,该查询将无法使用索引 - 因此,如果您有大量用户,这将非常痛苦。

    我假设您关心可维护性。从可维护性的角度来看,位掩码不像存储显式权限那样具有潜在问题域的表现力。您几乎可以肯定必须跨多个组​​件(包括数据库)同步位掩码标志的值。不是不可能,而是后背痛。

    所以,除非有另一种评估“更好”的方法,否则我会说位掩码路径不如将权限存储在规范化的数据库结构中。我不同意它会“更慢,因为你必须进行连接” - 除非你有一个完全功能失调的数据库,否则你将无法衡量这一点(而没有活动索引的好处的查询可能会变得显着即使有几千条记录也更慢)。

    【讨论】:

    • 由于布尔(或 SQL Server 中的位)列的cardinality 非常低,因此这些列上的索引完全没用。因此,标准化解决方案也不会提供该优化。
    • SQL Server 不将相邻的位域打包成字节,基本上将其存储为位掩码。
    【解决方案2】:

    就个人而言,我会使用关联表。

    位掩码字段很难查询和加入。

    您始终可以将其映射到您的 C# 标志枚举,如果性能变好并发布重构数据库。

    过早优化的可读性;)

    【讨论】:

    • 管理和维护。当关键信息在位掩码列中被混淆时,维护和管理存储在数据库中的数据会有多困难?几乎可以肯定,任何性能提升都不足以产生真正的影响。
    【解决方案3】:

    没有明确的答案,所以做对你有用的事情。但这是我的收获:

    如果

    使用选项1
    • 您希望权限增长到很多
    • 如果您可能需要对数据库存储过程本身进行权限检查
    • 您不希望有数百万用户,因此表中的记录不会大幅增长

    如果

    使用选项2
    • 权限将仅限于少数人
    • 您预计会有数百万用户

    【讨论】:

    • 数百万行在现代(甚至体面的传统)RDBMS 中是一个微不足道的数字
    • 是的,但考虑到您可能需要的索引以及在搜索期间添加索引书签的可能性,这会减慢整个过程,我更喜欢第二种选择。
    【解决方案4】:

    存储规范化的权限(即不在位掩码中)。虽然这显然不是您的场景的要求(尤其是在权限不会经常更改的情况下),但它会使查询变得更容易和更明显。

    【讨论】:

      【解决方案5】:

      出于以下原因,我建议不要使用位掩码:

      • 索引无法有效使用
      • 查询更难
      • 可读性/维护受到严重影响
      • 一般的开发人员不知道位掩码是什么
      • 灵活性降低(上限为 nr 位)

      根据您的查询模式、计划的功能集和数据分布,我会选择您的选项 1,甚至是简单的:

      user_permissions(
         user_id
        ,read     
        ,create   
        ,download 
        ,print    
        ,approve  
        ,primary key(user_id)
      );
      

      添加列是一种架构修改,但我猜测添加权限“Purge”将需要一些代码来配合它,因此权限可能不必像您想象的那样动态。

      如果您的数据分布有问题,例如 90% 的用户群没有单一权限,则以下模型也可以正常工作(但在进行较大扫描时会崩溃(一个 5 路连接与一个单次全表扫描)。

      user_permission_read(
         user_id
        ,primary key(user_id)
        ,foreign key(user_id) references user(user_id)
      )
      
      user_permission_write(
         user_id
        ,primary key(user_id)
        ,foreign key(user_id) references user(user_id)
      )
      
      user_permission_etcetera(
         user_id
        ,primary key(user_id)
        ,foreign key(user_id) references user(user_id)
      )
      

      【讨论】:

        【解决方案6】:

        我能想到的唯一一次我会使用位掩码字段来存储权限的情况是,当您真正受到物理内存量的限制时......就像在旧的移动设备上一样。事实上,您节省的内存量并不值得。即使在数百万用户中,硬盘空间也很便宜,并且您可以通过使用非位掩码方法更轻松地扩展权限等(这是关于报告谁拥有哪些权限等)

        我遇到的最大难题之一是直接在数据库中分配用户权限。我知道您应该尝试使用应用程序来管理自身,而不是一般地使用应用程序数据,但有时,这只是必要的。除非位掩码实际上是一个字符字段,并且您可以轻松查看某人拥有哪些权限而不是整数,否则请尝试向分析师等解释如何通过更新字段来向某人提供写访问权限等......并祈祷你的算术是正确的。

        【讨论】:

          【解决方案7】:

          当它们的结构不会改变并且总是一起使用时,它会很有用。这样,您几乎不需要往返服务器。它们在性能方面也很好,因为您可以在一个变量的单个分配中影响所有权限。

          我个人不喜欢它们...在一些性能密集型应用程序中,它们仍然被使用。我记得使用这些实现了一个国际象棋人工智能,因为你可以在一次比较中评估一个棋盘。使用起来很痛苦。

          【讨论】:

            【解决方案8】:

            我总是将其标准化存储除非数据库只是为您保存记录,除了检索和保存之外,您永远不会对此做任何事情。这种情况的一种情况是,如果在登录时,获取用户的权限字符串,并在服务器代码中对其进行处理和缓存。在那种情况下,它的非规范化实际上并不重要。

            如果您将其存储在字符串中并尝试在数据库级别对其进行处理,则必须做一些体操才能获得页面 X 的权限,这可能会很痛苦。

            【讨论】:

              【解决方案9】:

              使用标志枚举(位掩码)您的查询将运行得更快,因为您无需包含关联表的连接即可理解该值。

              【讨论】:

              • -1 这错误地暗示它将不会使用连接快速运行。您也没有考虑查询是什么。如果它正在检查是否存在特定权限,则在正确索引列上的连接将打开位掩码字段的大门,其按位操作将需要表扫描。
              • @Adam Robinson,(1) 不,这根本不意味着这一点。这意味着查询将运行更快,这是正确的。 (2) 您将关联表上优化程度最高的查询与整数字段上优化程度最低的查询进行比较。这真的不太实用。
              • 虽然您编写的用于解释位掩码的代码肯定比连接USER_PERMISSION 表更有效,但性能差异似乎不太可能有意义——这不太可能成为瓶颈操作 - 并且代码的清晰度大大降低。
              • 您的原始版本说“快”,而不是“更快”,就像现在一样,因此我的第一个评论。是的,我正在比较关联版本的“最优化的查询”,但它也是最有可能出现的版本。我将其与位掩码字段上的“优化最差”的查询进行比较,因为这也是可能的。无法在字段上创建按位索引,如果您计划在查询中检查权限,则按位操作是不可避免的。你有更好的选择吗?
              猜你喜欢
              • 1970-01-01
              • 2012-06-15
              • 2011-06-26
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-10-08
              • 2012-01-15
              相关资源
              最近更新 更多