yousign / safe-migrations
确保您的迁移安全
Requires
- php: ^8.1
- doctrine/dbal: ^3.7
- doctrine/migrations: ^3.3
- symfony/string: ^6.4|^7.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.9
- phpstan/phpstan: 1.10.50
- phpstan/phpstan-phpunit: 1.3.15
- phpunit/phpunit: ^10.4.2
- symfony/var-dumper: >=6.3.8|^7.0
README
确保您的迁移安全
🪄 特点
- PG 13+
- PHP 8.1
- Doctrine 迁移
🤷 为什么?
因为 SQL 迁移可能在数据库上执行重查询,这可能会降低您的应用程序速度。
⚙️ 配置
*对于 Symfony > 6.x
在您的项目中安装
$ composer req yousign/safe-migrations
在您的 services.yaml
中声明中间件
parameters: env(ENABLE_RETRY_LOCK_TIMEOUT): false services: Yousign\SafeMigrations\Doctrine\DBAL\Driver\Middleware\RetryLockTimeoutMiddleware: $isEnabled: '%env(bool:ENABLE_RETRY_LOCK_TIMEOUT)%'
创建迁移模板 migration.php.tpl
<?php declare(strict_types=1); namespace <namespace>; use Doctrine\DBAL\Schema\Schema; use Yousign\SafeMigrations\Doctrine\Migration; class <className> extends Migration { public function up(Schema $schema): void { <up> } }
在 doctrine_migrations.yaml
中将此模板设置为迁移的默认值
doctrine_migrations: custom_template: "%kernel.project_dir%/migrations/migration.php.tpl"
通过在 .env<.environment>
中的环境变量中启用锁定重试
ENABLE_RETRY_LOCK_TIMEOUT=true
这就完成了 ☕
▶️ 使用方法
迁移类
当您生成新的迁移时,这个类将扩展库中的 Migration
类,该类公开以下安全方法。
这些方法中的每一个都将生成正确的 SQL 请求集,以使请求的查询安全。
创建表
$this->createTable(table: 'test', columnDefinitions: [ 'id UUID NOT NULL', 'PRIMARY KEY(id)', ])
添加外键
$this->addForeignKey(table: 'address', name: 'fk_address_contact', column: 'contact', referenceTable: 'contact', referenceColumn: 'id', options: 'ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE')
重命名约束
$this->renameConstraint(table: 'address', from: 'id_pkey', to: 'pkey_id')
对列进行注释
$this->commentOnColumn(table: 'address', name: 'name', comment: null)
添加索引
注意:在表上添加索引将执行对表中所有列的 "analyze",以更新统计信息
$this->addIndex(name: 'idx_contact_email', table: 'contact', columns: ['email'], unique: false, usingMethod: 'GIN', where: 'country = "France"')
删除索引
$this->dropIndex(name: 'idx_contact_email')
重命名索引
$this->renameIndex(from: 'idx_email_signer', to: 'idx_signer_email')
添加列
$this->addColumn(table: 'contact', name: 'mobile', type: 'text', defaultValue: null, nullable: true)
删除列
$this->dropColumn(table: 'contact', name: 'landline')
设置列的默认值
$this->setDefaultOnColumn(table: 'contact', name: 'email', value: "'noreply@undefined.org'")
删除列的默认值
$this->dropDefaultOnColumn(table: 'contact', name: 'email')
设置列可空
$this->setColumnNullable(table: 'contact', name: 'email')
设置列不可空
$this->setColumnNotNullable(table: 'contact', name: 'email')
迁移执行
如果在执行 Doctrine 迁移时出现锁定,迁移将抛出 DriverException
(Doctrine\DBAL\Driver\Exception)。
如果 SQLSTATE 值为 55P03 (lock_not_available),如果查询不成功,则将重试 3 次,每次间隔 10 秒,然后抛出抛出的异常。
您将获得以下输出
3 次重试后失败
$ bin/symfony console d:m:m [notice] Migrating up to DoctrineMigrations\Version20231224200000 09:30:38 WARNING [app] (1/3) Lock timeout reached: retrying in 10 seconds... ["sql" => "ALTER TABLE test_retry ADD COLUMN name text DEFAULT NULL"] 09:30:51 WARNING [app] (2/3) Lock timeout reached: retrying in 10 seconds... ["sql" => "ALTER TABLE test_retry ADD COLUMN name text DEFAULT NULL"] 09:31:04 WARNING [app] (3/3) Lock timeout reached: retrying in 10 seconds... ["sql" => "ALTER TABLE test_retry ADD COLUMN name text DEFAULT NULL"] [error] Migration DoctrineMigrations\Version20231224200000 failed during Execution. Error: "An exception occurred while executing a query: SQLSTATE[55P03]: Lock not available: 7 ERROR: canceling statement due to lock timeout" 09:31:17 CRITICAL [console] Error thrown while running command "'d:m:m'". Message: "An exception occurred while executing a query: SQLSTATE[55P03]: Lock not available: 7 ERROR: canceling statement due to lock timeout" - An exception occurred while executing a query: SQLSTATE[55P03]: Lock not available: 7 ERROR: canceling statement due to lock timeout ["exception" => Doctrine\DBAL\Exception\DriverException^ { …},"command" => "'d:m:m'","message" => "An exception occurred while executing a query: SQLSTATE[55P03]: Lock not available: 7 ERROR: canceling statement due to lock timeout"]
1 次重试后成功
bin/symfony console d:m:m [notice] Migrating up to DoctrineMigrations\Version20231224200000 09:28:54 WARNING [app] (1/3) Lock timeout reached: retrying in 10 seconds... ["sql" => "ALTER TABLE test_retry ADD COLUMN name text DEFAULT NULL"] [notice] finished in 15446.1ms, used 38.5M memory, 2 migrations executed, 13 sql queries [OK] Successfully migrated to version: DoctrineMigrations\Version20231224200000
📋 常见问题解答
它与迁移捆绑包兼容吗?
当然,这个库与 doctrine/doctrine-migrations-bundle 没有兼容性问题。
🔗 参考
-
PostgreSQL 文档:锁定行为:必须阅读,它将帮助您了解许多关于 PG 锁的内容。
例如,某些 DDL 查询需要表上的独占锁定。当表被其他进程并发访问和修改时,获取锁定可能需要一些时间。锁定请求正在等待队列中,并且一旦入队,它也可能阻止该表上的其他查询。
-
其他安全迁移框架的示例
-
介绍 ZDD 的优秀文章
Les Patterns des Géants du Web – Zero Downtime Deployment - OCTO Talks !
-
了解如何在保持向后兼容性的同时更改架构的非常好的资源
-
GitLab 向贡献者解释他们如何使用数据库管理 ZDD
https://docs.gitlab.com/ee/development/what_requires_downtime.html
-
解释零停机时间架构更改的最佳实践和许多提示的文章
PostgreSQL at Scale: Database Schema Changes Without Downtime
-
非常好的视频,解释了ZDD与数据库模式更改的很多内容
-
关于这个主题的另一个好视频,但这次是英文的
-
Doctolib解释了他们如何在不中断的情况下管理迁移,我们基本上实现了同样的事情。
🤝 贡献
请阅读CONTRIBUTING.md,了解我们的行为准则以及向我们提交拉取请求的流程。
在编写您的修复/功能之后,您可以运行以下命令以确保一切仍然正常。
# Install dev dependencies $ make vendor # Running tests and quality tools locally $ make tests