换台电脑记录就没了:把 SQLite 迁到 MySQL

举报
茉莉风铃 发表于 2026/09/29 10:46:34 2026/09/29
【摘要】 小应用跑在本地一个 .db 文件里,换台机器数据就断了。这篇记一次把 SQLite 换成 MySQL 的完整过程:装什么包、建表语句要改哪些地方、占位符从 ? 换成 %s、数据怎么搬过去,以及连上之后遇到的空连接超时和中文乱码这两个坑。

有个读书记录的小项目一直是本地跑的,数据库就是个项目目录下的 books.db 文件。在家里的台式机上记了几十条,有天用笔记本想加一本,发现两边数据完全是两回事——文件在另一台机器上。

这个"数据跟着机器走"的问题,SQLite 天生无解。想让数据有个固定地方存,就得换成正经的数据库服务。我选了 MySQL,这篇记录整个迁移过程。

image.png

一、装驱动

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。真正麻烦的是那些跑一段时间才暴露的问题——中文乱码和空闲连接断开,都是本地短时间测不出来的。所以迁移完别急着收工,让它在服务器上安安静静待一晚上,第二天早上再点一下看看运行情况。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。