sql - PostgreSQL - "polymorphic table"对比 3 个表

标签 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

还有 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);

-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);

-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);

我将不得不多次运行这样的 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' 

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

我需要担心性能吗?

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

我正在考虑声明 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

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

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')

问题:

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

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

  1. 我是否需要在 permissions.item_type 上声明与 btree 不同的索引?

  2. 我是否需要每天为表运行几次vacuum analyze以使索引正常工作(两种选择)?


编辑1:

SQLFiddle 示例:

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

{ 我还删除了错误位置的反引号,谢谢@wildplasser }

最佳答案

我建议将对您的权限系统的所有访问抽象为几个模型类。不幸的是,我发现像这样的权限系统有时最终会成为性能瓶颈,而且我发现有时有必要显着重构您的数据表示。 因此,我的建议是尽量将与权限相关的查询隔离在几个类中,并尽量使这些类的接口(interface)独立于系统的其余部分。

这里的好方法示例就是您上面的内容。您实际上并没有加入主题表;在构建权限时,您已经有了您关心的主题 ID。

不良接口(interface)的例子是类接口(interface),它可以很容易地将权限表连接到任意其他 SQL。

我知道你问的是 SQL 方面的问题,而不是 SQL 之上的特定框架,但从 rails 约束名称来看,你似乎正在使用这样的框架,我认为利用它对您 future 的代码可维护性。

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

多态方法确实给了你更多的控制权,如果你遇到性能问题,你可能想检查一下转移到它是否会有帮助。 如果您选择多态方法,我建议您编写代码来检查并确保您的数据是一致的。也就是说,确保 resource_type 和 resource_id 对应于系统中存在的实际资源。 在任何情况下,如果应用程序问题迫使您对数据进行非规范化,以至于数据库约束不足以强制执行一致性,我都会提出该建议。

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

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

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

  • 具体化用户组权限。即创建一个物化 View ,将 user_group 权限与用户权限和用户组成员资格相结合。

根据我的经验,真正扼杀权限系统性能的是当您添加诸如允许一个组成为另一个组的成员之类的东西时。到那时,您很快就会到达需要缓存或物化 View 的地步。

不幸的是,如果没有实际的数据和查看真实的查询计划和实际性能,就很难给出更具体的建议。我认为,如果您为 future 的变化做好准备,您会没事的。

关于sql - PostgreSQL - "polymorphic table"对比 3 个表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44011889/

相关文章:

mysql - 限制通过 localhost 访问 MySQL DB,而不是通过 IP?

database - 将唯一的 Redis 实例推送到中央 Redis?

java - 如何将参数添加到 Spring Batch 项目阅读器?

php - 准备好的语句。执行后绑定(bind)是否正确?

mysql - 将 default_password_lifetime 变量设置为 0 后,mysql 中的过期用户无法工作

php - 将 SELECT 记录与 COUNT 结合起来

postgresql - 使用 glassfish 4 和 Postgres 在 eclipselink 中创建数据库不起作用

java - 将 java.sql.Connection 转换为 PGConnection

php - 在 PHP PostgreSQL 查询结果中找到或未找到结果时显示消息

sql - Oracle 给予 like %term 的优先级高于 %term%