How are you handling the LOOP? For me it would be critical that there was almost-zero-effort in "finding" the rows for the next batch. So I aim for this:
FROM MySourceTable AS S
WHERE S.MyClusteredIndexKey >= @StartPoint
AND S.MyClusteredIndexKey < @EndPoint
because this is really easy for SQL to do I definitely don't have anything in the WHERE clause that does a NOT EXISTS or anything like that (to find the start point, based on what has already copied)
When we bulk-delete stale rows I do something like
SELECT [T_ID] = IDENTITY(int, 1, 1),
WHERE ... any selection criteria I need ...
ORDER BY MyClusteredIndexKey
My #'TEMP table is then in clustered key order and I can use the IDENTITY column to "page" the #TEMP table.
I can then easily do
FROM #TEMP AS T
JOIN MyRealTable AS D
D.MyClusteredIndexKey = T.MyClusteredIndexKey
WHERE T_ID >= @StartPoint
AND T_ID < @EndPoint
and then I can "slide" the @Start and @EndPoint to the next "page". All the deletes, in each batch, are tightly-grouped in Clustered Index Order - so should be more efficient than "jumping around all over the place"!!
I'm sure you know, and are doing, all this so just mentioning it Belt & Braces