最近两年被问得最多的数据库问题,从来不是“Redis挂了怎么恢复”,也不是“分库分表怎么拆”,而是这两个:“MySQL 和 PostgreSQL 到底怎么选?”、“我这套老系统,该不该迁到 PostgreSQL?”
我入行是先写的 MySQL,后来因为一个 GIS 项目和向量检索需求开始重度使用 PostgreSQL,再往后两个库轮流维护了好几年,踩坑记录攒了一堆。说实话,这两个开源关系型数据库没有谁绝对碾压谁,只是脾气、性格、适用场景差别真的很大。这篇文章我想从设计哲学、安装部署、日常开发、数据同步、运维排障、面试高频点这几个维度,把 MySQL 和 PostgreSQL 做一次尽量全面的横向对比,把那些热搜里反复出现的坑一次性讲透。
1. 整体设计思路与选型分析
1.1 两个数据库的定位差异
MySQL 是 LAMP 时代走红的典型代表,它的核心设计目标就是“简单、快速、可靠”。在互联网早期,大部分业务场景都是“单机高并发读写 + 简单的关联查询”,MySQL 用 InnoDB 事务引擎配上成熟的主从复制,就能撑起绝大多数业务。你可以把 MySQL 理解成一把锋利的匕首:上手快、性能直给、运维生态成熟,但也意味着它在“功能全面性”上做了一些取舍。
PostgreSQL 走的是另一条路。它源自加州大学伯克利分校的 POSTGRES 项目,从一开始就奔着“功能最完备的关系型数据库”去设计。它支持复杂的查询优化器、丰富的数据类型、可扩展的索引体系、成熟的函数语言,甚至能通过扩展模块变成空间数据库、时序数据库、向量数据库。用瑞士军刀来形容它并不夸张:功能多到需要自己按需挑选,但也正因为如此,很多新手刚接触时会觉得“太重了”。
这个定位差异直接影响了选型。我自己的判断标准很简单:如果是典型的互联网业务系统,请求模型是“点查为主、事务不长、并发大”,MySQL 是稳妥选择;如果业务里有复杂报表、地理空间数据、JSON 文档检索、机器学习向量检索,或者希望一套库覆盖多种数据模型,PostgreSQL 的价值会大得多。
1.2 存储引擎与并发控制的底层差异
MySQL 的架构是插件式存储引擎,InnoDB 的 MVCC 实现依赖 undo log 回滚段,旧版本数据被集中放在 undo 表空间里。PostgreSQL 的 MVCC 则完全不同,旧版本行会直接留在数据页中,每一行记录上都有 xmin/xmax 事务标记,由并发的活跃事务快照来判断可见性。
这个差异带来的实际影响非常明显。PostgreSQL 表更新多了之后,页面里会堆满“死元组”,表就会膨胀,必须依赖 autovacuum 机制来回收空间。我在生产环境里见过一个 50GB 的表,业务高峰期更新量一大,物理文件膨胀到 120GB,正常情况下体感 SQL 变慢了两三倍。MySQL 虽然也需要 purge 清理 undo,但普通运维人员几乎不太需要关心它。
面试里这也是一道经典题:MySQL 的 MVCC 和 PostgreSQL 的 MVCC 实现有什么区别?最简单的回答就是:MySQL 把旧版本放到单独的 undo 空间里,读取无冲突;PostgreSQL 把旧版本留在原页面,靠事务标记判断,写入代价更低但需要 vacuum。能把这一点讲清楚,面试官就知道你是真看过源码架构而不是只背了概念。
1.3 生态与扩展能力差距
MySQL 的生态集中在复制、中间件、云托管上。主从复制、MGR、ProxySQL、MyCat,还有阿里系开源的那一堆中间件,让 MySQL 在高可用和读写分离方面非常顺手。但这些生态基本还是在“关系型模型”内部转。
PostgreSQL 的扩展能力是另外一个量级。它支持数组、JSONB、范围类型、网络类型、枚举类型,索引类型有 B-tree、Hash、GIN、BRIN、SP-GiST、GiST。最要命的是扩展模块:PostGIS 一装,它就成了专业空间数据库;pgvector 一装,它就能当向量数据库用;TimescaleDB 一装,它又能当时序数据库用。我做过一个项目,一个 PostgreSQL 实例同时承担了业务库、GIS 服务库、向量检索库三个角色,这在 MySQL 生态里几乎不可能一个实例搞定。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 安装部署对比:从 Windows 到 CentOS 再到 Docker
2.1 Windows 环境安装 MySQL 的实操笔记
MySQL 在 Windows 上的安装通常有两种方式:一种是下载 MSI 图形化安装包,另一种是下载 ZIP 解压版自己初始化。我个人推荐新手用 MSI 安装,安装向导会自动处理服务注册、环境变量和数据目录初始化。
需要注意几个细节:
- 端口默认是 3306,如果本机已经装了其他 MySQL 实例,或者有程序占用端口,安装时就要改掉,不然服务起不来。
- 字符集一定要选 utf8mb4,不是 utf8。utf8mb4 才是真正的四字节 UTF-8,能完整支持 emoji 和生僻字,utf8 在 MySQL 里是历史遗留的别名,很多场景下已经不够用了。
- 认证方式默认是 caching_sha2_password,这是 MySQL 8.0 之后的默认值。如果后续用老版本 JDBC、Python 旧驱动、Firedac 这类客户端连不上,多半就是这个问题。后面第四节我会专门讲这个报错。
安装完之后验证很简单,mysql -uroot -p 能进命令行就算成功。端口查看用 netstat -ano | findstr 3306,进程占用的时候这个命令最实用。
2.2 CentOS 7 编译安装 PostgreSQL 16 全过程
PostgreSQL 在 Linux 上最省事的安装方式是用发行版自带的包管理器,yum install postgresql-server 一装就行。但如果你想跟上最新大版本,或者想自定义编译参数,就需要源码编译。我在 CentOS 7 上编译安装 PostgreSQL 16 的步骤记录如下:
bash复制# 1. 安装依赖
yum install -y gcc readline-devel zlib-devel make
# 2. 下载源码包
wget https://ftp.postgresql.org/pub/source/v16.3/postgresql-16.3.tar.gz
tar -zxvf postgresql-16.3.tar.gz
cd postgresql-16.3
# 3. 配置、编译、安装
./configure --prefix=/usr/local/pgsql --with-pgport=5432
make -j$(nproc)
make install
# 4. 创建系统用户和数据目录
useradd postgres
mkdir -p /data/pgdata
chown -R postgres:postgres /data/pgdata
# 5. 用 postgres 用户初始化数据库
su - postgres -c "/usr/local/pgsql/bin/initdb -D /data/pgdata -E UTF8 --locale=en_US.UTF-8"
# 6. 启动
su - postgres -c "/usr/local/pgsql/bin/pg_ctl -D /data/pgdata -l /tmp/pg.log start"
这一步有几个容易翻车的点。编译之前必须装 readline-devel 和 zlib-devel,少了 readline 编译出的 psql 命令行没有历史记录功能,少了 zlib 会影响备份工具;--with-pgport 如果忘了配,默认是 5432,问题不大,但显式指定更稳妥;initdb 不能以 root 用户跑,必须切到 postgres 用户或者普通用户,否则它会直接拒绝执行。
初始化完成后,默认配置里 listen_addresses 是 localhost,如果要从别的机器连接,需要改 postgresql.conf 里的监听地址,同时去 pg_hba.conf 里添加对应的 host 连接规则。这两个文件改完必须重启或者 reload 才生效。
2.3 Docker 秒级部署 MySQL 与 PostgreSQL
如果只是为了本地研究、联调测试,用 Docker 部署绝对是最省心的。一个命令就能把数据库拉起来,不用忍受编译等待,也不用担心污染宿主机环境。
MySQL 的 Docker 部署命令:
bash复制docker run -d --name mysql8 \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=root123 \
-e TZ=Asia/Shanghai \
-v /data/mysql:/var/lib/mysql \
mysql:8.0
PostgreSQL 的 Docker 部署命令:
bash复制docker run -d --name pg16 \
-p 5432:5432 \
-e POSTGRES_PASSWORD=postgres \
-e TZ=Asia/Shanghai \
-v /data/pg:/var/lib/postgresql/data \
postgres:16
这里有个细节,MySQL 镜像默认的字符集可能不是 utf8mb4,最好在启动时加上 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci。PostgreSQL 容器要注意数据卷目录的权限问题,如果挂载了宿主机目录,需要保证目录属主和容器内 postgres 用户 uid 一致,否则启动时会报权限错误。至于 docker compose 写 PostgreSQL 新版本,比如 postgres:18,本质上跟 16 的写法没有区别,改镜像 tag 就行,新版兼容旧版配置。
yaml复制services:
postgres:
image: postgres:18
container_name: pg18
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: postgres
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
2.4 PG 启动报错锁文件权限不够的真实案例
先看这个热搜词:“无法创建锁文件 ‘/var/run/postgresql/.s.pgsql.5432.lock’: 权限不够”。这个报错我在生产环境遇到过不止一次,原因很简单:PostgreSQL 默认把 Unix Socket 文件放在 /var/run/postgresql 目录下,但这个目录属于 root 用户,postgres 系统用户没有写权限,所以启动时创建不了锁文件。
解决办法有两种。推荐做法是给 postgres 用户授权:
bash复制mkdir -p /var/run/postgresql
chown postgres:postgres /var/run/postgresql
另一种做法是修改 postgresql.conf 里的 unix_socket_directories,把它指向一个有权限的目录,比如 /tmp:
conf复制unix_socket_directories = '/tmp'
这个报错的本质,是操作系统的目录权限和数据库进程的运行用户不匹配。排查这类问题时不要慌,先看报错路径到底是哪个目录,再检查该目录属主,基本上就解决了。
3. 日常使用核心功能对比
3.1 SQL 语法差异与常见误区
MySQL 和 PostgreSQL 都号称“标准 SQL”,但实际用起来差异不少。这类差异在从 MySQL 迁到 PG 的时候最容易踩坑,我把几个高频的列出来:
- 分页语法:MySQL 用
LIMIT 10 OFFSET 20,PostgreSQL 两种都支持,但标准写法是OFFSET 20 FETCH FIRST 10 ROWS ONLY。 - 插入冲突处理:MySQL 用
INSERT ... ON DUPLICATE KEY UPDATE,PG 用INSERT ... ON CONFLICT (id) DO UPDATE SET ...,而且 PG 的语义更精确,可以明确指定冲突的约束。 - 字符串拼接:MySQL 里
'a' + 'b'结果是0,因为+被当成数字运算符;PG 里'a' + 'b'直接报错,正确写法是用||。 - 布尔值:MySQL 里
TRUE其实就是1,查询结果可能显示为 1;PG 里布尔类型是独立的true/false。 - INT(N) 显示宽度:MySQL 的
INT(5)只影响显示宽度,并不限制存储的数值范围,在 MySQL 8.0 里这个用法已经被废弃。热搜词里那个mysql中int+5,大概率就是问这个。所以建表时别再写INT(5)这种远古语法了,直接INT就行。
这些差异总结下来其实就是一个原则:MySQL 语法更“人性化松散”,PG 更“严格标准”。习惯 MySQL 写法的人去写 PG,最容易在字符串拼接和冲突处理上翻车。
3.2 存储过程与触发器:分隔符和错误信息的坑
MySQL 的存储过程和触发器最经典的坑就是分隔符。默认情况下 SQL 语句用分号作为结束符,但存储过程体内部也要用分号,如果直接执行,客户端会在第一个分号处就误以为语句结束了。所以必须先用 DELIMITER 把结束符改成其他符号:
sql复制DELIMITER $$
CREATE PROCEDURE sp_update_stock(
IN product_id INT,
IN delta INT
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '库存更新失败';
END;
START TRANSACTION;
UPDATE products SET stock = stock + delta WHERE id = product_id;
COMMIT;
END$$
DELIMITER ;
这里 DECLARE EXIT HANDLER FOR SQLEXCEPTION 是异常捕获,SIGNAL SQLSTATE 是主动抛出错误。开发存储过程时建议把错误处理放在开头,不要等到线上出了脏数据才发现没有回滚逻辑。
PostgreSQL 的 PL/pgSQL 就没有这种麻烦,它用 $$ 做美元引号包裹函数体,天然避开了分号冲突。错误处理也更现代,直接 BEGIN ... EXCEPTION WHEN OTHERS THEN ... END,还能用 RAISE EXCEPTION 抛错。整体上 PG 的存储过程开发体验更接近主流编程语言的感觉。
3.3 JSON 与全文检索能力对比
MySQL 8.0 的 JSON 类型已经做得不错,可以支持 JSON 字段上的索引(通过生成列)、JSON_TABLE 等函数。但说实话,真正复杂的 JSON 操作,PostgreSQL 的 JSONB 要成熟得多。JSONB 是二进制存储,自带高效的 @>、?、|| 等操作符,配合 GIN 索引可以做复杂的文档检索。比如 SELECT * FROM docs WHERE data @> '{"name": "张三"}'::jsonb,这种写法在 MySQL 里要实现同等效果会麻烦很多。
全文检索方面,MySQL 的全文索引只能建于 CHAR、VARCHAR、TEXT 类型上,默认不支持中文分词,需要配合第三方分词插件。PG 有专门的 tsvector/tsquery 类型和全文检索语法,支持自定义分词字典,中文场景下配合 zhparser 或 SCWS 也能用。不过说实话,如果业务对全文检索要求很高,我一般还是建议直接用 Elasticsearch,让数据库专注做事务查询,别硬扛搜索。
3.4 pgvector 扩展:把 PG 变成向量数据库
pgvector 是最近两年 PostgreSQL 生态里最火的扩展之一,它让 PG 能直接存储和检索向量数据,配合大模型做语义检索、RAG 应用非常方便。安装完扩展后,建表和使用方式如下:
sql复制CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
content TEXT,
embedding vector(768)
);
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);
SELECT id, content, 1 - (embedding <=> '[...]') AS similarity
FROM documents
ORDER BY embedding <=> '[...]'
LIMIT 10;
这里 vector(768) 表示存储 768 维的向量,维度要和 embedding 模型输出维度一致;索引有 hnsw 和 ivfflat 两种,HNSW 查询速度更快但内存占用更大,IVFFlat 更省资源但需要提前训练列表。
在 Windows 下安装 pgvector 会稍微麻烦点,需要下载对应 PostgreSQL 16 版本的 DLL,然后把 DLL 放到 C:\Program Files\PostgreSQL\16\lib 目录,把 SQL 控制文件放到 share\extension 目录。这一步缺一不可,很多人只放了 DLL,建扩展时报无法找到 vector 类型,就是这个原因。
4. 数据同步、迁移与连接问题
4.1 DataX 同步 MySQL 与 PostgreSQL 的可配置参数
DataX 是阿里开源的数据同步工具,支持 MySQL、PostgreSQL、SQL Server、Oracle、HDFS 等多种数据源。一个同步任务就是一个 JSON 配置文件,核心是 reader 和 writer 两部分。下面是一个从 MySQL 同步到 PostgreSQL 的示例:
json复制{
"job": {
"content": [
{
"reader": {
"name": "mysqlreader",
"parameter": {
"username": "sync_user",
"password": "sync_pass",
"column": ["id", "name", "created_at"],
"splitPk": "id",
"connection": [
{
"table": ["source_table"],
"jdbcUrl": ["jdbc:mysql://192.168.1.10:3306/source_db?useUnicode=true&characterEncoding=utf8"]
}
]
}
},
"writer": {
"name": "postgresqlwriter",
"parameter": {
"username": "pg_user",
"password": "pg_pass",
"column": ["id", "name", "created_at"],
"preSql": ["TRUNCATE TABLE target_table"],
"postSql": [],
"connection": [
{
"table": ["target_table"],
"jdbcUrl": "jdbc:postgresql://192.168.1.20:5432/target_db"
}
]
}
}
}
],
"setting": {
"speed": {
"channel": 4
}
}
}
}
DataX 有几个关键参数必须理解清楚。splitPk 是分片字段,DataX 会根据这个字段将查询拆分成多个子查询并发执行,建议使用主键或有索引的数值字段;channel 控制并发数,并不是越大越好,我实测 4-8 个 channel 对源库的压力比较可控;preSql 和 postSql 分别对应写目标表之前和之后执行的 SQL,像这种全量同步任务,通常用 TRUNCATE 清掉旧数据再写入;jdbcUrl 里 MySQL 和 PG 的驱动串格式不同,千万不能混用。
至于 MySQL、SQL Server、PostgreSQL 三库之间的同步组合,如果考虑两两组合且区分方向和全量/增量,确实能凑出很多种排列,热搜词里说的“9 种组合鬼斧神工”就是这么来的。实际项目中跨库同步建议优先评估 DataX、Flink CDC、Debezium,不要自己用代码硬写同步逻辑,维护成本太高。
4.2 连接 MySQL 8 报错:Firedac 认证协议不支持
这个报错我印象很深:“Firedac phys mysql client does not support authentication protocol requested”。出现这个问题的场景通常是 Delphi/C++ Builder 程序用 Firedac 连接 MySQL 8.0,报错翻译过来就是“物理 MySQL 客户端不支持服务器请求的认证协议”。
根因是 MySQL 8.0 默认的认证插件 caching_sha2_password 太新,老版本的 Firedac 驱动库不认识。解决办法有两个思路。
思路一:在 MySQL 端把用户改回旧认证方式:
sql复制ALTER USER 'app_user'@'%' IDENTIFIED WITH mysql_native_password BY 'password';
FLUSH PRIVILEGES;
思路二:升级 Firedac 驱动到支持 MySQL 8 认证协议的版本。如果项目不好改动,临时用思路一可以快速恢复,但建议后续选型时考虑把中间层换成标准 JDBC/ODBC 方式,避免客户端驱动长期卡在旧版本上。
类似的连接问题还有 MySQL 8 的 JDBC 驱动。老项目里如果用 com.mysql.jdbc.Driver,在 MySQL 8 下会直接报错,必须改成 com.mysql.cj.jdbc.Driver,同时连接串里加上 serverTimezone=Asia/Shanghai,否则时间字段会差 8 小时。
4.3 Excel 通过 ODBC 连接 PostgreSQL
PostgreSQL 连 Excel 也很常见,尤其是业务人员要做临时数据分析的时候。操作流程是:先安装 PostgreSQL ODBC 驱动(psqlODBC),然后在 Windows 的 ODBC 数据源管理器里新建一个系统 DSN,填写主机、端口、数据库、用户名密码,最后在 Excel 里通过“获取数据 -> 从其他源 -> 从 ODBC”选择这个 DSN 导入数据。
这里需要注意,Excel 在导入时默认会读取表的第一行作为列名,如果 PG 表字段名是带大小写的,ODBC 配置里建议勾选“显示混合大小写标识符”相关的选项,否则可能出现字段名对不上的情况。另外 ODBC 连接和 JDBC 不同,它对大数据量查询的内存控制较差,几百万行数据建议先在 PG 里用视图或者导出 CSV,别直接整表拉进 Excel。
4.4 同构与异构迁移的常用手段盘点
数据库迁移是每个开发都要面对的课题。MySQL 到 MySQL 用 mysqldump 或 xtrabackup,PG 到 PG 用 pg_dump 或 pg_basebackup,这两种同构迁移都比较成熟。
MySQL 到 PostgreSQL 这种异构迁移,情况就复杂一些。工具层面可以用 pgloader,它能自动完成表结构转换、类型映射、数据搬运,但碰到复杂存储过程、触发器、特殊类型时还是需要人工介入。DataX 可以做表到表的同步,适合大表并行迁移。逻辑上更稳妥的方案是先导出表结构和数据,再用脚本转换语法,最后用 PG 的 COPY 命令快速导入。COPY 的导入速度比逐条 INSERT 快一个数量级,我在迁移千万级数据时实测过,COPY 大概 3 分钟搞定,逐条 INSERT 跑了半小时还没结束。
5. 运维排障与性能问题实战
5.1 PG 的 WAL 日志占用磁盘过大
PostgreSQL 的 WAL(Write-Ahead Log)就是预写日志,类似 MySQL 的 binlog 和 redo log 的结合体。但它没有 MySQL 那么“自动管理”,如果配置不当,WAL 目录 pg_wal 很容易膨胀到几十 GB。
常见原因有几个。第一,max_wal_size 设置过大,导致系统不会频繁触发 checkpoint,WAL 文件不断累积。第二,开启了 wal_level = logical 或 archive_mode = on,但归档脚本没配置好,导致 WAL 无法归档清理。第三,存在未消费的 replication slot,数据库以为下游还在读取 WAL,会一直保留相关文件。
解决办法要从排查开始,先看 slot 状态:
sql复制SELECT slot_name, active, pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)
FROM pg_replication_slots;
如果某个 slot 的 active 是 false 且迟迟没有消费端,直接删除它:
sql复制SELECT pg_drop_replication_slot('slot_name');
再检查配置:
conf复制max_wal_size = 2GB
checkpoint_completion_target = 0.9
archive_mode = on
archive_command = 'test ! -f /backup/wal/%f && cp %p /backup/wal/%f'
正常情况下,调整之后 WAL 目录会随着 checkpoint 推进逐步释放。如果磁盘告急等不了,可以执行 CHECKPOINT 手动触发一次,但要注意这会影响数据库瞬间 IO。
5.2 MySQL 创建唯一约束时提示有重复数据
开发中经常遇到这种情况:线上表的数据已经有历史重复了,现在要加唯一索引,结果 ALTER TABLE ADD UNIQUE KEY 直接报错“Duplicate entry”。这种问题本质上不是 SQL 写错了,而是数据质量问题。
排查办法是先按目标字段分组统计重复记录:
sql复制SELECT email, COUNT(*)
FROM users
GROUP BY email
HAVING COUNT(*) > 1;
确认重复范围后,再决定是保留一条还是合并数据。常用的去重 SQL 以保留 id 最小那条为例:
sql复制DELETE FROM users
WHERE id NOT IN (
SELECT MIN(id)
FROM users
GROUP BY email
);
这类操作建议在业务低峰期执行,最好先备份原表。我见过有同事直接在线上大表跑去重 DELETE,结果锁了整表,业务直接开始堆积。无论 MySQL 还是 PG,大批量删除要分批处理,比如 LIMIT 1000 循环删,减小锁粒度。
5.3 端口号冲突与连接排查
MySQL 默认 3306,PostgreSQL 默认 5432,这是运维最基础的知识点,但越是基础越容易在实际部署时出问题。本机装了多套数据库服务,或者 Docker 端口映射冲突,导致服务起不来,这种排障我每个月都会遇到。
排查端口占用,Linux 用 ss -lntp | grep 3306 或 lsof -i :5432,Windows 用 netstat -ano | findstr 3306。如果看到进程 PID,再去任务管理器或 ps 里确认是什么程序占用的。还有一个容易忽略的点:云服务器的安全组和本地防火墙都要放行对应端口,否则数据库监听正常,但远程就是连不上。很多新手排查半天服务问题,最后发现是防火墙把端口挡了。
5.4 PostgreSQL 高可用部署方案简谈
PostgreSQL 的高可用方案比 MySQL 复杂,选择也更多。最经典的是基于流复制的 Patroni + etcd 方案,Patroni 负责管理 PostgreSQL 实例的生命周期,etcd 负责存储集群状态和选主。当一个节点故障时,Patroni 会自动把备机提升为主库。
部署层面最少需要 3 台机器:一个 etcd 节点(生产中建议 3 节点等等),两台 PostgreSQL。每台安装 Patroni,写入配置后启动,Patroni 会自动完成初始化和流复制搭建。相比 MySQL 的 MGR 或主从 + MHA 方案,Patroni 的自动故障切换做得更精细,但运维门槛也更高。
如果只是中小业务,不想引入太多组件,可以用 repmgr,它比 Patroni 轻量,但自动切换能力弱一些。高可用不是装一个工具就完事,还要考虑备份、延迟监控、脑裂防护,这些才是真正的难点。我见过不少团队把高可用方案搭好了,但平时从没演练过切换,等到真出故障时手动操作反而更慢。
6. 面试高频问题与学习路线建议
6.1 面试官最爱追问的几个对比题
把 MySQL 和 PostgreSQL 放在一起对比,是面试官非常喜欢出题的方向。我整理几个被问烂但很多人答不好的点:
- MySQL 索引为什么用 B+ 树?因为 B+ 树的非叶子节点不存数据,一页能存更多索引项,树更矮,IO 次数更少,而且叶子节点用链表串联,适合范围查询。
- PostgreSQL 和 MySQL 的 MVCC 实现差异?MySQL 用 undo log 保存旧版本,PG 在数据页里保留旧版本元组,前者回收简单,后者需要 vacuum。
- 两个数据库的 JSON 字段应该怎么选型?如果主要做点查和简单过滤,MySQL 8 够用;如果要做复杂 JSON 检索、聚合分析,PG 的 JSONB + GIN 索引更合适。
- 一个几十亿行的表,是选择 MySQL 分库分表还是 PG 单机硬扛?要看查询模式。如果是 OLTP 点查,分库分表更可控;如果是复杂分析查询,PG 的优化器更强,拆成单表反而更好。
回答这类问题的核心不是背答案,而是要建立“为什么这么设计”的思维。面试官真正想考察的是候选人对数据库底层机制的理解,而不是谁更好用。
6.2 我的学习路线建议
给新人的建议,不要只学一个数据库,两个都装起来,用同一套业务需求分别实现一遍。先安装 MySQL 和 PostgreSQL,做同样的建表、增删改查、事务回滚、存储过程、备份恢复,很快就能体会到两者的差异。
更进一步,可以自己写一个小的数据迁移脚本,用 DataX 把 MySQL 的表同步到 PG,再写几个含 JOIN 和窗口函数的查询在两个库中跑。这个流程走完,你对数据库的理解会超出大多数只会用一个库的同行。学完之后再去看面试题,你会发现很多所谓“深入原理”的问题,其实都是实际操作过就能理解的常识。
写在最后的体会
写到这里我想起一个很形象的类比:MySQL 像一把锋利耐用的匕首,单点性能强、修理快、生态成熟,适合大多数互联网业务一上来就干;PostgreSQL 像一套多功能工具套装,平时你可能只用到其中一把螺丝刀,但真遇到复杂场景时,它提供的功能维度是匕首给不了的。
我在实际工作中两个库都在用,核心交易库 MySQL,分析平台和 AI 检索库 PG,彼此不冲突。如果你面临选型,不妨列一下未来半年一定需要的功能,再决定哪一种更适合。如果只是个人学习和练手,那就都装,反正 Docker 一键就能搞定,没必要非要从两个里面挑一个。踩过的坑多了,自然就知道什么场景该用哪个。
