PHP:在团队开发中更改数据库结构



在PHP的世界中,数据库结构迁移工具是众所周知的-Doctrine ,CakePHP的PhinxLaravel的Yii-这是我想到的第一件事。 当然,还有十几个。 它们中的大多数都与迁移一起使用-用于对数据库架构进行增量更改的命令。

我不会描述为什么,在哈布雷(Habré)上有很多关于该主题的文章。 例如:


此外,随着团队经验的发展,我在不同分支机构的数据库结构不断变化。

原始SQL vs PHP API


我们用纯SQL编写迁移。 许多工具提供PHP api来编写翻译成SQL代码的指令。 现在我不明白为什么会这样? 这样的工具将始终受到其功能的限制。 它们不允许为特定引擎编写特定指令;您仍然必须使用纯SQL。 我不是在谈论编写程序和视图。

有人抱怨他不想学习ALTER命令的语法。。。嗯,我不知道,我打开了目录并写了这座山的例子,特别是在一个大型项目中。

数据迁移(INSERT,UPDATE)也总是用SQL编写。 因为您永远不能依赖ORM和模型的当前版本。 在一个版本中,它们不再是。

例如:

Rollback Country::delete()->where(....)->execute(); 

要回滚数据库的状态。 这个PHP类不再在仓库中。 您需要查找他所在的最后一次提交,然后从那里回滚。 Brrr ...

因此,SQL简单可靠:

 --TRANSACTION --UP ALTER TABLE authors ADD COLUMN code INT; ALTER TABLE posts ADD COLUMN slug TEXT; UPDATE authors SET ... --DOWN ALTER TABLE authors DROP COLUMN code; ALTER TABLE posts DROP COLUMN slug; 

DDL中的交易


过渡到PostgreSQL时,我忘记了像噩梦一样的中断迁移-迁移发生在中间,有些东西卷起来,有些东西不在那儿,坐下来编辑笔...这迫使我们编写原子的单行命令并一次运行一个。 交易一切都很简单:如果发生故障-一切都会回滚(嗯,几乎所有东西)))。 只需修复并重新启动即可。 自动装配会产生巨大的冲击,如果有东西掉落,它会迅速修复并上升。

视图(视图)和功能


这里的问题是它们不能像表中的ALTER那样进行增量更新。 需要DROP和CREATE。 即 关于差异(迁移文本),最终结果到底是什么还不清楚。 尤其是当逻辑扭曲时,这非常不便。 例如:

 --UP DROP VIEW ... CREATE VIEW mvstock AS SELECT (now() - '7 days'::interval) AS refreshed_at, o.pid, COALESCE(sum(o.debit), 0)::integer AS debit, COALESCE(sum(o.credit) FILTER (WHERE d.type <> 104), 0)::integer AS credit, COALESCE(sum(o.debit), 0) - COALESCE(sum(o.credit), 0)::integer AS total FROM operations o JOIN docs d ON d.id = o.doc_id AND d.deleted_at IS NULL WHERE d.closed_at < (now() - '7 days'::interval) AND d.type <> 500 GROUP BY o.pid WITH DATA; --DOWN DROP VIEW ... CREATE VIEW mvstock AS SELECT (now() - '10 days'::interval) AS refreshed_at, o.pid, COALESCE(sum(o.debit), 0)::integer AS debit, COALESCE(sum(o.credit) FILTER (WHERE d.type <> 104), 0)::integer AS credit, COALESCE(sum(o.debit), 0) - COALESCE(sum(o.credit), 0)::integer AS total FROM operations o JOIN docs d ON d.id = o.doc_id AND d.deleted_at IS NULL WHERE d.closed_at < (now() - '10 days'::interval) AND d.type <> 500 GROUP BY o.pid WITH DATA; 

这里发生了什么变化?

我们停止了这样一个事实,即迁移旁边是一个爸爸,其中存储了当前视图和过程代码,该代码在回滚迁移中进行了更新和复制。

现在,差异变得像:



回到Avito,我们为版本存储过程代码提供了一个有趣的解决方案

通常,这种情况提出了一个很好的问题-如何查看数据库结构的特定对象中的更改历史记录。 对于每个表,我想查看与特定任务解决方案相关的更改历史记录。



在Habré上发现了一种有趣的方法来自动修复数据库结构中的更改。

与分支机构合作


我永恒的痛苦是如何在两个A分支和B分支之间切换,每个分支都对数据库的结构进行了编辑。



有必要回滚A分支中的迁移(我们还必须记住哪些迁移和迁移多少),然后切换到B分支并回滚新迁移。 好吧,如果我们的编辑兼容,我可以切换到第二个分支,并从B滚动其他迁移。

如果没有呢? 如果我有多个这样的分支机构? 然后回滚所有这些审查状态? 我一直讨厌它...

现在,当切换到其他人的分支机构时,我可以自动删除其他人的迁移并滚动当前迁移:



其中:

D-在A分支中启动的A迁移,但它们不在当前分支中,建议删除它们
A-出现在新分支中且需要滚动的B迁移

在一个基座上进行测试和自动组装时,它变得极为方便。 没有任何意义或机会让每个分支机构从头开始创建基础。 切换到分支并自动同步数据库状态。

编号和执行顺序


我所知道的所有工具都是按时间标记迁移的,这是一个很好的解决方案。 如果我编写了几次迁移,则保留了必要的顺序。 另一个开发人员可以在另一个线程中拥有任何日期,甚至可以是我的-在我们与他一起滚动的顺序中都没有关系,我们的更改彼此独立。 即使我们使用相同的表(按列添加),所有必要的更改都将以任何顺序进行。 最主要的是要尊重我的依存编辑的顺序。



我不考虑需要编辑相同内容的情况-这些观点始终是一致的。 好吧,否则在组装和测试阶段会失败。

这是一个有趣的例子。

我们在一个视图或过程中进行不同的编辑,即 在那些通过删除而更新的结构中。 即 例如,我在视图中添加了col_A列,并向同事添加了col_B。 因此,如果他的代码在我的发布之后推出,那么他的专栏将没有我的专栏:

 CREATE VIEW vusers AS SELECT login, name, -- .... 
A分行B分行
 DROP VIEW vusers; CREATE VIEW vusers AS SELECT login, name, col_A, -- .... 
 DROP VIEW vusers; CREATE VIEW vusers AS SELECT login, name, col_B, -- .... 
在这种情况下,必须使一个分支依赖于另一个。

另一个有趣的情况是迁移中的更正。

最重要的是,无论您对其进行了多少更改,都将不再再次应用已应用的迁移(您需要先回滚,然后再次应用它)。 即 您发送了Migration用于测试,所有规则,然后您意识到了这一点并进行了少量编辑。 但是测试或您使用它的其他服务器对此一无所知。

在这些情况下,我们重命名迁移文件,添加新的版本号,以便迁移器开始将其解释为2个命令-回滚1和回滚2,
例如:



回滚


即使无法将基础恢复到原始状态,也请始终编写ROLLBACK。 例如,DROP TABLE,它可以是哪种ROLLBACK?

在这种情况下,我们编写一个空的CREATE TABLE。 最重要的是,开发系统始终可以在分支之间轻松切换。 对于PROD,已经在不同级别上确定了不可逆的修订管理。 我可以复制表,也可以重命名表而不是删除表。 但是编写迁移的原则-必须进行回滚,以将基础的结构恢复到初始级别,并且数据已经可以实现。

在战斗环境中,我一生只使用了1-2次回滚。 而且一直在开发中。 因此,我总是检查回滚是否将所有内容返回到所需状态。

开发人员经常会在回滚中犯错误。 因为 他们主要专注于新编辑,经过测试并与他们合作。 其他人员和流程已经在进行回滚。 因此,我始终将迁移测试为UP-ROLLBACK-UP

一个有趣的问题出现在永久测试基础上(未删除数据库)。 他们编写了一个迁移文件,回滚工作正常,将其发送给测试,测试人员以新格式生成了数据,尝试回滚,但他们没有提供新数据。 经典例子

 ALTER TABLE abc ALTER COLUMN code SET NULL 

太好了! 经过测试,数据库中充满了NULL值。 回滚:

 ALTER TABLE abc ALTER COLUMN code SET NOT NULL 

反之亦然:-(

您需要添加命令:

 DELETE FROM abc WHERE code IS NULL 

困难在于,如果我们不打算每次都从头开始重新创建数据库,则需要牢记这一点,而不要使其自动化。

关于数据删除的一点

通常,我们尝试不立即删除填充的表和列。 最好重命名或创建副本,然后在所有内容都固定下来并且数据失去相关性之后再删除它:

 ALTER TABLE user_logs RENAME TO user_logs_20190223; --  CREATE TABLE user_logs_20190223 AS TABLE user_logs; 

迁移者


我们现在正在与Laravel合作-他有一个标准的,熟悉的迁移管理引擎。 如果需要,即使它仍在PHP类中,也可以用纯SQL编写。 但是我反复尝试使其以我们需要的方式工作,导致产生了单独的回购:

  • 该解决方案包括两部分-lib和特定控制台的实现(Laravel,Symfony)。 您可以集成到任何控制台中,或者至少集成到网络枪口中。
  • 没有配置和连接-为什么,当它已经在您的项目中时。 紧扣您与接口的连接,然后开始。
  • SQL回滚存储在数据库中。 这是在分支之间切换所必需的。
  • 在Postgesql,Mysql上测试(无事务)。 原则上,它适用于任何基础和结构,因为使用了原始格式。


参考文献
- 迁移库
- 在Laravel / Artisan下实施
- 在Symfony /控制台下实施

Source: https://habr.com/ru/post/zh-CN435438/


All Articles