Skip to content

安装 docker 并将数据库从 sqlite 迁移到 mysql

发布日期:2024-01-04

本文介绍了我在 windows 平台和 debian 平台安装 docker 的过程,并将我的 nestjs 服务器使用的 sqlite 数据库迁移到 mysql。最后对 mysql 数据库进行定时备份。


在 win10 安装 docker-desktop

  1. 进入 bios 开启 svm
  2. 打开设置-应用和功能-相关设置-程序和功能-Hyper-V
  3. 此时安装 docker-desktop 尝试运行,出现 wsl 报错,或者长时间卡在初始化状态
  4. 以管理员身份运行 cmd,执行命令 wsl --status 查看 wsl 状态,提示执行 wsl --update
  5. 执行 wsl --update 等待更新完毕
  6. 执行 wsl --install 安装 linux,此时会自动安装 Ubuntu,安装完毕后按提示输入新用户的 username 和 password
  7. 重启,再次运行 docker-desktop,成功运行

docker-desktop 安装 mongoDb 并连接

首先在 Images 中下载最新的 mongodb 镜像,运行镜像到容器,并指定端口映射,此处映射到 27017 端口

端口映射

下载 MongoDB Compass,一个免费的图形界面 mongodb 管理软件,按照默认连接 mongodb://localhost:27017 即可连接到数据库

本来想用 mongodb 来替代 sqlite,但是 mongodb 不能用 sequelize,为了避免大量代码重写,选择安装 mysql。这篇文章解释了 sequelize 不能用于 mongodb 的原因 https://medium.com/sequelize-to-mongoose-and-vice-versa-a-developers/sequelize-to-mongoose-and-vice-versa-a-developers-guide-906bdf79e81

docker-desktop 安装 mysql 并连接

在镜像中下载最新的 mysql 镜像(8.2),配置 3306 端口映射,添加 Environment variables(环境变量)variable 填写必要环境变量 MYSQL_ROOT_PASSWORD,value 填写密码,启动镜像。

用于连接 mysql 的客户端的选择了 Mysql Workbench,官方免费跨平台的图形化界面,但目前只支持到 mysql 8.0,连接时会提示部分功能无法适配,选择继续连接即可。

创建 nest 数据库:mysql -u root -p 输入密码,进入 mysql 命令行,create database nest; 创建 nest 数据库,show databases; 显示所有的数据库。

启动服务器,可以连接上数据库了。参考 nestjs 文档教程 https://docs.nestjs.com/recipes/sql-sequelize

查看已创建的表:use nest; show tables;

在 debian 上安装 docker 并运行 mysql 镜像

不能直接 apt install docker,这是错误的做法

阅读官方文档,这篇文档详细描述了怎样在 debian 上安装 docker https://docs.docker.com/engine/install/debian/#os-requirements

鉴于 Mysql Workbench 只支持到 mysql 8.0,而目前主流使用的 mysql 版本分别是 5.7 和 8.0,因此我选择安装目前最新的 8.0 镜像 mysql 8.0.35。在 https://hub.docker.com/ 上可以找到 mysql 的最新镜像。

docker pull mysql:8.0.35-debian
docker image ls // 查看本地镜像
docker run -p 3336:3306 --name mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:8.0.35-debian // 需要在服务器开启 3336 端口,此处可以选择其他端口回避一些对 3306 端口的直接攻击;-d 表示后台运行,mysql:8.0.35-debian 表示指定运行的镜像版本,如果写 mysql 则会重新下载最新版的 mysql 镜像
docker ps // 查看当前运行的容器
docker exec -it mysql bash // 进入容器内部,mysql 是上面给容器起的 neme
mysql -u root -p // 以 root 用户进入 mysql,-p 表示输入密码
show tables; // 列出所有的表

从 sqlite 迁移到 mysql

下载 sqlite3 命令行工具,把数据库 nest.db 转换为 nest.sql

.open nest.db 
.output nest.sql
.dump

使用 Mysql Workbench 图形化界面,连接 mysql 数据库,选择菜单栏 server - Data Import。选择 Import from Self-contained File 并选择 nest.sql 文件,在下方 new 按钮可以选择需要把数据导入到哪个 Schema,也就是 database。点击 Start Import 开始导入,导入过程中可能会遇到各种报错,使用 vscode 打开 nest.sql 修改其中 mysql 不兼容的语法,总结如下

  1. 移除所有的 [BEGIN TRANSACTION] [COMMIT] 以及 任何包含 [sqlite_sequence] 的(整)行
  2. 将所有 [autoincrement] 改为 [auto_increment]
  3. 利用 vscode 正则替换修复时间日期为 mysql 支持的 datetime 格式。我在 sqlite 中使用了 TEXT 类型来储存时间日期,如 YYYY-MM-DD HH:MM:SS.SSS,需要把所有时间日期替换为替换为 mysql 的 datetime 类型 YYYY-MM-DD HH:MM:SS

修改后再次提交,修复报错,直至完成

迁移后启动服务器,发现查不到 users 表的内容。原来 sequelize 使用 Class 创建的 Model,默认是大写开头的,然而旧表中有小写开头的表(早期没有使用 Class 创建表,创建的是 users 表),因此 sequelize 自动帮我创建了大写开头的 Users 表,因此查不到用户。为了解决这个问题,我把 nest.sql 中所有的小写表名改成了大写,删除 nest 数据库,重新导入。

drop database nest; // 删除 nest 数据库

重新导入后,数据恢复正常。

数据库备份

在使用 sqlite 的时候,我通过一个定时任务,在每天零点将数据库文件复制一份,放到指定的位置,并以日期命名。在 mysql 中,有以下几种方式进行备份:

  1. 在 mysql workbench 中备份,点击菜单栏 server - Data Export,左侧刷新 schema,选择 nest,下方选择到处目录,点击导出按钮,即可完成数据库备份。

  2. 调用 docker 命令备份数据库

// 此处 -u 和 -p 后面没有空格
docker exec mysql sh -c 'exec mysqldump --databases nest -uroot -p{password}' > /root/backup/mysql_20240104.sql 
// mysqldump: [Warning] Using a password on the command line interface can be insecure.

如果按照上面这样调用,mysql 会提示你不安全,但仍然备份成功了。接下来要解决账号密码的安全问题

docker exec -it mysql bash // 进入容器内部
cd /etc/mysql
vim my.cnf // 此处可能报错找不到 vim 命令,只要 apt-get update 然后 apt install vim 即可,此处下载非常慢

// 写入以下内容
[client]
host = localhost
user = root
password = 'your_pwd'

// 保存,尝试执行以下命令,注意不要把这个 `-it` 的命令写进 sh 脚本,否则 nodejs 将无法调用
// i 表示交互模式(interactive)t 表示终端模式(terminal),合起来表示交互式终端,然后在 nodejs 调用定时脚本时没有终端,因此会报错,执行失败。
docker exec -it mysql mysqldump --defaults-file=/etc/mysql/my.cnf --databases nest > /root/backup/20240104.sql

注意检查备份出来的文件大小,当文件大小和先前备份的大小差距过大时,要检查命令是否出错。找不到 cnf 文件时文件大小只有几百k,而备份了全部数据库时,文件大小到了 3M 以上,而我的数据库正常备份大小只有 800k

接下来将上述命令写入 sh 脚本:

CONTAINER=mysql
FILENAME=/root/backup/$(date "+%Y%m%d").sql

// 还有一个坑在:单引号里面不能直接使用 ${DB_NAME} 语法,这是因为在单引号内部,变量引用会被保持原样,不会被展开。可以使用双引号来回避这个问题,我在这里直接写 nest。
docker exec ${CONTAINER} sh -c 'exec mysqldump --defaults-file=/etc/mysql/my.cnf --databases nest' > ${FILENAME}

通过 nestjs 定时任务触发,即可完成数据库备份

一些问题

迁移数据后丢失了 AUTO_INCREMENT 自增计数

导入数据库之后,发现能查但不能新增,原来是因为数据库丢失了 AUTO_INCREMENT,以及其自增计数。删除 nest 数据库后,我重新导入备份的 nest.sql,并修改其中的相关字段,如下是 Account 表的结构。

CREATE TABLE `Accounts` (
  `id` int NOT NULL AUTO_INCREMENT,
  `expend` int DEFAULT NULL,
  `income` int DEFAULT NULL,
  `gzhId` varchar(255) DEFAULT NULL,
  `reason` varchar(255) DEFAULT NULL,
  `date` datetime DEFAULT NULL,
  `createdAt` datetime NOT NULL,
  `updatedAt` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=677 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

注意不要直接把 mysql workbench 的 DLL 复制过来修改,上面这些就是复制过来的,导致导入一直报错,而且报错很奇怪,可能是字符编码的问题。

最终我设置 sequelize 的 sync: { alter: false,force: true } 强制更改数据库结构,sequelize 把不符合 Model 的表 Accounts 所有行都删除了。我添加了两行之后导出数据库备份 test.sql 来观察和报错的 nest.sql 有什么不一样,最后发现没有不一样,但复制 test.sql 中的 DLL 到 nest.sql 中修改自增计数,再重新导入数据库就成功了。

win10 开启 hyper-v 导致开发服务端口监听失败

有一个本地服务监听 4000 端口,在调试完 docker-desktop 之后无法正常启动

failed to start server. error:
Error: listen EACCES: permission denied 0.0.0.0:4000

原来是因为 4000 是 Hyper-V 的保留端口,只要避开保留端口即可。查看保留端口,在 cmd 执行以下命令

> netsh interface ipv4 show excludedportrange protocol=tcp

开始端口    结束端口
----------    --------
      3679        3778      
      3779        3878
      3980        4079
      4093        4192
      4348        4447
      5357        5357
     50000       50059     *

* - 管理的端口排除。

可见,4000 端口在保留端口范围内,于是我切换到 6000 端口运行,服务运行起来了,但是浏览器无法访问,提示如下

无法访问此网站
网址为 http://localhost:6000/ 的网页可能暂时无法连接,或者它已永久性地移动到了新网址。
ERR_UNSAFE_PORT

6000 端口是浏览器的不安全端口,因此无法访问。我记得曾经踩过 6666 端口的坑,没想到 6000 端口也是坑。这个问题只要修改端口就可以了

Power by vitepress