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

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

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


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

项目信息 一句话说明
本篇目标 SQLite → PostgreSQL 数据迁移
代码行数 ~60行(迁移脚本)
依赖 psycopg(Python驱动)+ postgres:18-alpine Docker镜像
核心功能 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最本质的区别,一张图说清:

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

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

换成PG后多了两个概念

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

三、第1步:Docker起PostgreSQL

Docker技能:

bash 复制代码
docker run -d --name pg-daily \
  -e POSTGRES_PASSWORD=你的密码 \
  -e POSTGRES_DB=daily \
  -v pg-daily-data:/var/lib/postgresql/data \
  -p 5432: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 把服务的默认端口映射出来

✅ 验证服务活着

bash 复制代码
docker exec -it pg-daily psql -U postgres -d daily -c "\dt"

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


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

安装Python驱动

bash 复制代码
pip install psycopg
pip freeze > requirements.txt

建表SQL的方言差异对照

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

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

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


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

新建migrate_sqlite_to_pg.py:

python 复制代码
"""migrate_sqlite_to_pg.py ------ 把早报站数据从SQLite搬进PostgreSQL"""
import sqlite3

import psycopg

PG_DSN = "postgresql://postgres:你的密码@localhost:5432/daily"
SQLITE_FILE = "daily.db"


def migrate() -> 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冲突自动跳过)
    with psycopg.connect(PG_DSN) as conn:
        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 ''
            )""")
        with conn.cursor() as cur:
            for row in rows:
                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转义,或改用环境变量+关键字参数连接

✅ 运行验证

bash 复制代码
python migrate_sqlite_to_pg.py
# 从SQLite读出812条
# 迁移完成:PostgreSQL中共812条

六、第4步:验收清单

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

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

① psql: error: connection refused

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

✅ 解法:

bash 复制代码
docker ps                  # 看容器状态和端口映射
docker logs pg-daily       # 看启动日志

② password authentication failed for user "postgres"

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

✅ 解法:

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

③ relation "articles" does not exist

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

✅ 解法:psql里------

bash 复制代码
\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的演进方向------性能优化和开发者体验是持续投入的重点。


九、课后练习

# 练习 难度 提示
1 psql三连 :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仓库地址】

相关推荐
sbjdhjd1 小时前
云安全 | Docker 容器逃逸复盘(一):从隔离边界到运行时链路,如何确认自己身处容器
网络安全·docker·云原生·容器·kubernetes·云计算·云安全
苦瓜打怪兽1 小时前
PostgreSQL 报错“字段 datlastsysoid 不存在”排查与解决
数据库·postgresql
IT_陈寒1 小时前
SpringBoot自动配置的坑:你以为的捷径可能是弯路
前端·人工智能·后端
paopaokaka_luck1 小时前
高校社团管理(AI辅助任务分配、协同过滤算法推荐、ECharts数据可视化、活动参与闭环、校园交流与反馈、器材借还管理)
java·前端·javascript·spring boot·数据分析·echarts
Dovis(誓平步青云)1 小时前
导览音频切换太快,旧讲解不能覆盖新展品
android·前端·javascript·ecmascript·音视频·宠物
梦想不只是梦与想11 小时前
docker-compose.yml
docker·docker-compose·docker run
福兮说12 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
郑州光合科技余经理13 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
小呆呆66613 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试