Пакеты и чанки
insertGroup() и upsertGroup() превращают большой массив строк в несколько
многострочных запросов вместо одного запроса на строку. Эта страница объясняет,
как эти запросы строятся — чанкование, суффиксацию плейсхолдеров на каждую строку
и одно правило формы, которое держит их корректными.
Зачем вообще чанковать
Базы данных ограничивают число связанных параметров и размер пакета одного
запроса. Наивный «один гигантский INSERT» на 100 тыс. строк вылетел бы за оба
предела. Чанкование делит вход на пакеты фиксированного размера (array_chunk) и
выдаёт один многострочный запрос на пакет, держа каждый запрос комфортно внутри
лимитов.
| Метод | chunkSize по умолчанию |
|---|---|
insertGroup |
1000 |
upsertGroup |
500 |
Пустой входной массив коротко замыкается в no-op ещё до построения любого SQL.
Суффиксация плейсхолдеров
Внутри одного чанка каждой строке нужны собственные плейсхолдеры — нельзя
привязать :name дважды с разными значениями. Поэтому ключи каждой строки
суффиксируются индексом строки в чанке (_0, _1, …):
строки: [{name:'A', email:'a@x'}, {name:'B', email:'b@x'}]
columns: (name, email)
values: (:name_0, :email_0), (:name_1, :email_1)
binds: :name_0 => 'A', :email_0 => 'a@x',
:name_1 => 'B', :email_1 => 'b@x'Список колонок берётся из ключей строки; секция значений повторяет один
суффиксированный кортеж на строку; все привязки сливаются в один execute.
upsertGroup строит те же кортежи значений, затем дописывает
драйвер-специфичную секцию конфликта (см.
Определение драйвера).
Правило формы строки
У порождённого INSERT один список колонок, построенный из строк в чанке.
Суффиксированные плейсхолдеры каждой строки выдаются в VALUES. Чтобы запрос
сошёлся, строки в чанке должны иметь одни и те же колонки.
Держите ключи единообразными внутри чанка
Если у строк в одном чанке разные наборы ключей, единый список колонок и
покортежные плейсхолдеры на строку могут разойтись — порождая некорректный запрос
или привязывая значения не в те колонки. Нормализуйте строки (одни ключи, один
порядок) перед пакетным вызовом. Значения null в отдельных строках по-прежнему
выбрасываются поштучно, поэтому колонка, которая null в одной строке, но
присутствует в другой, — это несовпадение формы; подставьте явное значение вместо
null, когда колонка обязана присутствовать.
Поштучное отбрасывание NULL
Как и в одиночном insert(), значение null удаляется из этой строки — колонка
падает к значению по умолчанию из базы. В пакете это и есть та самая опасность
формы выше: выбрасывание ключа из одной строки меняет её набор колонок
относительно соседей.
Компромиссы размера чанка
- Меньшие чанки — меньше памяти на запрос, больше обращений к базе, безопаснее для очень широких строк (много колонок × много строк быстрее подходит к пределу параметров).
- Большие чанки — меньше обращений, выше пропускная способность, больше памяти и больше связанных параметров на запрос.
Отталкивайтесь от умолчаний, если строки необычно широкие или узкие. Грубый
ориентир: держите chunkSize × колонокНаСтроку заметно ниже лимита связанных
параметров вашего драйвера.
Атомарность
Каждый чанк — это отдельный запрос. insertGroup / upsertGroup не открывают
транзакцию поверх чанков — если пятый чанк упадёт, первые четыре уже
зафиксированы. Оберните весь вызов в beginTransaction() / commit()
(унаследованные от PDO), когда нужна семантика «всё-или-ничего» на весь пакет.
Связанное
- Вставка записей — руководство на уровне задачи
- Апсерты — использование пакетного апсерта
- Определение драйвера — секция конфликта по драйверам
- Привязка параметров — как типизируется каждое суффиксированное значение