关系型数据库rdbms的一个主要特征即多表,外键关联或/并加以约束。关系型数据库的优点,顾名思义,能够通过数据库本身的操作来建立、保持并维护关系,由此在集中的数据存储中,可以提供很多便利。
不过,rdbms因为将关系纳入数据存储中,反而给自己造成了很多麻烦。数据建模可以很程式化的进行,这就是双刃剑,一些ORM工具很形象的将对象与表对应,而将引用与关系对应,看起来似乎彻底的解决了rdbms非对象化的问题,但实际上是绕了很大的一个圈。
关系型数据库最常见的一个用法,即通过关联查询获取分散于各个表中的记录。一个问题是,什么信息需要如此大动干戈,从多张表中获取?下面以常见用户/权限系统中的模型为例,解决这个问题!
- 用户,用户自身包括用户名/密码、用户资料
- 组织结构,自身包括组织资料
- 权限,权限实际上只有一个名字,在具体体现到功能前,这个东西毫无意义
- 用户间,没有任何关系
- 组织间,存在一个上下级的联系
- 权限间,有些系统会将权限分组,但那只是一个打包的快捷方式,权限组完全可以理解为是权限以外的东西
- 用户与组织,用户有一个或者多个组织,这在以前的系统中用rdbms实现无比麻烦,有些系统甚至只能做到一个用户同时只属于一个组织
- 用户与权限,用户同时具备多个权限,问题同上,权限组的加入让问题更加复杂化
- 组织与权限,有些系统甚至将组织和权限关联,因此不得不做出一些约定,否则其实现没有确定性
我们现在从系统功能出发来分解这些要素:
- 用户登录,用户名/密码就足够了,所以基本用户表就应该只有用户名/密码
- 用户资料维护,只需要用户资料,与用户名关联。用户资料的修改以前也会用表来完成,实际上,你只需要一个文件或一个字段来存储用户所有的资料,因为所有修改每次完全提交所有资料字段都不为过。很容易理解,你绝对不会将员工word格式的简历存储在数据库中,那么又何必把他的电话号码家庭住址也存储起来?
- 组织结构调整。实际上只是上下级关系的调整,只需要记录每个组织的上级部门的名字即可,当然专门弄一个关系表也ok。
- 权限列表和权限组维护。权限组单独一张表记录包含的权限名,权限自身是名字的平面表,之间都没有任何关系。具体的原因自己想去。
- 用户和组织关系维护。建立一张关系表,慢慢维护去吧。一般来说,从组织来维护用户比较合理,因为人事变更是组织自身的周期性行为,而不是个人的突发性行为。
- 用户和权限关系维护。维护一张关系表,事实上,这张表就足以应付所有的权限校验,因此从优化角度来说,将权限的命名变得有意义相当重要:根据当前或选定用户,获取他所有的权限,并且通过权限的名字就能够了解具体的权限定义做出判断。
- 组织和权限关系维护。请不要实现为关系表,宁可将此实现为组织-用户和用户-权限的两次查询在前端做组合。例如某个需求是,A部门下所有员工都能够获得访问a1系统的首页权限,完全可以实现为:管理员查询A部门下所有员工,选中他们,查找所有权限中是否有“访问a1系统的首页权限”,有的话,赋予当前选择的用户,没有的话新建这么一个名字的权限并赋予当前选择的用户,并在完成后通知a1系统的开发人员,将这个名字加入到校验逻辑中去。
这样一来,我们有了:
- 用户密码表,用户名/密码2个字段
- 用户资料文件夹,文件按用户名取名。
- 组织结构表,只存名字和上级组织名字,2个字段
- 权限表,权限的名字1个字段
- 权限组表,权限组的名字和所含权限以分隔符分割的联合,2个字段
- 用户和组织关系,用户名和组织名2个字段
- 用户和权限关系,用户名和权限名2个字段
进一步的优化是:
- 组织以域名写法来规范,例如XX公司XX部XX2科,那就是XX2科.XX部.XX公司,这是ldap/x.500的标准写法(我更倾向于从大到小的中国式写法,这样通过自然排序就能够获得树状结构)。如此命名,连上级组织字段都不用写了。如果用户只有一个组织,那么用户和组织关系表也不用写了。
- 用户的所有权限存为一个字段,以分隔符分割(可选的)。一般来说,用户权限也就几十个,全部取出没有任何问题,校验方可以很方便的做判断。一个做法是,用户A有权限a1,a2,a3,那么权限字段就是|a1|a2|a3|,a3校验时,只需要做indexOf('|a3|')即可。另一个优化方案是保证权限名自然有序,这样校验时就可以用2分法加速。
优化结果是:
- 用户密码表,2字段
- 用户资料文件夹
- 组织结构表,1字段
- 权限表,1字段
- 权限组表,2字段
- 用户和组织关系,2字段,1对1(或1对多,1用户多组织时)
- 用户和权限关系,2字段,1对1(或1对多,方案2)
我们看一下优化后的好处:
- 用户密码表很重要,可以单独用加密性强的库或者表存它
- 用户资料文件夹可以很方便的做备份,离职员工也能够做资料存档。如果使用json或者xml格式存储的话,浏览器直接解析,对于接口调用来说太方便了。并且,文件系统可以利用操作系统级别的ACL来管理,安全性细度更高
- 与当前用户相关的东西,除用户资料外,在用户登录后都可以一次取出,然后塞到session中去。例如他的组织,他的权限列表。由于这些信息非常细碎,而且只在登录后做一次,没必要通过文件缓存。
- 权限、组织结构,都可以一次取出并用json文件缓存供浏览器使用,用户与组织关系可按组织分文件缓存。由于每个表字段少或者关系清楚,用文件缓存就很容易确定缓存刷新的时机。