Skip to main content

What is row-level security?

by Databricks Staff

  • Row-level security filters table data by user identity, role or session context, ensuring each person sees only the rows they are authorized to access across dashboards, notebooks, APIs and other tools.
  • Effective RLS depends on clear access logic, reliable keying columns and separate controls for read and write actions, supported by testing across multiple user roles.
  • RLS is most effective as part of layered governance alongside table permissions, column-level security, data masking and audit logging, since it does not protect sensitive columns or aggregate results on its own.

Row-level security (RLS) is a database access control that limits which rows of a table a user can read or change based on their identity, role or session context.

Instead of restricting access to whole tables or specific columns, RLS filters data row by row. The database engine applies the filter automatically at query time, so the same rule holds no matter which tool the user uses to access the data.

RLS is part of fine-grained access control, alongside:

  • Column-level security
  • Data masking
  • Table-level grants

For example, a salesperson may query the company’s orders table but only see orders for their assigned region, even though the table contains every region’s data. The user writes a normal SELECT statement, and the engine returns only the rows they’re allowed to see.

RLS is now a core building block for multi-tenant SaaS, regional data segregation and compliance use cases. This article covers how RLS works, where it helps, where it falls short and how it works on the Databricks Platform.

How does row-level security work?

Row-level security works by applying a filter rule, often called a policy or predicate, to a table. When a user runs a query, the database engine automatically applies that filter and returns only the rows the user is allowed to see.

In practice, RLS usually works in three steps:

  1. The user runs a query: The user writes a standard query without adding any security filters themselves.
  2. The database checks the user’s identity: The engine evaluates the user through a built-in function like CURRENT_USER, a session variable set by the application or a mapping table that connects users and groups to permitted data.
  3. The engine filters the result: The RLS predicate returns TRUE for rows the user can see and FALSE for everything else. Only rows that pass the predicate are returned.

Because enforcement happens at the database layer, the same rule applies consistently across every access path, including BI dashboards, notebooks, ad-hoc SQL, APIs and third-party tools. That consistency is what makes RLS powerful: one rule, applied everywhere, enforced by the engine.

Most engines also distinguish between read-side and write-side enforcement. A read predicate controls what a SELECT query returns. A write predicate, often defined separately with a WITH CHECK clause, controls which rows a user can insert, update or delete.

The two predicates can be the same, but they do not have to be. For example, a user might be allowed to read rows for every region but only insert rows for their own region. Defining both sides matters when a table accepts writes, because skipping the write check is one of the most common ways teams misconfigure RLS in production.

Row-level security vs. column-level security and other access controls

RLS is one of several fine-grained access controls, and in production it’s almost always paired with others. The table below shows how each control fits.

ControlWhat it restrictsTypical use case
Row-level security (RLS)Specific rows in a tableLimit users to their region, tenant or department
Column-level security (CLS)Specific columns in a tableHide salary, SSN or PII columns from analysts
Object-level security (OLS)Entire tables, views or measuresBlock access to a sensitive dataset altogether
Data maskingVisible values within a columnShow only the last 4 digits of a card number
GRANT / REVOKETable-level read/write permissionsAllow or deny access to the table as a whole

These controls are designed to layer. A typical setup uses table-level grants to control who can access a table, RLS to scope which rows are visible, and column-level security or data masking to protect sensitive fields within those rows. Treating them as a stack rather than a menu of alternatives makes governance both auditable and resilient. A misconfiguration in one layer doesn't compromise the others.

Common use cases for row-level security

RLS is the standard way to enforce who can see what inside a shared table, filtering rows based on a user's attributes against a keying column like region, tenant, or classification. Most teams reach for it when one dataset has to serve multiple audiences with different visibility rules.

  • Multi-tenant SaaS: Isolate each customer’s data inside shared tables using a tenant_id column and session context. This avoids the operational cost of one schema or one database per tenant while keeping each customer’s data fully separated at query time.
  • Regional segregation: Restrict sales, HR or order data so users only see records for their country or region, without splitting the underlying table by geography.
  • Departmental access: Give finance, marketing and operations teams access to the same table but different rows, mapped by a department or cost-center column.
  • Regulatory compliance: Enforce data residency rules, for example keeping EU records visible only to EU-based staff under GDPR, or restricting protected categories under HIPAA, CCPA or industry-specific regulations.
  • Healthcare and clinical data: Let clinicians share a patient table while seeing only their own patients, supporting HIPAA minimum-necessary access without duplicating records across silos.
  • Partner and vendor portals: Share a single dataset across external partners while filtering each to their own records, so one source-of-truth table can power dozens of partner-facing views.

How to implement row-level security: 4 steps

The general pattern is consistent across platforms, with vendor-specific syntax filled in where needed.

  1. Identify the filter logic: Decide what determines access: user ID, group membership, region, tenant ID or a mapping table. The filter logic should be derivable from session context or a stable lookup, not from values the user controls at query time.
  2. Add or confirm the keying column: Make sure the table has a column the filter can use, such as tenant_id, region or owner_id. If no such column exists yet, plan a backfill before the policy goes live, and consider indexing the column to keep the predicate cheap.
  3. Define the policy or row filter: Write the predicate that returns TRUE for rows the user is allowed to see, and a separate check for writes if the table accepts them. Keep the logic in SQL where possible. Most engines optimize SQL predicates better than function calls into other languages.
  4. Test with multiple user identities: Run queries as different roles and confirm the right rows appear and that nothing leaks across tenants. Include a negative test: a user with no matching rows should see an empty result, not an error, and a privileged user should be tested separately to confirm owner-bypass behavior.
REPORT

The agentic AI playbook for the enterprise

Benefits of row-level security

Moving access logic into the data layer pays off in several practical ways. The shorthand is that the database becomes the source of truth for access, rather than every application that touches the data.

  • Centralized logic: Access rules live with the data, not scattered across application code or BI tools.
  • Consistent enforcement: The same rule applies whether a user queries from a notebook, a dashboard or an API.
  • Defense in depth: RLS adds a second layer of protection if app-layer checks are bypassed or buggy.
  • Simpler application code: Developers don’t need to append manual WHERE clauses in every query.
  • Easier audits: Compliance teams can review one policy instead of tracing access logic across systems.
  • Faster onboarding for new tools: A new BI tool or notebook environment inherits existing row-level rules without custom integration work.

Limitations and risks of row-level security

RLS is powerful, but it has well-known gotchas teams should plan for. Most of these surface only in production or during an audit, which makes them worth knowing upfront.

Admin and owner bypass

In many databases, table owners and high-privilege admins bypass RLS by default. PostgreSQL, for example, requires the FORCE ROW LEVEL SECURITY setting to apply policies to the table owner, and similar settings exist in other engines. This is a common audit finding: assume privileged users see every row unless your configuration explicitly forces the policy to apply to them. Test the policy from a privileged session, not just a regular one, before signing off on it.

No column or summary hiding

RLS filters rows, but it does not hide columns or block aggregate results. An analyst blocked from seeing individual EU records can still run SELECT COUNT(*) over the unfiltered table if RLS isn’t paired with column or aggregate restrictions. Pair RLS with column-level security or data masking to close that gap, and consider whether aggregate queries themselves need to be governed for the most sensitive tables.

Performance overhead

Every query gets the RLS predicate applied, which can slow performance if the filter logic is complex or the keying column isn’t indexed. Index the columns the policy references and keep the predicate as simple as possible. Prefer straightforward CASE expressions over subqueries or mapping-table lookups inside the filter. If the engine supports it, materialize the user-to-rows mapping into a small, well-indexed table rather than computing it on the fly.

Debugging complexity

Empty result sets caused by RLS look identical to “no matching data.” Developers chasing a missing row often spend hours before they realize the policy filtered it out. Log the effective user identity and policy version during development, give engineers a way to confirm whether RLS is active when results look wrong and document the policy in the same place as the table schema so it’s discoverable.

Misconfigured write rules

RLS policies often have two sides: a USING clause that filters what users can read and a WITH CHECK clause that controls what they can insert or update. Defining one without the other is a classic mistake: read filtering with no write check lets users insert or update rows they shouldn’t own. Always define both sides when the table accepts writes, and run a write-side test as part of the policy review.

Row-level security on the Databricks Platform

On the Databricks Platform, row-level security is handled through row filters in Unity Catalog, Databricks’ unified governance layer for data and AI. The pattern is straightforward: define a SQL user-defined function that returns true for the rows a given user is allowed to see, then attach it to the target table. The filter runs automatically at query time, using the current user’s identity or session context to determine which rows to return.

Row filters are enforced consistently across Databricks SQL, notebooks, jobs and connected BI tools, with no custom logic required per surface. They work alongside column masks for full fine-grained access control, and every query that touches a filtered table is captured in Unity Catalog’s lineage and audit logs, so governance and security teams can see exactly which policies apply to which tables and which users have queried what.

Frequently asked questions

What is dynamic row-level security? Dynamic RLS evaluates the access rule at query time using the current user's identity or session context, so the same policy returns different results for different users. All modern RLS implementations work this way, including Databricks' ABAC policies, row filters and dynamic views.

What is the difference between row-level security and column-level security? RLS restricts which rows a user can see; column-level security restricts which columns, typically to hide sensitive fields like salary or Social Security numbers. Most production deployments use both together.

Is row-level security enough on its own to secure sensitive data? No. RLS handles row visibility but doesn't mask column values, block aggregate queries or replace identity and access management. Pair it with column-level security, table-level grants and audit logging as part of a defense-in-depth strategy.

How does Databricks implement row-level security? Through Unity Catalog, with three options: ABAC policies, table-level row filters and dynamic views. ABAC is recommended for governance at scale; row filters and dynamic views are available for more bespoke needs.

Does row-level security affect query performance? Yes, but the impact is usually manageable. Keep policy logic simple, index the columns the policy references and prefer SQL UDFs over Python UDFs. Profile queries before and after policy changes to catch regressions early.

Get started with fine-grained access control on Databricks

Row-level security is most effective as part of a broader governance model that also covers columns, masking, lineage and audit. See how Unity Catalog brings row-level security, column masking and unified governance together on the Databricks Platform.

Get the latest posts in your inbox

Subscribe to our blog and get the latest posts delivered to your inbox.