把MySQL数据库迁移到SQLite,并不是简单地把SQL文件执行一遍就能完成。两者虽然都支持SQL标准,但在类型系统、建表语法和函数行为上存在显著差异。直接导入通常会遇到语法错误或数据精度丢失。下面从几个关键角度分析迁移中的具体注意事项。

一、数据类型映射与建表差异
MySQL的数据类型比SQLite丰富得多,例如TINYINT、DATETIME、DECIMAL、ENUM等。SQLite采用动态类型系统,存储类型只有NULL、INTEGER、REAL、TEXT、BLOB五种。迁移时如果不做映射,很多字段会以不正确的亲和性创建,导致比较、排序或计算异常。比如MySQL中的INT可以映射为INTEGER,BIGINT也映射为INTEGER,但超出64位范围的大整数需要转成TEXT保存。
建表语句中的反引号是MySQL专有语法,SQLite默认不识别,需要替换为双引号或直接去掉。SQLite也支持AUTOINCREMENT关键字,但仅能用于INTEGER PRIMARY KEY字段,不能像MySQL那样给任意整数列加自增属性。DECIMAL和NUMERIC在SQLite中会被当作NUMERIC亲和,精度可能丢失,建议迁移前确认金额等字段是否需要保留为TEXT并配合程序端处理。
另一个常见问题是UNSIGNED属性。SQLite没有无符号整数概念,INT UNSIGNED会报错,需要改为INTEGER并借助CHECK约束限制非负。ENUM和SET类型在SQLite中没有对应实现,应改为TEXT字段加CHECK约束,或者直接去掉约束保留字符串。
二、自增主键与日期时间处理
MySQL中惯用的id INT AUTO_INCREMENT PRIMARY KEY写法在SQLite中要改成id INTEGER PRIMARY KEY AUTOINCREMENT。注意顺序和类型必须严格匹配:只有INTEGER PRIMARY KEY本身就能作为rowid别名,添加AUTOINCREMENT可保证已删除的rowid不会被重用。如果写成INT PRIMARY KEY AUTOINCREMENT会报错,因为INT类型在SQLite中不等于INTEGER。
日期时间方面,MySQL的DATETIME和TIMESTAMP支持范围、默认值和时区行为与SQLite差异很大。SQLite没有专门的日期时间类型,通常用TEXT存储ISO8601格式字符串,例如2024-05-21 10:30:00,或用INTEGER存Unix时间戳。迁移时如果继续使用MySQL的CURRENT_TIMESTAMP默认值,SQLite可以识别,但要注意ON UPDATE CURRENT_TIMESTAMP属性在SQLite中不支持自动更新,需要由应用程序显式写入。
如果原表大量使用DATETIME DEFAULT '0000-00-00 00:00:00',SQLite会将其当作普通文本,不会报错,但日期函数无法解析,可能造成查询错误。建议迁移脚本中统一替换为NULL或1970-01-01 00:00:00。
三、SQL语法和函数兼容性调整
MySQL的LIMIT offset, count写法在SQLite中需要改成LIMIT count OFFSET offset。例如MySQL语句SELECT * FROM users LIMIT 10, 20,迁移后应写为SELECT * FROM users LIMIT 20 OFFSET 10。字符串连接函数也不一样:MySQL使用CONCAT(),SQLite使用||运算符。如果程序里有大量这类SQL,需要统一替换。
需要注意REPLACE INTO在MySQL和SQLite中语义不同。MySQL的REPLACE INTO会删除再插入,触发删除触发器;SQLite的REPLACE实际上等效于INSERT OR REPLACE,行为更接近UPSERT。迁移后要确认触发器逻辑是否受影响。UPDATE多表连接、INSERT ... ON DUPLICATE KEY UPDATE等语法在SQLite中不支持,需要改写为INSERT ... ON CONFLICT DO UPDATE。
下面是一个迁移前后的SQL对比示例:
-- MySQL 原表结构
CREATE TABLE `orders` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`user_id` INT NOT NULL,
`amount` DECIMAL(10,2) DEFAULT NULL,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`status` ENUM('pending','paid','cancelled') DEFAULT 'pending'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- SQLite 调整后
CREATE TABLE orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
amount TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
status TEXT DEFAULT 'pending' CHECK(status IN ('pending','paid','cancelled'))
);
需要特别留意ENGINE=InnoDB、CHARSET等表属性,SQLite完全忽略这些,但仍可能引起解析错误,建议直接删除。字符集方面SQLite默认UTF-8存储,MySQL的utf8mb4数据一般可以保持兼容,但要注意某些历史数据是latin1编码时需要先转换。
四、迁移工具与实战流程
手动改造大型数据库的SQL脚本容易遗漏,推荐使用迁移工具辅助。常见做法是使用数据库管理工具如DB Browser for SQLite、SQLite Studio等,先导出MySQL数据为CSV或JSON,再导入SQLite。对于结构差异,可以用Python脚本读取MySQL的information_schema生成SQLite建表语句,自动完成类型映射。一些工具如mysql2sqlite脚本可以快速转换,但它对复杂类型和触发器的支持有限,仍需人工检查。
迁移流程建议分四步:第一步从MySQL导出结构和数据,建议使用mysqldump --compatible=ansi --skip-extended-insert降低语法差异;第二步将导出文件中的反引号、ENGINE、CHARSET、UNSIGNED等关键字清理掉,并把INT AUTO_INCREMENT改为INTEGER PRIMARY KEY AUTOINCREMENT;第三步导入SQLite,记录所有报错并逐条修复;第四步对比行数和关键字段的校验和,确保数据完整性。
下面给出一个Python脚本片段,演示如何读取MySQL表结构并生成SQLite兼容的CREATE语句:
import re
def mysql_to_sqlite_create(mysql_ddl):
# 去掉反引号
ddl = mysql_ddl.replace('`', '')
# 移除 ENGINE 和 CHARSET 等表选项
ddl = re.sub(r'\sENGINE=\w+', '', ddl)
ddl = re.sub(r'\sDEFAULT CHARSET=\w+', '', ddl)
ddl = re.sub(r'\sCOLLATE=\w+', '', ddl)
# 替换 INT AUTO_INCREMENT
ddl = re.sub(r'\bINT\s+AUTO_INCREMENT\b', 'INTEGER AUTOINCREMENT', ddl, flags=re.IGNORECASE)
# 替换 UNSIGNED
ddl = re.sub(r'\bUNSIGNED\b', '', ddl, flags=re.IGNORECASE)
# ENUM 转为 TEXT
ddl = re.sub(r'\bENUM\([^)]*\)', 'TEXT', ddl, flags=re.IGNORECASE)
return ddl
sample = "CREATE TABLE `users` (`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `name` VARCHAR(50), `role` ENUM('admin','user')) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"
print(mysql_to_sqlite_create(sample))
运行后可以看到生成的SQLite语句已经去掉了不兼容属性。实际迁移还需要处理外键约束:SQLite默认关闭外键,需要执行PRAGMA foreign_keys = ON;才会启用,而且MySQL的ON DELETE CASCADE等动作需要手动转换。
迁移完成后,务必对应用程序进行回归测试,重点检查时间字段、布尔值、金额精度和分页查询。SQLite是文件型数据库,没有用户权限体系,原MySQL中的账号密码和授权逻辑需要转移到应用层实现。另外,SQLite的并发写入能力较弱,适合单机或小规模团队场景,如果原MySQL承载高并发写入,迁移前应评估读写负载是否适合。