Який найшвидший спосіб зробити об'ємну вставку в Postgres?


242

Мені потрібно програмно вставити 10 мільйонів записів у базу даних postgres. В даний час я виконую 1000 записів вставки в одному "запиті".

Чи є кращий спосіб зробити це, якесь твердження, яке я не знаю?

Відповіді:


211

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


33
Я написав трохи більше деталей, щоб детально розповісти в stackoverflow.com/questions/12206600/… .
Крейг Рінгер

24
@CraigRinger Вау, "трохи більше деталей" - це найкраще заниження, яке я бачив за весь тиждень;)
culix

Постарайтеся Install-Package NpgsqlBulkCopy
Елёр

1
-Індекси Since також використовуються для фізичного компонування db-записів. Не впевнений, чи добре видалення індексів у будь-якій базі даних.
Фарджад

Але ваше рекомендовано, нічого в пам'яті !!! І якщо ваш розмір партії може бути невеликим, дуже-дуже погано працював це клас :( Я намагаюся npgsql CopyIn клас, тому що це як CSV-форматизоване відображення в PG-заяві запиту. Ви можете спробувати для Big Table?
Elyor

93

Існує альтернатива використанню 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?
Грант Хамфріс

Якщо ви зіткнулися з проблемою дозволів, перш ніж спробувати це, використовуйте КОПІЮВАННЯ ... ВІД STDIN
Ендрю Скотт Еванс

Якщо ви використовуєте безпеку на рівні рядків, це найкраще, що ви можете зробити. "COPY FROM не підтримується для таблиць із захистом рівня рядків" версії 12.
Eloff

КОПІЯ набагато швидше, ніж розширений INSERT
hipertracker

24

Один із способів прискорити роботу - це явно виконувати декілька вставок або копій в рамках транзакції (скажімо, 1000). Поведінка Postgres за замовчуванням полягає в здійсненні після кожного твердження, тому, групуючи коміти, ви можете уникнути накладних витрат. Як йдеться у посібнику у відповіді Даніеля, вам, можливо, доведеться відключити автокомісію, щоб це працювало. Також зверніть увагу на коментар внизу, що дозволяє збільшити розмір wal_buffers до 16 Мб також може допомогти.


1
Варто зазначити, що ліміт на кількість вставок / копій, які ви можете додати до тієї самої транзакції, ймовірно, набагато вищий за все, що ви спробуєте. Ви можете додати мільйони і мільйони рядків за одну транзакцію і не зіткнутися з проблемами.
Sumeet Jain

@SumeetJain Так, я просто зауважую швидкість «солодкого місця» з точки зору кількості копій / вставок на транзакцію.
Дана Сане

Чи заблокує це таблиця під час виконання транзакції?
Фея Ламбда

15

UNNESTФункція з масивами може використовуватися разом із синтаксисом багатозначних VALUES. Я думаю, що цей метод є більш повільним, ніж використання, COPYале він корисний мені в роботі з psycopg та python (python listпереданий cursor.executeстає pg 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

В основному це залежить від (іншої) діяльності в базі даних. Такі операції ефективно заморожують всю базу даних для інших сеансів. Інша увага - це модель даних та наявність обмежень, тригерів тощо.

Мій перший підхід - це завжди: створити таблицю (temp) зі структурою, подібною до цільової таблиці (створити таблицю tmp AS select * з target, де 1 = 0), і почати з читання файлу в таблиці temp. Потім я перевіряю, що можна перевірити: дублікати, ключі, які вже є у цілі тощо.

Тоді я просто роблю "вставку в цільовий вибір * від tmp" або подібне.

Якщо це не вдається або триває занадто довго, я перериваю це і розглядаю інші методи (тимчасово відміняючи індекси / обмеження тощо)



6

Я щойно стикався з цим питанням і рекомендую csvsql ( релізи ) для масового імпорту в Postgres. Щоб виконати об'ємну вставку, яку ви просто createdbвикористаєте csvsql, а потім скористайтеся , яка підключається до вашої бази даних та створює окремі таблиці для всієї папки CSV-файлів.

$ createdb test 
$ csvsql --db postgresql:///test --insert examples/*.csv

1
Для csvsql, щоб також очистити вихідний файл csv від будь-яких можливих помилок форматування, найкраще дотримуватися цих інструкцій , докладнішу документацію тут
неділя

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

Найбільш частим методом "прийому файлів" (головним чином у Big Data) є збереження оригінального файлу у форматі 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.