将标志存储为位掩码而不是使用关联表最好是什么时候?
我正在开发一个应用程序,用户有不同的权限来使用不同的function(例如读取,创build,下载,打印,批准等)。 预期权限列表不会经常更改。 我有几个如何将这些权限存储在数据库中的选项。
选项2会在哪些情况下更好?
选项1
使用关联表。
用户 ---- UserId(PK) 名称 部
允许 ---- PermissionId(PK) 名称
User_Permission ---- UserId(FK) PermissionId(FK)
选项2
为每个用户存储一个位掩码。
用户 ---- UserId(PK) 名称 部 权限
[Flags] enum Permissions { Read = 1, Create = 2, Download = 4, Print = 8, Approve = 16 }
灿烂的问题!
首先,让我们对“更好”做一些假设。
我假设你不太在乎磁盘空间 – 从空间的angular度来看,掩码是有效的,但是如果你使用的是SQL服务器,我不太确定。
我假设你关心速度。 使用计算时,位掩码可以非常快 – 但查询位掩码时将无法使用索引。 这应该不重要,但是如果你想知道哪些用户有创build访问权限,你的查询将是类似的
select * from user where permsission & CREATE = TRUE
(今天,在路上还没有访问SQL Server)。 由于math运算,该查询将无法使用索引 – 因此,如果您拥有大量的用户,这将非常痛苦。
我假设你关心可维护性。 从可维护性的angular度来看,位掩码不如存储显式权限的底层问题域那么有performance力。 几乎可以肯定的是,必须跨多个组件(包括数据库)同步位掩码标志的值。 不是不可能的,但背后的痛苦。
所以,除非有另一种评估“更好”的方式,否则我认为掩码路由不如在正常化的数据库结构中存储权限。 我不认为这会比较慢,因为你必须做一个连接,除非你有一个完全失效的数据库,否则你将无法衡量这一点(而没有活跃索引的好处的查询可能会变得明显即使有几千条logging也会变慢)。
就个人而言,我会使用关联表。
位掩码字段非常难以查询和join。
你总是可以将它映射到你的C#标记枚举,如果性能变成问题并重构数据库。
过早优化的可读性;)
存储正常化的权限(即不在位掩码中)。 虽然这显然不是你的情况(特别是如果权限不会经常改变)的要求,它会使查询更容易,更明显。
没有明确的答案 ,所以做什么适合你 。 但是,这是我的看法:
使用选项1 if
- 您希望权限增长到很多
- 如果您可能需要在数据库存储过程本身中执行权限检查
- 您不希望数百万用户使表中的logging不会大量增长
使用选项2 if
- 权限将被限制为less数
- 您预计数百万的用户
我唯一能想到的是什么时候使用一个位掩码字段来存储权限,就是当你真的真的限制了你有多less物理内存……就像在旧的移动设备上一样。 事实上,你保存的内存数量是不值得的。 即使在数百万用户的硬盘空间也很便宜,而且您可以通过使用非位掩码方法(这关于谁拥有什么权限进行报告等)来扩展权限等。
我遇到的最大麻烦之一是直接在数据库中分配用户权限。 我知道你应该尝试使用应用程序来pipe理自己,而不是使用一般的应用程序数据,但有时候,这是必要的。 除非位掩码实际上是一个字符字段,并且您可以很容易地看到某人拥有什么权限而不是一个整数,请尝试向分析师等解释如何通过更新字段来向某人写入访问等。你的算术是正确的。
当他们的结构不会改变时,它会很有用,并且总是一起使用。 这样,你有几个往返服务器。 它们在性能上也是很好的,因为你可以在一个variables的单个赋值中影响所有的权限。
我个人不喜欢他们…在一些性能激烈的应用程序,他们仍然使用。 我记得使用这些棋子来执行一个国际象棋,因为你可以通过一个比较评估一个棋盘。这是一个痛苦的工作。
除非数据库仅仅是为你保存logging, 否则我会一直存储它,除了检索和保存,你永远不会做任何事情。 这种情况是,如果login时,您的用户的权限string被提取,并在服务器代码中处理和caching。 在这种情况下,它是非规范化的并不重要。
如果你将它存储在一个string中并试图在数据库级别上进行操作,那么你必须做一些体操操作才能获得页面X的权限,这可能是很痛苦的。
我build议不要使用位掩码,原因如下:
- 索引不能有效使用
- 查询比较困难
- 可读性/维护受到严重影响
- 那里的平均开发者不知道什么是位掩码
- 灵活性降低(一个数字中的位数上限)
根据您的查询模式,计划的function集和数据分布,我将使用您的选项1,甚至是简单的:
user_permissions( user_id ,read ,create ,download ,print ,approve ,primary key(user_id) );
添加一个列是一个架构修改,但我的猜测是,添加一个特权“清除”,将需要一些代码去配合它,所以特权可能不必像你想象的那样dynamic。
如果你有一些不良的数据分布,比如90%的用户群没有一个单独的权限,那么下面的模型也可以正常工作(但是在进行较大的扫描时会崩溃)(一个5路连接vs一个全表扫描)。
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) )
您的查询将使用flags枚举(位掩码)运行得更快,因为您无需在关联的表中包含连接以便理解值。