← 返回首页
从 SQLite 到 PostgreSQL:小型项目数据库迁移实战
去年我用 SQLite 做了一个小型的博客系统,部署简单,开发效率高。但随着数据量的增长和并发访问的增加,SQLite 的写锁问题开始显现。这篇文章记录了将该项目迁移到 PostgreSQL 的全过程。
为什么迁移
SQLite 作为嵌入式数据库,在小规模场景下表现出色。但当项目同时在线用户超过 50 人时,写操作串行化导致明显延迟。具体来说:
- 并发写入时出现 SQLITE_BUSY 错误
- WAL 模式下依然有性能瓶颈
- 缺乏细粒度的权限控制
- 需要更强大的全文搜索和 JSON 查询能力
迁移准备
在开始迁移之前,我梳理了整体的迁移步骤:
- 导出 SQLite 数据为中间格式
- 在 PostgreSQL 中创建对应的 schema
- 数据类型映射与转换
- 导入数据并验证完整性
- 修改 ORM 配置与连接
- 灰度切换与回滚方案
数据类型映射
SQLite 的数据类型比较宽松,而 PostgreSQL 类型严格,需要仔细映射。我整理了一个映射表:
SQLite → PostgreSQL
-------------------------------
INTEGER → INTEGER / BIGINT
REAL → DOUBLE PRECISION
TEXT → TEXT / VARCHAR(n)
BLOB → BYTEA
DATETIME → TIMESTAMP
BOOLEAN → BOOLEAN (SQLite 存储为 0/1)
AUTOINCREMENT → SERIAL / IDENTITY
数据迁移脚本
我写了一个 Python 脚本来完成数据导出和导入。核心思路是逐表读取 SQLite 数据,转换成 PostgreSQL 兼容的格式后批量插入。
import sqlite3
import psycopg2
from psycopg2.extras import execute_values
sqlite_conn = sqlite3.connect('blog.db')
pg_conn = psycopg2.connect('postgresql://user:pass@localhost/blog')
tables = ['users', 'posts', 'comments', 'tags']
for table in tables:
rows = sqlite_conn.execute(f'SELECT * FROM {table}').fetchall()
cols = [d[0] for d in sqlite_conn.execute(f'PRAGMA table_info({table})')]
placeholders = ','.join(['%s'] * len(cols))
col_names = ','.join(cols)
execute_values(pg_conn.cursor(),
f'INSERT INTO {table} ({col_names}) VALUES %s',
rows, page_size=1000)
pg_conn.commit()
print(f'Migrated {len(rows)} rows to {table}')
ORM 适配
项目使用 Eloquent ORM(Laravel),迁移主要修改 .env 中的数据库配置。需要注意 PostgreSQL 的 schema 搜索路径和连接池设置:
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=blog
DB_USERNAME=blog_user
DB_PASSWORD=secure_password
DB_SCHEMA=public
Eloquent 对 PostgreSQL 的支持很好,大部分查询语法无需修改。少数使用了 SQLite 特定函数的查询需要手动适配。
回滚策略
为了安全迁移,我采用了双写 + 灰度切换的策略:
- 先保持 SQLite 为主库,同步写入 PostgreSQL
- 运行一周确保 PostgreSQL 数据完整
- 将 10% 的流量切换到 PostgreSQL
- 观察无误后逐步切到 100%
- 保留 SQLite 数据文件一个月以备回滚
迁移效果
迁移完成后,页面响应时间从平均 180ms 降低到 45ms,并发能力从约 50 并发提升到 500+。全文搜索功能也让博客的站内搜索体验大幅提升。
总结:对于预期会增长的小型项目,建议从一开始就使用 PostgreSQL。如果已经用了 SQLite 也不用担心,迁移过程比想象中要平滑得多。
渝公网安备50023002020384号