Какой самый быстрый способ сделать массовую вставку в Postgres?

242

Мне нужно программно вставить десятки миллионов записей в базу данных postgres. В настоящее время я выполняю тысячи операторов вставки в одном «запросе».

Есть ли лучший способ сделать это, какое-то массовое выражение вставки, о котором я не знаю?

ясень
источник

Ответы:

211

В PostgreSQL есть руководство о том, как лучше всего заполнить базу данных изначально, и они предлагают использовать команду COPY для массовой загрузки строк. В руководстве есть и другие полезные советы по ускорению процесса, такие как удаление индексов и внешних ключей перед загрузкой данных (и последующее добавление их обратно).

Дэн Лью
источник
33
Я написал немного более подробно разработать в stackoverflow.com/questions/12206600/... тоже.
Крейг Рингер
24
@CraigRinger Ого, «немного больше деталей» - лучшее из всего, что я видел за всю неделю;)
culix
Постарайтесь Install-Package NpgsqlBulkCopy
Элёр
1
-Так как индексы также используются для физического размещения записей БД. Не уверен, что удаление индексов в любой базе данных - хорошая идея.
Фархад
Но ваши рекомендованные, ничего в памяти !!! И если размер вашего пакета может быть небольшим, очень-очень плохо сработал его класс :( Я пытаюсь использовать класс npgsql CopyIn, потому что это похоже на отображение в формате CSV в операторах запросов PG. Вы можете попробовать для Big Table?
Elyor
94

Существует альтернатива использованию COPY - синтаксис многострочных значений, поддерживаемый Postgres. Из документации :

INSERT INTO films (code, title, did, date_prod, kind) VALUES
    ('B6717', 'Tampopo', 110, '1985-02-10', 'Comedy'),
    ('HG120', 'The Dinner Game', 140, DEFAULT, 'Comedy');

Приведенный выше код вставляет две строки, но вы можете расширять его произвольно, пока не достигнете максимального количества подготовленных токенов операторов (это может быть 999 долларов, но я не уверен на 100% в этом). Иногда нельзя использовать COPY, и это достойная замена для таких ситуаций.

Бен Харпер
источник
12
Знаете ли вы, как производительность этого метода по сравнению с COPY?
Грант Хамфрис
Если вы столкнулись с проблемой прав доступа, прежде чем пытаться это сделать, используйте COPY ... FROM STDIN
Эндрю Скотт Эванс
Если вы используете безопасность на уровне строк, это лучшее, что вы можете сделать. «COPY FROM не поддерживается для таблиц с безопасностью на уровне строк»
начиная с
COPY намного быстрее, чем расширенный INSERT
hipertracker
24

Одним из способов ускорить процесс является явное выполнение нескольких вставок или копий в транзакции (скажем, 1000). Поведение Postgres по умолчанию заключается в фиксации после каждого оператора, поэтому, пакетируя коммиты, вы можете избежать некоторых накладных расходов. Как говорится в ответе Даниэля, возможно, вам придется отключить автокоммит, чтобы это работало. Также обратите внимание на комментарий внизу, который предлагает увеличить размер wal_buffers до 16 МБ, также может помочь.

Дана вменяемая
источник
1
Стоит отметить, что ограничение на количество вставок / копий, которые вы можете добавить к одной и той же транзакции, вероятно, намного выше, чем все, что вы пытаетесь сделать. Вы можете добавить миллионы и миллионы строк в одной транзакции и не столкнуться с проблемами.
Сумит Джайн
@SumeetJain Да, я просто отмечаю скорость «сладкое пятно» с точки зрения количества копий / вставок на транзакцию.
Дана вменяемая
Будет ли это блокировать таблицу во время выполнения транзакции?
Лямбда-фея
15

UNNESTФункция с массивами может использоваться вместе с многорядным синтаксисом VALUES. Я думаю, что этот метод медленнее, чем использование, COPYно он полезен для меня при работе с psycopg и python (python listпередается в cursor.executepg ARRAY):

INSERT INTO tablename (fieldname1, fieldname2, fieldname3)
VALUES (
    UNNEST(ARRAY[1, 2, 3]), 
    UNNEST(ARRAY[100, 200, 300]), 
    UNNEST(ARRAY['a', 'b', 'c'])
);

без VALUESиспользования подвыбора с дополнительной проверкой существования:

INSERT INTO tablename (fieldname1, fieldname2, fieldname3)
SELECT * FROM (
    SELECT UNNEST(ARRAY[1, 2, 3]), 
           UNNEST(ARRAY[100, 200, 300]), 
           UNNEST(ARRAY['a', 'b', 'c'])
) AS temptable
WHERE NOT EXISTS (
    SELECT 1 FROM tablename tt
    WHERE tt.fieldname1=temptable.fieldname1
);

тот же синтаксис для массовых обновлений:

UPDATE tablename
SET fieldname1=temptable.data
FROM (
    SELECT UNNEST(ARRAY[1,2]) AS id,
           UNNEST(ARRAY['a', 'b']) AS data
) AS temptable
WHERE tablename.id=temptable.id;
НДПУ
источник
11

Вы можете использовать, COPY table TO ... WITH BINARYчто « несколько быстрее, чем текст и форматы CSV ». Делайте это только в том случае, если у вас есть миллионы строк для вставки и если вы знакомы с двоичными данными.

Вот пример рецепта в Python, использующего psycopg2 с двоичным вводом .

Майк Т
источник
9

В основном это зависит от (другой) активности в базе данных. Такие операции эффективно замораживают всю базу данных для других сеансов. Другое соображение - это модель данных и наличие ограничений, триггеров и т. Д.

Мой первый подход всегда: создать (временную) таблицу со структурой, аналогичной целевой таблице (создать таблицу tmp AS select * from target, где 1 = 0), и начать с чтения файла во временную таблицу. Затем я проверяю, что можно проверить: дубликаты, ключи, которые уже существуют в цели, и т. Д.

Затем я просто делаю «сделать вставку в целевой select * from tmp» или подобное.

Если это не удается или занимает слишком много времени, я прекращаю его и рассматриваю другие методы (временное удаление индексов / ограничений и т. Д.)

wildplasser
источник
6

Я только что столкнулся с этой проблемой и рекомендую csvsql ( релизы ) для массового импорта в Postgres. Чтобы выполнить массовую вставку, вы просто createdbи затем используете ее csvsql, которая подключается к вашей базе данных и создает отдельные таблицы для всей папки CSV.

$ createdb test 
$ csvsql --db postgresql:///test --insert examples/*.csv
Сара Фростенсон
источник
1
Для csvsql, чтобы также очистить исходный csv от любых возможных ошибок форматирования, лучше следовать этим инструкциям , больше документации здесь
sal
0

Внешний файл - это лучшие и типичные объемные данные.

Термин «объемные данные» относится к «большому количеству данных», поэтому естественно использовать исходные необработанные данные без необходимости их преобразования в SQL. Типичными файлами необработанных данных для «массовой вставки» являются форматы CSV и JSON .

Массовая вставка с некоторым преобразованием

В приложениях ETL и процессах приема данных нам нужно изменить данные перед их вставкой. Временная таблица потребляет (много) дискового пространства, и это не быстрый способ сделать это. Обертка внешних данных PostgreSQL (ИДП) является лучшим выбором.

CSV пример . Предположим , что tablename (x, y, z)на SQL и CSV файл , как

fieldname1,fieldname2,fieldname3
etc,etc,etc
... million lines ...

Вы можете использовать классический SQL COPYдля загрузки ( как и исходные данные), в который tmp_tablenameони вставляют отфильтрованные данные tablename... Но, чтобы избежать потребления диска, лучше всего проглотить непосредственно

INSERT INTO tablename (x, y, z)
  SELECT f1(fieldname1), f2(fieldname2), f3(fieldname3) -- the transforms 
  FROM tmp_tablename_fdw
  -- WHERE condictions
;

Вам нужно подготовить базу данных для FDW, и вместо статической tmp_tablename_fdwвы можете использовать функцию, которая ее генерирует :

CREATE EXTENSION file_fdw;
CREATE SERVER import FOREIGN DATA WRAPPER file_fdw;
CREATE FOREIGN TABLE tmp_tablename_fdw(
  ...
) SERVER import OPTIONS ( filename '/tmp/pg_io/file.csv', format 'csv');

Пример JSON . Набор из двух файлов, myRawData1.jsonи Ranger_Policies2.jsonможет поступать в организм с помощью:

INSERT INTO tablename (fname, metadata, content)
 SELECT fname, meta, j  -- do any data transformation here
 FROM jsonb_read_files('myRawData%.json')
 -- WHERE any_condiction_here
;

где функция jsonb_read_files () читает все файлы папки, определенные маской:

CREATE or replace FUNCTION jsonb_read_files(
  p_flike text, p_fpath text DEFAULT '/tmp/pg_io/'
) RETURNS TABLE (fid int,  fname text, fmeta jsonb, j jsonb) AS $f$
  WITH t AS (
     SELECT (row_number() OVER ())::int id, 
           f as fname,
           p_fpath ||'/'|| f as f
     FROM pg_ls_dir(p_fpath) t(f)
     WHERE    f like p_flike
  ) SELECT id,  fname,
         to_jsonb( pg_stat_file(f) ) || jsonb_build_object('fpath',p_fpath),
         pg_read_file(f)::jsonb
    FROM t
$f$  LANGUAGE SQL IMMUTABLE;

Отсутствие потоковой передачи gzip

Наиболее частый метод «загрузки файлов» (главным образом в больших данных) - это сохранение исходного файла в формате gzip и передача его с помощью потокового алгоритма , всего, что может работать быстро и без использования диска в каналах Unix:

 gunzip remote_or_local_file.csv.gz | convert_to_sql | psql 

Таким образом, идеальный (будущий) вариант сервера для формата .csv.gz.

Питер Краусс
источник