Как работает COPY и почему он намного быстрее, чем INSERT?

сегодня я провел свой день, улучшая производительность моего скрипта Python, который толкает данные в мою базу данных Postgres. Ранее я вставлял записи как таковые:

query = "INSERT INTO my_table (a,b,c ... ) VALUES (%s, %s, %s ...)";
for d in data:
    cursor.execute(query, d)

затем я переписал свой скрипт так, что он создает файл в памяти, чем используется для Postgres' COPY команда, которая позволяет мне копировать данные из файла в мою таблицу:

f = StringIO(my_tsv_string)
cursor.copy_expert("COPY my_table FROM STDIN WITH CSV DELIMITER AS E't' ENCODING 'utf-8' QUOTE E'b' NULL ''", f)

на COPY метод ошеломляюще быстрее.

METHOD      | TIME (secs)   | # RECORDS
=======================================
COPY_FROM   | 92.998    | 48339
INSERT      | 1011.931  | 48377

но я не могу найти информация о том, почему? Как это работает иначе, чем многострочный INSERT так что это делает его намного быстрее?

посмотреть этот тест а также:

# original
0.008857011795043945: query_builder_insert
0.0029380321502685547: copy_from_insert

#  10 records
0.00867605209350586: query_builder_insert
0.003248929977416992: copy_from_insert

# 10k records
0.041108131408691406: query_builder_insert
0.010066032409667969: copy_from_insert

# 1M records
3.464181900024414: query_builder_insert
0.47070908546447754: copy_from_insert

# 10M records
38.96936798095703: query_builder_insert
5.955034017562866: copy_from_insert

3 ответов


здесь действует ряд факторов:

  • задержка сети и задержки туда и обратно
  • накладные расходы на каждый оператор в PostgreSQL
  • контекстные переключатели и задержки планировщика
  • COMMIT затраты, если для людей, делающих одну фиксацию за вставку (вы не)
  • COPY - специфические оптимизации для массовой загрузки

задержки в Сети

если сервер удален, вы можете быть "оплата "фиксированного времени" цена " за выписку, скажем, 50 мс (1/20 секунды). Или больше для некоторых облачных БД. Поскольку следующая вставка не может начаться, пока последняя не завершится успешно, это означает, что ваш максимум тариф вставок 1000 строк / туда и обратно-латентност-в-МС в секунду. При задержке 50 мс ("время пинга") это 20 строк в секунду. Даже на локальном сервере эта задержка ненулевая. Wheras COPY просто заполняет окна отправки и получения TCP и строки потоков так же быстро поскольку БД может записывать их, а сеть может передавать их. Он не сильно зависит от задержки и может вставлять тысячи строк в секунду в одну и ту же сетевую ссылку.

стоимость каждого оператора в PostgreSQL

существуют также затраты на синтаксический анализ, планирование и выполнение оператора в PostgreSQL. Он должен принимать блокировки, открывать файлы отношений,искать индексы и т. д. COPY пытается сделать все это один раз, в начале, а затем просто сосредоточиться на загрузке строк так же быстро, как вероятный.

затраты на переключение задач/контекста

дополнительные затраты времени оплачиваются из-за того, что операционной системе приходится переключаться между postgres, ожидающими строки, пока ваше приложение готовит и отправляет ее, а затем ваше приложение ждет ответа postgres, пока postgres обрабатывает строку. Каждый раз, когда вы переключаетесь с одного на другое, вы теряете немного времени. Больше времени потенциально впустую приостанавливать и возобновлять различное низкоуровневое состояние ядра когда процессы входят и оставьте состояния ожидания.

отсутствует оптимизация копирования

кроме того, COPY имеет некоторые оптимизации, которые он может использовать для некоторых видов нагрузок. Если нет сгенерированного ключа и любые значения по умолчанию являются константами, например, он может предварительно вычислить их и полностью обойти исполнитель, быстро загружая данные в таблицу на более низком уровне, который полностью пропускает часть нормальной работы PostgreSQL. Если ты ... --5--> или TRUNCATE в той же транзакции вы COPY, он может сделать еще больше трюков для ускорения загрузки, минуя обычную бухгалтерию транзакций, необходимую в многопользовательской базе данных.

несмотря на это, PostgreSQL COPY все еще может сделать намного больше, чтобы ускорить вещи, которые он еще не знает, как это сделать. Он может автоматически пропускать обновления индекса, а затем перестраивать индексы, если вы меняете более определенной части таблицы. Он может выполнять обновления индекса пакетами. Намного больше.

Commit затраты

последнее, что нужно учитывать, это затраты на фиксацию. Это, вероятно, не проблема для вас, потому что psycopg2 по умолчанию для открытия транзакции и не фиксации, пока вы не скажете Это. Если вы не сказали ему использовать autocommit. Но для многих драйверов DB autocommit является значением по умолчанию. В таких случаях вы будете делать одну фиксацию для каждого INSERT. Это означает, что один диск flush, где сервер гарантирует, что он записывает все данные в памяти на диск и говорит дискам записывать свои собственные кэши постоянное хранилище. Это может занять долго время, и меняет много основанное на оборудовании. Мой SSD-ноутбук NVMe BTRFS может делать только 200 fsyncs / second, против 300,000 несинхронизированных записей / second. Таким образом, он будет загружать только 200 строк/секунду! Некоторые серверы могут выполнять только 50 fsyncs / second. Некоторые могут сделать 20,000. Поэтому, если вам нужно регулярно фиксировать, попробуйте загружать и фиксировать пакетами, делать многорядные вставки и т. д. Потому что COPY только одна фиксация в конце, затраты на фиксацию незначительны. Но это также значит COPY не удается восстановить ошибки частично через данные; это отменяет всю массовую нагрузку.


Copy использует массовую загрузку, то есть вставляет несколько строк каждый раз, в то время как простая вставка делает одну вставку за раз, однако вы можете вставить несколько строк с помощью insert, следуя синтаксису:

insert into table_name (column1, .., columnn) values (val1, ..valn), ..., (val1, ..valn)

дополнительные сведения об использовании массовой нагрузки см., например,самый быстрый способ загрузить 1m строк в postgresql от Daniel Westermann.

вопрос о том, сколько строк вы должны вставить за один раз, зависит от длины линии, хорошее правило вставить 100 строк на инструкцию insert.


do вставляет в транзакцию для ускорения.

тестирование в bash без транзакции:

>  time ( for((i=0;i<100000;i++)); do echo 'INSERT INTO testtable (value) VALUES ('$i');'; done ) | psql root | uniq -c
 100000 INSERT 0 1

real    0m15.257s
user    0m2.344s
sys     0m2.102s

и с проводки:

> time ( echo 'BEGIN;' && for((i=0;i<100000;i++)); do echo 'INSERT INTO testtable (value) VALUES ('$i');'; done && echo 'COMMIT;' ) | psql root | uniq -c
      1 BEGIN
 100000 INSERT 0 1
      1 COMMIT

real    0m7.933s
user    0m2.549s
sys     0m2.118s