SQLite和MySQL都是市面上使用极其广泛的关系型数据库,前者轻量、零配置、单文件存储,常用于移动端、嵌入式和小型工具;后者是成熟的服务型数据库,广泛部署在Web后端。虽然两者都支持标准SQL,但在实际写SQL的过程中,你会发现不少语句在一个数据库能跑,换到另一个就报错。这些差异主要集中在数据类型、自增主键、Upsert、日期处理、字符串拼接和分页等地方。本文把这些常见差异逐一整理出来,方便你在数据库迁移或双端兼容开发时快速查阅。

数据类型与建表语句的差异
MySQL是强类型数据库,建表时必须明确指定每个字段的类型和长度,比如VARCHAR(255)、INT、DECIMAL(10,2)等,写错了直接报错。而SQLite采用的是动态类型系统,字段类型只有五大存储类别:NULL、INTEGER、REAL、TEXT和BLOB,你甚至可以写一个MySQL里的类型名,SQLite也只会按亲和性去映射,不会报错。
一个很典型的区别是布尔值。MySQL有TINYINT(1)表示布尔,SQLite则直接接受BOOLEAN关键字,但底层存储仍是INTEGER,true存为1,false存为0。另外MySQL里常用的ENUM枚举类型、UNSIGNED无符号修饰符,在SQLite里都不存在,需要用TEXT加CHECK约束来模拟。
-- MySQL 建表
CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
status ENUM('active', 'disabled') DEFAULT 'active',
balance DECIMAL(10,2) DEFAULT 0.00
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- SQLite 建表
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
status TEXT DEFAULT 'active' CHECK (status IN ('active', 'disabled')),
balance REAL DEFAULT 0.0
);
自增主键的写法完全不同
自增主键是两者差异最大的地方之一。MySQL使用AUTO_INCREMENT关键字,且该字段必须是主键或唯一索引,插入时可以显式传NULL让数据库自动分配。SQLite则用AUTOINCREMENT关键字,但有个前提:字段类型必须写成INTEGER PRIMARY KEY,而且INTEGER这个拼写不能换成INT,否则AUTOINCREMENT不生效。
更细的区别在于行为语义。MySQL的自增计数在数据库重启后可能被重置为最大值加一,而SQLite的AUTOINCREMENT会通过内部表sqlite_sequence记录历史最大值,保证主键严格递增、不复用已删除的最大ID。如果只是INTEGER PRIMARY KEY而不加AUTOINCREMENT,SQLite在删除最大ID后可能会复用该值,这点在设计有外键关联的表时要格外注意。
另外获取刚插入的自增ID的方式也不同:MySQL用LAST_INSERT_ID()函数,SQLite用last_insert_rowid(),两者的会话隔离行为类似,都只返回当前连接最后一次插入的行ID。
分页、Upsert与插入语法的区别
分页查询方面两者基本一致,都用LIMIT加OFFSET,MySQL还支持简写形式LIMIT 20, 10,表示跳过20条取10条,SQLite同样支持这种逗号写法,这是少数高度兼容的地方。
真正容易踩坑的是Upsert(存在则更新,不存在则插入)。MySQL使用ON DUPLICATE KEY UPDATE语法,依赖主键或唯一索引冲突;SQLite则使用ON CONFLICT子句,写法借鉴了PostgreSQL,需要在子句里明确指定冲突的字段。两者的插入忽略写法也不同,MySQL用INSERT IGNORE,SQLite用INSERT OR IGNORE。
-- MySQL 的 Upsert
INSERT INTO stock (sku, qty) VALUES ('A100', 5)
ON DUPLICATE KEY UPDATE qty = qty + 5;
-- SQLite 的 Upsert(3.24.0 以上版本)
INSERT INTO stock (sku, qty) VALUES ('A100', 5)
ON CONFLICT(sku) DO UPDATE SET qty = qty + 5;
-- SQLite 的插入忽略
INSERT OR IGNORE INTO stock (sku, qty) VALUES ('A100', 5);
还有一个细节:MySQL支持INSERT INTO t SET col1=1, col2=2这种SET风格的插入写法,SQLite不支持,只接受标准的VALUES形式或多行批量插入。
函数与日常查询的差异
字符串拼接是最常见的差异点。MySQL默认使用CONCAT函数,比如CONCAT(a, '-', b);SQLite没有CONCAT,只能用双竖线运算符,写成a || '-' || b。如果SQL里大量用了CONCAT,迁移到SQLite时必须全部改写,或者自己注册自定义函数。
日期时间函数的差异更大。MySQL有NOW()、CURDATE()、DATE_FORMAT()、DATEDIFF()等一整套函数;SQLite则只有少数几个核心函数:date()、time()、datetime()、strftime()和julianday()。格式化日期时MySQL用DATE_FORMAT(created_at, '%Y-%m-%d'),SQLite要用strftime('%Y-%m-%d', created_at),虽然格式符看起来像,但函数名完全不同。计算时间差时,MySQL用DATEDIFF返回天数,SQLite要用julianday做减法再处理。
-- 当前时间
SELECT NOW(); -- MySQL
SELECT datetime('now', 'localtime'); -- SQLite
-- 格式化日期
SELECT DATE_FORMAT(created_at, '%Y-%m-%d') FROM orders; -- MySQL
SELECT strftime('%Y-%m-%d', created_at) FROM orders; -- SQLite
-- 两个日期相差天数
SELECT DATEDIFF('2024-06-01', '2024-05-01'); -- MySQL
SELECT CAST(julianday('2024-06-01') - julianday('2024-05-01') AS INT); -- SQLite
其他函数差异还包括:字符串截取MySQL用SUBSTRING或SUBSTR,SQLite只有SUBSTR;判断空值两者都有IFNULL,但MySQL的IF()函数在SQLite里要用CASE WHEN替代;类型转换MySQL用CAST也支持隐式转换较多,SQLite主要靠CAST且行为更严格。另外SQLite默认不区分大小写的比较只对ASCII字母有效,中文场景下的模糊查询行为也需要额外验证。
迁移与兼容性建议
如果你在做一个需要同时支持两种数据库的项目,建议尽量写标准SQL,避开各家方言:分页统一用LIMIT和OFFSET,条件判断统一用CASE WHEN,字符串拼接可以考虑在应用层完成而不依赖数据库函数。自增主键的建表语句可以放在各自的迁移脚本里分开维护。
需要特别提醒的是SQL注释里的风险点:MySQL支持#开头的单行注释,SQLite不支持,会把#当成语法错误。导出MySQL的SQL脚本导入SQLite前,除了改写ENGINE、CHARSET等子句,还要记得处理注释格式和反引号包裹的标识符,SQLite虽然能兼容大部分反引号写法,但建议统一改成双引号或直接去掉,兼容性更稳妥。掌握这些差异点后,在两款数据库之间切换或迁移就会顺畅很多。