Sequelize.js:如何使用迁移和同步

166

我离项目上线越来越近了。在项目上线后,我的大计划就是要改变数据库结构 -- 在现有表中增加新列,创建新表,并建立现有和新模型之间的新关联。

目前我只有测试数据,因此还没有涉及到Sequelize迁移。我每次更改数据库时都不介意清除测试数据,所以启动应用程序时我会运行sync force: true命令,如果我已经更改过模型定义。这将删除所有表格并从头开始创建它们。如果我忽略force选项,它只会创建新表格。但如果现有表格发生了变化,这就没用了。

那么,当我添加迁移时,情况会如何呢?显然,我不想清除已经存在(带有数据)的表格,因此,sync force: true已经不是可行的选择。在其他我参与开发的应用程序中(如Laravel和其他框架),作为应用程序部署过程的一部分,我们运行迁移命令来运行任何待处理的迁移。但在这些应用程序中,第一个迁移有一个框架数据库,其中的数据库状态为开发早期的某个时间点 -- 第一个alpha版本或其他版本。因此,即使是稍晚加入的应用程序实例也可以一次性运行所有迁移,并跟上步伐。

那么,我该如何在Sequelize中生成这样的“第一个迁移”呢?如果我没有这个迁移,那么在某个时候启动的新应用程序实例将没有框架数据库来运行迁移,或者它将在开始时运行同步并创建具有所有新表等的新状态数据库,但当尝试运行迁移时,由于它们是为原始数据库和每个后续迭代编写的,所以它们就不再有意义了。

我的思维过程是:在每个阶段,初始数据库加上每个迁移序列应等于(加上或减去数据)运行 sync force: true 时生成的数据库。这是因为代码中的模型描述描述了数据库结构。所以,如果没有迁移表,我们只需运行同步并标记所有迁移为已完成,即使它们没有运行也可以。这是我需要做的吗(如何?),还是 Sequelize 应该自己做这个,或者说我走错了路?如果我在正确的领域,那么肯定应该有一种很好的方法来自动生成大部分迁移,给定旧模型(按提交哈希值?或甚至每个迁移都可以与提交相关联?我承认我在一个非便携式的 git-centric 宇宙中思考),和新模型。它可以比较结构并生成需要从旧到新转换数据库的命令,然后开发人员可以进行任何必要的调整(删除/转换特定数据等)。

当我使用 --init 命令运行 sequelize 二进制文件时,它会给我一个空的迁移目录。然后当我运行 sequelize --migrate 时,它会让我创建一个名为 SequelizeMeta 的表,里面什么也没有,也没有其他表。显然不是因为那个二进制文件不知道如何引导我的应用程序并加载模型。

我一定是漏了什么。

简而言之:我应该如何设置我的应用程序及其迁移,以便将各种实例的实时应用程序更新到最新状态,以及没有遗留起始数据库的全新应用程序?


2
我已经回答了关于你的工作流程的问题,但理想情况下,所有的表都应该使用迁移来设置。即使现在你正在使用sync,迁移的想法是“生成”整个数据库,所以依赖骨架本身就是一个问题。例如,Ruby on Rails工作流程使用迁移来完成所有事情,一旦你习惯了它,它就非常棒。编辑:是的,我注意到这个问题很旧,但是考虑到从未有过令人满意的答案,而且人们可能会来这里寻求指导,我觉得我应该做出贡献。 - Fernando Cordeiro
12个回答

102

生成“第一次迁移”

在您的情况下,最可靠的方法几乎是手动完成。我建议使用sequelize-cli工具。语法非常简单:

sequelize init
...
sequelize model:create --name User --attributes first_name:string,last_name:string,bio:text
这将同时创建模型和迁移。然后,手动将现有的模型与使用sequelize-cli生成的模型进行合并,并对迁移执行相同的操作。在完成这些步骤后,如果可能的话,清空数据库,并运行。
sequelize db:migrate

这将创建具有迁移的模式。您应该只执行一次此操作,以切换到正确的模式开发流程(不使用sync:force,而是使用权威的迁移)。

稍后,当您需要更改模式时:

  1. 创建一个迁移:sequelize migration:create
  2. 在您的迁移文件中编写上下函数
  3. 根据迁移文件中的更改,手动更改您的模型
  4. 运行sequelize db:migrate

在生产环境中运行迁移

显然,您无法通过ssh到生产服务器并手动运行迁移。使用umzug,一个与框架无关的Node.JS迁移工具,在应用程序启动之前执行待处理的迁移。

您可以像这样获取待处理/尚未执行的迁移列表:

umzug.pending().then(function (migrations) {
  // "migrations" will be an Array with the names of
  // pending migrations.
}); 
然后执行迁移(在回调函数内部)。execute方法是一个通用的函数,用于运行每个指定迁移的相应函数。
umzug.execute({
  migrations: ['some-id', 'some-other-id'],
  method: 'up'
}).then(function (migrations) {
  // "migrations" will be an Array of all executed/reverted migrations.
});
我的建议是在应用程序启动之前进行此操作,并且每次尝试提供路由之前都要这样做。就像这样:
umzug.pending().then(function(migrations) {
    // "migrations" will be an Array with the names of
    // pending migrations.
    umzug.execute({
        migrations: migrations,
        method: 'up'
    }).then(function(migrations) {
        // "migrations" will be an Array of all executed/reverted migrations.
        // start the server
        app.listen(3000);
        // do your stuff
    });
});
我现在不能试试这个,但是乍一看应该可以工作。 更新于2016年4月 一年过去了,仍然有用,所以分享一下我目前的技巧。现在,我正在安装sequelize-cli包作为必需的实时依赖,并且修改package.json中的NPM启动脚本,像这样:
...
"scripts": {
  "dev": "grunt && sequelize db:migrate && sequelize db:seed:all && node bin/www",
  "start": "sequelize db:migrate && sequelize db:seed:all && node bin/www"
},
...
在生产服务器上,我唯一需要做的就是运行npm start命令。这个命令将运行所有的迁移,应用所有的种子数据,并启动应用服务器。不需要手动调用umzug。

3
这听起来就像我想要的。它似乎并不像应该那样神奇和自动化,但也许这是最好的结果了。然而,我目前没有用Sequelize,并且不能立即测试这个。但如果有其他人同意这个解决方案很好,我会接受这个答案。我仍然觉得有点遗憾,似乎没有办法从模型版本之间的差异中自动进行这些迁移。 - tremby
6
@tremby,我使用过的唯一一个真正“理解”模型的框架是Django。它会分析模型,并询问诸如“好吧,看起来你在模型User中将字段名称更改为first_name。您想要为此创建迁移吗?” 在Django中,这个过程几乎像魔法一样,而我使用过的其他工具则假设了我上面提到的同样的迁移方法:您需要自己编写迁移,并深入了解要添加哪种类型的字段以适应当前的模型状态。 - f1nn
2
你可以摆脱 pending 状态然后执行 umzug.up().then(function (migrations) { app.listen(3000); })。根据 umzug 文档,这将执行所有未完成的 migrations。 - Vinay
@f1nn,我有一个关于你的设置的问题,你是如何处理应用程序集群和可用性的?我将在我的工作流中集成pm2,也许它不能直接与npm脚本一起使用。 - Diosney
@f1nn 我最后执行了 ./node_modules/sequelize-cli/bin/sequelize db:migrate,然后使用 node 模块 child_process.exec() 方法设置路由,这样 umzug 对于应用程序来说是隐藏的,并且包含在 app.js 中,使其能够与 pm2 start app.js 一起工作。 - Diosney
显示剩余5条评论

18

我自己也在学习这个,但我认为现在建议使用迁移,这样你就可以适应它们。我发现找出迁移中需要放置的内容的最佳方法是查看由sequelize.sync() 创建的表上的SQL,然后从那里构建迁移。

migrations -c [migration name] 

将在迁移目录中创建模板迁移文件。然后,您可以使用需要创建的字段填充它。此文件需要包括用于关联等的createdAt/updatedAt字段。

对于初始表创建,应有以下操作:

migration.dropTable('MyTable');

但是随后对表结构的更新可能会省略这一步骤,直接使用alter table。

./node_modules/.bin/sequelize --migrate

例如,创建的一个示例可能如下所示:

module.exports = {
  up: function(migration, DataTypes, done) {
    migration.createTable(
        'MyTable',
        {
          id: {
            type: DataTypes.INTEGER,
            primaryKey: true,
            autoIncrement: true
          },
          bigString: {type: DataTypes.TEXT, allowNull: false},
          MyOtherTableId: DataTypes.INTEGER,
          createdAt: {
            type: DataTypes.DATE
          },
          updatedAt: {
            type: DataTypes.DATE
          }
        });
    done();
  },
  down: function(migration, DataTypes, done) {
    migration.dropTable('MyTable');
    done();
  }

重新开始:

./node_modules/.bin/sequelize --migrate --undo
./node_modules/.bin/sequelize --migrate

我正在使用咖啡来运行种子文件以填充表格:

coffee server/seed.coffee

这个代码只是包含一个类似以下的create函数:

user = db.User.create
  username: 'bob'
  password: 'suruncle'
  email: 'bob@bob.com'
.success (user) ->
  console.log 'added user'
  user_id = user.id
  myTable = [
    field1: 'womp'
    field2: 'rat'

    subModel: [
      field1: 'womp'
     ,
      field1: 'rat'
    ]
  ]

请记得将 sync() 从您的模型中移除,否则它会覆盖迁移和种子所做的内容。

文档当然可以在http://sequelize.readthedocs.org/en/latest/docs/migrations/找到,但基本答案是您必须自己添加所有内容以指定所需字段。 它不会为您完成此操作。


6
我并不是在询问如何创建和运行迁移 - 正如你所指出的那样,所有这些都可以在文档中找到。我想知道的是,在可重用应用程序的情况下,如何在现有实例需要更新到较新的数据库版本,而新实例需要从头开始创建数据库时使用它们。或许你正在回答我的问题,并且说我根本不应该使用sync(),而是应该在迁移中创建初始数据库并对其进行所有更改。这是你的意思吗? - tremby
1
@tremby 我想这就是他的意思。您可以使用同步并处理结果,或者您可以手动创建所有迁移。我们的框架以Rails方式生成基于模式差异的迁移文件,如果Sequelize也能为我完成这个过程,那就太好了。手动创建迁移实在太繁琐了... - mpowered
1
很遗憾,你不能像Rails的schema.rb一样使用sequelize.sync()生成一个创建所有基本表和索引的脚本作为第一个迁移。阅读完这篇文章后,最好的选择似乎是将初始模式导出为SQL,然后将其放入第一个迁移的大型exec语句中。从那里开始,您可以针对已知的“1.0版本”起点运行增量更改。 - thom_nic

15

现在,在开发过程中,有一个选项可以通过更改表的结构来同步当前表。使用来自sequelize github repo的最新版本,您可以使用alter参数运行sync。

Table.sync({alter: true})

文档中的警告:

将表格修改以适应模型。不建议在生产环境中使用。删除已从模型中删除或更改类型的列中的数据。


9

我阅读了这篇文章和类似的问题,但它并没有真正回答我的问题。迁移对于在本地数据库中启动和更新生产数据非常有用。

我在这里提出了问题,并回答了它: 如何处理 sequelize 迁移和初始化的工作流程?

新项目的 TL-DR 版本:

  1. 按照传统方法使用纯 SQL 脚本或者使用图形化工具设计数据库模式
  2. 当你完成了 95% 的数据库模式并且对它满意后,将整个 .sql 文件移到 sequelize 中
  3. 进行首次迁移。在存放 models 的文件夹中运行 sequelize init:migrate 命令
  4. 创建第一个迁移文件。在命令行中运行 sequelize migration:generate --name [name_of_your_migration]
  5. 在迁移文件中,加入以下代码
("use strict");
/**
 * DROP SCHEMA public CASCADE; CREATE SCHEMA public
 * ^ there's a schema file with all the tables in there. it drops all of that, recreates
 */
const fs = require("fs");
const initialSqlScript = fs.readFileSync("./migrations/sql/Production001.sql", {
  encoding: "utf-8",
});
const db = require("../models");
module.exports = {
  up: () => db.sequelize.query(initialSqlScript),
  down: () =>
    db.sequelize.query(`DROP SCHEMA public CASCADE; CREATE SCHEMA public;
`),
};

enter image description here

使用这个通用的文件夹结构

enter image description here

  1. 现在你的 Sequelize 设置已经与初始数据库模式同步
  2. 当你想要编辑你的数据库模式时,再次运行此命令 sequelize migration:generate --name [name_of_your_migration]
  3. 在这里进行修改,包括 updown 迁移路径。这些是更改列名称、删除、添加列等的 ALTER 语句
  4. 运行 sequelize db:migrate
  5. 你希望模型同步到远程数据库的更改,所以现在你可以通过 npm install sequelize-auto 命令安装它。
  6. 这将读取数据库上的当前数据库模式并自动生成模型文件。使用类似于以下的命令:sequelize-auto -o "./models" -d sequelize_auto_test -h localhost -u my_username -p 5432 -x my_password -e postgres,该命令可以在 https://github.com/sequelize/sequelize-auto 找到

你可以使用 Git 查看模型的差异记录,其中应该只包含反映数据库模型更改的内容。顺便说一下,如果你使用 sequelize auto,请永远不要直接修改 models,因为它会为你生成这些文件。同样,你也不再需要通过 SQL 文件直接修改数据库模式,尽管这是一个选项,因为你也可以导入那些 .sql 文件。

现在你的数据库模式已经更新,而且你已经正式转换为 sequelize 数据库迁移。

一切都受版本控制。这是数据库和后端开发人员理想的工作流程。


3
现在使用新的Sequelize迁移非常简单。 以下是您可以执行的示例。
    'use strict';

    var Promise = require('bluebird'),
        fs = require('fs');

    module.exports = {
        up: function (queryInterface, Sequelize) {

            return Promise
                .resolve()
                .then(function() {
                    return fs.readFileSync(__dirname + '/../initial-db.sql', 'utf-8');
                })
                .then(function (initialSchema) {
                    return queryInterface.sequelize.query(initialSchema);
                })
        },

        down: function (queryInterface, Sequelize) {
            return Promise
                .resolve()
                .then(function() {
                    return fs.readFileSync(__dirname + '/../drop-initial-db.sql', 'utf-8');
                })
                .then(function (dropSql) {
                    return queryInterface.sequelize.query(dropSql);
                });
        }
    };
请记住,您必须在数据库配置中设置以下内容:"dialectOptions": { "multipleStatements": true } ,以便执行多条SQL语句。

这只是删除并重新创建数据库吗? - TWilly
我认为使用一个初始的大型 SQL 文件并不是推荐的方法,因为它会占用适配器和数据库,否则将是数据库无关的,因为你可以在开发中使用 SQLite,在生产中使用 MariaDB 或其他。 - Diosney

2
Sequelize可以异步地运行任意SQL语句。 我的做法是:
  • 生成一个迁移(用作第一个迁移);
  • 转储数据库,例如:mysql_dump -uUSER -pPASS DBNAME > FILE.SQL
  • 将完整的转储粘贴为文本(危险),或者在Node中加载包含完整转储的文件:
    • var baseSQL = "LOTS OF SQL and it's EVIL because you gotta put \ backslashes before line breakes and \"quotes\" and/or sum" + " one string for each line, or everything will break";
    • var baseSQL = fs.readFileSync('../seed/baseDump.sql');
  • 在Sequelize迁移上运行此转储:
module.exports = {
  up: function (migration, DataTypes) {
    var baseSQL = "whatever" // I recommend loading a file
    migration.migrator.sequelize.query(baseSQL);
  }
}
那应该处理好了设置数据库的事情,尽管异步操作可能会成为一个问题。如果发生这种情况,我会考虑一种方法来推迟返回 up sequelize 函数,直到异步的 query 函数完成。 更多关于 mysql_dump 的信息:http://dev.mysql.com/doc/refman/5.1/en/mysqldump.html
更多关于 Sequelize 迁移的信息:http://sequelize.readthedocs.org/en/latest/docs/migrations/
更多关于在 Sequelize 迁移中运行 SQL 的信息:https://github.com/sequelize/sequelize/issues/313

2

使用版本控制。 应用程序的版本取决于数据库的版本。 如果新版本需要更新数据库,请为其创建迁移。

更新:我决定放弃迁移(KISS原则)并在需要时运行脚本update_db(同步强制关闭:false)。


1
与我对user1916988答案的回应类似,您是在说我根本不应该使用sync(),而是需要手动编写从旧版本模型架构到新版本模型架构的迁移吗? - tremby
我因为你的更新而+1了。实际上,我也在考虑做同样的事情。手动编写所有迁移时,应用程序可以完成这项工作有点愚蠢,所以我打算编写一个手动脚本,运行一次应用程序并运行同步函数。 - Sallar

1

朋友,我曾经有同样的问题,并成功理解了如何使用它们。

我开始时没有使用ORM sequelize,因此我已经有了数据模型。
我必须使用sequelize-auto自动生成模型,并使用您创建的此文件生成其迁移https://gist.github.com/ahelord/a7a7d293695b71aadf04157f0f7dee64并在sync中执行 ({Force: false})
这是在dev环境中。我需要对模型和迁移进行版本控制,并在每次拉取代码时执行它们。

在生产中,服务器只会在上面运行,因此您只需要运行迁移,并在每个提交中管理如何对模型进行版本控制,而不会停止后端。


1

这是我的当前工作流程。我可以接受建议。

  1. 设置Sequelize创建不存在的表
  2. 设置Sequelize删除并重新创建名为_blank的空数据库中的所有表
  3. 使用MySQL工具比较_blank和现有数据库,并使用该工具同步更改。仍在寻找一款可以在Mac上实现此功能的经济实惠工具。MySQL Workbench看起来可以从现有模式导入模型,然后同步模式。尝试弄清如何通过命令行轻松完成此操作。

这样您就不必手动更新迁移表并担心打错字,但仍然可以获得ORM。


1
有点晚了,在阅读文档后,你不需要进行你所说的第一次迁移。你只需要调用sync来创建表格即可。

sequelize.sync()

您也可以通过执行以下操作来运行简单的模型同步:

Project.sync(),但我认为sequelize.sync()对于项目的一般情况更有用(只要在启动时导入了好的模型)。

(摘自http://sequelizejs.com/docs/latest/models#database-synchronization

这将创建所有初始结构。之后,您只需创建迁移即可演变您的模式。

希望能有所帮助。


9
我认为你没有仔细阅读原始帖子,或者可能我表述不够清楚。我非常清楚 sequelize.sync() 的作用。 - tremby

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,