VPD 核心是 **Policy Function 動態產生 WHERE 條件**。Oracle 9i 僅支援 **Dynamic Policy**:每次查詢、每行資料都呼叫函數 → 高併發下 CPU 飆升。10g 引入四種進階類型,針對「謂詞靜態程度」優化。
WHERE am_name = 'SCOTT'
↑ ↑
靜態部 動態部(隨用戶/上下文變化)
策略類型本質上是在**快取靜態部、僅在動態部真正改變時重新評估**。
| 類型 | 快取層級 | 重新評估觸發條件 | 適用場景 | 風險 |
|---|---|---|---|---|
| CONTEXT_SENSITIVE | Session 級 | 應用上下文 (SYS_CONTEXT) 發生變化 | 謂詞結構固定,值來自上下文(如 balance < SYS_CONTEXT('ctx','maxbal')) | 低 |
| STATIC | SGA 級 (跨 Session) | **永不**(首次解析後永久快取) | 謂詞完全由 DB User 決定,會話間不變(多租戶共用 APPUSER,登入觸發器設定 CUST_NAME) | 高:若上下文實際變了,會回傳錯誤資料 |
| SHARED_CONTEXT_SENSITIVE | SGA 級 (跨物件) | 上下文變化 **或** 切換不同物件但上下文未變 | 多表共用同一 Policy Function,上下文相同(如 SAVINGS/CHECKING 同規則) | 低 |
| SHARED_STATIC | SGA 級 (跨物件+Session) | 永不 | 多表共用函數,謂詞完全由 DB User 決定 | 同 STATIC |
CREATE OR REPLACE CONTEXT vpdctx USING pkg_vpd_context;
CREATE OR REPLACE PACKAGE pkg_vpd_context AS
PROCEDURE set_maxbal(p_val NUMBER);
PROCEDURE set_cust_name(p_val VARCHAR2);
END;
/
CREATE OR REPLACE PACKAGE BODY pkg_vpd_context AS
PROCEDURE set_maxbal(p_val NUMBER) IS
BEGIN DBMS_SESSION.SET_CONTEXT('vpdctx', 'maxbal', p_val); END;
PROCEDURE set_cust_name(p_val VARCHAR2) IS
BEGIN DBMS_SESSION.SET_CONTEXT('vpdctx', 'cust_name', p_val); END;
END;
/
CREATE OR REPLACE FUNCTION vpd_balance_policy(
p_schema VARCHAR2, p_table VARCHAR2
) RETURN VARCHAR2 IS
BEGIN
RETURN 'balance < SYS_CONTEXT(''vpdctx'', ''maxbal'')';
END;
/
BEGIN
DBMS_RLS.ADD_POLICY(
object_schema => 'FINANCE',
object_name => 'ACCOUNTS',
policy_name => 'BALANCE_LIMIT',
function_schema => 'SECURITY',
policy_function => 'VPD_BALANCE_POLICY',
policy_type => DBMS_RLS.CONTEXT_SENSITIVE, -- 關鍵
statement_types => 'SELECT, INSERT, UPDATE, DELETE',
sec_relevant_cols => 'BALANCE'
);
END;
/
-- Java/.NET 連接池取得連接後立即呼叫
BEGIN pkg_vpd_context.set_maxbal(100000); END;
-- 或資料庫登入觸發器
CREATE OR REPLACE TRIGGER set_vpd_ctx_after_logon
AFTER LOGON ON DATABASE
BEGIN
IF USER = 'APPUSER' THEN
pkg_vpd_context.set_maxbal(get_user_limit(USER));
END IF;
END;
場景:Web 用戶 TAO、KARTHIK 共用 APPUSER,登入觸發器設定 CUST_NAME。
若改用 STATIC,首次登入 TAO 解析 → 謂詞 CUST_NAME='ACME' 寫入 SGA 快取。
隨後 KARTHIK 登入(同 APPUSER),**不重新評估**,直接復用 CUST_NAME='ACME' → **資料洩露**。
鐵律:除非 100% 確定「同一 DB User 的所有 Session、謂詞永遠相同」,**禁用 STATIC**。
場景:SAVINGS、CHECKING 兩表共用 vpd_balance_policy。
用戶查詢 SAVINGS → 評估函數 → 快取謂詞。
緊接查詢 CHECKING → **Dynamic/Context_Sensitive 會再次評估**(因物件不同)。
改用 SHARED_CONTEXT_SENSITIVE → 偵測到上下文未變、**跨物件復用謂詞**,省一次函數呼叫。
-- 監控策略命中率
SELECT policy_name, pf_owner, function, policy_type,
execution_count, total_time/1000 total_ms
FROM DBA_POLICIES p
JOIN V$VPD_POLICY v ON v.POLICY_NAME = p.POLICY_NAME
WHERE p.OBJECT_SCHEMA = 'FINANCE';
EXECUTION_COUNT 應遠小於查詢次數(Context_Sensitive/Shared 生效證明)。TOTAL_MS 除以執行次數 = 平均函數耗時,目標 < 1ms。SEC_RELEVANT_COLS_OPT => DBMS_RLS.ALL_ROWS 實現「列級掩碼」而非行級過濾。V$VPD_POLICY 監控執行次數與耗時,異常即優化函數或調整策略類型。VPD 策略類型的選擇,本質是 **「快取粒度」與「安全性」的權衡**。Dynamic 最安全最慢,Static 最快最危險,Context_Sensitive 是黃金分割點。架構師應在**安全模型設計階段**即規劃上下文命名空間、策略分類、共用函數範圍,而非上線後被效能問題倒逼修改。記住:**Policy Function 越簡單,系統越穩健。**