Sql 以小批量从CTE中删除记录

Sql 以小批量从CTE中删除记录,sql,sql-server,stored-procedures,Sql,Sql Server,Stored Procedures,我需要从最近编写的表中删除旧记录。我的问题是,当另一个进程尝试读取或写入表时,我的delete语句会失败并导致死锁。 它是大型存储过程的一部分,它以事务隔离级别read uncommitted和死锁优先级低开始 如何转换此cte和delete语句以部分删除记录?例如,如果我可以选择cte(x)中运行delete语句的x/500条记录的计数,那么一段时间将是完美的 ;with chargesToDelete(id, ciid) as ( select c.id, ci.Id f

我需要从最近编写的表中删除旧记录。我的问题是,当另一个进程尝试读取或写入表时,我的delete语句会失败并导致死锁。 它是大型存储过程的一部分,它以
事务隔离级别read uncommitted
死锁优先级低开始

如何转换此cte和delete语句以部分删除记录?例如,如果我可以选择cte(x)中运行delete语句的x/500条记录的计数,那么一段时间将是完美的

  ;with chargesToDelete(id, ciid) as (
        select c.id, ci.Id from @chargeids c
        left join xxx.dbo.chargeitems ci on ci.Charge_Id = c.id
        where ci.id is null
    )
    delete from xxx.dbo.charges
        where Id in (select id from chargesToDelete);

您可以使用会话变量
@@ROWCOUNT
DELETE
上循环一个
TOP N

SELECT 1 -- Forces @@ROWCOUNT = 1

WHILE @@ROWCOUNT > 0
BEGIN

    delete TOP (500) I from 
        xxx.dbo.charges AS I
    where 
        exists (
            select 
                'to delete' 
            from 
                @chargeids c
                left join xxx.dbo.chargeitems ci on ci.Charge_Id = c.id
            where 
                ci.id is null AND
                c.id = I.Id)

END
如果您不想使用sentinel
选择
(这将返回结果给客户端,如果有的话),您可以使用变量

DECLARE @ForceStart BIT = 1

WHILE @@ROWCOUNT > 0 OR @ForceStart = 1
BEGIN

    SET @ForceStart = 0

    delete TOP (500) I from 
        xxx.dbo.charges AS I
    where 
        exists (
            select 
                'to delete' 
            from 
                @chargeids c
                left join xxx.dbo.chargeitems ci on ci.Charge_Id = c.id
            where 
                ci.id is null AND
                c.id = I.Id)

END
如果处理子查询需要很长时间,则可能需要创建一个临时表,其中包含要删除的ID,并在循环中与其连接

作为旁注,如果您正在检查
xxx.dbo.chargeitems
上记录的不存在性,则执行
不存在
将比
左连接
的速度快,且
为空


编辑:使用临时表格保存ID:

-- Create temporary table
IF OBJECT_ID('tempdb..#ChargesToDelete') IS NOT NULL
    DROP TABLE #ChargesToDelete

SELECT DISTINCT
    c.id
INTO
    #ChargesToDelete
from 
    @chargeids c
    left join xxx.dbo.chargeitems ci on ci.Charge_Id = c.id
where 
    ci.id is null

CREATE CLUSTERED INDEX CI_ChargesToDelete ON #ChargesToDelete (id)


-- Delete rows in batches
SELECT 1 -- Forces @@ROWCOUNT = 1

WHILE @@ROWCOUNT > 0
BEGIN

    delete TOP (500) I from 
        xxx.dbo.charges AS I
    where 
        exists (select 'to delete' from #ChargesToDelete AS C where c.id = I.Id)

END
您可以尝试以下代码:

declare @deleted table (id int)
declare @amountToDelete int = 2

delete from @deleted
    delete top (@amountToDelete) from xxx.dbo.charges
    output deleted.* into @deleted
    where not exists(select 1 from xxx.dbo.chargeitems
                     where charge_id = xxx.dbo.charges.id)

while (select count(*) from @deleted) = @amountToDelete
begin
    delete from @deleted
    delete top (@amountToDelete) from xxx.dbo.charges
    output deleted.* into @deleted
    where not exists(select 1 from xxx.dbo.chargeitems
                     where charge_id = xxx.dbo.charges.id)
end
@amountToDelete
表示一次要删除的记录批的大小。另一个变量类型为
table
,在循环的一次运行中保存已删除的
id
s。停止循环的条件是,删除的记录少于应该删除的记录(这意味着有最后一条记录要删除,并且它们已被删除),这与此条件相对应:

while (select count(*) from @deleted) = @amountToDelete

第一次删除在循环之前(外部)执行。如果首先删除所有的重新记录,代码将不会进入循环。

这是一个“批量删除”问题吗?Aaron Bertrand在这里写过这样的博客:虽然解决方案不使用CTE,但它似乎在不锁定整个表的情况下执行删除操作。在第三次运行期间再次引发死锁。如何运行此删除以避免死锁?@Perrier确保此操作不在事务内,或者在每次删除后(在循环内)执行显式的begin transaction和commit。您还必须检查死锁是否与针对这些表运行的另一个过程或脚本有关。SP以try-catch块启动,以便正确处理错误。但据我所知,try不会启动事务。@Perrier使用需要删除的ID创建一个临时表,然后使用此表加入
费用
(而不是每次加入
费用项目
)。这将减少共享锁定时间。另外,请确保SP的其他部分上没有锁定提示(如带有(UPDLOCK)
)。