10

Tôi đang sử dụng PostgreSQL 9.5 (nhưng nâng cấp có thể nói 9.6).PostgreSQL - "bảng đa hình" so với 3 bảng

Tôi có quyền bàn:

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 

Và 3 bảng cho nhiều-nhiều hiệp hội

-companies_permissions (+ chỉ số khai)

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 (+ tuyên bố chỉ số)

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 (chỉ số + Tờ khai)

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

tôi sẽ phải chạy truy vấn SQL như thế này rất nhiều lần:

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' 

Hãy nói rằng chúng tôi có khoảng 10000 cho phép và số tiền tương tự các bản ghi bên trong các bảng khác.

Tôi có cần phải lo lắng về hiệu suất không?

Ý tôi là ... Tôi có 4 LEFT OUTER JOIN s và nó sẽ trả lại kết quả khá nhanh (ví dụ: < 200ms).

Tôi đã suy nghĩ về tuyên bố 1 "đa hình" bảng, một cái gì đó như:

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 

Sau đó, tôi có thể chạy truy vấn như thế này:

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

CÂU HỎI:

  1. Tùy chọn nào tốt hơn/nhanh hơn? Có lẽ có cách nào tốt hơn để làm điều này?

a) 4 bảng (permissions, companies_permissions, user_groups_permissions, users_permissions) b) 2 bảng (permissions, permissables)

  1. Tôi có cần phải khai báo các chỉ số khác biệt so với btree trên permissions.item_type?

  2. Tôi có cần chạy vài lần mỗi ngày vacuum analyze cho các bảng để làm cho chỉ mục hoạt động (cả hai tùy chọn)?


EDIT1:

ví dụ SQLFiddle:

  1. wildplasser đề nghị (từ bình luận), không làm việc: http://sqlfiddle.com/#!15/9723f8/1
  2. Original truy vấn (4 bảng): http://sqlfiddle.com/#!15/9723f8/2

{Tôi cũng đã xóa các dấu backticks ở địa điểm sai nhờ @wildplasser}

+2

Cố gắng di chuyển các điều kiện đề cập đến các bảng LEFT JOINed sang phần ON .... Điều này sẽ tránh tất cả các 'OR NULL' xấu xí trong mệnh đề WHERE. Và không sử dụng backticks cho literals. – wildplasser

+0

@wildplasser cảm ơn bạn đã trả lời. vâng tôi biết về backtips (OS X thêm chúng thay vì dấu nháy đơn) ... Tôi đã thử giải pháp của bạn - nhưng tôi nhận được kết quả kết hợp. SQLfiddle -> http://sqlfiddle.com/#!15/9723f8/1 –

+0

Trong khi phiên bản 'cũ' hoạt động - http://sqlfiddle.com/#!15/9723f8/2. C 'NG 'INNER JOIN' sẽ không hoạt động ở đây. Tôi cần phải nhận được một cái gì đó giống như trong phiên bản gốc đó là '(điều kiện OR condition2 HOẶC condition3)'. –

Trả lời

3

Tôi khuyên bạn nên tóm tắt tất cả quyền truy cập vào hệ thống quyền của bạn thành một vài lớp mô hình. Thật không may, tôi đã thấy rằng hệ thống cho phép như thế này đôi khi kết thúc là tắc nghẽn hiệu suất, và tôi thấy rằng đôi khi cần thiết để tái cấu trúc đáng kể dữ liệu đại diện của bạn. Vì vậy, đề xuất của tôi là cố gắng giữ các truy vấn liên quan đến quyền được phân lập trong một vài lớp và cố gắng giữ giao diện cho các lớp đó độc lập với phần còn lại của hệ thống.

Ví dụ về các cách tiếp cận tốt ở đây là những gì bạn có ở trên. Bạn không thực sự tham gia vào bảng chủ đề; bạn đã có các ID chủ đề mà bạn quan tâm khi xây dựng các quyền.

Ví dụ về giao diện kém sẽ là các giao diện lớp giúp dễ dàng tham gia các bảng quyền vào SQL khác tùy ý.

Tôi hiểu rằng bạn đã đặt câu hỏi dưới dạng SQL chứ không phải là một khung cụ thể trên SQL, nhưng từ tên giới hạn đường ray, có vẻ như bạn đang sử dụng một khung như vậy. để duy trì mã trong tương lai của bạn.

Trong trường hợp 10.000 hàng, tôi nghĩ rằng một trong hai cách tiếp cận sẽ hoạt động tốt. Tôi không thực sự chắc chắn rằng các phương pháp tiếp cận sẽ khác nhau. Nếu bạn nghĩ về kế hoạch truy vấn được tạo ra, giả sử bạn đang nhận được một số lượng nhỏ các hàng từ bảng, thì phép nối có thể được xử lý với một vòng lặp đối với mỗi bảng theo cách chính xác giống như truy vấn hoặc có thể được xử lý giả định rằng chỉ mục có khả năng trả lại một số lượng nhỏ các hàng. Tôi đã không cung cấp một bộ dữ liệu hợp lý vào Postgres để tìm hiểu xem đó là những gì nó thực sự đưa ra một bộ dữ liệu thực sự. Tôi có độ tin cậy khá cao rằng Postgres đủ thông minh để làm điều đó nếu có ý nghĩa.

Cách tiếp cận đa hình giúp bạn kiểm soát nhiều hơn một chút và nếu bạn gặp phải các vấn đề về hiệu suất, bạn có thể muốn kiểm tra xem việc di chuyển đến nó có giúp ích gì không. Nếu bạn chọn phương pháp đa hình, tôi khuyên bạn nên viết mã để đi qua và kiểm tra để đảm bảo rằng dữ liệu của bạn nhất quán. Tức là, đảm bảo rằng resource_type và resource_id tương ứng với các tài nguyên thực sự tồn tại trong hệ thống của bạn. Tôi muốn đưa ra đề xuất đó trong bất kỳ trường hợp nào mà các mối quan tâm của ứng dụng buộc bạn phải chuẩn hóa dữ liệu của mình sao cho các ràng buộc cơ sở dữ liệu không đủ để thực thi tính nhất quán.

Nếu bạn bắt đầu chạy vào vấn đề hiệu suất, đây là những loại điều bạn có thể cần phải làm trong tương lai:

  • Tạo một bộ nhớ cache trong các đối tượng bản đồ ứng dụng của bạn (chẳng hạn như chủ đề) vào tập quyền cho các đối tượng đó.

  • Tạo bộ nhớ cache trong ứng dụng lưu vào bộ nhớ cache tất cả các quyền mà người dùng đã cho có (bao gồm các nhóm họ là thành viên) cho các đối tượng trong ứng dụng của bạn.

  • Vật chất hóa quyền của nhóm người dùng. Đó là tạo ra một khung nhìn vật chất kết hợp các quyền user_group với các quyền của người dùng và các thành viên nhóm người dùng.

Trong kinh nghiệm của tôi, điều thực sự giết hiệu suất của hệ thống cấp phép là khi bạn thêm thứ gì đó như cho phép một nhóm làm thành viên của một nhóm khác. Tại thời điểm đó bạn rất nhanh chóng có được một điểm mà bạn cần bộ nhớ đệm hoặc quan điểm vật chất.

Thật không may, thật khó để đưa ra lời khuyên cụ thể hơn mà không thực sự có dữ liệu của bạn và xem xét các kế hoạch truy vấn thực và hiệu suất thực. Tôi nghĩ rằng nếu bạn chuẩn bị cho những thay đổi trong tương lai, bạn sẽ ổn thôi.

4

Có thể đó là câu trả lời rõ ràng, nhưng tôi nghĩ tùy chọn có 3 bảng sẽ ổn. Cơ sở dữ liệu SQL rất giỏi khi thực hiện các hoạt động join và bạn có 10.000 bản ghi - đây không phải là một lượng lớn dữ liệu, vì vậy tôi không chắc chắn điều gì làm cho bạn nghĩ rằng sẽ có vấn đề về hiệu suất.

Với chỉ mục thích hợp (btree nên OK), nó sẽ hoạt động nhanh và thực sự bạn có thể tiến xa hơn và tạo dữ liệu mẫu cho bảng và xem truy vấn của bạn thực sự hoạt động như thế nào trên số lượng dữ liệu thực.

Tôi cũng không nghĩ rằng bạn sẽ cần phải lo lắng về điều gì đó giống như chạy chân không theo cách thủ công. Về các tùy chọn hai, bảng đa hình, nó có thể không phải là rất tốt vì bây giờ bạn có một lĩnh vực resource_id duy nhất mà có thể chỉ ra các bảng khác nhau mà là một nguồn của các vấn đề (ví dụ, do một lỗi bạn có thể có một bản ghi với resource_type = User và resource_id trỏ đến Company - cấu trúc bảng không ngăn được). Một lưu ý nữa: bạn không kể bất cứ điều gì về quan hệ giữa Người dùng, UserGropup và Công ty - nếu tất cả họ đều liên quan, có thể lấy quyền chỉ bằng cách sử dụng id người dùng, tham gia cũng gropus và các công ty để người dùng.

Và một thứ nữa: bạn không cần id s trong nhiều bảng, không có gì xấu xảy ra nếu bạn có chúng, nhưng đủ để có permission_iduser_id và biến chúng thành khóa chính kết hợp.

+0

Tôi đồng ý. Một điều nữa: nếu bạn có một chỉ mục trên '(permission_id, company_id)', bạn không cần một chỉ mục trên '(permission_id)'. –

2

Bạn có thể cố gắng không chuẩn hóa quan hệ nhiều-nhiều trong trường quyền trên mỗi bảng trong 3 bảng (người dùng, user_group, công ty).

Bạn có thể sử dụng trường này để lưu trữ quyền trong định dạng JSON và chỉ sử dụng nó để đọc (SELECT). Bạn vẫn có thể sử dụng các bảng nhiều thành viên để thay đổi quyền của người dùng, nhóm và công ty cụ thể, chỉ cần viết trình kích hoạt cho họ, sẽ cập nhật trường quyền cho phép bất thường bất cứ khi nào có thay đổi mới trên nhiều người dùng bàn. Với giải pháp này, bạn sẽ vẫn nhận được thời gian thực hiện truy vấn nhanh trên các SELECT, đồng thời giữ mối quan hệ bình thường hóa và tuân thủ các tiêu chuẩn cơ sở dữ liệu.

Dưới đây là một kịch bản ví dụ, mà tôi đã viết cho mysql cho một mối quan hệ một-nhiều, nhưng một điều tương tự có thể được áp dụng cho trường hợp của bạn cũng như:

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

Tôi đã sử dụng phương pháp này nhiều lần, và nó có ý nghĩa khi các câu lệnh SELECT đông hơn và quan trọng hơn các câu lệnh INSERT, UPDATE và DELETE.

2

Trong trường hợp bạn không thường xuyên thay đổi quyền của mình, lượt xem được thực hiện có thể tăng tốc độ tìm kiếm của bạn rất nhiều. Tôi sẽ chuẩn bị một ví dụ dựa trên cài đặt của bạn sau ngày hôm nay và sẽ đăng nó. Sau đó, chúng ta có thể làm một số điểm chuẩn.

Tuy nhiên, lượt xem vật chất yêu cầu cập nhật chế độ xem vật hoá sau khi thay đổi dữ liệu. Vì vậy, giải pháp đó có thể nhanh, nhưng sẽ tăng tốc truy vấn của bạn chỉ khi dữ liệu cơ bản không được thay đổi thường xuyên.

Các vấn đề liên quan