批量删除CTE中的记录

时间:2018-07-03 10:02:23

标签: sql sql-server stored-procedures

我需要从最近正在写入的表中删除旧记录。我的问题是,当其他进程尝试读取或写入表时,我的delete语句将因死锁而失败。 它是一个大型存储过程的一部分,该存储过程以Transaction isolation level read uncommitteddeadlockpriority low开头。

如何转换此cte和delete语句以部分删除记录?例如,如果我可以选择在cte(x)中为x / 500条记录运行delete语句的记录数,那会很完美。

  ;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);

2 个答案:

答案 0 :(得分:1)

您可以在@@ROWCOUNT上使用会话变量TOP NDELETE进行循环。

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

如果您不想使用前哨SELECT(它将结果返回给客户端,如果有的话),则可以使用变量。

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上的记录是否不存在,则进行NOT EXISTS的速度要比LEFT JOINIS NULL快。


编辑:使用临时表保存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

答案 1 :(得分:0)

您可以尝试以下代码:

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。停止循环的条件是删除的记录少于应该删除的记录(这意味着要删除的最后一条记录已经被删除),这与以下条件相对应:

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

第一次删除是在循环之前(循环之外)执行的。如果一开始删除所有后退,则代码不会进入循环。