Как добавить индекс на колонку в большой таблице без длительной блокировки на запись?

Ответ

Для добавления индекса в большую таблицу без блокировки DML-операций (INSERT, UPDATE, DELETE) используются специальные неблокирующие режимы, которые зависят от используемой СУБД.

PostgreSQL

В PostgreSQL для этого используется опция CONCURRENTLY.

CREATE INDEX CONCURRENTLY idx_name ON table_name (column_name);

Ключевые особенности и риски:

  • Отсутствие блокировок: Команда не блокирует операции записи в таблицу, позволяя приложению работать в штатном режиме.
  • Ресурсоемкость: Процесс занимает больше времени и потребляет больше ресурсов CPU и I/O по сравнению со стандартным CREATE INDEX.
  • Риск неудачи: Если построение индекса завершается с ошибкой, он остается в системе как невалидный (invalid). Такой индекс нужно удалить вручную (DROP INDEX CONCURRENTLY idx_name) и запустить команду заново.
  • Транзакции: Эту команду нельзя выполнять внутри блока транзакции (BEGIN...COMMIT).

MySQL (InnoDB)

В MySQL (для движка InnoDB) используется комбинация ALGORITHM=INPLACE и LOCK=NONE.

ALTER TABLE table_name 
ADD INDEX idx_name (column_name), ALGORITHM=INPLACE, LOCK=NONE;

Ключевые особенности:

  • ALGORITHM=INPLACE: Позволяет избежать создания полной копии таблицы, что значительно ускоряет процесс и снижает нагрузку.
  • LOCK=NONE: Разрешает параллельное выполнение DML-операций во время создания индекса.
  • В современных версиях MySQL (5.6+) такое поведение часто является стандартным для ADD INDEX, но явное указание параметров является хорошей практикой для предсказуемости.

Ответ 18+ 🔞

А, ну это же про то, как индекс навесить, чтобы всю эту движуху с данными не останавливать! Да, блядь, классика — база тупит, всё висит, а тебе надо срочно индекс прилепить, чтобы запросы не ебали мозг. Слушай, тут главное — не накосячить, а то можно такую дичь устроить, что потом полгода откатывать.

Ну, короче, в PostgreSQL всё просто, как три копейки, но с подвохом, ёпта. Там есть волшебное слово CONCURRENTLY. Кидаешь его в запрос, и он старается не мешать.

CREATE INDEX CONCURRENTLY idx_name ON table_name (column_name);

Но тут, сука, есть нюансы, на которые надо глаза вылупить:

  • Не блокирует: Это главный плюс. Пока индекс строится, твоё приложение может дальше писать, обновлять и удалять записи, как ни в чём не бывало. Не то что раньше, блядь, — повесил индекс и пошёл чай пить на полчаса, пока все операции в очередь встанут.
  • Жрёт как не в себя: За эту неблокируемость приходится платить. Процесс будет дольше и сожрёт больше твоего CPU и дискового ввода-вывода. Так что лучше это делать не в час пик, а то все на тебя смотреть будут, как на идиота.
  • Может накрыться медным тазом: Вот это самый, блядь, неприятный момент. Если что-то пойдёт не так (а оно может, особенно на живой, ебучей базе), индекс останется в системе, но будет помечен как невалидный, invalid. И лежит себе, как пизда с ушами, никому не нужный. Его потом вручную выносить надо: DROP INDEX CONCURRENTLY idx_name и начинать всё заново. Волнение, блядь, на нуле.
  • Не в транзакцию: Эту команду, сука, нельзя засунуть в BEGIN...COMMIT. Она сама по себе, как хитрая жопа.

А в MySQL (InnoDB) — своя атмосфера

Там уже другие заклинания. Нужно явно указать, что алгоритм на месте (INPLACE) и блокировок никаких (NONE).

ALTER TABLE table_name 
ADD INDEX idx_name (column_name), ALGORITHM=INPLACE, LOCK=NONE;

Что тут важно:

  • ALGORITHM=INPLACE — это чтобы таблицу целиком не копировать, а то можно овердохуища времени и места потратить. Делает всё аккуратненько, на месте.
  • LOCK=NONE — это святое. Прямая команда: "Не смей, блядь, блокировать запись!".
  • В новых версиях MySQL это часто и так по умолчанию, но, чувак, лучше явно прописать. Потому что доверия, блядь, ноль — а вдруг он в какой-то момент решит по-старинке сделать и всё заблокирует? Так что перестрахуйся, не ленись.