SQL 服务器 - 使用交叉应用的行级安全性
SQL Server - Row Level security using Cross Apply
我正在为 SQL Server/Azure SQL 数据库中的行级安全性开发过滤器谓词。
与可见性硬币相关的应用程序逻辑要求必须阅读大量 tables 才能了解确定的用户是否可以阅读或更少阅读一行。我制定了以下逻辑:
- 过滤谓词的内联table值函数;
-- 在其中,一个 CTE 用于获取用户的所有配置文件。此 CTE 的结果必须使用 CROSS APPLY 运算符与一组内联 Table 值函数连接。
代码如下:
CREATE FUNCTION [scr].[prj_Projects](@ProjectId INT, @FilterId1 INT, @FilterId2 INT, @FilterId3 INT, @FilterId4 INT, @FilterId5 INT, @FilterId6 INT)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN (
WITH UserProfiles AS (
SELECT up.Id
FROM dbo.users u
INNER JOIN dbo.UsersProfiles up ON up.UserId = u.Id
INNER JOIN dbo.Profiles p ON p.id = up.ProfileId
WHERE SESSION_CONTEXT(N'UserId') = u.Id
)
SELECT Result = 1
FROM UserProfiles up
CROSS APPLY [scr].[prj_ProfilesFilter1](up.Id, @FilterId1)
CROSS APPLY [scr].[prj_ProfilesFilter2](up.Id, @FilterId2)
CROSS APPLY [scr].[prj_ProfilesFilter3](up.Id, @FilterId3)
CROSS APPLY [scr].[prj_ProfilesFilter4](up.Id, @FilterId4)
CROSS APPLY [scr].[prj_ProfilesFilter5](up.Id, @FilterId5)
CROSS APPLY [scr].[prj_ProfilesFilter6](up.Id, @FilterId6)
)
GO
在查询一个ITVF之后(它们的结构都一样)。
CREATE OR ALTER FUNCTION [scr].[prj_ProfilesFilter1] (@UserProfileId INTEGER, @FilterId1 INTEGER)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN (
WITH UserProfile AS (
SELECT DISTINCT upba.FilterId1
FROM dbo.UsersProfilesFilters upba
WHERE upba.UserProfileId = @UserProfileId
), Datas AS (
SELECT b.Id
FROM dbo.Filters1 b
INNER JOIN UserProfile c ON c.FilterId1 = b.Id
UNION ALL
SELECT b.Id
FROM dbo.Filters1 b
WHERE NOT EXISTS (SELECT 1 FROM UserProfile)
UNION ALL
SELECT -1
WHERE NOT EXISTS (SELECT 1 FROM UserProfile)
) SELECT Id
FROM Datas d
WHERE d.Id = ISNULL(@FilterId1 , -1)
)
GO
本来以为设计还可以,可惜表演很烂。与执行计划无关(例如,我只看到查找而没有扫描),但问题与查询执行的大量扫描计数和逻辑读取有关(非常非常高)。这很奇怪,因为每个交叉应用 returns 只有一行并且只有设置操作。
对于如何避免这种大量的逻辑读取,您有什么想法吗?
我认为这是与 RLS
相关的错误
更新:
这里是查询的执行计划:https://www.brentozar.com/pastetheplan/?id=r1mHXespO
正如我所说,问题与查询执行的逻辑读取数和扫描计数有关,因为执行计划似乎没问题。
好的,我想通了问题:必须转换 SESSION_CONTEXT 过程的结果,否则 SQL 服务器无法对查询的基数进行正确的假设。铸造SESSION_CONTEXT,性能变得非常好
WHERE CAST(SESSION_CONTEXT(N'UserMail') AS NVARCHAR(255) = u.Email
我正在为 SQL Server/Azure SQL 数据库中的行级安全性开发过滤器谓词。
与可见性硬币相关的应用程序逻辑要求必须阅读大量 tables 才能了解确定的用户是否可以阅读或更少阅读一行。我制定了以下逻辑:
- 过滤谓词的内联table值函数; -- 在其中,一个 CTE 用于获取用户的所有配置文件。此 CTE 的结果必须使用 CROSS APPLY 运算符与一组内联 Table 值函数连接。
代码如下:
CREATE FUNCTION [scr].[prj_Projects](@ProjectId INT, @FilterId1 INT, @FilterId2 INT, @FilterId3 INT, @FilterId4 INT, @FilterId5 INT, @FilterId6 INT)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN (
WITH UserProfiles AS (
SELECT up.Id
FROM dbo.users u
INNER JOIN dbo.UsersProfiles up ON up.UserId = u.Id
INNER JOIN dbo.Profiles p ON p.id = up.ProfileId
WHERE SESSION_CONTEXT(N'UserId') = u.Id
)
SELECT Result = 1
FROM UserProfiles up
CROSS APPLY [scr].[prj_ProfilesFilter1](up.Id, @FilterId1)
CROSS APPLY [scr].[prj_ProfilesFilter2](up.Id, @FilterId2)
CROSS APPLY [scr].[prj_ProfilesFilter3](up.Id, @FilterId3)
CROSS APPLY [scr].[prj_ProfilesFilter4](up.Id, @FilterId4)
CROSS APPLY [scr].[prj_ProfilesFilter5](up.Id, @FilterId5)
CROSS APPLY [scr].[prj_ProfilesFilter6](up.Id, @FilterId6)
)
GO
在查询一个ITVF之后(它们的结构都一样)。
CREATE OR ALTER FUNCTION [scr].[prj_ProfilesFilter1] (@UserProfileId INTEGER, @FilterId1 INTEGER)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN (
WITH UserProfile AS (
SELECT DISTINCT upba.FilterId1
FROM dbo.UsersProfilesFilters upba
WHERE upba.UserProfileId = @UserProfileId
), Datas AS (
SELECT b.Id
FROM dbo.Filters1 b
INNER JOIN UserProfile c ON c.FilterId1 = b.Id
UNION ALL
SELECT b.Id
FROM dbo.Filters1 b
WHERE NOT EXISTS (SELECT 1 FROM UserProfile)
UNION ALL
SELECT -1
WHERE NOT EXISTS (SELECT 1 FROM UserProfile)
) SELECT Id
FROM Datas d
WHERE d.Id = ISNULL(@FilterId1 , -1)
)
GO
本来以为设计还可以,可惜表演很烂。与执行计划无关(例如,我只看到查找而没有扫描),但问题与查询执行的大量扫描计数和逻辑读取有关(非常非常高)。这很奇怪,因为每个交叉应用 returns 只有一行并且只有设置操作。
对于如何避免这种大量的逻辑读取,您有什么想法吗? 我认为这是与 RLS
相关的错误更新: 这里是查询的执行计划:https://www.brentozar.com/pastetheplan/?id=r1mHXespO 正如我所说,问题与查询执行的逻辑读取数和扫描计数有关,因为执行计划似乎没问题。
好的,我想通了问题:必须转换 SESSION_CONTEXT 过程的结果,否则 SQL 服务器无法对查询的基数进行正确的假设。铸造SESSION_CONTEXT,性能变得非常好
WHERE CAST(SESSION_CONTEXT(N'UserMail') AS NVARCHAR(255) = u.Email