Самым быстрым будет именно
insert into public.copy_users select * from public.users
Все прочие варианты будут медленнее, но могут использоваться в жизни чтобы хотя бы как-то понимать, на каком этапе копирование, с какой скоростью идёт и сколько осталось.
20гб это не так уж много, вероятно куда-то упираетесь существенно.
- посмотрите какие и сколько у вас индексов на таблице куда копируете, т.е. на
copy_users
. Если это возможно по задаче, то удалить с таблицы все индексы и построить их после копирования данных - будет быстрее.
- аналогично с foreign key, проверьте их наличие, проверьте наличие индексов на той таблице, куда эти FK ссылаются
- если на
copy_users
есть триггеры - посмотрите, можете ли вы их отключить/удалить на время копирования данных. Особенно for each row триггеры даже быстрые сами по себе, но помноженные на число копируемых строк в таблицу - величина существенная
- посмотрите вообще на определение таблицы, нет ли чего интересного. RLS политики, переопределения rule, необычный (не heap) table access method, не FDW ли это вообще вместо локальной таблицы
- далее железо - где выполняется запрос? есть ли ресурсы именно железа или ресурсы IO или CPU и так перегружены. На HDD, с недостатком RAM и с необходимостью обновлять индексы в процессе - будет работать примерно бесконечно
- далее настройки - может быть нужно временно поднять
max_wal_size
и checkpoint_timeout
Для нарезки операции на части нужно какое-то селективное поле или комбинацию полей.
select attname, null_frac, n_distinct from pg_stats where schemaname = ? and tablename = ?;
данные статистики самой базы можно использовать как отправную точку. При этом смотрим на существующие индексы, какие поля можем использовать для эффективного чтения данных (если есть уникальные индексы - то это наши фавориты).
Далее выбираем диапазон значений по вкусу и пробуем запустить копирование первого куска небольшого размера. Нам нужно оценить, сколько это займёт времени. Считаем грубо примерное время на всё копирование данных и оцениваем, подходит ли это.
Делать ровно N, не больше и не меньше строк в пачке, не имеет смысла. Нужен только разумного времени выполнения шаг (до десятка минут). Получилось не 100тыс строк, а 5тыс или 500тыс - ну и ладно. Главное что знаем где сейчас процесс и что он вообще идёт.
хинт: "итератор" через limit offset или row_number будут самыми худшими идеями. Просто потому, как оба этих метода работают на самом деле, сколь много бесполезной работы те делают на смещении в условный миллион строк (внимательно посмотрите в explain - даже если у вас Index Scan, то вы прочитаете все 1001000 строк только чтобы отбросить первые 1000000 и вернуть 1000)