换台电脑记录就没了:把 SQLite 迁到 MySQL
有个读书记录的小项目一直是本地跑的,数据库就是个项目目录下的 books.db 文件。在家里的台式机上记了几十条,有天用笔记本想加一本,发现两边数据完全是两回事——文件在另一台机器上。
这个"数据跟着机器走"的问题,SQLite 天生无解。想让数据有个固定地方存,就得换成正经的数据库服务。我选了 MySQL,这篇记录整个迁移过程。

一、装驱动
Python 连 MySQL 常用两个包:pymysql(纯 Python,好装)和 mysqlclient(C 实现,快但要编译环境)。图省事就用 pymysql:
pip install pymysql
连接:
import pymysql
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='bookuser',
password='你的密码',
database='books',
charset='utf8mb4', # 这个不能少,见后面的坑
cursorclass=pymysql.cursors.DictCursor, # 让结果能按列名取
)
DictCursor 相当于 SQLite 里的 row_factory = sqlite3.Row,加上之后查询结果是字典,能 row['title'] 取值,跟之前的代码保持一致。
二、建表语句要改哪些
SQLite 那套建表语句不能直接搬过来,有几处得改:
CREATE TABLE IF NOT EXISTS books (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
author VARCHAR(100),
status VARCHAR(20) NOT NULL DEFAULT 'reading',
rating INT,
notes TEXT,
finished_at VARCHAR(20)
) DEFAULT CHARSET=utf8mb4;
对照一下差异:
| SQLite 写法 | MySQL 写法 | 说明 |
|---|---|---|
INTEGER PRIMARY KEY AUTOINCREMENT |
INT AUTO_INCREMENT PRIMARY KEY |
自增关键字不一样,顺序也不同 |
TEXT |
VARCHAR(200) / TEXT |
MySQL 里短字符串用 VARCHAR 并指定长度 |
| 布尔当整数存 | TINYINT(1) |
SQLite 没有布尔类型,MySQL 有 |
| 不用管字符集 | DEFAULT CHARSET=utf8mb4 |
不加这行中文可能变问号 |
utf8mb4 要特别说一下。MySQL 里有个叫 utf8 的字符集,但它最多只支持 3 个字节,存不了 emoji。要用 utf8mb4 才是完整的 UTF-8。这是 MySQL 的老坑,第一次配的人十个有九个中招。
三、占位符从 ? 换成 %s
这是改动量最大的一处。SQLite 用 ?,pymysql 用 %s,所有 SQL 都得过一遍:
# SQLite 的写法
cur.execute('INSERT INTO books (title, author) VALUES (?, ?)', (title, author))
# MySQL 的写法
cur.execute('INSERT INTO books (title, author) VALUES (%s, %s)', (title, author))
如果项目里 SQL 很多,一条条改很痛苦。我当时是把所有数据库操作收进一个 db.py,只改那一个文件就完事了。这也算个经验:哪怕是小项目,早点把数据库操作集中到一个模块,后面换数据库能省很多事。
单元素元组那个坑还在——(title,) 的逗号别漏。
四、把老数据搬过去
两个库结构不一样,最简单粗暴的办法是:从 SQLite 读出来,插进 MySQL。
import sqlite3, pymysql
src = sqlite3.connect('books.db')
src.row_factory = sqlite3.Row
rows = src.execute('SELECT title, author, status, rating, notes, finished_at FROM books').fetchall()
dst = pymysql.connect(host='127.0.0.1', user='bookuser', password='密码',
database='books', charset='utf8mb4')
with dst.cursor() as cur:
for r in rows:
cur.execute(
'INSERT INTO books (title, author, status, rating, notes, finished_at) '
'VALUES (%s, %s, %s, %s, %s, %s)',
(r['title'], r['author'], r['status'], r['rating'], r['notes'], r['finished_at'])
)
dst.commit()
dst.close()
src.close()
我是先跑一遍只打印不插入确认字段对得上,再真正执行的。数据是搬没了就找不回来的东西,别嫌麻烦。
五、连接池的问题
改完之后本地跑得好好的,部署上去第二天早上第一次访问就报错:
pymysql.err.OperationalError: (2013, 'Lost connection to MySQL server during query')
原因是 MySQL 有个 wait_timeout,默认 8 小时,一个连接空闲超过这个时间,服务端会主动断开。但你的 Python 程序不知道,还拿着这个连接去查,就报连接丢失。
几种解法,从简单到正规:
- 每次查询都新建连接、用完就关。小应用访问量低,这样最简单也最省心,代价是每次多一次握手。
- 用之前先
conn.ping(reconnect=True),断了自动重连。一行代码的事。 - 上连接池(比如 DBUtils 或 SQLAlchemy 的池)。访问量上来了再考虑。
我选了第二种,在取连接的地方加个 ping:
conn = get_conn()
conn.ping(reconnect=True) # 断了自动重连
几个迁移时容易漏的点
charset参数别忘。不加的话 pymysql 默认用latin1,中文直接乱码,而且是存进去就乱了,改回来很麻烦。AUTO_INCREMENT的拼写。两个单词中间是下划线,不是空格,写错会报语法错误。- SQLite 的
INSERT OR REPLACE在 MySQL 里没有。对应的是INSERT ... ON DUPLICATE KEY UPDATE,语法不一样。 - 迁移前先备份。把
books.db复制一份再动手,这是底线。 - 日期字段。SQLite 里我存的是
2026-09-15字符串,MySQL 里如果想用DATE类型也行,但要确认格式能被识别。懒得改就继续用 VARCHAR,排序照样正确。 - 别在生产库上试迁移脚本。先在本地起个 MySQL 练一遍,确认无误再动真数据。
记录总结
SQLite 换 MySQL,改动集中在四处:驱动和连接方式、建表语句的类型和自增写法、占位符从 ? 改成 %s、以及字符集要显式指定 utf8mb4。真正麻烦的是那些跑一段时间才暴露的问题——中文乱码和空闲连接断开,都是本地短时间测不出来的。所以迁移完别急着收工,让它在服务器上安安静静待一晚上,第二天早上再点一下看看运行情况。
- 点赞
- 收藏
- 关注作者
评论(0)