PostgreSQL - "多态表" vs 3桌

时间:2017-05-16 21:28:28

标签: 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(+ indices declaration)

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(+ indices declaration)

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(+ indices declaration)

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 s它应该会很快返回结果(比如说<200ms)。

我正在考虑宣布1&#34;多态&#34;表,如:

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. 哪个选项更好/更快?也许有更好的方法来做到这一点?
  2. a)4个表(permissions, companies_permissions, user_groups_permissions, users_permissions) b)2个表(permissions, permissables

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

    2. 我是否需要每天运行几次vacuum analyze表格以使索引有效(两个选项)?

    3. EDIT1:

      SQLFiddle示例:

      1. wildplasser建议(来自评论),不工作:http://sqlfiddle.com/#!15/9723f8/1
      2. 原始查询(4个表):http://sqlfiddle.com/#!15/9723f8/2
      3. {我也在错误的地方删除了反引号,感谢@wildplasser}

4 个答案:

答案 0 :(得分:5)

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

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

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

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

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

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

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

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

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

  • 具体化用户组权限。这是创建一个物化视图,它将user_group权限与用户权限和用户组成员资格相结合。

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

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

答案 1 :(得分:4)

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

使用正确的索引(btree应该没问题),它应该可以快速工作,实际上你可以更进一步,为你的表生成样本数据,看看你的查询实际上如何处理实际的数据量。

我也不认为你需要担心像手动操作真空一样。

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

还有一点需要注意:你没有告诉用户,UserGropup和公司之间的关系 - 如果它们全部相关,也可以仅使用用户ID获取权限,也可以将gropus和公司加入用户

还有一个:你在许多表中都不需要id,如果你拥有它们就没有什么不好的事情,但它足以让permission_id和{{} {1}}并使它们成为复合主键。

答案 2 :(得分:2)

您可以尝试在3个表(user,user_group,company)中的每个表的权限字段中对多对多关系进行非规范化。

您可以使用此字段以JSON格式存储权限,并仅将其用于读取(SELECT)。您仍然可以使用多对多表来更改特定用户,组和公司的权限,只需在它们上写一个触发器,只要多对多关系发生新的更改,就会更新非规范化的权限字段表。使用此解决方案,您仍然可以在SELECT上快速查询执行时间,同时保持关系规范化并符合数据库标准。

这是一个示例脚本,我为mysql编写的一对多关系,但类似的东西也适用于你的情况:

https://github.com/martintaleski/mysql-denormalization/blob/master/one-to-many.sql

我多次使用过这种方法,当SELECT语句数量超过INSERT,UPDATE和DELETE语句时更有意义。

答案 3 :(得分:2)

如果您不经常更改权限,实体化视图可能会极大地加快您的搜索速度。我将根据您今天晚些时候的设置准备一个示例并发布。之后,我们可以做一些基准测试。

尽管如此,物化视图需要在更改数据后更新物化视图。因此,该解决方案可能很快,但只有在基本数据不经常更改时才会加快查询速度。

相关问题