估计的删除计划或不存在的工单的实际删除计划:
delete from ticket where ticket_guid = newid()
--or
delete from ticket where ticket_guid is null
表明安全功能“驻留在”两个“删除”之间:
最左边的部分是 T-SQL 删除(delete 语句)
右侧是工单的集群删除(和附件的级联删除)
安全功能是“执行和评估”之后 [嵌套循环的内部]行被删除/删除[嵌套循环的外部]但之前删除行的已提交 [t-sql delete]。
高级描述可以是:对于每个工单行,删除工单和相应的附件,输出已删除的外键:ticket_guid(序列运算符,由底部 TableSpool 提供)和每个已删除的 guid(嵌套循环,外部)执行安全函数(嵌套循环的内部)左半连接,因为安全函数不能返回任何结果,如果函数的结果为空或不为空,则评估/断言,如果不为空,票行是“已删除”并移至下一张票。
如果安全函数引用了任何行被删除的表,那么这些表将在行删除之后重新访问,并且如果函数的结果取决于已删除的行,那么安全将始终失败。
这可以使用一个函数来测试,该函数检查假定的“待删除”(实际上它之前已被删除)ticket_guid 是否存在:
create table dbo.ticketX
(
ticket_guid uniqueidentifier default(newsequentialid()) primary key clustered,
ticket_client_guid uniqueidentifier,
ticket_description varchar(10)
);
go
insert into dbo.ticketX(ticket_client_guid, ticket_description)
select v.guid, v.descr
from
(values (newid(), 'ticketA'), (newid(), 'ticketB'), (newid(), 'ticketC')) as v(guid, descr)
go
insert into dbo.ticketX(ticket_client_guid, ticket_description)
select v.guid, v.descr
from
(values (newid(), 'abc'), (newid(), 'ticketE'), (newid(), 'ticketF')) as v(guid, descr)
go
create function dbo.fn_ticketX_delete(@ticket_guid uniqueidentifier)
returns table
with schemabinding
as
return
(
select 1 as fn_securitypredicate_result
where exists(select 1 from dbo.ticketX where ticket_guid = @ticket_guid)
)
go
create security policy dbo.ticketX_security
add block predicate dbo.fn_ticketX_delete(ticket_guid) on dbo.ticketX before delete
with (state=on, schemabinding=on);
go
由于在删除行后对函数求值,删除的ticket_guid不存在,函数什么也不返回,安全检查总是失败:
--The attempted operation failed because the target object 'xyz.dbo.ticketX' has a block predicate that conflicts with this operation.
delete from dbo.ticketX;
可以使用调整后的安全函数来测试某些行被删除(直到违反安全性并回滚整个语句)的事实:
drop security policy dbo.ticketX_security;
go
--allow deletion of tickets with description like ticket%
create or alter function dbo.fn_ticketX_delete(@ticket_description varchar(10))
returns table
with schemabinding
as
return
(
select 1 as fn_securitypredicate_result
where @ticket_description like 'ticket%'
--where exists(select 1 from dbo.ticketX where ticket_guid = @ticket_guid)
)
go
create security policy dbo.ticketX_security
add block predicate dbo.fn_ticketX_delete(ticket_description) on dbo.ticketX before delete
with (state=on, schemabinding=on);
go
delete from dbo.ticketX
output deleted.* --some rows "get deleted"
问题:如何通过脏读来克服这个问题并制作
删除策略按预期工作?
没有脏读。事实上,如果脏读是可能的,安全功能可能会起作用:
RETURN
SELECT 1 AS fn_securitypredicate_result
WHERE (SELECT attachment_description
FROM dbo.attachment with(nolock) --if this were possible/enforced, it could work ??
WHERE attachment_ticket_guid = @ticket_guid) = 'some
由于创建安全策略声明有点误导,因此预期被颠覆了。
更准确的声明是:
CREATE SECURITY POLICY xyz... ADD BLOCK PREDICATE... after row is deleted BEFORE DELETE commits;
一般来说,最好以确定性的方式处理 RLS,即:在不受 DML 操作影响的值上评估常量的安全功能。在某种程度上,安全性本质上是确定性的。如果安全功能依赖于其他表(或非固定/持久的列/值*),则它变得不确定(且不可预测),但最终结果,安全策略评估必须是确定性的。
*您可以尝试在票据表上创建一个计算列,该列检查附件描述 = 'some specific description' 的存在并在安全函数中使用该列,但仍然是该列(不持久,因为它访问数据)是在删除票证和附件行之后计算的,它对于按照您设想的方式执行策略没有任何用处。
让我们考虑一下,只有在存在
链接到它的附件,附件作者基本上是在
描述:“请删除我的票 {signature}”。
如果您考虑要求,它与安全性无关==谁可以删除数据,它与根据您的业务规则的数据完整性有关:何时/在什么条件下可以删除数据。如果您想将此逻辑封装在 dbschema 中,那么附件触发器是执行它的最佳位置。