RLS(Row Level Security)とは、行ごとの読み書きをポリシーで絞る PostgreSQL の機能です。

  • RLS とポリシーの仕組み、BYPASSRLS の意味、Supabase で RLS を設定するときの順番
  • 権限が足りているのに、エラーも警告も無く行数がゼロに見える理由と、本当にゼロなのかを見分ける手順
  • 読み取り専用のロールの作り方、件数の出し方、RLS を当てる前に測る所を、当社の記録で

AI に開発の仕事を任せる考え方は AIエージェント の頁にまとめています。DB の周りは prisma migrate と PgBouncer の頁が別にあります。この頁の主題は「RLS の下で数を測ると何が起きるか」です。

PostgreSQL の RLS の仕組み:見えない行はエラーにならない

RLS は表ごとに ALTER TABLE … ENABLE ROW LEVEL SECURITY で有効にし、どの行を誰に見せるかを CREATE POLICY で書きます。有効なのに当てはまるポリシーが無いと、既定の拒否として扱われ、行は見えず更新もできません(PostgreSQL の文書)。

ALTER TABLE <表> ENABLE ROW LEVEL SECURITY;

CREATE POLICY <ポリシー名> ON <表>
  FOR SELECT
  TO <ロール>
  USING (<見せてよい行の条件>);

ここで効いてくるのが、USING と WITH CHECK の失敗の仕方の違いです。読むときの絞り込みは、失敗しても何も言いません。

場面どうなるか
有効でポリシーが無い既定の拒否。行は見えず、変更もできない
USING が false か null の行その行が見えないだけ。通常はエラーにならない
WITH CHECK が false か null の行挿入・更新がエラーになる
permissive のポリシーが複数OR で結ぶ(既定)
restrictive のポリシーAND で結ぶ
TRUNCATE・REFERENCES表全体に効く操作で、RLS の対象外
一意制約・主キー・外部キーの検査整合性のため常に RLS を通らない

出典: PostgreSQL「Row Security Policies」https://www.postgresql.org/docs/current/ddl-rowsecurity.html ・「CREATE POLICY」https://www.postgresql.org/docs/current/sql-createpolicy.html (どちらも 2026-11-06 に確認)。

BYPASSRLS とは:どのロールに RLS が効かないか

BYPASSRLS は、ロールがすべての RLS のポリシーを通らずに読み書きできる属性です。既定は NOBYPASSRLS で、付けられるのはスーパーユーザーか、自分も BYPASSRLS を持つロールだけです(PostgreSQL の文書)。RLS が効くかどうかは、ロールの種類で次のように分かれます。

ロールRLS
普通のロール(NOBYPASSRLS)効く
表の owner通常は効かない。FORCE ROW LEVEL SECURITY を付けると効く
BYPASSRLS を持つロール常に効かない
スーパーユーザー常に効かない

出典: PostgreSQL「Row Security Policies」https://www.postgresql.org/docs/current/ddl-rowsecurity.html ・「CREATE ROLE」https://www.postgresql.org/docs/current/sql-createrole.html (どちらも 2026-11-06 に確認)。

公式の文書は、黙って行が落ちる怖さの例としてバックアップを挙げています。パラメータ row_security を off にしても RLS を通り抜けるわけではなく、ポリシーで結果が絞られる問い合わせをエラーにします。pg_dump は既定でこの off を使うので、権限が足りなければ黙って欠けるのではなくエラーになります(PostgreSQL の文書)。

同じ SQL で、片方のロールだけ count(*) が 0 になる

同じ DB に同じ SQL を撃って、結果が割れました。BYPASSRLS の無いロールは、RLS が有効の表で、エラーも警告も無く count(*) が 0 になりました。BYPASSRLS の在るロールは、推定行数以上を読みました(社の実測 2026-09-18)。

しかも has_table_privilege は両方のロールで true でした(社の実測 2026-09-18)。権限の不足には見えないので、0 という答えをそのまま信じる形になります。表に触る権限と、どの行が見えるかは別の話です。

これで間違えかけた例もあります。FORCE の付いた表の監査ログが 0 行なのを見て、「呼ばれていない」と書きかけた席がありました(社の実測 2026-09-18)。FORCE の付いた表は owner にも RLS が効くので、owner で読んでも安心できません(PostgreSQL の文書)。

その 0 が本当の 0 かを見分ける、測る前の手順

判別の手順は次の 3 つです(社の実測 2026-09-18)。

  1. 先に、今のロールが RLS を通り抜けるかを撃ちます。結果を数字の横に残します(社の実測 2026-09-18)。
    SELECT rolbypassrls FROM pg_roles WHERE rolname=current_user;
  2. relrowsecurity=true AND reltuples>0 なのに count(*)=0 の表が無いことを示します。推定では行が在るのに数えると無い表は、RLS に隠された疑いがあります(社の実測 2026-09-18)。
  3. reltuples=-1 は未 ANALYZE で、判別できません。0 に混ぜず、判別不能として分けます(社の実測 2026-09-18)。

どのロールで測るかは、問いによって逆になります。

問い測るロール理由
普段の業務の問いbypass しない方が安全見えてよい範囲だけで答えが出る
「どこからも参照されていないから消す」の判定bypass が必要他のテナントの行が視界に入らない 0 を全体の 0 と読むと、使われている物を消す

出典: 社の実測 2026-09-18。

読み取り専用のロールに BYPASSRLS を付けて確かめる

読み取り専用のロールに BYPASSRLS を付けるか、別の接続で読むかの 2 択で、前者を採りました(社の実測 2026-09-19)。ロールは LOGIN・BYPASSRLS・pg_read_all_data の組み合わせで作りました(社の実測 2026-09-20)。

CREATE ROLE <読み取り専用のロール> LOGIN BYPASSRLS;
GRANT pg_read_all_data TO <読み取り専用のロール>;

作った後の確かめは、3 つの対照です(社の実測 2026-09-20)。

対照何を確かめるか
current_user測っているのが、そのロール自身であること
UPDATE の拒否読めても書けないこと
owner と 5 表の行数の一致行が隠れずに見えていること

出典: 社の実測 2026-09-20。

後日、rolbypassrls=t であることと、UPDATE … WHERE false が read-only で落ちることも確かめました(社の実測 2026-09-28)。条件が false の UPDATE は行を変えないので、書けないことをこの形で測れます。

反省が 1 つあります。権限を測ろうとして実際に表を作ってしまい、すぐ消しました(社の実測 2026-09-20)。権限の有無を「作ってみる」で測ってはいけません。

別の時に、新しい役割を NOLOGIN・BYPASSRLS で作り、既存の役割と権限を突き合わせたこともあります。差は search_path の 1 行だけで、表・sequence・関数・schema の差は 0 でした。突き合わせが差を拾えることは、陽性対照の 20 行で見ています(社の実測 2026-10-27)。

件数の欄は 0 でなく「測定不能」と出す

移行の試し撃ち(dry_run)で、件数の欄が RLS のある表を 0 と出しました(社の実測 2026-09-20)。読む側には「移す物が無い表」に見えてしまいます。

直した後は 0 ではなく「測定不能(推定 309 行)」と出します。RLS の無い表は数字のままです(社の実測 2026-10-02)。測れなかったことと、0 だったことを同じ欄の同じ形で出さない、という直し方です。

information_schema で権限を測ると、同じ形で 0 に見える

権限を調べる側にも似た穴があります。information_schema は測るロールに見える範囲しか返さないので、権限が在っても 0 や false に見えます(社の実測 2026-09-25)。

正しくは relacl・has_table_privilege・pg_default_acl を読み、開いていると分かっている schema で非 0 が出ることも確かめます(社の実測 2026-09-25)。結果が 0 のときに「測り方が 0 しか返さない」可能性を潰す、という点で手順の考え方は行数と同じです。

Supabase の RLS の設定と、当社の新しい表の順番

Supabase の文書は、公開の schema の表はすべて RLS を有効にするよう求めています。有効にしてポリシーがまだ無い間は、publishable key で API からデータを読めません。未認証の要求は anon、サインイン済みの要求は authenticated という Postgres のロールで動きます。

Supabase の文書の要点内容
ポリシーと権限は別既存のプロジェクトでは anon・authenticated に既定で権限が付く。ポリシーを足しても権限は取り消されないので、要らなければ revoke する
secret keyservice_role で認証し、bypassrls を持つので RLS を通らない。ただし要求にユーザーのアクセストークンが乗っていれば、そのユーザーのポリシーが効く
auth.uid()未認証だと null を返す。auth.uid() = user_id は偽になり、ポリシーは黙って失敗する。文書は auth.uid() IS NOT NULL を足す書き方を勧めている
BYPASSRLS を持つロールログイン情報を共有しない

出典: Supabase「Row Level Security」https://supabase.com/docs/guides/database/postgres/row-level-security (2026-11-06 に確認)。

当社が新しい表で踏む順番は次の通りです(社の実測 2026-09-28・2026-10-04・2026-10-21)。

  1. anon・authenticated を REVOKE します。
  2. RLS を有効にし、最小の GRANT とポリシーを付けます。
  3. 撃った後に anon の SELECT が 0 であることを測ります(社の実測 2026-09-28・2026-10-04・2026-10-21)。
  4. 戻しの手順も、撃って元の状態に戻るところまで実測します(社の実測 2026-09-28・2026-10-04・2026-10-21)。

退避の表にも RLS を付けます。RLS の表には \copy で戻せなかったので、一時表に入れてから INSERT する形にしました(社の実測 2026-09-28・2026-10-04・2026-10-21)。

db push の後と ENABLE の前に確かめる所

RLS は、DB を建てれば付いてくる物ではありません。staging の DB を作り直したとき、prisma db push だけでは RLS が無効のままだったので、全表で有効にしました(社の実測 2026-10-14)。

逆に、有効にする側にも止まる形があります。接続ロールが owner でも BYPASSRLS でもない表に、ポリシー無しで ENABLE すると、定期処理のロックが取れず 503 で止まる形だと、当てる前に測って分かりました。そこで止めたので、事故は起きていません(社の実測 2026-10-10)。既定の拒否は、人の問い合わせだけでなく定期処理にも同じように効きます。

よくある質問

RLS を有効にしたら count(*) が 0 になったのはなぜ?

ポリシーに当てはまらない行は見えないだけで、通常はエラーになりません(PostgreSQL の文書)。当社でも、権限の関数が true を返すロールで、エラーも警告も無く 0 になりました(社の実測 2026-09-18)。まず rolbypassrls を見て、推定行数と比べます。

BYPASSRLS とスーパーユーザー、表の owner の違いは?

スーパーユーザーと BYPASSRLS を持つロールは、常に RLS を通りません。表の owner も通常は通りませんが、FORCE ROW LEVEL SECURITY を付けた表では RLS が効きます(PostgreSQL の文書)。

読み取り専用のロールに BYPASSRLS を付けても大丈夫?

BYPASSRLS はすべてのポリシーを通らなくする属性です(PostgreSQL の文書)。当社は付けた後に、UPDATE が拒否されることまで実際に確かめました(社の実測 2026-09-20)。Supabase の文書は、この属性を持つロールのログイン情報を共有しないよう書いています。

Supabase で RLS を有効にしただけで足りる?

足りません。ポリシーが無ければ publishable key からは読めず、一方で anon・authenticated に付いた権限はポリシーを足しても取り消されません(Supabase の文書)。当社は REVOKE・RLS・最小の GRANT・ポリシーを揃え、撃った後に anon の SELECT が 0 であることを測っています(社の実測 2026-09-28・2026-10-04・2026-10-21)。

row_security を off にすれば RLS を通り抜けられる?

通り抜けられません。off は、ポリシーで結果が絞られる問い合わせをエラーにする設定です(PostgreSQL の文書)。黙って行が欠けるのを避けたいときに使う物で、通り抜けるには BYPASSRLS などの属性が要ります。

出典

  • PostgreSQL「Row Security Policies」https://www.postgresql.org/docs/current/ddl-rowsecurity.html (2026-11-06 に確認)
  • PostgreSQL「CREATE ROLE」https://www.postgresql.org/docs/current/sql-createrole.html (2026-11-06 に確認)
  • PostgreSQL「CREATE POLICY」https://www.postgresql.org/docs/current/sql-createpolicy.html (2026-11-06 に確認)
  • Supabase「Row Level Security」https://supabase.com/docs/guides/database/postgres/row-level-security (2026-11-06 に確認)
  • 社の実測 2026-09-18:同じ SQL をロールを変えて撃った行数の比較と、判別の手順
  • 社の実測 2026-09-19・2026-09-20・2026-09-28:読み取り専用のロールを作って確かめた記録
  • 社の実測 2026-09-20・2026-10-02:移行の試し撃ちの件数の欄と、その直し
  • 社の実測 2026-09-25:information_schema で権限を測ったときの見え方
  • 社の実測 2026-09-28・2026-10-04・2026-10-21:新しい表に RLS を付ける順番と戻し
  • 社の実測 2026-10-10:RLS を当てる前に測った記録
  • 社の実測 2026-10-14:staging の DB の作り直し
  • 社の実測 2026-10-27:新しい役割と既存の役割の権限の突き合わせ

ブルーランプ 記事の編集部/更新日 2026-11-06