PostgreSQL row-level security cho SaaS giúp thực thi cô lập tenant trong cơ sở dữ liệu. Tìm hiểu khi nào nên dùng, cách viết policy và những điều cần tránh.

Trong một app SaaS, lỗi bảo mật nguy hiểm nhất là lỗi xuất hiện khi bạn đã scale. Bạn bắt đầu với một quy tắc đơn giản như “người dùng chỉ thấy dữ liệu của tenant họ”, rồi nhanh chóng đưa ra endpoint mới, thêm một truy vấn báo cáo, hoặc giới thiệu một join vô tình bỏ qua kiểm tra.
Ủy quyền chỉ ở phía app sẽ vỡ dưới áp lực vì quy tắc bị rải rác. Một controller kiểm tra tenant_id, controller khác kiểm tra membership, một job nền quên, và một đường dẫn “admin export” giữ trạng thái “tạm thời” trong nhiều tháng. Ngay cả các đội cẩn thận cũng bỏ sót chỗ.
PostgreSQL row-level security (RLS) giải quyết một vấn đề cụ thể: bắt cơ sở dữ liệu phải thực thi hàng nào được nhìn thấy cho một request. Mô hình tư duy đơn giản: mọi SELECT, UPDATE, và DELETE đều bị lọc tự động bởi các policy, giống như mọi request bị lọc bởi middleware xác thực.
Phần “row” quan trọng. RLS không bảo vệ mọi thứ:
Một ví dụ cụ thể: bạn thêm endpoint liệt kê projects với join tới invoices cho dashboard. Với ủy quyền chỉ phía app, bạn dễ lọc projects theo tenant nhưng quên lọc invoices, hoặc join trên một khóa làm chéo tenant. Với RLS, cả hai bảng có thể thực thi cô lập tenant, nên truy vấn sẽ fail-safe thay vì rò rỉ dữ liệu.
Đổi chác có thật. Bạn viết ít mã ủy quyền lặp lại hơn và giảm số chỗ có thể rò rỉ. Nhưng bạn cũng phải làm việc mới: thiết kế policy cẩn thận, test sớm, và chấp nhận rằng một policy có thể chặn truy vấn bạn mong đợi chạy.
RLS có thể cảm thấy là việc thêm vào cho tới khi app của bạn vượt quá vài endpoint. Nếu bạn có ranh giới tenant nghiêm ngặt và nhiều đường dẫn truy vấn (màn hình danh sách, tìm kiếm, xuất dữ liệu, công cụ admin), đặt quy tắc trong DB nghĩa là bạn không phải nhớ thêm cùng bộ lọc ở mọi chỗ.
RLS phù hợp mạnh khi quy tắc buồn tẻ và phổ quát: “người dùng chỉ thấy hàng của tenant họ” hoặc “người dùng chỉ thấy project mà họ là thành viên”. Trong những thiết lập đó, policy giảm sai sót vì mọi SELECT, UPDATE, DELETE đều đi qua cùng cổng, ngay cả khi truy vấn được thêm sau này.
Nó cũng hữu ích trong các app đọc nhiều nơi mà logic lọc nhất quán. Nếu API của bạn có 15 cách khác nhau để tải invoices (theo trạng thái, theo ngày, theo khách hàng, theo tìm kiếm), RLS cho phép bạn dừng việc cài đặt lại lọc tenant trên từng truy vấn và tập trung vào tính năng.
RLS gây phiền khi các quy tắc không dựa trên hàng. Các quy tắc theo trường như “bạn có thể thấy salary nhưng không thấy bonus” hoặc “che cột này trừ khi bạn là HR” thường biến thành SQL vụng về và ngoại lệ khó bảo trì.
Nó cũng không phù hợp cho báo cáo nặng thực sự cần truy cập rộng. Đội thường tạo các role bypass cho “chỉ job này”, và đó là nơi sai sót tích tụ.
Trước khi cam kết, quyết định liệu bạn muốn DB là cổng cuối cùng hay không. Nếu có, lập kế hoạch kỷ luật: test hành vi DB (không chỉ phản hồi API), coi migration là thay đổi bảo mật, tránh bypass tạm thời, quyết định cách job nền xác thực, và giữ policy nhỏ gọn và có thể tái dùng.
Nếu bạn dùng công cụ tạo backend, nó có thể tăng tốc delivery, nhưng không loại trừ nhu cầu về roles rõ ràng, tests và mô hình tenant đơn giản. (Ví dụ, Koder.ai dùng Go và PostgreSQL cho backend sinh tự động, và bạn vẫn muốn thiết kế RLS một cách có chủ ý thay vì “rắc nó vào sau”.)
RLS dễ nhất khi schema của bạn đã rõ ràng ai sở hữu gì. Nếu bạn bắt đầu với mô hình mơ hồ và cố “fix trong policy”, thường bạn sẽ gặp truy vấn chậm và bug khó hiểu.
Chọn một khóa tenant duy nhất (như org_id) và dùng nhất quán. Hầu hết các bảng thuộc tenant nên có nó, ngay cả khi chúng tham chiếu tới bảng khác đã có org_id. Điều này tránh join bên trong policy và giữ các kiểm tra USING đơn giản.
Một quy tắc thực tế: nếu một hàng nên biến mất khi khách hàng hủy, nó có lẽ cần org_id.
Policy RLS thường trả lời một câu hỏi: “Người này có phải là thành viên của org này, và họ có thể làm gì?” Khó suy ra điều đó từ các cột ad hoc.
Giữ các bảng lõi nhỏ và đơn giản:
users (một hàng cho mỗi người)orgs (một hàng cho mỗi tenant)org_memberships (user_id, org_id, role, status)project_memberships cho truy cập theo projectVới cấu trúc đó, policy có thể kiểm tra membership bằng một lookup có index.
Không phải mọi thứ đều cần org_id. Bảng tham chiếu như countries, product categories, hay plan types thường được chia sẻ giữa các tenant. Làm chúng chỉ đọc cho hầu hết vai trò, và đừng gắn chúng với một org cụ thể.
Dữ liệu thuộc tenant (projects, invoices, tickets) nên tránh kéo chi tiết tenant qua các bảng chia sẻ. Giữ bảng chia sẻ tối thiểu và ổn định.
Foreign keys vẫn hoạt động với RLS, nhưng deletes có thể làm bạn ngạc nhiên nếu role thực hiện xóa không “thấy” các hàng phụ thuộc. Lập kế hoạch cascade cẩn thận và test luồng xóa thực tế.
Index các cột mà policy lọc, đặc biệt org_id và các khóa membership. Một policy đọc như WHERE org_id = ... không nên trở thành quét toàn bảng khi bảng có hàng triệu dòng.
RLS là một công tắc per-table. Khi bật, PostgreSQL ngừng tin mã app sẽ nhớ bộ lọc tenant. Mọi SELECT, UPDATE, DELETE bị lọc bởi policy, và mọi INSERT, UPDATE bị xác thực bởi policy.
Thay đổi tư duy lớn nhất: khi bật RLS, các truy vấn từng trả về dữ liệu có thể bắt đầu trả về 0 hàng mà không báo lỗi. Đó là PostgreSQL thực thi kiểm soát truy cập.
Policy là các quy tắc nhỏ gắn với bảng. Chúng dùng hai kiểm tra:
USING là bộ lọc đọc. Nếu một hàng không khớp USING, nó vô hình cho SELECT, và không thể là mục tiêu của UPDATE hay DELETE.WITH CHECK là cửa cho ghi. Nó quyết định các hàng mới hoặc đã thay đổi được phép cho INSERT hoặc UPDATE.Một mẫu SaaS phổ biến: USING đảm bảo bạn chỉ thấy hàng từ tenant của bạn, và WITH CHECK đảm bảo bạn không chèn hàng vào tenant người khác bằng cách đoán tenant_id.
Khi bạn thêm nhiều policy sau này, điều này quan trọng:
PERMISSIVE (mặc định): một hàng được cho phép nếu bất kỳ policy nào cho phép nó.RESTRICTIVE: một hàng chỉ được cho phép nếu tất cả policy restrictive cho phép nó (bên cạnh hành vi permissive).Nếu bạn dự định xếp lớp các quy tắc như khớp tenant cộng thêm kiểm tra role và membership project, policy restrictive có thể làm ý định rõ hơn, nhưng cũng dễ khiến bạn tự khóa nếu quên một điều kiện.
RLS cần một giá trị “ai đang gọi” đáng tin cậy. Các lựa chọn phổ biến:
app.user_id và app.tenant_id).SET ROLE ...) theo request, có thể hoạt động nhưng tăng chi phí vận hành.Chọn một cách và áp dụng khắp nơi. Trộn nguồn identity giữa các dịch vụ là con đường nhanh dẫn tới bug khó hiểu.
Dùng quy ước dễ đoán để dump schema và log dễ đọc. Ví dụ: {table}__{action}__{rule}, như projects__select__tenant_match.
Nếu bạn mới với RLS, bắt đầu với một bảng và một proof nhỏ. Mục tiêu không phải phủ kín hoàn hảo. Mục tiêu là khiến DB từ chối truy cập chéo-tenant ngay cả khi app bị lỗi.
Giả sử bảng projects đơn giản. Trước tiên, thêm tenant_id theo cách không làm hỏng ghi.
ALTER TABLE projects ADD COLUMN tenant_id uuid;
-- Backfill existing rows (example: everyone belongs to a default tenant)
UPDATE projects SET tenant_id = '11111111-1111-1111-1111-111111111111'::uuid
WHERE tenant_id IS NULL;
ALTER TABLE projects ALTER COLUMN tenant_id SET NOT NULL;
Tiếp theo, tách ownership khỏi access. Mẫu phổ biến: một role sở hữu bảng (app_owner), role khác dùng bởi API (app_user). Role API không nên là owner bảng, nếu không nó có thể bypass policy.
ALTER TABLE projects OWNER TO app_owner;
REVOKE ALL ON projects FROM PUBLIC;
GRANT SELECT, INSERT, UPDATE, DELETE ON projects TO app_user;
Bây giờ quyết định cách request báo cho Postgres biết đang phục vụ tenant nào. Một cách đơn giản là setting theo request. App đặt nó ngay sau khi mở transaction.
-- inside the same transaction as the request
SELECT set_config('app.current_tenant', '22222222-2222-2222-2222-222222222222', true);
Bật RLS và bắt đầu với quyền đọc.
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
CREATE POLICY projects_tenant_select
ON projects
FOR SELECT
TO app_user
USING (tenant_id = current_setting('app.current_tenant')::uuid);
Chứng minh nó hoạt động bằng thử hai tenant khác nhau và kiểm tra số hàng thay đổi.
Policy đọc không bảo vệ ghi. Thêm WITH CHECK để chặn inserts và updates chui vào tenant sai.
CREATE POLICY projects_tenant_write
ON projects
FOR INSERT, UPDATE
TO app_user
WITH CHECK (tenant_id = current_setting('app.current_tenant')::uuid);
Một cách nhanh để kiểm tra hành vi (bao gồm failure) là giữ một script SQL nhỏ bạn có thể chạy lại sau mỗi migration:
BEGIN; SET LOCAL ROLE app_user;SELECT set_config('app.current_tenant', '\u003ctenant A\u003e', true); SELECT count(*) FROM projects;INSERT INTO projects(id, tenant_id, name) VALUES (gen_random_uuid(), '\u003ctenant B\u003e', 'bad'); (sẽ fail)UPDATE projects SET tenant_id = '\u003ctenant B\u003e' WHERE ...; (sẽ fail)ROLLBACK;Nếu bạn chạy script đó và nhận kết quả giống nhau mỗi lần, bạn có một baseline tin cậy trước khi mở rộng RLS sang bảng khác.
Hầu hết đội bắt đầu dùng RLS vì chán lặp lại cùng kiểm tra ủy quyền trong mọi truy vấn. Tin tốt là hình dạng policy cần thường nhất quán.
Một số bảng tự nhiên thuộc sở hữu của một user (notes, API tokens). Những bảng khác thuộc tenant mà truy cập phụ thuộc vào membership. Xem hai loại này là hai mẫu khác nhau.
Với dữ liệu owned-by-owner, policy thường kiểm tra created_by = app_user_id(). Với dữ liệu tenant, policy thường kiểm tra user có một hàng membership cho org hay không.
Một cách thực tế để giữ policy dễ đọc là tập trung identity vào các helper SQL nhỏ và tái dùng chúng:
-- Example helpers
create function app_user_id() returns uuid
language sql stable as $$
select current_setting('app.user_id', true)::uuid
$$;
create function app_is_admin() returns boolean
language sql stable as $$
select current_setting('app.is_admin', true) = 'true'
$$;
Đọc thường rộng hơn ghi. Ví dụ, bất kỳ thành viên org nào cũng có thể SELECT projects, nhưng chỉ editors mới UPDATE, và chỉ owners mới DELETE.
Giữ rõ ràng: một policy cho SELECT (membership), một policy cho INSERT/UPDATE với WITH CHECK (role), và một policy cho DELETE (thường nghiêm ngặt hơn update).
Tránh “tắt RLS cho admin.” Thay vào đó, thêm một cửa thoát bên trong policy, như app_is_admin(), để bạn không vô tình cấp quyền toàn bộ cho một role dịch vụ chung.
Nếu bạn dùng deleted_at hoặc status, đưa nó vào policy SELECT (deleted_at is null). Nếu không, ai đó có thể “hồi sinh” hàng bằng cách đổi flag mà app tưởng là cuối cùng.
WITH CHECK thân thiệnINSERT ... ON CONFLICT DO UPDATE phải thỏa WITH CHECK cho hàng sau khi ghi. Nếu policy yêu cầu created_by = app_user_id(), đảm bảo upsert đặt created_by lúc insert và không ghi đè nó lúc update.
Nếu bạn sinh code backend, các mẫu này đáng được biến thành template nội bộ để bảng mới bắt đầu với mặc định an toàn thay vì một canvas trống.
RLS tuyệt vời cho tới khi một chi tiết nhỏ làm nó trông như PostgreSQL “ngẫu nhiên” ẩn hoặc hiển thị dữ liệu. Những lỗi dưới đây tiêu tốn nhiều thời gian nhất.
Cạm bẫy đầu tiên là quên WITH CHECK trên insert và update. USING kiểm soát những gì bạn có thể thấy, không phải những gì bạn được phép tạo. Nếu thiếu WITH CHECK, một lỗi app có thể ghi hàng vào tenant sai, và bạn có thể không biết vì cùng user đó không đọc được hàng đó.
Một rò rỉ phổ biến khác là “leaky join.” Bạn lọc projects đúng, rồi join tới invoices, notes hoặc files chưa được bảo vệ giống vậy. Cách sửa nghiêm ngặt nhưng đơn giản: mọi bảng có thể tiết lộ dữ liệu tenant cần policy riêng, và views không nên dựa vào chỉ một bảng được an toàn.
Các mẫu lỗi thường xuất hiện sớm:
WITH CHECK.Policy tham chiếu cùng bảng (trực tiếp hoặc qua view) có thể tạo ra đệ quy bất ngờ. Một policy có thể kiểm tra membership bằng cách query view đọc bảng được bảo vệ nữa, dẫn tới lỗi, truy vấn chậm, hoặc policy không bao giờ match.
Cấu hình role là nguồn nhầm lẫn khác. Owner bảng và role nâng cao có thể bypass RLS, nên test của bạn pass trong khi user thực không thể (hoặc ngược lại). Luôn test với cùng role quyền thấp mà app dùng.
Cẩn trọng với SECURITY DEFINER functions. Chúng chạy với quyền owner của function, nên helper như current_tenant_id() có thể ổn, nhưng một function “tiện lợi” đọc dữ liệu có thể vô tình đọc chéo tenant nếu bạn không thiết kế để tôn trọng RLS.
Cũng đặt search_path an toàn trong security definer functions. Nếu không, function có thể lấy object khác cùng tên, và logic policy của bạn có thể im lặng trỏ vào thứ sai tùy session state.
Bug RLS thường là do thiếu bối cảnh, không phải “SQL sai.” Một policy có thể đúng trên giấy nhưng vẫn fail vì session role khác bạn nghĩ, hoặc request không đặt tenant và user mà policy dựa vào.
Cách tin cậy để tái hiện một báo cáo production là mô phỏng cùng setup session cục bộ và chạy đúng query. Thường có nghĩa là:
SET ROLE app_user; (hoặc role API thực tế)SELECT set_config('app.tenant_id', 't_123', true); và SELECT set_config('app.user_id', 'u_456', true);SELECT current_user, current_setting('app.tenant_id', true), current_setting('app.user_id', true);Khi bạn không chắc policy nào được áp dụng, kiểm tra catalog thay vì đoán. pg_policies hiển thị mỗi policy, lệnh, và biểu thức USING/WITH CHECK. Kết hợp với pg_class để xác nhận RLS bật trên bảng và không bị bypass.
Vấn đề hiệu năng có thể trông như vấn đề auth. Một policy join table membership hoặc gọi function có thể đúng nhưng chậm khi bảng lớn lên. Dùng EXPLAIN (ANALYZE, BUFFERS) trên query tái hiện và tìm sequential scans, nested loops bất ngờ, hoặc các filter áp muộn. Thiếu index trên (tenant_id, user_id) và bảng membership là nguyên nhân thường gặp.
Cũng nên log ba giá trị mỗi request ở lớp app: tenant ID, user ID, và role DB dùng cho request. Khi những giá trị đó không khớp bạn nghĩ, RLS sẽ hành xử “sai” vì input sai.
Cho tests, giữ vài tenant seed và làm rõ failure. Một suite nhỏ thường bao gồm: “Tenant A không thể đọc Tenant B”, “user không có membership không thấy project”, “owner có thể update, viewer không”, “insert bị chặn trừ khi tenant_id khớp context”, và “override admin chỉ áp dụng nơi dự định”.
Xem RLS như dây an toàn, không phải feature có thể bật/tắt. Sai sót nhỏ biến thành “mọi người thấy dữ liệu của mọi người” hoặc “mọi thứ trả về 0 hàng.”
Đảm bảo thiết kế bảng và quy tắc policy khớp mô hình tenant.
tenant_id). Nếu không, ghi rõ lý do (ví dụ bảng tham chiếu toàn cục).FORCE ROW LEVEL SECURITY trên những bảng đó.USING. Writes phải có WITH CHECK để inserts/updates không thể chuyển row sang tenant khác.tenant_id hoặc join qua bảng membership, thêm index phù hợp.Một kịch bản sanity đơn giản: user tenant A có thể đọc invoices của họ, chỉ có thể chèn invoice cho tenant A, và không thể update invoice để thay đổi tenant_id.
RLS chỉ mạnh khi roles app dùng phù hợp.
bypassrls.Hình dung một app B2B nơi công ty (orgs) có projects, và projects có tasks. Users có thể thuộc nhiều org, và user có thể là thành viên của một số projects mà không phải tất cả. Đây là một fit tốt cho RLS vì DB có thể thi hành cô lập tenant ngay cả khi một endpoint API quên bộ lọc.
Mô hình đơn giản: orgs, users, org_memberships (org_id, user_id, role), projects (id, org_id), project_memberships (project_id, user_id), tasks (id, project_id, org_id, ...). Việc có org_id trên tasks là có chủ ý. Nó giữ policy đơn giản và giảm bất ngờ khi join.
Rò rỉ kinh điển xảy ra khi tasks chỉ có project_id, và policy kiểm tra quyền thông qua join tới projects. Một lỗi (policy permissive trên projects, một join bỏ điều kiện, hoặc một view thay đổi context) có thể phơi bày tasks từ org khác.
Một đường chuyển an toàn tránh phá traffic production:
org_id cho tasks, thêm bảng membership).tasks.org_id từ projects.org_id, rồi thêm NOT NULL.Truy cập support thường xử lý tốt nhất bằng một role break-glass hẹp, không phải bằng tắt RLS. Giữ nó tách biệt khỏi tài khoản support bình thường và làm rõ khi nào được dùng.
Ghi chép các quy tắc để policy không trôi: session variables cần set là gì (user_id, org_id), bảng nào phải mang org_id, “member” nghĩa là gì, và vài ví dụ SQL nên trả về 0 hàng khi chạy dưới org sai.
RLS dễ sống chung khi bạn đối xử nó như một thay đổi sản phẩm. Triển khai theo từng phần nhỏ, chứng minh hành vi bằng tests, và giữ hồ sơ rõ ràng lý do mỗi policy tồn tại.
Kế hoạch rollout thường hiệu quả:
projects) và khóa nó.Sau khi bảng đầu ổn, làm các thay đổi policy có chủ ý. Thêm bước review policy vào migrations, và kèm một ghi chú ngắn về ý định (ai nên truy cập gì và vì sao) cùng cập nhật test tương ứng. Điều này ngăn “thêm OR cho xong” dần dần biến thành lỗ hổng.
Nếu bạn di chuyển nhanh, các công cụ như Koder.ai (koder.ai) có thể giúp sinh Go + PostgreSQL khởi điểm qua chat, rồi bạn có thể xếp các chính sách RLS và tests lên trên với cùng kỷ luật như backend viết tay.
Cuối cùng, giữ các lan can an toàn trong suốt rollout. Chụp snapshot trước migration policy, luyện rollback đến khi thấy nhàm chán, và giữ một đường break-glass nhỏ cho support mà không tắt RLS cho toàn hệ thống.
RLS bắt buộc PostgreSQL phải quyết định hàng nào có thể nhìn thấy hoặc ghi cho một request, nên việc cô lập tenant không phụ thuộc vào việc mọi endpoint đều nhớ thêm WHERE tenant_id = .... Lợi ích chính là giảm các lỗi “quên lọc” khi ứng dụng mở rộng và truy vấn nhân lên.
Đáng để dùng khi quy tắc truy cập đồng nhất và dựa trên hàng, như cô lập tenant hoặc truy cập theo membership, và khi bạn có nhiều đường dẫn truy vấn (tìm kiếm, xuất dữ liệu, giao diện admin, job nền). Thường không đáng với các quy tắc theo trường (per-field) phức tạp, nhiều ngoại lệ, hoặc các báo cáo cần đọc ngang tenant.
RLS xử lý hiển thị hàng và kiểm soát ghi cơ bản. Quyền truy cập cột thường cần views hoặc quyền cột, và các quy tắc nghiệp vụ phức tạp (ví dụ: ownership thanh toán, luồng phê duyệt) vẫn thuộc logic ứng dụng hoặc các ràng buộc cơ sở dữ liệu được thiết kế cẩn thận.
Tạo một role quyền thấp cho API (không phải owner bảng), bật RLS, rồi thêm chính sách SELECT và chính sách INSERT/UPDATE với WITH CHECK. Dùng một biến session theo request (ví dụ app.current_tenant) và xác minh rằng đổi giá trị này làm thay đổi hàng bạn thấy và có thể viết.
Một mặc định phổ biến là biến session theo request, được đặt ngay khi bắt đầu transaction, như app.tenant_id và app.user_id. Điều quan trọng là nhất quán: mọi đường dẫn mã (web requests, jobs, script) phải đặt cùng các giá trị mà policy kỳ vọng, nếu không bạn sẽ thấy hành vi “trả về 0 hàng” gây khó hiểu.
USING quyết định bộ lọc đọc. Nếu một hàng không thỏa USING, nó sẽ vô hình cho và không thể là target của /. là cửa cho ghi: nó quyết định các hàng mới hoặc thay đổi được phép khi hoặc . ngăn chặn việc chèn/đổi row vào tenant khác kể cả khi app truyền sai.
Bởi vì nếu chỉ thêm USING, endpoint bị lỗi vẫn có thể INSERT hoặc UPDATE hàng vào tenant khác, và bạn có thể không nhận ra vì cùng user đó không thể đọc hàng lỗi đó. Luôn ghép quy tắc đọc với WITH CHECK tương ứng cho ghi để dữ liệu xấu không được tạo ra ngay từ đầu.
Tránh join bên trong policy bằng cách đặt khóa tenant (ví dụ org_id) trực tiếp trên các bảng thuộc tenant, ngay cả khi chúng tham chiếu bảng khác đã có org_id. Thêm bảng membership rõ ràng (org_memberships, tùy chọn project_memberships) để policy chỉ cần một lookup có index thay vì suy luận phức tạp.
Đầu tiên, tái tạo cùng ngữ cảnh session mà app dùng bằng cách đặt cùng role và session settings, rồi chạy đúng SQL query. Xác nhận RLS đang bật và kiểm tra pg_policies để xem USING và WITH CHECK nào áp dụng, vì lỗi RLS thường do thiếu bối cảnh identity hơn là “SQL sai”.
Có. Xem mã sinh như điểm khởi đầu, không phải hệ thống an ninh hoàn chỉnh. Nếu dùng Koder.ai để sinh backend Go + PostgreSQL, bạn vẫn phải định nghĩa mô hình tenant, đặt identity session nhất quán, và thêm policies cùng tests có chủ ý để các bảng mới không được phát hành thiếu bảo vệ.
SELECTUPDATEDELETEWITH CHECKINSERTUPDATEWITH CHECKtenant_id