DB Migrate那点事

勿忘初心2018-12-05 13:22

此文已由作者左裕初授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验


前言

db migrate这种数据库管理的方式正在被移植到各种各样的语言和框架。据我所知,db migrate的思想最早由Ruby On Rails引入,所以熟知Ruby On Rails的同学基本可以忽略这篇文章(除非对db migrate思想的nodejs实现有浓厚兴趣),但是如果你是第一次接触它,它真的很有用。

要解决的问题

很多情况下,我们开发有一个本地的环境,一个测试环境,一个正式的环境。本地若是多几个分支,而每个分支都要对数据库进行修改(如加一列,加一表)的话,就会凸显出数据库管理上的麻烦。更麻烦的是,一般来说,只有代码会放到代码仓库里头,数据库的不一致,常常会导致程序员:“它在我这儿是好的啊”。

方案

问题的本质在于要同步数据库的结构(而非数据本身)。事实上db migrate不过是借鉴了版本管理方面的思想。举例来说,我们进行两步的操作,第一步建立一个user表,第二步,为username建个唯一索引。

所以对于第一个版本来说,执行这个版本的操作就是createTable,而回退这个操作则是dropTable。

对于第二个版本来说,它建立在第一个版本的基础上,执行这个版本的操作是createIndex,而回退这个操作则是dropIndex。


为什么比直接写sql语句和shell脚本要好

我确实接触过不少项目是拿SQL直接做数据库版本管理的,甚至是一些企业软件,拿着一堆SQL找客户更是家常便饭。之所以推荐DB migrate,个人觉得有如下原因:

  1. 程序语言支持的功能远比shell脚本+SQL的方案要多,“错误处理+回滚”套装拿shell+sql写起来很不方便,当需要执行一些“要么成功要么什么都不做”的操作的时候,程序语言比直接写SQL轻量太多。

  2. 多数据库支持,考虑到各个数据库的细微差别,db migrate可以把细节隐藏到驱动当中。(让我回想起为SQL Server和DB2各写一份脚本的岁月。。。)

  3. 多操作系统支持。win跟unix的shell各写一份也不是什么新闻了。

实现

大部分语言都提供了db migrate思想的实现。我采用了nodejs的sequelize。原因如下,

  1. 比起Ruby,在国内,nodejs更好装。

  2. nodejs的包管理更胜一筹

通过一个package.json,可以描述做db migrate的所有依赖。通常来说,一个sequelize, sequlize-cli,加上一个数据库驱动(如mysql),便可以完成以上的操作了。


mkdir migration-show && cd migration-show

npm install --save sequelize sequelize-cli mysql

./node_modules/sequelize-cli/bin/sequelize init:migrations # 初始化Migration文件夹

./node_modules/sequelize-cli/bin/sequelize init:config # 初始化配置文件

此时目录结构


.
├── config
│   └── config.json
├── migrations
├── node_modules
│   ├── mysql
│   ├── sequelize
│   └── sequelize-cli
└── package.json

config指定了用于测试,开发,部署的mysql配置,migrations正是存储了数据库变化的脚本

修改配置文件config/config.json,配置正确的服务器域名端口密码,然后创建migrations脚本


./node_modules/sequelize-cli/bin/sequelize migration:create

migrations文件夹中即多出一个Migration文件


.
├── config
│   └── config.json
├── migrations
│   └── 20160104204428-unnamed-migration.js
├── node_modules
│   ├── mysql
│   ├── sequelize
│   └── sequelize-cli
└── package.json

用标准js语法修改migration文件创建users表,这个表里头有一列叫Id。


'use strict';

module.exports = {
  up: function (queryInterface, Sequelize) {
      return queryInterface.createTable('users', { id: Sequelize.INTEGER });
  },

  down: function (queryInterface, Sequelize) { 
      return queryInterface.dropTable('users');
  }
};

注意到它既有up函数又有Down函数,只要这两个操作满足上述的状态机,就保证了数据库操作是可重复的。

最后执行DB Migrate看看结果


./node_modules/sequelize-cli/bin/sequelize db:migrate

可以看到数据库里头多了两个表

users表正如我们定义的一样,SequelizeMeta则记录了有哪些Migration已经进入了数据库。正是这个表,保证了每一个migration操作是幂等的(即无论操作多少次,结果都是一致的)。


mysql> show tables;
+------------------------+
| Tables_in_database_dev |
+------------------------+
| SequelizeMeta          |
| users                  |
+------------------------+
2 rows in set (0.00 sec)
mysql> show columns from users;
+-------+---------+------+-----+---------+-------+
| Field | Type    | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id    | int(11) | YES  |     | NULL    |       |
+-------+---------+------+-----+---------+-------+
1 row in set (0.00 sec)

mysql> select * from SequelizeMeta;
+-------------------------------------+
| name                                |
+-------------------------------------+
| 20160104204428-unnamed-migration.js |
+-------------------------------------+
1 row in set (0.00 sec)

总结和拓展

db migrate在数据库管理方面有重大作用,这个思想同时也可以用于软件升级,笔者曾经利用该思想做过企业软件升级。除了表结构,它同样可以对数据库中一些预定的数据进行管理。再者,对于up/drop的操作,也可以在里头实现数据备份功能,让开发者更加高枕无忧。


免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐

更多网易技术、产品、运营经验分享请点击