卷 VI · 现场CH 24深度 24/24

从 MySQL 到 PostgreSQL

最后一章,把全书对 MySQL 的对照汇成一张能直接抄的速查表——语法、类型、UPSERT、大小写与那些坑。带着你多年的 MySQL 直觉,稳稳落地 PostgreSQL。然后,我们用「当真」这两个字,给这趟旅程收个尾。

迁移速查语法映射类型映射送上路

语法速查:写法上的差异

你在 MySQL 里写在 PostgreSQL 里写
`column`(反引号)"column"(双引号,且区分大小写)
id INT AUTO_INCREMENTid integer GENERATED ALWAYS AS IDENTITY
LIMIT 10, 20(偏移,行数)LIMIT 20 OFFSET 10
CONCAT(a, b)a || b(或也支持 concat
IFNULL(a, b)COALESCE(a, b)
NOW(), CURDATE()now(), current_date
col LIKE '%x%'(默认不分大小写)col ILIKE '%x%'(PG 的 LIKE 区分大小写)
INSERT ... ON DUPLICATE KEY UPDATEINSERT ... ON CONFLICT (key) DO UPDATE
REPLACE INTOINSERT ... ON CONFLICT ... DO UPDATE
GROUP_CONCAT(x)string_agg(x, ',')
DESCRIBE t / SHOW TABLES\d t / \dt(psql 元命令)
SHOW CREATE TABLE t\d t,或 pg_dump -t t --schema-only
字符串用 "..."'...'字符串只能'...'"..." 是标识符

类型速查:建表时的映射

MySQL 类型PostgreSQL 对应备注
TINYINT(1) / BOOLEANbooleanPG 有真布尔,t/f
INT, BIGINTinteger, bigintPG 没有 UNSIGNED,用 CHECK (x>=0)
DECIMAL(p,s)numeric(p,s)钱用它,别用 float
DATETIME / TIMESTAMPtimestamptz几乎总用带时区的那个
VARCHAR(n) / TEXTtextPG 里长度不影响性能,多用 text
ENUM('a','b')CREATE TYPE ... AS ENUMPG 的枚举是独立可共享类型
JSONjsonb用二进制的 jsonb,能索引
BLOBbytea二进制数据
逗号分隔的字符串text[] 数组 或 拆表PG 有真数组

UPSERT:从 ON DUPLICATE KEY 到 ON CONFLICT

「插入,如果已存在就更新」是高频操作。PostgreSQL 的写法更明确——你要指明基于哪个唯一约束判断冲突

-- MySQL
INSERT INTO stock (book_id, qty) VALUES (3, 10)
ON DUPLICATE KEY UPDATE qty = qty + 10;

-- PostgreSQL
INSERT INTO stock (book_id, qty) VALUES (3, 10)
ON CONFLICT (book_id) DO UPDATE SET qty = stock.qty + 10;
-- 也能 DO NOTHING(冲突就跳过);EXCLUDED 指代「本来要插入的那行」

四个最容易栽的坑

⚠ 坑一:标识符大小写

PostgreSQL 会把不加引号的标识符统一折成小写。你写 CREATE TABLE Users (userName text),实际建的是 users(username)。而 MySQL 在 Linux 上表名大小写敏感、在 macOS/Windows 上不敏感(取决于文件系统)。麻烦在于:如果你加了双引号建表 CREATE TABLE "Users",那以后就必须每次都写 "Users",写 users 会找不到。建议:全用小写 + 下划线命名,永不加引号,一劳永逸。

⚠ 坑二:LIKE 区分大小写

MySQL 的 LIKE 默认区分大小写(受排序规则影响);PostgreSQL 的 LIKE 区分大小写。要不区分,用 ILIKE,或用 citext 扩展类型(大小写不敏感的文本),或 lower(col) = lower(?)(记得配表达式索引)。迁移时所有依赖「不区分大小写模糊匹配」的查询都要检查。

⚠ 坑三:默认隔离级别不同

MySQL(InnoDB) 默认 REPEATABLE READ,PostgreSQL 默认 READ COMMITTED(第 17 章)。如果你的业务逻辑依赖了 MySQL 默认给的「事务内两次读一致」,迁到 PG 的 RC 下会悄悄失效。要么显式把事务设成 REPEATABLE READ,要么改逻辑(比如用 SELECT ... FOR UPDATE 加锁)。

⚠ 坑四:那些「悄悄接受」的写法会开始报错

这是全书主线的回响:你在 MySQL 里习以为常的宽松——非法日期变 0000-00-00、字符串塞进 int 列、GROUP BY 里选没分组的列、超长字符串被截断——迁到 PostgreSQL 全会报错。迁移时你多半会撞见一批「原来一直在悄悄出错」的数据和查询。这不是 PG 在为难你,是它把你数据里一直存在、只是没人告诉你的问题,一次性暴露出来了。修掉它们,你的系统会更健康。

▶ 上手 · 别手动搬,用 pgloader

真要迁移一个 MySQL 库,别一张表一张表手动导。用 pgloader——它能连上 MySQL,自动做类型映射、建表、搬数据,还会把大部分上面的差异处理掉。一条命令起步:

$ pgloader mysql://user:pass@host/olddb postgresql://user:pass@host/newdb

迁完,务必用类生产数据跑一遍你的核心查询和 EXPLAIN,把报错的、变慢的逐个收拾——那份清单,往往就是你数据质量和查询习惯的一次体检报告。

收尾:为什么值得「当真」

二十四章走下来,我们其实只讲了一件事。

你最初对 PostgreSQL 的印象,也许只是「听说更强大」。现在你知道了,它强的不是功能列表更长,而是一种一以贯之的态度:它把你告诉它的每一件事都当真。你说这列是整数,它就绝不让一个 'abc' 混进来;你写下一条约束,它就替你挡住任何入口的违规;你开一个事务,它就保证全有或全无;你交给它一句查询,它就认认真真为你搜索最省的执行路径;并发再复杂,它用 MVCC 让每个人都看到一个一致而诚实的世界。

◆ 「当真」的回报,是「可信」

「当真」的代价,是你要在前期多花点心思——认真选类型、认真写约束、偶尔被一个报错拦下来当场修。但它的回报,是一样在数据世界里珍贵得不得了的东西:可信

当数据库把每件事都当真,你就敢相信库里的每一个值——每个日期都是真日期,每笔余额都非负,每个订单都挂在真实的客户上,每一次并发都没骗过任何人。你不用再在应用层反复防御性校验,不用半夜被一个 0000-00-00 惊醒,不用对着一份对不上的报表怀疑人生。这份「敢相信」,就是「当真」最终交到你手里的礼物。

MySQL 是个好脾气的伙伴,很多场景它完全够用,你多年的 SQL 功底也一点不会浪费——它们在 PostgreSQL 上照样管用,只是更严谨了。这本书不是要你抛弃谁,是想让你在下一次建表、下一次写查询、下一次做技术选型时,多一个视角:我要不要让这件事,被当真?

去装一个 PostgreSQL,把你手头的项目建一张表试试。让它拒绝你一次脏数据——那一刻你会明白,被当真,是一种踏实。


— 全书完 · SERIOUS —
愿你的每一个值,都名副其实。