PostgreSQL - "多态表"与3个表

not*_*ere 12 sql database postgresql one-to-many polymorphic-associations

我正在使用PostgreSQL 9.5(但升级可以说是9.6).

我有权限表:

CREATE TABLE public.permissions
(
  id integer NOT NULL DEFAULT nextval('permissions_id_seq'::regclass),
  item_id integer NOT NULL,
  item_type character varying NOT NULL,
  created_at timestamp without time zone NOT NULL,
  updated_at timestamp without time zone NOT NULL,
  CONSTRAINT permissions_pkey PRIMARY KEY (id)
)
-- skipping indices declaration, but they would be present
-- on item_id, item_type
Run Code Online (Sandbox Code Playgroud)

3个表用于多对多关联

-companies_permissions(+指数声明)

CREATE TABLE public.companies_permissions
(
  id integer NOT NULL DEFAULT nextval('companies_permissions_id_seq'::regclass),
  company_id integer,
  permission_id integer,
  CONSTRAINT companies_permissions_pkey PRIMARY KEY (id),
  CONSTRAINT fk_rails_462a923fa2 FOREIGN KEY (company_id)
      REFERENCES public.companies (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT fk_rails_9dd0d015b9 FOREIGN KEY (permission_id)
      REFERENCES public.permissions (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)

CREATE INDEX index_companies_permissions_on_company_id
  ON public.companies_permissions
  USING btree
  (company_id);

CREATE INDEX index_companies_permissions_on_permission_id
  ON public.companies_permissions
  USING btree
  (permission_id);

CREATE UNIQUE INDEX index_companies_permissions_on_permission_id_and_company_id
  ON public.companies_permissions
  USING btree
  (permission_id, company_id);
Run Code Online (Sandbox Code Playgroud)

-permissions_user_groups(+索引声明)

CREATE TABLE public.permissions_user_groups
(
  id integer NOT NULL DEFAULT nextval('permissions_user_groups_id_seq'::regclass),
  permission_id integer,
  user_group_id integer,
  CONSTRAINT permissions_user_groups_pkey PRIMARY KEY (id),
  CONSTRAINT fk_rails_c1743245ea FOREIGN KEY (permission_id)
      REFERENCES public.permissions (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT fk_rails_e966751863 FOREIGN KEY (user_group_id)
      REFERENCES public.user_groups (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)

CREATE UNIQUE INDEX index_permissions_user_groups_on_permission_and_user_group
  ON public.permissions_user_groups
  USING btree
  (permission_id, user_group_id);

CREATE INDEX index_permissions_user_groups_on_permission_id
  ON public.permissions_user_groups
  USING btree
  (permission_id);

CREATE INDEX index_permissions_user_groups_on_user_group_id
  ON public.permissions_user_groups
  USING btree
  (user_group_id);
Run Code Online (Sandbox Code Playgroud)

-permissions_users(+索引声明)

CREATE TABLE public.permissions_users
(
  id integer NOT NULL DEFAULT nextval('permissions_users_id_seq'::regclass),
  permission_id integer,
  user_id integer,
  CONSTRAINT permissions_users_pkey PRIMARY KEY (id),
  CONSTRAINT fk_rails_26289d56f4 FOREIGN KEY (user_id)
      REFERENCES public.users (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT fk_rails_7ac7e9f5ad FOREIGN KEY (permission_id)
      REFERENCES public.permissions (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)

CREATE INDEX index_permissions_users_on_permission_id
  ON public.permissions_users
  USING btree
  (permission_id);

CREATE UNIQUE INDEX index_permissions_users_on_permission_id_and_user_id
  ON public.permissions_users
  USING btree
  (permission_id, user_id);

CREATE INDEX index_permissions_users_on_user_id
  ON public.permissions_users
  USING btree
  (user_id);
Run Code Online (Sandbox Code Playgroud)

我将不得不像这样运行SQL查询很多次:

SELECT
"permissions".*,
"permissions_users".*,
"companies_permissions".*,
"permissions_user_groups".* 
FROM "permissions"
LEFT OUTER JOIN
  "permissions_users" ON "permissions_users"."permission_id" = "permissions"."id"
LEFT OUTER JOIN
  "companies_permissions" ON "companies_permissions"."permission_id" = "permissions"."id"
LEFT OUTER JOIN
  "permissions_user_groups" ON "permissions_user_groups"."permission_id" = "permissions"."id"
WHERE
  (companies_permissions.company_id = <company_id> OR
  permissions_users.user_id in (<user_ids> OR NULL) OR
  permissions_user_groups.user_group_id IN (<user_group_ids> OR NULL)) AND
permissions.item_type = 'Topic' 
Run Code Online (Sandbox Code Playgroud)

假设我们在其他表中拥有大约10000多个权限和类似数量的记录.

我需要担心性能吗?

我的意思是......我有4 LEFT OUTER JOIN秒,它应该很快返回结果(比如说<200ms).

我正在考虑声明1个"多态"表,例如:

CREATE TABLE public.permissables
(
  id integer NOT NULL DEFAULT nextval('permissables_id_seq'::regclass),
  permission_id integer,
  resource_id integer NOT NULL,
  resource_type character varying NOT NULL,
  created_at timestamp without time zone NOT NULL,
  updated_at timestamp without time zone NOT NULL,
  CONSTRAINT permissables_pkey PRIMARY KEY (id)
)
-- skipping indices declaration, but they would be present
Run Code Online (Sandbox Code Playgroud)

然后我可以像这样运行查询:

SELECT
  permissions.*,
  permissables.*
FROM permissions
LEFT OUTER JOIN
  permissables ON permissables.permission_id = permissions.id
WHERE
  permissions.item_type = 'Topic' AND
  (permissables.owner_id IN (<user_ids>) AND permissables.owner_type = 'User') OR
  (permissables.owner_id = <company_id> AND permissables.owner_type = 'Company') OR
  (permissables.owner_id IN (<user_groups_ids>) AND permissables.owner_type = 'UserGroup')
Run Code Online (Sandbox Code Playgroud)

问题:

  1. 哪个选项更好/更快?也许有更好的方法来做到这一点?

a)4个表(permissions, companies_permissions, user_groups_permissions, users_permissions)b)2个表(permissions, permissables)

  1. 我需要比申报不同的索引btreepermissions.item_type

  2. 我是否需要每天运行几次vacuum analyze表来使索引工作(两个选项)?


EDIT1:

SQLFiddle示例:

  1. wildplasser建议(来自评论),不工作:http://sqlfiddle.com/#!15/9723f8/1
  2. 原始查询(4个表):http://sqlfiddle.com/#!15/9723f8/2

{我也在错误的地方删除了反引号,感谢@wildplasser}

Bor*_*rov 5

也许这是一个显而易见的答案,但我认为 3 个表的选项应该没问题。SQL 数据库擅长进行join操作,并且您有 10,000 条记录——这根本不是大量数据,所以我不确定是什么让您认为会出现性能问题。

使用适当的索引(btree 应该没问题),它应该可以快速运行,实际上您可以更进一步,为您的表生成示例数据,并查看您的查询实际上如何处理实际数据量。

我也不认为您需要担心手动运行真空之类的事情。

关于选项二,多态表,它可能不是很好,因为您现在有单个resource_id字段可以指出不同的表,这是问题的根源(例如,由于错误,您可以使用 resource_type=User和resource_id 指向Company- 表结构不会阻止它)。

还有一点要注意:您没有说明 User、UserGropup 和 Company 之间的任何关系 - 如果它们也都相关,则可以仅使用用户 ID 获取权限,同时将 gropus 和公司加入用户。

还有一:你不需要idS IN多对多的表,如果你有他们什么也没有发生,但它足有permission_iduser_id,使他们成为复合主键。


Sam*_*man 5

我建议将对权限系统的所有访问权限抽象为几个模型类.不幸的是,我发现像这样的权限系统有时最终会成为性能瓶颈,而且我发现有时需要对数据表示进行重要的重构.因此,我的建议是尝试将与权限相关的查询保留在几个类中,并尝试将接口保持为独立于系统其余部分的那些类.

这里的好方法的例子就是你上面的内容.您实际上并未加入主题表; 在构建权限时,您已经拥有了关注的主题ID.

坏接口的示例是类接口,可以很容易地将权限表连接到任意其他SQL.

我理解你用SQL而不是基于SQL的特定框架提出问题,但是从rails约束名称看起来你正在使用这样的框架,我认为利用它对你未来的代码很有用可维护性.

在10,000行的情况下,我认为任何一种方法都可以正常工作.我真的不确定这些方法会有什么不同.如果您考虑生成的查询计划,假设您从表中获取了少量行,则可以使用针对每个表的循环来处理连接,其方式与处理或查询的方式完全相同,假设索引很可能会返回少量行.我没有向Postgres提供合理的数据集,以确定这是否是它给出真实数据集的实际效果.我有相当高的信心,如果这样做有意义,Postgres足够聪明.

多态方法确实可以让您获得更多控制权,如果遇到性能问题,您可能需要检查是否有助于它.如果您选择多态方法,我建议您编写代码并检查以确保您的数据一致.也就是说,确保resource_type和resource_id对应于系统中存在的实际资源.在任何情况下,我都会提出建议,因为应用程序问题会迫使您对数据进行非规范化,这样数据库约束就不足以强制实现一致性.

如果您开始遇到性能问题,以下是您将来可能需要做的事情:

  • 在应用程序中创建缓存,将对象(例如主题)映射到这些对象的权限集.

  • 在应用程序中创建缓存,缓存给定用户拥有的所有权限(包括他们所属的组),以用于应用程序中的对象.

  • 实现用户组权限.这是创建一个物化视图,它将user_group权限与用户权限和用户组成员身份相结合.

根据我的经验,真正杀死权限系统性能的是当你添加类似允许一个组成为另一个组的成员的东西时.此时,您很快就会到达需要缓存或物化视图的位置.

不幸的是,如果没有真正的数据并查看真实的查询计划和真实的性能,提供更具体的建议真的很难.我想如果你为未来的改变做准备,你会没事的.