Який найшвидший спосіб зробити об'ємну вставку в 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.