INSERT INTO - Простая команда с непростым характером

INSERT — это оператор для добавления новых строк в таблицу.

Классика выглядит так:

INSERT INTO customers (name, age, city) VALUES ('Оля', 28, 'Москва');

Главные слова:

  • INSERT INTO — куда добавляем,
  • VALUES — что именно добавляем.
INSERT INTO - Простая команда с непростым характером

А пока подписывайся на мой канал На связи: SQL Там я публикую посты про особенности и нюансы SQL. Этот канал про то, как не бояться баз данных, понимать, что такое JOIN, GROUP BY и почему NULL ≠ 0. Его я веду с нуля подписчиков. Присоединяйся!

При вставке значений в таблицу можно выделить одиночную вставку и массовую вставку. Одиночная - это когда в запросе содержится вставка одной строки, а массовая - нужно вставить больше чем одну строку.

INSERT INTO products (name, price) VALUES ('Яблоки', 100); INSERT INTO products (name, price) VALUES ('Груши', 120); -- и так 1000 раз 😅
INSERT INTO products (name, price) VALUES ('Яблоки', 100), ('Груши', 120), ('Бананы', 150);

Один INSERT на 1000 строк работает быстрее, чем 1000 отдельных запросов, потому что база открывает и закрывает транзакцию только один раз.

Транзакция — это логическая единица работы с базой данных, набор действий (обычно INSERT, UPDATE, DELETE и т. д.), которые выполняются как одно целое.
База гарантирует, что или все действия внутри транзакции будут выполнены, или не выполнится ни одно.

Например:

  • Перевод денег с карты на карту.
  • Снять деньги с карты А.
  • Зачислить деньги на карту Б.

Если выполнить только первый шаг, а второй не получится — деньги «пропадут». Транзакция гарантирует, что либо оба шага выполнятся, либо оба отменятся.

Так вот, даже если мы явно в коде не прописываем начало и окончание транзакции, то наша база автоматически оборачивает наш запрос началом и окончанием транзакции

BEGIN; INSERT INTO products (name, price) VALUES ('Яблоки', 100); COMMIT; BEGIN; INSERT INTO products (name, price) VALUES ('Груши', 120); COMMIT; -- и так 1000 раз 😅
BEGIN; INSERT INTO products (name, price) VALUES ('Яблоки', 100), ('Груши', 120), ('Бананы', 150); COMMIT;

Но при этом вставлять одним запросом млн строк - это плохо. Можно словить блокировку. Поэтому для большого объема вставки - лучше дробить на несколько маленьких частей.

Оптимальный размер батча — подбирается экспериментально. Обычно от 5k до 50k строк за один заход.

При вставке данных в таблицу у нас часто используется уникальный идентификатор строки, часто этот идентификатор является автоинкрементом, т.е. база сама записывает значение в это поле.

CREATE TABLE users ( id SERIAL, -- автоинкремент name TEXT ); INSERT INTO users (name) VALUES ('Оля'); -- id = 1 INSERT INTO users (name) VALUES ('Маша'); -- id = 2

Каждый INSERT добавляет новую запись, а автоинкремент гарантирует уникальный идентификатор для этой строки.

  • Без него нужно было бы самому считать, какой следующий номер ставить.
  • Автоинкремент экономит время и предотвращает ошибки.

Но автоинкремент не гарантирует, что значения в поле id будут "последовательными", он гарантирует, что значения в этом поле будут уникальными.

Получается, что при INSERT в поле с автоинкрементом могут быть "дырки".Это получается, например, в следующих кейсах:
1. Отмененная вставка:
- Сделали INSERT, база выделила id = 5.
- Транзакцию откатили (ROLLBACK).
- Id 5 пропал, следующие вставки идут с 6.
2. Удаление строк:
- Если удалить записи, то номера исчезнут, но новые не «подтянутся» к освободившимся.
3. Параллельная вставка:
- Две транзакции одновременно вставляют строки.
- Каждая получает свой id, даже если одна потом откатится → тоже появляются пропуски.

В PostgreSQL номера генерируются через объект SEQUENCE.

Если ты вручную добавил строку с id = 9999, а sequence «застрял» на 5000.То, когда при следующих попытках осуществить вставку строки БД дойдет до значения 9999 - этот INSERT упадёт с ошибкой: «дубликат ключа». Потому что sequence не обновляется автоматически! Sequence не смотрит на максимальный id в таблице. Он просто отдаёт своё следующее число.

Решение может быть:

ALTER SEQUENCE … RESTART WITH <нужный номер>.

На вставку строк в таблицу может быть вызов триггера. Потому что вставка строки - это новая информация. А в зависимости от новой информации у нас могут быть разная логика подсчета того или иного показателя. Например, расчет бонусов для клиента зависит от суммы заказа. Поэтому при каждой вставке в таблицу заказов будет вызван триггер по расчету бонусов для клиента.

CREATE TRIGGER bonus AFTER INSERT ON orders FOR EACH ROW EXECUTE FUNCTION bonus_cnt();

Каждая новая строка вызовет bonus_cnt().
Даже если ты вставляешь 1000 строк, триггер вызовется 1000 раз.

Есть еще такое понятие как UPSERT.

UPSERT = INSERT + UPDATE, т.е. «вставить новую запись, а если такая запись уже есть — обновить существующую».

Пример: у нас есть таблица users с колонкой id. Мы хотим добавить пользователя с id = 1.

  • Если пользователя нет → вставляем.
  • Если пользователь есть → обновляем его данные.

Но такой INSERT + UPDATE в каждой СУБД реализуется по разному.Одна и та же логика «вставить или обновить» в коде не переносится напрямую между базами. То, что называется UPSERT в PostgreSQL, будет работать иначе в MySQL и совсем иначе в SQL Server.

INSERT ... ON CONFLICT (id) DO UPDATE ... -- Вставляет запись, если id ещё нет; иначе обновляет эту запись. PostgreSQL
INSERT ... ON DUPLICATE KEY UPDATE ... -- MySQL
MERGE ... -- SQL Server

По сути, UPSERT — это концепция, а не единый универсальный оператор.

На скорость INSERT влияет наличие индексов в таблице. Чем больше индексов, тем долше будет выполняться вставка. Каждая новая строка должна обновить все индексы. При массовых загрузках индексы иногда отключают, а потом создают заново

А еще, иногда база говорит: «Вставка прошла успешно», но на диск ещё ничего не записано.

  • PostgreSQL и MySQL (InnoDB) используют write-ahead log: запись сначала идёт в журнал, потом — на диск.
  • Если прямо в этот момент выключить сервер → можно потерять часть данных.

Решается настройкой fsync, commit и уровней надёжности транзакций.

INSERT — это не просто «добавить данные». Это про индексы, блокировки, автоинкременты, батчи и даже про то, как СУБД пишет на диск.

Начать дискуссию