This seems like a good use case for using the OFFSET clause... Something along theses lines.
Note: because you're sorting on the clustered key, there should be no sort operation in the execution plan.IF OBJECT_ID('tempdb..#test_data', 'U') IS NOT NULL
BEGIN DROP TABLE #test_data; END;
CREATE TABLE #test_data (
oddly_spaced_id BIGINT NOT NULL,
update_val INT NULL
);
WITH
cte_n1 (n) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (n)),
cte_n2 (n) AS (SELECT 1 FROM cte_n1 a CROSS JOIN cte_n1 b),
cte_n3 (n) AS (SELECT 1 FROM cte_n2 a CROSS JOIN cte_n2 b),
cte_Tally (n) AS (
SELECT TOP (1000000)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM
cte_n3 a CROSS JOIN cte_n3 b
)
INSERT #test_data (oddly_spaced_id)
SELECT DISTINCT
ABS(CHECKSUM(NEWID()) % t.n) * t.n
FROM
cte_Tally t
ALTER TABLE #test_data ADD PRIMARY KEY (oddly_spaced_id);
--==============================================================
DECLARE
@total_rows INT = (SELECT COUNT(1) FROM #test_data td),
@position INT = 0;
WHILE @position < @total_rows
BEGIN
WITH
cte_get_batch AS (
SELECT td.update_val
FROM
#test_data td
ORDER BY
td.oddly_spaced_id
OFFSET @position ROWS FETCH NEXT 10000 ROWS ONLY
)
UPDATE gb SET
gb.update_val = @position
FROM
cte_get_batch gb;
SET @position = @position + 10000;
END;
SELECT TOP 20000 * FROM #test_data td ORDER BY td.oddly_spaced_id;