Какой самый быстрый способ сделать массовую вставку в 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» или подобное.

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



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.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.