Setting up RLS
Considerations and Limitations
First introduced in Azure SQL, in January 2015.
Row Level Security(RLS) enables the implementation of restrictions on data
Row level security introduces predicate based access control where the
predicate is used as a criterion to determine whether or not the user has
the appropriate access to the data.
The predicate can be anything from the characteristics of the user
executing the query (role membership, execution context) to complex
business logic involving multiple tables of the database to SQL Server
The enforcement logic lies inside the database and schema is bound to
RLS works transparently at
query time, no app changes
Compatible with RLS in other
Enforcement logic resides
inside database and is
schema-bound to the table it
protects providing greater
security. Reduced application
maintenance and complexity
Store data intended for many consumers in a single database/table while at the same time restricting row-level read and write access based on users’ execution
Setting Up RLS
User-defined inline table-valued function (iTVF) implementing security logic.
Schema bound to the table so that no changes can be done to the security policy
under the hood.
Can be arbitrarily complicated, containing joins with other tables.
Performance wise, predicate functions get optimized to provide comparable
performance to views, as if the logic were directly embedded in the original
Still, the more complex the security logic gets, the heavier the performance
impact may get.
Binds a predicate function to a particular table, applying it for all queries.
Two types of predicates: filter predicates and blocking predicates (more on that
in a bit).
Collection of security predicates for managing security across multiple tables.
Can be turned on and off at will (STATE = ON|OFF).
Can be created either by using SCHEMABINDING or not. The recommended (and
default) practice is with SCHEMABINDING on.
Attempts to alter the columns of a table referenced by a schema bound security
policy will result in an error. However, columns not referenced by the predicate
can be altered.
Attempts to add a predicate on a table that already has one defined for the
specified operation (regardless of whether it is enabled or disabled) results in an
Defining multiple active security policies that contain non-overlapping
Filter predicates are applied while reading data from the base table, and it affects
all get operations.
DELETE statements (i.e. user cannot delete rows that are filtered).
UPDATE statements (i.e. user cannot update rows that are filtered, although it is possible to update rows
in such way that they will be subsequently filtered).
A filter predicate will silently filter out the rows that fail to pass the security
That means that no error message will be returned to the user if he tries to update or delete rows that
he is not allowed to.
The application can INSERT any rows, regardless of whether or not they will be
filtered during any other operation.
If the dbo user, a member of the db_owner role, or the table owner queries against
a table that has a security policy defined and enabled the rows are
filtered/restricted as defined by the security policy.
Enforce granular control over write access to data for different users, including
scenarios that require separate access logic for INSERT, UPDATE, and DELETE
Blocking predicates affect ALL write operations (inserts/updates/deletes).
Four options to choose from when declaring a blocking predicate:
AFTER INSERT and AFTER UPDATE predicates can prevent users from updating rows to values that
violate the predicate.
BEFORE UPDATE predicates can prevent users from updating rows that currently violate the predicate.
BEFORE DELETE predicates can block delete operations.
If none of the above is set then the predicate covers every operation.
Create a separate schema for the security objects.
And give permissions to that schema to the security manager.
Additionally the security manager does not require any additional permissions to
the underlying tables.
Avoid type conversions in the predicate functions and be very careful of
Recursion can be used.
If the recursion is direct the optimizer will detect it and optimize it accordingly.
If the recursion is indirect (e.g. the predicate function uses another function that calls the predicate
function) then the optimizer cannot detect the recursion and a performance issue may occur.
Do not rely on SET options, especially session-specific ones.
Keep the security logic as simple as possible to allow easy maintenance and
minimal performance degradation.
DBCC SHOW_STATISTICS will show statistics of unfiltered data.
When using columnstore indexes, it is possible that the optimizer may modify the
query plan such that it does not use batch mode, because row-level security
applies a function.
Temporal tables are compatible with RLS but the security policy must be applied
individually in each table (current and history).
Memory optimized tables are compatible with RLS. The predicate function must be
defined using the NATIVE_COMPILATION option.
Partitioned views are compatible with filter predicates but not with blocking predicates.
That means that a partitioned view CANNOT be created on top of a table with a block predicate defined
Security policies can be created on top of indexed views BUT the creation of
indexed views on top of tables that have a security policy is prohibited. (row
lookups through the inde bypass the policy).
Row-Level security is incompatible with Filestream.
RLS is incompatible with Polybase.
Considerations and Limitations