之前有朋友提到資料的隔離,這裡先介紹一種傳統SQL的隔離:RLS的隔離。
1. 應用層:在SESSION_CONTEXT中設定 TenantId
首先,使用資料庫用戶端庫的資料相關路由 API 連接配接到分片資料庫。應用程式仍必須告知資料庫哪個 TenantId 正在使用連接配接。TenantId 告知 RLS 安全政策哪些行必須篩選為屬于其他租戶。将目前租戶 ID 存儲在連接配接的SESSION_CONTEXT中。
SESSION_CONTEXT的替代方法是使用CONTEXT_INFO。但SESSION_CONTEXT是更好的選擇。SESSION_CONTEXT更易于使用,但它預設傳回 NULL,并且支援鍵值對。zai
在傳回connection前把SESSION_CONTEXT附加到connection上:
cmd.CommandText =
@"exec sp_set_session_context
@key=N'TenantId', @value=@shardingKey";
cmd.Parameters.AddWithValue("@shardingKey", shardingKey);
cmd.ExecuteNonQuery();
2. 資料層:建立行級别安全政策
建立安全政策以篩選每個租戶可以通路的行
現在,應用程式在查詢之前使用目前 TenantId 設定SESSION_CONTEXT,RLS 安全政策可以篩選查詢并排除具有不同 TenantId 的行。
RLS 在 Transact-SQL 中實作。使用者定義的函數定義通路邏輯,安全政策将此函數綁定到任意數量的表。對于此項目:
- 該函數驗證應用程式是否已連接配接到資料庫,以及存儲在SESSION_CONTEXT中的 TenantId 是否與給定行的 TenantId 比對。
- 應用程式已連接配接,而不是其他某個 SQL 使用者。
- 篩選器謂詞允許滿足 TenantId 篩選器的行通過選擇、更新和删除查詢。
- BLOCK 謂詞可防止未通過篩選器的行入或更新。
- 如果尚未設定SESSION_CONTEXT,則該函數傳回 NULL,并且沒有行可見或無法插入。
若要在所有分片上啟用 RLS,可以看一下此 T-SQL。建立一個policy 去過濾資料。
CREATE SCHEMA rls; -- Separate schema to organize RLS objects.
GO
CREATE FUNCTION rls.fn_tenantAccessPredicate(@TenantId int)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS fn_accessResult
-- Use the user in your application's connection string.
-- Here we use 'dbo' only for demo purposes!
WHERE DATABASE_PRINCIPAL_ID() = DATABASE_PRINCIPAL_ID('dbo')
AND CAST(SESSION_CONTEXT(N'TenantId') AS int) = @TenantId;
GO
CREATE SECURITY POLICY rls.tenantAccessPolicy
ADD FILTER PREDICATE rls.fn_tenantAccessPredicate(TenantId) ON dbo.Blogs,
ADD BLOCK PREDICATE rls.fn_tenantAccessPredicate(TenantId) ON dbo.Blogs,
ADD FILTER PREDICATE rls.fn_tenantAccessPredicate(TenantId) ON dbo.Posts,
ADD BLOCK PREDICATE rls.fn_tenantAccessPredicate(TenantId) ON dbo.Posts;
GO
如果稍後添加新表,請更改安全政策以在新表上添加 FILTER 和 BLOCK 謂詞。
ALTER SECURITY POLICY rls.tenantAccessPolicy
ADD FILTER PREDICATE rls.fn_tenantAccessPredicate(TenantId) ON dbo.MyNewTable,
ADD BLOCK PREDICATE rls.fn_tenantAccessPredicate(TenantId) ON dbo.MyNewTable;
GO
添加預設限制以自動填充插入的租戶 ID
可以在每個表上放置預設限制,以便在插入行時使用目前存儲在SESSION_CONTEXT中的值自動填充 TenantId。下面是一個示例。
-- Create default constraints to auto-populate TenantId with the
-- value of SESSION_CONTEXT for inserts.
ALTER TABLE Blogs
ADD CONSTRAINT df_TenantId_Blogs
DEFAULT CAST(SESSION_CONTEXT(N'TenantId') AS int) FOR TenantId;
GO
ALTER TABLE Posts
ADD CONSTRAINT df_TenantId_Posts
DEFAULT CAST(SESSION_CONTEXT(N'TenantId') AS int) FOR TenantId;
GO