前言
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文件
-
添加Postgres
-
开放对应的数据库端口
最终的 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分别添加进去。
连接前检查服务器或者服务器提供商的防火墙是否开放了对外的
10001和10002端口
最终可以看到:
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,yTrue (t) 0,no,off,false,f,nFalse (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工具就能搞定。














