# 策展 · X (Twitter) 🔥

> 作者：PlanetScale (@PlanetScale) · 平台：X (Twitter) · 日期：2026-05-01

> 原始來源：https://x.com/PlanetScale/status/2049893419972231458

## 中文摘要

Postgres RLS 理論誘人，實務卻充滿陷阱與效能隱患。

PlanetScale 文章尖銳批判 Postgres 的 Row Level Security (RLS)，指出其看似嵌入式存取控制的優勢，在實際應用中因政策管理複雜、攻擊面擴大及效能損失而弊大於利，建議改用應用層授權。

**存取管理的麻煩**

**朋友與家人：政策配置難以擴展**  
RLS 讓管理員在資料庫內定義安全政策，而非應用層，類似給家裡訪客特定抽屜鑰匙：人人可進銀器櫃，但僅家人能進洗衣間。小規模看似理想，使用者任意存取資料庫，政策確保不洩漏不該看的資料。但隨著資料庫成長，測試與擴展政策近乎不可能。每新增應用功能，都須確保 RLS 政策保護正確資料列；遺漏新增政策極易發生，尤其需手動同步至程式碼庫。若單一政策出錯，整個資料即暴露。文章強調，與程式碼同處的管理存取遠比記得為每個新資料表、欄位或功能寫政策簡單。

**派對場景：連接池衝突**  
Postgres 採用每連接一程式的架構，每位直接以角色連線的使用者如湧入家裡的訪客，100 人即擁擠不堪。PgBouncer 連接池器可重用少量直接連接，讓多位客戶端連線，但搭配 RLS 會遺失上游客戶端身份。傳統解決是用本地變數而非角色定義政策，例如：  
```
CREATE POLICY user_isolation ON orders
  FOR ALL USING (user_id = current_setting('app.tenant_id')::bigint);
```  
應用程式須包裝每個交易設定變數：  
```
BEGIN;
SET LOCAL app.tenant_id = '1234';
SELECT * FROM orders;
COMMIT;
```  
這需大量額外程式碼管理每個交易的本地變數；遺漏 SET LOCAL，current_setting() 會回傳空字串或依政策拋錯。

**討厭鄰居：攻擊面擴大**  
RLS 如在查詢後附加額外 WHERE 子句，除非使用者無讀取權限，否則查詢仍會執行，即便無資料回傳。在複雜聯結或無索引查詢，這損害資料庫效能。惡意使用者重試查詢，RLS 僅阻擋資料洩漏，無法阻止查詢執行，浪費 CPU 週期，甚至讓合法使用者餓死。無足夠速率限制下，任何應用使用者僅擊中 API 端點即可 DDoS 資料庫。文章批評，應在應用層驗證使用者權限執行查詢，而非依賴 RLS 管理安全。

**大型鑰匙圈：效能嚴重影響**  
RLS 政策通常每資料列執行一次，任何函數或複雜邏輯皆逐列運行，如友人從龐大鑰匙圈找冰箱鑰匙浪費時間。解決之道是將函數包入子查詢。文章提供基準測試，比較 5 種設定：  
- RLS 搭配 VOLATILE 函數  
- RLS 搭配 STABLE 函數  
- RLS 搭配 VOLATILE 函數 + 快取  
- RLS 搭配 STABLE 函數 + 快取  
- 無 RLS  

VOLATILE 函數（Postgres 新函數預設）可修改資料或連續呼叫回傳不同值；STABLE 函數不改資料，同交易內連續呼叫應回傳相同值。但 RLS 下，Postgres 不快取 STABLE 值逐列評估。透過 SELECT 包裝函數呼叫，產生 InitPlan 節點（僅外層計劃執行一次並快取），而非 SubPlan（逐列重算）。EXPLAIN 顯示無快取成本高逾 3 倍，實際延遲差距更大。基準圖表證明，無快取的昂貴函數讓 RLS 成昂貴開銷；雖某些情境與無 RLS 一樣快，但 RLS 增添需持續優化的程式碼層，小錯即大效能損失。可自行測試其基準程式庫。

**屋主權限：擁有者繞過風險**  
每個 Postgres 資料表有擁有者，以擁有者角色連線時，RLS 政策不適用，須明確啟用：  
```
ALTER TABLE users FORCE ROW LEVEL SECURITY;
```  
即使如此，具 SUPERUSER 屬性的角色永遠繞過 RLS。易忽略且測試易錯，非擁有者角色下政策測試通過，生產環境卻以擁有者運行洩漏資料。

**做火腿三明治：嚴格模式複雜**  
友人 Andy 做火腿三明治，有冰箱與器具權限，但無雜貨清單權限，使用完所有芥末需你補貨。RLS 下，Andy 查詢碰不到清單，須單獨更新。無 RLS 簡單；有 RLS 則權限不一致，跨表更新麻煩。一法是用多角色多交易，但應用層過度繁瑣。更好解是用 SECURITY DEFINER 函數，以擁有者角色運行繞過 RLS，限定參數。但這又需應用層管理安全，函數版本控制難，遷移工具雖含 SQL 函數與政策，仍易致頭痛。應用需同步資料庫函數變更，改定義、名稱或回傳值恐需新遷移或精細更新。

**最終結算：整體開銷過高**  
管理家裡每件物品鑰匙、誰持何鑰、誰可進誰委託後，應用程式邏輯幾與無 RLS 無異。RLS 政策存於 pg_policies，非程式碼庫；標準遷移工具不追蹤政策變更，成獨立手動流程易漂移。新增欄位或重命名表可無聲破壞政策，直至應用故障影響使用者。搭配 PgBouncer，每查詢仍需應用程式碼加本地變數辨識使用者，錯配損害如無 RLS。應用層早驗權限防垃圾查詢，RLS 益處難見。查詢優化更難，受限可見資料，需客製函數與權限，程式碼與資料庫邏輯管理雪上加霜。

**正確作法：避開 RLS**  
PlanetScale 堅決不薦依賴 Postgres RLS，偶有有用情境，但大規模正確實作下，益處速轉缺點，開銷不僅效能，還包括開發體驗與複雜度。推應用層授權如中介軟體、ORM 範圍限定或專用權限表，讓邏輯可見、可測、與使用程式碼同處。資料庫應視為倉庫，非家屋。  
（文章作者 Josh Brown，2026 年 4 月 30 日發布，透過家屋隱喻生動諷刺 RLS 實務缺陷，呼籲開發者勿被理論誘惑。）

## 標籤

其他, 產業趨勢, Postgres, PlanetScale
