Пакет · cdo

Пакеты и чанки

insertGroup() и upsertGroup() превращают большой массив строк в несколько многострочных запросов вместо одного запроса на строку. Эта страница объясняет, как эти запросы строятся — чанкование, суффиксацию плейсхолдеров на каждую строку и одно правило формы, которое держит их корректными.

Зачем вообще чанковать

Базы данных ограничивают число связанных параметров и размер пакета одного запроса. Наивный «один гигантский INSERT» на 100 тыс. строк вылетел бы за оба предела. Чанкование делит вход на пакеты фиксированного размера (array_chunk) и выдаёт один многострочный запрос на пакет, держа каждый запрос комфортно внутри лимитов.

Метод chunkSize по умолчанию
insertGroup 1000
upsertGroup 500

Пустой входной массив коротко замыкается в no-op ещё до построения любого SQL.

Суффиксация плейсхолдеров

Внутри одного чанка каждой строке нужны собственные плейсхолдеры — нельзя привязать :name дважды с разными значениями. Поэтому ключи каждой строки суффиксируются индексом строки в чанке (_0, _1, …):

text
строки: [{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), когда нужна семантика «всё-или-ничего» на весь пакет.

Связанное