PostgreSQL - "polymorphic table" 对比 3 个表
PostgreSQL - "polymorphic table" vs 3 tables
我正在使用 PostgreSQL 9.5(但可以升级到 9.6)。
我有权限 table:
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 table 用于多对多关联
-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'
假设我们在其他 table 中有大约 10000 多个权限和类似数量的记录。
我需要担心性能吗?
我的意思是...我有 4 LEFT OUTER JOIN
s,它应该 return 结果相当快(比如 <200ms)。
我正在考虑声明 1 "polymorphic" table,类似于:
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')
问题:
- 哪个选项是better/faster?也许有更好的方法来做到这一点?
a) 4 tables (permissions, companies_permissions, user_groups_permissions, users_permissions
)
b) 2 tables (permissions, permissables
)
是否需要在 permissions.item_type
上声明与 btree
不同的索引?
我是否需要每天 运行 几次 vacuum analyze
table 秒才能使索引正常工作(两种选择)?
编辑 1:
SQL小提琴示例:
- wildplasser 建议(来自评论),无效:http://sqlfiddle.com/#!15/9723f8/1
- 原始查询(4 tables):http://sqlfiddle.com/#!15/9723f8/2
{ 我还删除了错误位置的反引号,感谢@wildplasser }
也许这是一个显而易见的答案,但我认为带有 3 table 的选项应该没问题。 SQL 数据库擅长执行 join
操作并且您有 10,000 条记录 - 这根本不是大量数据,所以我不确定是什么让您认为会出现性能问题。
使用适当的索引(btree 应该没问题),它应该可以快速运行,实际上您可以更进一步并为您生成示例数据 tables 并查看您的查询实际上如何在真实环境中运行数据量。
我也不认为您需要担心 运行 手动吸尘之类的事情。
关于选项二,多态 table,它可能不是很好,因为您现在只有一个 resource_id
字段,它可以指向不同的 tables,这是一个来源的问题(例如,由于一个错误,你可以有一个 resource_type=User
和 resource_id 指向 Company
的记录 - table 结构没有阻止它)。
请注意:您没有说明 User、UserGroup 和 Company 之间的任何关系 - 如果它们也都相关,则可以仅使用用户 ID、加入 gropus 和公司来获取权限给用户。
还有一个:在许多 table 中你不需要 id
,如果你有它们也没什么不好的,但是有 permission_id
和user_id
并使它们成为复合主键。
我建议将对权限系统的所有访问权限抽象为几个模型 classes。不幸的是,我发现像这样的权限系统有时最终会成为性能瓶颈,而且我发现有时有必要显着重构您的数据表示。
因此,我的建议是尝试将与权限相关的查询隔离在几个 classes 中,并尝试使这些 classes 的接口独立于系统的其余部分。
这里的好方法示例就是您上面的内容。你实际上并没有加入反对话题table;您在构建权限时已经有了您关心的主题 ID。
不良接口的示例是 class 接口,这些接口可以轻松地将权限 table 加入任意其他 SQL。
我知道你问的问题是 SQL 而不是 SQL 之上的特定框架,但是从 rails 约束名称看来你正在使用这样的一个框架,我认为利用它对您以后的代码可维护性很有用。
在 10,000 行的情况下,我认为这两种方法都可以。
我实际上不确定这些方法是否会完全不同。如果您考虑生成的查询计划,假设您从 table 中获取少量行,则可以使用与 or 完全相同的方式对每个 table 进行循环处理连接假设索引可能 return 少量行,则可能会处理查询。
我没有向 Postgres 提供一个合理的数据集来弄清楚它是否真的在给定真实数据集的情况下所做的。我有相当高的信心,如果这样做有意义的话,Postgres 足够聪明,可以做到这一点。
多态方法确实给了您更多的控制权,如果您 运行 遇到性能问题,您可能需要检查转向它是否有帮助。
如果您选择多态方法,我建议您编写代码来检查并确保您的数据是一致的。也就是说,确保 resource_type 和 resource_id 对应于系统中存在的实际资源。
在任何情况下,如果应用程序问题迫使您对数据进行非规范化,以至于数据库约束不足以强制执行一致性,我都会提出该建议。
如果您开始 运行 遇到性能问题,以下是您以后可能需要做的事情:
在您的应用程序中创建缓存,将对象(例如主题)映射到这些对象的权限集。
在您的应用程序中创建一个缓存,缓存给定用户(包括他们所属的组)对您应用程序中对象的所有权限。
具体化用户组权限。即创建一个物化视图,将 user_group 权限与用户权限和用户组成员资格相结合。
根据我的经验,真正会破坏权限系统性能的是当您添加诸如允许一个组成为另一个组的成员之类的内容时。到那时,您很快就会到达需要缓存或物化视图的地步。
不幸的是,如果没有实际的数据和查看真实的查询计划和实际性能,就很难给出更具体的建议。我认为,如果你为未来的变化做好准备,你会没事的。
您可以尝试在 3 个 table(用户、user_group、公司)中的每个权限字段中对多对多关系进行非规范化。
您可以使用此字段以JSON格式存储权限,并且仅用于阅读(SELECTs)。您仍然可以使用多对多 table 来更改特定用户、组和公司的权限,只需在它们上编写一个触发器,只要有新的更改,它就会更新非规范化的权限字段多对多 table。使用此解决方案,您仍然可以在 SELECT 秒内获得快速查询执行时间,同时保持关系规范化并符合数据库标准。
这是我为 mysql 编写的一个示例脚本,用于一对多关系,但类似的事情也适用于您的情况:
https://github.com/martintaleski/mysql-denormalization/blob/master/one-to-many.sql
我已经多次使用这种方法,当 SELECT 语句比 INSERT、UPDATE 和 DELETE 语句数量更多且更重要时,它就有意义了。
如果您不经常更改权限,实体化视图可能会大大加快您的搜索速度。我将在今天晚些时候根据您的设置准备一个示例,并将 post 它。之后,我们可以做一些基准测试。
然而,实体化视图需要在更改数据后更新实体化视图。因此该解决方案可能很快,但只有在基本数据不经常更改的情况下才会加快查询速度。
我正在使用 PostgreSQL 9.5(但可以升级到 9.6)。
我有权限 table:
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 table 用于多对多关联
-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'
假设我们在其他 table 中有大约 10000 多个权限和类似数量的记录。
我需要担心性能吗?
我的意思是...我有 4 LEFT OUTER JOIN
s,它应该 return 结果相当快(比如 <200ms)。
我正在考虑声明 1 "polymorphic" table,类似于:
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')
问题:
- 哪个选项是better/faster?也许有更好的方法来做到这一点?
a) 4 tables (permissions, companies_permissions, user_groups_permissions, users_permissions
)
b) 2 tables (permissions, permissables
)
是否需要在
permissions.item_type
上声明与btree
不同的索引?我是否需要每天 运行 几次
vacuum analyze
table 秒才能使索引正常工作(两种选择)?
编辑 1:
SQL小提琴示例:
- wildplasser 建议(来自评论),无效:http://sqlfiddle.com/#!15/9723f8/1
- 原始查询(4 tables):http://sqlfiddle.com/#!15/9723f8/2
{ 我还删除了错误位置的反引号,感谢@wildplasser }
也许这是一个显而易见的答案,但我认为带有 3 table 的选项应该没问题。 SQL 数据库擅长执行 join
操作并且您有 10,000 条记录 - 这根本不是大量数据,所以我不确定是什么让您认为会出现性能问题。
使用适当的索引(btree 应该没问题),它应该可以快速运行,实际上您可以更进一步并为您生成示例数据 tables 并查看您的查询实际上如何在真实环境中运行数据量。
我也不认为您需要担心 运行 手动吸尘之类的事情。
关于选项二,多态 table,它可能不是很好,因为您现在只有一个 resource_id
字段,它可以指向不同的 tables,这是一个来源的问题(例如,由于一个错误,你可以有一个 resource_type=User
和 resource_id 指向 Company
的记录 - table 结构没有阻止它)。
请注意:您没有说明 User、UserGroup 和 Company 之间的任何关系 - 如果它们也都相关,则可以仅使用用户 ID、加入 gropus 和公司来获取权限给用户。
还有一个:在许多 table 中你不需要 id
,如果你有它们也没什么不好的,但是有 permission_id
和user_id
并使它们成为复合主键。
我建议将对权限系统的所有访问权限抽象为几个模型 classes。不幸的是,我发现像这样的权限系统有时最终会成为性能瓶颈,而且我发现有时有必要显着重构您的数据表示。 因此,我的建议是尝试将与权限相关的查询隔离在几个 classes 中,并尝试使这些 classes 的接口独立于系统的其余部分。
这里的好方法示例就是您上面的内容。你实际上并没有加入反对话题table;您在构建权限时已经有了您关心的主题 ID。
不良接口的示例是 class 接口,这些接口可以轻松地将权限 table 加入任意其他 SQL。
我知道你问的问题是 SQL 而不是 SQL 之上的特定框架,但是从 rails 约束名称看来你正在使用这样的一个框架,我认为利用它对您以后的代码可维护性很有用。
在 10,000 行的情况下,我认为这两种方法都可以。 我实际上不确定这些方法是否会完全不同。如果您考虑生成的查询计划,假设您从 table 中获取少量行,则可以使用与 or 完全相同的方式对每个 table 进行循环处理连接假设索引可能 return 少量行,则可能会处理查询。 我没有向 Postgres 提供一个合理的数据集来弄清楚它是否真的在给定真实数据集的情况下所做的。我有相当高的信心,如果这样做有意义的话,Postgres 足够聪明,可以做到这一点。
多态方法确实给了您更多的控制权,如果您 运行 遇到性能问题,您可能需要检查转向它是否有帮助。 如果您选择多态方法,我建议您编写代码来检查并确保您的数据是一致的。也就是说,确保 resource_type 和 resource_id 对应于系统中存在的实际资源。 在任何情况下,如果应用程序问题迫使您对数据进行非规范化,以至于数据库约束不足以强制执行一致性,我都会提出该建议。
如果您开始 运行 遇到性能问题,以下是您以后可能需要做的事情:
在您的应用程序中创建缓存,将对象(例如主题)映射到这些对象的权限集。
在您的应用程序中创建一个缓存,缓存给定用户(包括他们所属的组)对您应用程序中对象的所有权限。
具体化用户组权限。即创建一个物化视图,将 user_group 权限与用户权限和用户组成员资格相结合。
根据我的经验,真正会破坏权限系统性能的是当您添加诸如允许一个组成为另一个组的成员之类的内容时。到那时,您很快就会到达需要缓存或物化视图的地步。
不幸的是,如果没有实际的数据和查看真实的查询计划和实际性能,就很难给出更具体的建议。我认为,如果你为未来的变化做好准备,你会没事的。
您可以尝试在 3 个 table(用户、user_group、公司)中的每个权限字段中对多对多关系进行非规范化。
您可以使用此字段以JSON格式存储权限,并且仅用于阅读(SELECTs)。您仍然可以使用多对多 table 来更改特定用户、组和公司的权限,只需在它们上编写一个触发器,只要有新的更改,它就会更新非规范化的权限字段多对多 table。使用此解决方案,您仍然可以在 SELECT 秒内获得快速查询执行时间,同时保持关系规范化并符合数据库标准。
这是我为 mysql 编写的一个示例脚本,用于一对多关系,但类似的事情也适用于您的情况:
https://github.com/martintaleski/mysql-denormalization/blob/master/one-to-many.sql
我已经多次使用这种方法,当 SELECT 语句比 INSERT、UPDATE 和 DELETE 语句数量更多且更重要时,它就有意义了。
如果您不经常更改权限,实体化视图可能会大大加快您的搜索速度。我将在今天晚些时候根据您的设置准备一个示例,并将 post 它。之后,我们可以做一些基准测试。
然而,实体化视图需要在更改数据后更新实体化视图。因此该解决方案可能很快,但只有在基本数据不经常更改的情况下才会加快查询速度。