什么时候最好将标志存储为位掩码而不是使用关联表?


76

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

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

选项1

使用关联表。

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

选项2

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

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

Answers:


63

精彩的问题!

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

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

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

select * from user where permsission & CREATE = TRUE

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

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

因此,除非有另一种评估“更好”的方法,否则我会说位掩码路由不如将权限存储在规范化的数据库结构中那么好。我不同意它会“变慢,因为您必须进行连接”-除非您的数据库完全无法正常运行,否则您将无法进行测量(而没有有效索引的查询会变得很明显即使有几千条记录,速度也较慢)。


6
精彩回答!
Lieven Keersmaekers,

5
由于布尔(或SQL Server中为bit)列的基数极低,因此这些列上的索引完全没有用。因此,规范化解决方案也不会提供该优化。
Clodoaldo Neto 2013年

SQL Server不会将相邻的位字段打包为字节,基本上将其存储为位掩码。
暗恋

12

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

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

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

可读性过早的优化;)


6
管理和维护。当在位掩码列中混淆关键信息时,维护和管理数据库中存储的数据将有多困难?而且几乎可以肯定,任何性能提升都不足以产生真正的改变。
菲利普·凯利

5

存储标准化的权限(即不在位掩码中)。虽然它显然不是一个要求,为您的方案(特别是如果权限不会经常更改),它将使查询更容易和更明显。


5

没有明确的答案,所以为你做什么工作。但是,这是我的收获:

如果使用选项1

  • 您希望权限增加到许多
  • 如果您可能需要对数据库存储过程本身进行权限检查
  • 您不会期望数以百万计的用户,以便表中的记录不会大量增长

如果使用选项2

  • 权限将限于少数
  • 您期望数百万用户

在现代(甚至是体面的遗产)RDBMS中,数百万行是微不足道的数目
Adam Robinson

是的,但是考虑到您可能需要的索引以及在搜索过程中使用索引书签的可能性,这会减慢该过程的速度,我更喜欢第二种选择。
Aliostad 2011年

1

我唯一能想到的是何时会真正使用位掩码字段存储权限的时间是,当您真正真正受限于拥有多少物理内存时……就像在旧的移动设备上一样。实际上,您节省的内存量不值得。即使是成千上万的用户,硬盘空间也很便宜,并且您可以使用非位掩码方法(这是关于报告谁拥有什么权限等)来轻松扩展权限等。

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


1

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

我个人不喜欢它们...在一些性能密集的应用程序中,它们仍在使用。我记得使用这些工具来实现国际象棋AI,因为您可以在一个比较中评估一个棋盘。


1

除非数据库只是为您保存记录,否则我将始终将其标准化存储,并且除了检索和保存之外,您将永远不会做任何事情。一种解决方案是在登录时获取用户的权限字符串,并在服务器代码中对其进行处理和缓存。在那种情况下,对其进行非规范化真的没什么大不了的。

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


1

我建议不要使用位掩码,原因如下:

  • 索引无法有效使用
  • 查询更难
  • 可读性/维护受到严重影响
  • 那里的普通开发人员不知道什么是位掩码
  • 灵活性降低(数字中最大位数为nr个)

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

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

添加列是对模式的修改,但是我猜想添加特权“清除”将需要一些代码,因此特权可能不必像您想象的那样动态。

如果您的数据分布不佳,例如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)
)

-2

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


4
-1这错误地暗示它不会通过联接快速运行。您也不考虑查询什么。如果要检查是否存在特定的权限,则在正确索引的列上进行的联接将使位掩码字段消失,其位操作需要进行表扫描。
亚当·罗宾逊

@亚当·鲁滨逊(Adam Robinson),(1)不,这实际上并不意味着任何。这意味着查询将运行得更快,这是正确的。(2)您正在将关联表上最优化的查询与整数字段上最不优化的查询进行比较。那真的不是很实用。
smartcaveman 2011年

1
虽然您编写的解释位掩码的代码肯定有可能比连接USER_PERMISSION表更有效,但性能差异似乎不太可能有意义-这不太可能成为瓶颈操作-并且存在代码中的清晰度大打折扣。
贾斯汀·凯夫

您的原始版本说的是“快速”,而不是现在的“更快”,因此,我的第一个评论。是的,我正在为关联版本比较“最优化的查询”,但这也是最有可能使用的版本。我正在将其与bitmask字段上的“最差优化”查询进行比较,因为这又很可能就位了。无法在字段上创建按位索引,并且如果您计划在查询中检查权限,则按位操作是不可避免的。您有更好的选择吗?
亚当·罗宾逊
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.