Как загрузить CSV файл с миллионом строк в базу данных и какой синтаксис использовать
Что проверяют
Знаете ли вы, что построчный INSERT для миллиона строк не годится, и умеете ли пользоваться механизмом массовой загрузки своей СУБД из Go. Следом обычно спрашивают, что делать с битыми строками и как загрузить файл, не держа его целиком в памяти.
Как отвечать
Под «синтаксисом» чаще всего имеют в виду COPY в PostgreSQL. Каркас ответа:
- чтение потоком:
csv.NewReader(file)и цикл поRead(), а неReadAll(). Полезные настройки:Commaдля файлов с;,ReuseRecord = trueпротив лишних аллокаций,FieldsPerRecordдля проверки числа колонок; - загрузка через
COPY: вpgxэтоconn.CopyFrom(ctx, pgx.Identifier{"items"}, columns, src), где источник реализует интерфейсpgx.CopyFromSourceи отдаёт строки по одной. Миллион узких строк так грузится за секунды или десятки секунд; - SQL на стороне сервера:
COPY items FROM '/path/file.csv' WITH (FORMAT csv, HEADER true)читает файл с машины базы и требует прав, для файла с клиента есть\copyвpsql; - пакетный
INSERT: запасной вариант, многострочныйVALUESвнутри транзакции. В PostgreSQL на один запрос не больше 65 535 параметров, поэтому размер пачки считается от числа колонок; - MySQL:
LOAD DATA LOCAL INFILE, вgo-sql-driver/mysqlфайл нужно явно разрешить черезmysql.RegisterLocalFile.
Для грязных данных надёжнее грузить в промежуточную таблицу без ограничений, а затем переносить через INSERT ... SELECT с проверками и ON CONFLICT. Тяжёлые индексы на целевой таблице иногда выгоднее создать после загрузки.
Где спотыкаются
Выполняют db.Exec на каждую строку вне транзакции. Это миллион сетевых обращений и миллион коммитов, загрузка растягивается на многие минуты.
Выходят из цикла на любой ошибке Read(). Конец файла нужно отличать через errors.Is(err, io.EOF), а ошибку разбора логировать с номером строки, иначе загрузка молча обрывается на середине.
Не смотрят на сам файл. Выгрузка из Excel часто приходит с BOM, который прилипает к имени первой колонки, с разделителем ; и запятой в дробных числах, и всё это нужно обработать до вставки.