拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

【Web全栈进阶】PostgreSQL上手:Docker跑库 + 把早报站从SQLite迁过去

【Web全栈进阶】PostgreSQL上手:Docker跑库 + 把早报站从SQLite迁过去

今天不写新功能,做一次“搬家”:把早报站的数据从SQLite搬进PostgreSQL——这是整个二季的地基工程。

🎯本篇产出:一个跑在Docker里的PostgreSQL、一份可重复执行的数据迁移脚本、以及“为什么换”的完整决策链。含代码约60行。


📌 太长不看版(给想快速上手的你)

项目信息一句话说明
本篇目标SQLite → PostgreSQL 数据迁移
代码行数~60行(迁移脚本)
依赖psycopg(Python驱动)+postgres:18-alpineDocker镜像
核心功能Docker起库 + 数据搬家 + 幂等脚本
跑起来的命令docker run ...→python migrate_sqlite_to_pg.py
核心知识点DSN连接串、SQL方言差异、ON CONFLICT DO NOTHING
做完你能得到一个生产级数据库 + 可重复执行的迁移脚本

⚠️诚实声明范围:今天完成“环境 + 数据”,明天(下一篇)完成“代码”。


一、先回答“为什么换”——SQLite的三个天花板

第28篇选型时我说“SQLite够了”——当时是真的够:单机、单写者、每天5篇文章。但产品要面对真实用户了,三个天花板会依次撞上:

#天花板具体表现
①并发写锁SQLite同一时刻只允许一个写入者,两个用户同时操作就是database is locked
②没有迁移体系改表结构靠删库重建(第17篇⑥),表越多这招越危险
③类型与特性类型宽松、缺JSONB/数组/全文检索,数据一复杂就捉襟见肘

🎯 决策框架

📌技术选型不是永恒决定,是“当时场景”的最优解。

场景变了(单机玩具 → 公网产品),选型就该升级——推翻自己不是打脸,是成长。

PostgreSQL是这个场景下的生产标配:并发读写、完整的类型系统、成熟的迁移生态。


二、两分钟概念课:从“文件库”到“服务”

SQLite和PostgreSQL最本质的区别,一张图说清:

SQLite:程序直接读写的“文件” PostgreSQL:一个独立运行的“服务” ┌─────────────┐ ┌──────────┐ ┌──────────┐ │ your.py │──读写──→ daily.db │ your.py │──→│ PG 服务 │──→ data/ └─────────────┘ └──────────┘ └──────────┘ (客户端) (端口 5432)

📌 SQLite是嵌在你程序里的一个文件;PostgreSQL是独立运行的数据库服务,你的程序通过网络连它。

换成PG后多了两个概念

概念说明
连接串(DSN)postgresql://用户名:密码@主机:5432/库名——五段式解剖:协议 / 用户 / 密码 / 主机:端口 / 数据库
客户端连上服务的工具——命令行psql、图形化DBeaver、或你代码里的驱动psycopg

三、第1步:Docker起PostgreSQL

Docker技能:

dockerrun-d--namepg-daily\-ePOSTGRES_PASSWORD=你的密码\-ePOSTGRES_DB=daily\-vpg-daily-data:/var/lib/postgresql/data\-p5432:5432\postgres:18-alpine

💡为什么用18-alpine而不是18?alpine变体体积更小(约60MB),生产环境推荐固定小版本标签(如18.6-alpine)避免意外升级。学习阶段用18-alpine即可。

📖 逐参数拆解

参数作用
POSTGRES_PASSWORD设超级用户密码
POSTGRES_DB=daily启动时自动建一个叫daily的库
-v pg-daily-data:/var/lib/postgresql/data数据卷——数据放卷里,代码放镜像里,容器删了数据还在
-p 5432:5432把服务的默认端口映射出来

✅ 验证服务活着

dockerexec-itpg-daily psql-Upostgres-ddaily-c"\dt"

能进入psql交互界面(哪怕显示Did not find any tables),服务就绪。


四、第2步:安装驱动 + 建表——SQL方言的第一课

安装Python驱动

pipinstallpsycopg pip freeze>requirements.txt

建表SQL的方言差异对照

把早报站的建表语句翻译成PG版,顺便认识方言差异:

写法SQLite(旧)PostgreSQL(新)
自增主键INTEGER PRIMARY KEY AUTOINCREMENTINTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY
占位符?(sqlite3)%s(psycopg)
去重插入INSERT OR IGNOREINSERT … ON CONFLICT (url) DO NOTHING
类型检查宽松(类型亲和)严格(类型不对直接拒绝)

🚨最后一条要单独强调:SQLite的宽松是“惯着你”,PG的严格是“保护你”——数字列塞文本当场报错,数据质量问题在入库前就暴露,而不是三个月后在报表里。


五、第3步:数据搬家脚本——可重复执行的迁移

新建migrate_sqlite_to_pg.py:

"""migrate_sqlite_to_pg.py —— 把早报站数据从SQLite搬进PostgreSQL"""importsqlite3importpsycopg PG_DSN="postgresql://postgres:你的密码@localhost:5432/daily"SQLITE_FILE="daily.db"defmigrate()->None:# ① 从SQLite读出全部旧数据src=sqlite3.connect(SQLITE_FILE)rows=src.execute("SELECT url, title, date, summary FROM articles").fetchall()src.close()print(f"从SQLite读出{len(rows)}条")# ② 建表(幂等)+ 逐条写入(url冲突自动跳过)withpsycopg.connect(PG_DSN)asconn:conn.execute(""" CREATE TABLE IF NOT EXISTS articles ( id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, url TEXT UNIQUE NOT NULL, title TEXT NOT NULL, date TEXT, summary TEXT DEFAULT '' )""")withconn.cursor()ascur:forrowinrows:cur.execute("INSERT INTO articles (url, title, date, summary) ""VALUES (%s, %s, %s, %s) ""ON CONFLICT (url) DO NOTHING",row)conn.commit()# ③ 验证:总数对得上吗?n=conn.execute("SELECT COUNT(*) FROM articles").fetchone()[0]print(f"迁移完成:PostgreSQL中共{n}条")if__name__=="__main__":migrate()

📌 三个细节

#细节说明
①%s占位符psycopg用%s(SQLite是?,方言差异见第4节)
②ON CONFLICT (url) DO NOTHING让脚本重复运行不出错(幂等思想)
③密码特殊字符密码里的特殊字符(如@)在DSN里要URL转义,或改用环境变量+关键字参数连接

✅ 运行验证

python migrate_sqlite_to_pg.py# 从SQLite读出812条# 迁移完成:PostgreSQL中共812条

六、第4步:验收清单

1.dockerps→ pg-daily容器Up状态2. psql查询:SELECT COUNT(*)FROM articles → 与SQLite原数量一致3. 抽查3行中文内容无乱码4. 删掉容器重建(数据卷还在)→ 数据完整 → 卷持久化生效5. 脚本重跑 → 数量不变(幂等)6. 全程无报错后提交Git
gitadd.gitcommit-m"数据迁移:SQLite → PostgreSQL(脚本可重复执行)"

七、常见报错:这6个,迁移日的标配(重点!)

①psql: error: connection refused

🔍 原因:容器没起来,或-p 5432:5432忘了映射。

✅ 解法:

dockerps# 看容器状态和端口映射dockerlogs pg-daily# 看启动日志

②password authentication failed for user "postgres"

🔍 原因:密码不对,或DSN里密码含@ : /等特殊字符没转义。

✅ 解法:

  • 先用纯字母数字密码跑通;
  • 确需特殊字符,用URL编码(@→%40)。

③relation "articles" does not exist

🔍 原因:连接到了别的库,或建表没执行。

✅ 解法:psql里——

\dt# 看当前库的表\l# 列出所有库\c daily# 切库

📌先确认“人在哪家银行”,再谈余额。


④ 占位符混用:?写进PG的SQL直接报语法错

🔍 原因:sqlite3用?,psycopg用%s——方言不同。

✅ 解法:本篇对照表背下来;后续SQLAlchemy会替你抹平差异(下一篇的主角)。


⑤psycopg.errors.InvalidTextRepresentation

🔍 原因:PG类型严格——往数字列塞了文本、或空字符串塞进了非文本列。

✅ 解法:对照建表语句检查数据类型。

📌这正是换PG的价值:坏数据当场拦截。


⑥ 早报站还在读SQLite

🔍 原因:不是bug——代码层的连接切换在下一篇(SQLAlchemy 2.0)完成,本篇只搬数据。

✅ 解法:无。

📌诚实声明范围:今天完成“环境 + 数据”,明天完成“代码”。


八、 PostgreSQL 18值得关注的新特性

特性说明
异步I/O(AIO)存储读取性能最高提升3倍
UUIDv7新增uuidv7()函数,时间戳排序与唯一性兼得
B-tree Skip Scan多列索引查询优化,减少全表扫描
并行GIN索引构建大表索引创建更快

💡 这些特性对早报站当前规模影响不大,但了解它们有助于你理解PostgreSQL的演进方向——性能优化和开发者体验是持续投入的重点。


九、课后练习

#练习难度提示
1psql三连:SELECT COUNT(*)、按来源分组统计、找出最新的3篇文章⭐⭐全部在psql里完成
2泛化脚本:把migrate脚本参数化(python migrate.py 源.db 目标dsn)⭐⭐⭐变成通用小工具
3卷实验:docker rm -f pg-daily后重建容器(同名数据卷)→ 数据完整⭐⭐亲手验证第27篇的“数据放卷里”
4(选做)pgloader对比:了解一键迁移工具pgloader,对比手写脚本⭐⭐⭐知道工具,才知道自己在简化什么

📦 配套代码

迁移脚本与建表SQL已上传Git(01-pg-migration/):【gitee仓库地址】

返回列表