Memos数据库迁移(手动方案)

前言

Memos 是一个非常好用的开源、自托管的笔记服务。可以说从它刚在Github上发布的时候我就一直在用它了。

但是随着它不断的更新,逐步开发了MySQL以及Postgres数据库的支持,但是一直未推出完善可靠的数据库迁移服务。

最近打算将之前的MySQL迁移到Postgres时,使用pgloader等工具,总能出现各种问题,不尽人意,因此只能手动对其数据迁移。

说明

Memos的本质是笔记服务,因此我的数据基本上都是文字形式,即使有图片,大多数都是以外链形式存在,即Memos本身并不存储图片,从而使得迁移工作相对简单。

如果你的数据中有很多图片,以下方法可能不适合你。

准备

Navicat数据库管理软件

步骤

基于我的Memos是Docker compose搭建的:

services:
  mysql:
    image: mysql:latest
    restart: unless-stopped
    volumes:
      - ./data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: memos
      MYSQL_USER: memos
      MYSQL_PASSWORD: password
  memos:
    image: neosmemo/memos:stable
    restart: always
    depends_on:
      - mysql
    ports:
      - "5230:5230"
    environment:
      - MEMOS_DRIVER=mysql
      - MEMOS_DSN=memos:password@(mysql:3306)/memos

第一步: 修改compose文件

  1. 添加Postgres

  2. 开放对应的数据库端口

最终的 compose 文件基本如下:

services:
  mysql:
    image: mysql:latest
    restart: unless-stopped
    ports:
      - "10001:3306"  # <--- 1.暴露MySQL端口
    volumes:
      - ./data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: memos
      MYSQL_USER: memos
      MYSQL_PASSWORD: password

  postgres:   # <--- 2.添加postgres数据库
    image: postgres:15
    restart: unless-stopped
    ports:
      - "10002:5432"  # <--- 3.暴露postgres端口
    environment:
      POSTGRES_DB: memos
      POSTGRES_USER: memos
      POSTGRES_PASSWORD: password
    volumes:
      - postgres_data:/var/lib/postgresql/data

  memos:
    image: neosmemo/memos:stable
    restart: always
    depends_on:
      - postgres  # <--- 4.改成postgres数据库
    ports:
      - "5230:5230"
    environment:
      - MEMOS_DRIVER=postgres  # <--- 5.对应更改
      - MEMOS_DSN=postgresql://memos:password@postgres:5432/memos?sslmode=disable  # <--- 6.对应更改

volumes:  # <--- 7.添加卷
  postgres_data:

我们分析一下上面的代码:

  • MySQL数据库正常启动,添加了对外的端口:10001

  • 添加Postgres数据库,并添加了对外的端口:10002

  • 将Memos数据库变更为postgres

确定这些后,通过 docker compose up -d 启动容器。

第二步: 初始化Memos

说是初始化,其实也就是因为使用了Postgres,导致这个容器是全新的,登陆页面后会要求创建用户,正常创建即可,尝试发布几个笔记。正常的话进入下一步。

第三步:数据库连接

打开Navicat软件,新建数据库连接,将上述的MySQL和Postgres分别添加进去。

连接前检查服务器或者服务器提供商的防火墙是否开放了对外的 1000110002 端口

最终可以看到:

MySQL的数据储存在【memos】-【memos】中:

Postgres的数据储存在【memos】-【public】-【Tables】-【memos】中:

对比2个数据库的差异,可以看得出来结构一致,区别在与 created_ts 以及 updated_ts ,在MySQL中使用的是日期格式(YYYY-MM-DD HH:MM:SS),而Postgres中使用的是Unix 时间戳 (Unix Timestamp),还有个不同在于 pinned (置顶),MySQL的 0 或者 1 ,与Postgres的 f 以及 t,实际上无需转换。

PostgreSQL 中 BOOLEAN 类型的字段可以接受以下所有输入形式,并自动把它们转换为标准的 boolean

输入值 (不区分大小写) Postgres 理解为
1, yes, on, true, t, y True (t)
0, no, off, false, f, n False (f)

通过这一步可以确认,我们只需将MySQL中的笔记按照Posters的格式导入到现在的数据库中应该就能正常识别。

第四步:导出MySQL数据库中的笔记

右键【Memos】-【Export Wizard...】,选择 csv 格式,一路下一步,直到导出得到 memo.csv

第五步:转换memo.csv中的时间戳

memo.csv上传至服务器或者有python环境的系统中,在同一目录创建一个python脚本convert.py,内容为:

import csv
import time
from datetime import datetime, timezone, timedelta

# 定义输入和输出文件名
input_file = 'memo.csv'   # 你的原始文件名
output_file = 'memos_converted.csv' # 转换后的文件名

# 定义时间格式:日/月/年 时:分:秒
date_format = "%d/%m/%Y %H:%M:%S"

# 定义时区:北京时间 (UTC+8)
tz_beijing = timezone(timedelta(hours=8))

def to_timestamp(date_str):
    try:
        # 解析时间字符串
        dt = datetime.strptime(date_str, date_format)
        # 强制设定为北京时间,然后转为 Unix 时间戳 (秒)
        dt = dt.replace(tzinfo=tz_beijing)
        return int(dt.timestamp())
    except ValueError:
        return date_str # 如果解析失败,返回原值

print("正在转换...")

with open(input_file, mode='r', encoding='utf-8', newline='') as infile, \
     open(output_file, mode='w', encoding='utf-8', newline='') as outfile:

    reader = csv.reader(infile)
    writer = csv.writer(outfile, quoting=csv.QUOTE_ALL) # 保持所有字段带引号

    headers = next(reader)
    writer.writerow(headers) # 写入表头

    for row in reader:
        # created_ts 在第4列 (索引3)
        row[3] = to_timestamp(row[3])
        # updated_ts 在第5列 (索引4)
        row[4] = to_timestamp(row[4])

        writer.writerow(row)

print(f"转换完成!文件已保存为: {output_file}")

在该目录输入 python3 convert.py 运行该转换脚本。

查看 memos_converted.csv 文件,时间戳已经转换成功:

第六步:导入Postgres数据库中

来到Navicat,清空之前测试的几条数据:(或者在表中直接选中那几条数据进行删除也行)

删除后需要点击底部的 Refresh 刷新才能更新状态

然后点击上方的 【Import】 图标-【csv文件】-【Add File...】选择转换后的memos_converted.csv

一路下一步,在下面界面中选择全部:

继续下一步,直至结束:

完成后还是点击查看底部工具栏的刷新图标,确认导入完成即可。

验证

回到网页,进行刷新,以前的数据已经出现,状态也正确。

🛑🛑🛑特别注意:

测试中这一步我可以正常发布,但是在生产过程中,有几百几千条笔记,每个笔记都有一个uid,因此很可能会遇到在发布的时候会提示:该uid已被使用的错误提示

强烈建议在Navicat中,点击左上角的 【新建查询】 (New Query),运行以下这条命令:

-- 把 memo 表的发号器设置到当前最大的 ID
SELECT setval('memo_id_seq', (SELECT MAX(id) FROM memo));

确定没问题之后,恢复成正常的compose配置文件即可(去掉mysql,2个端口映射):

services:
  postgres:
    image: postgres:15
    restart: unless-stopped
    environment:
      POSTGRES_DB: memos
      POSTGRES_USER: memos
      POSTGRES_PASSWORD: password
    volumes:
      - postgres_data:/var/lib/postgresql/data

  memos:
    image: neosmemo/memos:stable
    restart: always
    depends_on:
      - postgres
    ports:
      - "5230:5230"
    environment:
      - MEMOS_DRIVER=postgres
      - MEMOS_DSN=postgresql://memos:password@postgres:5432/memos?sslmode=disable

volumes:
  postgres_data:

最后

即使这个方法有一丁点的局限性,无法迁移导出对应上传的图片等资源,仅仅针对文字类的。但对我个人而已已经够用了。

我现在仅演示了从MySQL到Postgres的过程,但是如果是SQLite的话也是一样的流程,具体没有尝试,最多由于表的独特性,可能python脚本会相应变更,这部分交给各类AI工具就能搞定。

0 0 投票数
文章评分
订阅评论
提醒
guest
1 评论
最旧
最新 最多投票
内联反馈
查看所有评论
滚动至顶部
1
0
希望看到您的想法,请您发表评论x