【问题标题】:PostgreSQL - "polymorphic table" vs 3 tablesPostgreSQL - “多态表”与 3 个表
【发布时间】:2017-05-16 21:28:28
【问题描述】:

我使用的是 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 JOINs,它应该会很快返回结果(比如

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

【问题讨论】:

  • 尝试将引用 LEFT JOINed 表的条件移动到 ON ... 部分。这将避免 WHERE 子句中所有丑陋的OR NULLs。并且不要对文字使用反引号。
  • @wildplasser 感谢您的回复。是的,我知道 backtips(OS X 添加了它们而不是单引号)......我尝试了你的解决方案——但我得到了不正确的结果。 SQLfiddle -> sqlfiddle.com/#!15/9723f8/1
  • 虽然old 版本有效——sqlfiddle.com/#!15/9723f8/2。还 INNER JOIN 不会在这里工作。我需要得到类似 (condition OR condition2 OR condition3) 的原始版本。

标签: sql database postgresql one-to-many polymorphic-associations


【解决方案1】:

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

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

不良接口的示例是类接口,可以轻松地将权限表连接到任意其他 SQL 中。

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

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

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

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

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

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

  • 实现用户组权限。即创建一个物化视图,将 user_group 权限与用户权限和用户组成员资格相结合。

根据我的经验,真正影响权限系统性能的事情是当您添加诸如允许一个组成为另一个组的成员之类的内容时。到那时,您很快就会到达需要缓存或物化视图的地步。

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

【讨论】:

    【解决方案2】:

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

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

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

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

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

    还有一个:你不需要在多对多表中使用ids,如果你有它们就不会发生任何坏事,但是拥有permission_iduser_id 并使其成为复合主键就足够了.

    【讨论】:

    • 我同意。还有一件事:如果您在 (permission_id, company_id) 上有索引,则在 (permission_id) 上不需要索引。
    【解决方案3】:

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

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

    这是一个示例脚本,我为 mysql 编写了一对多关系,但类似的事情也可以应用于您的案例:

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

    我已经多次使用这种方法,当 SELECT 语句数量多于 INSERT、UPDATE 和 DELETE 语句并且更重要时,它是有意义的。

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-02-09
        • 1970-01-01
        • 2021-12-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多