Миграции Yii2 на рабочем сайте: как писать безопаснее
Миграции Yii2 удобны тем, что изменения базы можно хранить рядом с кодом. Но на рабочем сайте миграция — это не просто технический файл. Она может заблокировать таблицу, удалить данные, не откатиться назад или сломаться на середине из-за уже существующей записи.
Писать миграции для production лучше так, чтобы они были предсказуемыми, повторяемыми и аккуратными к существующим данным.
safeUp и safeDown
Если база поддерживает транзакции для выполняемых операций, safeUp и safeDown дают дополнительную защиту. Но важно помнить: не все DDL-операции во всех СУБД полноценно откатываются транзакцией.
class m260630_000001_add_index_to_posts extends Migration
{
public function safeUp()
{
$this->createIndex(
'idx_blog_posts_status_published_at',
'{{%blog_posts}}',
['status', 'published_at']
);
}
public function safeDown()
{
$this->dropIndex(
'idx_blog_posts_status_published_at',
'{{%blog_posts}}'
);
}
}
Даже если используется safeUp, перед крупной миграцией всё равно нужен бэкап. Транзакция не заменяет нормальный план отката.
Проверять существование данных
Если миграция добавляет справочники, категории или настройки, не стоит слепо вставлять записи с уникальными slug. На рабочем сайте они уже могут существовать.
$exists = $this->db->createCommand(
'SELECT id FROM {{%blog_categories}} WHERE slug = :slug LIMIT 1',
[':slug' => 'guides']
)->queryScalar();
if ($exists === false || $exists === null) {
$this->insert('{{%blog_categories}}', [
'name' => 'Инструкции',
'slug' => 'guides',
'created_at' => time(),
'updated_at' => time(),
]);
}
Так миграция не упадёт, если запись была создана вручную раньше.
Не использовать truncate для контентных таблиц
truncate в миграции почти всегда опасен для таблиц с реальным контентом. Он удаляет всё, включая чужие записи. Для отката нужно удалять только то, что создала конкретная миграция.
$this->delete('{{%blog_posts}}', [
'slug' => [
'first-post',
'second-post',
],
]);
Если записи могли быть изменены после создания, безопаснее искать их по slug и метке created_at, которую использовала миграция.
batchInsert для большого набора
Если нужно вставить много строк, batchInsert обычно аккуратнее и быстрее, чем десятки отдельных insert. Но для сложной логики с проверкой существования иногда удобнее отдельные вставки.
$this->batchInsert('{{%blog_tags}}', ['name', 'slug', 'created_at', 'updated_at'], [
['PHP', 'php', $time, $time],
['Yii2', 'yii2', $time, $time],
]);
При наличии уникальных индексов лучше заранее проверить, нет ли таких slug в таблице.
Индексы на больших таблицах
Добавление индекса на большую таблицу может занять время и заблокировать запись. Перед такой миграцией нужно оценить размер таблицы и нагрузку. Иногда индекс лучше добавлять в окно низкой активности.
SELECT COUNT(*) FROM orders;
Для MySQL/MariaDB поведение зависит от версии и типа операции. Не стоит считать, что createIndex всегда мгновенный.
Изменение колонок
Изменение типа колонки или добавление NOT NULL без default может сломать существующие данные. Сначала нужно проверить, есть ли пустые значения.
SELECT COUNT(*)
FROM users
WHERE phone IS NULL;
Если данные нужно заполнить, лучше делать это явным шагом.
$this->update('{{%users}}', ['phone' => ''], ['phone' => null]);
Миграции с данными
Миграции для наполнения данными должны быть осторожны с чужим контентом. Хорошая практика — иметь список slug, которые добавляет миграция, и в safeDown удалять только их связи и записи.
private $postSlugs = [
'technical-checklist',
'site-support-guide',
];
Если миграция создаёт связи many-to-many, откат должен сначала удалить связи, потом основные записи. Иначе можно упереться во внешние ключи.
Проверка перед запуском
- Миграция запускается на копии базы.
- safeDown не удаляет чужие данные.
- Проверены уникальные ключи и существующие записи.
- Большие таблицы оценены по размеру.
- Нет truncate для таблиц с контентом.
- Для индексов выбраны понятные имена.
- Есть бэкап перед запуском на production.
- После миграции проверены логи и основные страницы.
Хорошая миграция скучная: она делает ровно то, что нужно, не трогает лишнее и понятно откатывается. На рабочем сайте это важнее, чем короткий код.
Комментарии (0)
Пока нет комментариев. Будьте первым!