But we don't want to lock our table.
Makes sense in many sistuations But you did not disclose your actual setup. Do you even need a lock? Is there concurrent write activity? If not, is there enough storage to write another copy of the table? Then it might be preferable to build a new pristine, updated table in the background, then switch, and delete the old one. See:
Assuming concurrent write activity to the table. And you don't want to block too much of it for too long. And you want to reuse dead tuples to keep table bloat and index bloat at bay. So updating in batches makes sense. You have to COMMIT (and VACUUM) between batches so that space occupied by dead tuples can be reused. And spread out writes across the table to allow consecutive transactions to produce and consume dead tuples in same blocks.
Transaction control statements (like COMMIT) are allowed in procedures or anonymous code blocks in DO statements in Postgres 11 or newer. Others answer provided solutions using that.
autovacuum should be running with aggressive settings, to release dead tuples for reuse in time. Or run VACUUM manually at some intervals - but that cannot (currently) be run in transaction context at all (only as individual command), so not possible in a PL/pgSQL loop.
Postgres 10 or older
No transaction control in code block allowed, yet. We can emulate autonomous transactions with dblink, though. See:
Could look like:
DO
$do$
DECLARE
_cur int := 0; -- just start with 0 unless min is far off
_step int := 10000; -- batch size
_max CONSTANT int := (SELECT max(id) FROM account); -- max id
_val CONSTANT text := 'SOME name';
BEGIN
-- as superuser, or you must also provide the password for the current role;
PERFORM dblink_connect('dbname=' || current_database()); -- current db
LOOP
RAISE NOTICE '%', _cur;
PERFORM dblink_exec( -- committed implicitly!
$$
UPDATE account
SET name = 'SOME name'
WHERE id BETWEEN _cur AND _cur + _step -- gaps don't matter unless huge
AND name IS DISTINCT FROM 'SOME name' -- avoid empty updates
$$);
_cur := _cur + _step;
EXIT WHEN _cur > _max; -- stop when done
END LOOP;
PERFORM dblink_disconnect();
END
$do$;
I also added another predicate:
AND name IS DISTINCT FROM 'SOME name' -- avoid empty updates
To skip the cost for empty updates where the row already has the new name. Only useful if that can happen. See:
You may want to split it up further, and run VACUUM in between. And you may want to use some other column for selection than id (one that is not clustered) to get a good spread across the whole table.