概述
为什么需要了解PostgreSQL?
作为MySQL开发者,你可能已经习惯了MySQL的简洁和易用性。但在某些场景下,PostgreSQL提供了更强大的功能和更好的标准兼容性。本文将从MySQL开发者的视角,帮助你理解两者的差异,避免常见的"坑"。
快速对比表
| 特性 | MySQL | PostgreSQL |
|---|---|---|
| 类型 | 关系型数据库 | 关系型数据库(支持对象关系) |
| 许可证 | GPL/商业许可 | PostgreSQL License(类似BSD) |
| SQL标准 | 部分支持 | 高度兼容SQL标准 |
| 存储引擎 | 多种(InnoDB、MyISAM等) | 单一存储引擎 |
| ACID支持 | InnoDB支持 | 完全支持 |
| 并发控制 | MVCC(InnoDB) | MVCC(多版本并发控制) |
| 扩展性 | 有限 | 高度可扩展(支持自定义函数、类型等) |
设计理念对比
MySQL的设计理念
MySQL的设计哲学是**"简单、快速、可靠"**:
- 易用性优先: 默认配置即可快速上手
- 性能优先: 在简单场景下性能优异
- 灵活性: 提供多种存储引擎选择
- Web应用友好: 为Web应用场景优化
PostgreSQL的设计理念
PostgreSQL的设计哲学是**"功能完整、标准兼容、可扩展"**:
- 标准兼容: 严格遵循SQL标准
- 功能丰富: 内置大量高级特性
- 可扩展性: 支持自定义函数、类型、操作符
- 数据完整性: 严格的类型检查和约束
架构差异
PostgreSQL架构 MySQL架构 Postmaster进程 客户端连接 Backend进程 查询解析器 查询优化器 存储管理器 表空间 WAL日志 共享缓冲区 连接管理器 客户端连接 查询解析器 查询优化器 存储引擎接口 InnoDB引擎 MyISAM引擎 Memory引擎
关键差异:
- MySQL: 多存储引擎架构,可以选择不同的存储引擎
- PostgreSQL: 单一存储引擎,但功能更强大
使用习惯差异 - MySQL开发者的PostgreSQL迁移指南
1. 字符串引号的使用
MySQL的习惯
sql
-- MySQL中单引号和双引号都可以用于字符串
SELECT * FROM users WHERE name = "John";
SELECT * FROM users WHERE name = 'John';
-- MySQL中反引号用于标识符(表名、列名)
SELECT * FROM `order` WHERE `user-id` = 1;
PostgreSQL的注意事项
sql
-- PostgreSQL中只能用单引号表示字符串
SELECT * FROM users WHERE name = 'John'; -- ✅ 正确
SELECT * FROM users WHERE name = "John"; -- ❌ 错误!双引号用于标识符
-- PostgreSQL中双引号用于标识符(区分大小写)
SELECT * FROM "Order" WHERE "user-id" = 1; -- ✅ 正确(注意大小写敏感)
SELECT * FROM order WHERE user-id = 1; -- ❌ 错误(会被转换为小写)
迁移建议:
- 统一使用单引号表示字符串
- 避免使用双引号,除非确实需要大小写敏感的标识符
2. 字符串拼接
MySQL的习惯
sql
-- MySQL使用CONCAT函数
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM users;
-- MySQL也支持||操作符(需要设置SQL_MODE)
SET sql_mode = 'PIPES_AS_CONCAT';
SELECT first_name || ' ' || last_name AS full_name FROM users;
PostgreSQL的注意事项
sql
-- PostgreSQL使用||操作符(标准SQL)
SELECT first_name || ' ' || last_name AS full_name FROM users; -- ✅ 推荐
-- PostgreSQL也支持CONCAT函数(但参数处理不同)
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM users; -- ✅ 也可以
-- PostgreSQL的CONCAT会自动处理NULL
SELECT CONCAT('Hello', NULL, 'World'); -- 返回 'HelloWorld'
SELECT 'Hello' || NULL || 'World'; -- 返回 NULL
迁移建议:
- 优先使用
||操作符(更符合SQL标准) - 注意NULL值的处理差异
3. LIMIT和OFFSET的使用
MySQL的习惯
sql
-- MySQL的LIMIT语法
SELECT * FROM users LIMIT 10;
SELECT * FROM users LIMIT 10 OFFSET 20;
SELECT * FROM users LIMIT 20, 10; -- MySQL特有语法:LIMIT offset, count
PostgreSQL的注意事项
sql
-- PostgreSQL只支持标准SQL语法
SELECT * FROM users LIMIT 10; -- ✅ 正确
SELECT * FROM users LIMIT 10 OFFSET 20; -- ✅ 正确
SELECT * FROM users LIMIT 20, 10; -- ❌ 错误!不支持这种语法
-- PostgreSQL推荐使用标准语法
SELECT * FROM users ORDER BY id LIMIT 10 OFFSET 20;
迁移建议:
- 统一使用
LIMIT count OFFSET offset语法 - 避免使用MySQL特有的
LIMIT offset, count语法
4. 自增主键的处理
MySQL的习惯
sql
-- MySQL使用AUTO_INCREMENT
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100)
);
-- 插入时不需要指定id
INSERT INTO users (name) VALUES ('John');
PostgreSQL的注意事项
sql
-- PostgreSQL使用SERIAL或IDENTITY
-- 方式1: SERIAL(传统方式,但推荐使用IDENTITY)
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100)
);
-- 方式2: IDENTITY(SQL标准,推荐)
CREATE TABLE users (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(100)
);
-- 插入时同样不需要指定id
INSERT INTO users (name) VALUES ('John');
迁移建议:
- 使用
SERIAL类型(最简单,与AUTO_INCREMENT最相似) - 或者使用
GENERATED ALWAYS AS IDENTITY(更符合SQL标准)
5. 日期时间处理
MySQL的习惯
sql
-- MySQL的日期函数
SELECT NOW();
SELECT DATE_FORMAT(created_at, '%Y-%m-%d') FROM users;
SELECT DATE_ADD(created_at, INTERVAL 1 DAY) FROM users;
PostgreSQL的注意事项
sql
-- PostgreSQL使用标准SQL函数
SELECT NOW(); -- ✅ 相同
SELECT CURRENT_TIMESTAMP; -- ✅ 标准SQL
-- PostgreSQL使用TO_CHAR格式化(与MySQL的DATE_FORMAT不同)
SELECT TO_CHAR(created_at, 'YYYY-MM-DD') FROM users;
-- PostgreSQL使用INTERVAL类型
SELECT created_at + INTERVAL '1 day' FROM users; -- 注意:'1 day'是字符串
SELECT created_at + INTERVAL '1 DAY' FROM users; -- 也可以
迁移建议:
- 学习PostgreSQL的日期格式化函数
TO_CHAR - 使用
INTERVAL类型进行日期运算
6. 布尔类型的处理
MySQL的习惯
sql
-- MySQL没有真正的布尔类型,使用TINYINT(1)
CREATE TABLE users (
id INT PRIMARY KEY,
is_active TINYINT(1) DEFAULT 1
);
-- 查询时可以使用0/1或TRUE/FALSE
SELECT * FROM users WHERE is_active = 1;
SELECT * FROM users WHERE is_active = TRUE;
PostgreSQL的注意事项
sql
-- PostgreSQL有真正的BOOLEAN类型
CREATE TABLE users (
id INT PRIMARY KEY,
is_active BOOLEAN DEFAULT TRUE
);
-- 查询时使用TRUE/FALSE/NULL
SELECT * FROM users WHERE is_active = TRUE;
SELECT * FROM users WHERE is_active; -- 简写形式
SELECT * FROM users WHERE NOT is_active; -- 否定形式
-- PostgreSQL中不能直接使用0/1
SELECT * FROM users WHERE is_active = 1; -- ❌ 错误!
迁移建议:
- 使用
BOOLEAN类型替代TINYINT(1) - 查询时使用
TRUE/FALSE而不是1/0
7. 字符串比较的大小写敏感性
MySQL的习惯
sql
-- MySQL默认不区分大小写(取决于字符集和排序规则)
CREATE TABLE users (
name VARCHAR(100)
);
-- 默认情况下不区分大小写
SELECT * FROM users WHERE name = 'john'; -- 会匹配'John'
SELECT * FROM users WHERE name = 'JOHN'; -- 也会匹配'John'
PostgreSQL的注意事项
sql
-- PostgreSQL默认区分大小写
CREATE TABLE users (
name VARCHAR(100)
);
-- 默认情况下区分大小写
SELECT * FROM users WHERE name = 'john'; -- 只匹配'john'
SELECT * FROM users WHERE name = 'John'; -- 只匹配'John'
-- 使用ILIKE进行不区分大小写匹配
SELECT * FROM users WHERE name ILIKE 'john'; -- 匹配'john'、'John'、'JOHN'
-- 使用LOWER/UPPER函数
SELECT * FROM users WHERE LOWER(name) = LOWER('john');
迁移建议:
- 注意大小写敏感性差异
- 使用
ILIKE或LOWER()/UPPER()函数进行不区分大小写匹配
8. 分组查询的严格性
MySQL的习惯
sql
-- MySQL允许SELECT非聚合列(虽然不推荐)
SELECT department, name, COUNT(*)
FROM users
GROUP BY department;
-- MySQL可能返回任意一个name值(取决于sql_mode)
PostgreSQL的注意事项
sql
-- PostgreSQL严格要求:SELECT的列必须在GROUP BY中或使用聚合函数
SELECT department, COUNT(*)
FROM users
GROUP BY department; -- ✅ 正确
SELECT department, name, COUNT(*)
FROM users
GROUP BY department; -- ❌ 错误!name不在GROUP BY中
-- 正确的写法
SELECT department, name, COUNT(*)
FROM users
GROUP BY department, name; -- ✅ 正确
迁移建议:
- 确保GROUP BY包含所有非聚合列
- 或者使用聚合函数处理非分组列
9. 子查询的别名要求
MySQL的习惯
sql
-- MySQL在某些情况下可以省略子查询别名
SELECT * FROM (
SELECT * FROM users
) AS temp;
PostgreSQL的注意事项
sql
-- PostgreSQL严格要求子查询必须有别名
SELECT * FROM (
SELECT * FROM users
) AS temp; -- ✅ 正确(别名是必需的)
SELECT * FROM (
SELECT * FROM users
); -- ❌ 错误!缺少别名
迁移建议:
- 始终为子查询提供别名
10. 转义字符的处理
MySQL的习惯
sql
-- MySQL使用反斜杠转义
SELECT * FROM users WHERE name = 'O\'Brien';
PostgreSQL的注意事项
sql
-- PostgreSQL默认不使用反斜杠转义(标准SQL)
SELECT * FROM users WHERE name = 'O''Brien'; -- ✅ 使用两个单引号
-- 如果启用escape_string_warning,可以使用E前缀
SELECT * FROM users WHERE name = E'O\'Brien'; -- ✅ 使用E前缀启用转义
迁移建议:
- 使用两个单引号表示单引号字符
- 或者使用
E'...'前缀启用转义
性能对比分析
查询性能对比
简单查询性能
| 操作类型 | MySQL优势场景 | PostgreSQL优势场景 |
|---|---|---|
| 简单SELECT | 简单查询、主键查询 | 复杂查询、多表JOIN |
| INSERT | 高并发插入 | 大批量插入 |
| UPDATE | 简单更新 | 复杂更新、条件更新 |
| DELETE | 简单删除 | 复杂删除 |
复杂查询性能
PostgreSQL在复杂查询方面通常表现更好:
sql
-- 复杂查询示例:PostgreSQL的查询优化器更强大
SELECT
u.id,
u.name,
COUNT(o.id) AS order_count,
SUM(o.amount) AS total_amount,
AVG(o.amount) AS avg_amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at >= '2024-01-01'
GROUP BY u.id, u.name
HAVING COUNT(o.id) > 5
ORDER BY total_amount DESC
LIMIT 10;
性能差异原因:
- PostgreSQL的查询优化器更先进
- 支持更多的优化策略(如并行查询)
- 统计信息更准确
并发性能对比
PostgreSQL并发控制 MySQL并发控制 MVCC快照 读操作 新版本行 写操作 无锁读取 共享锁 读操作 排他锁 写操作 锁等待
关键差异:
- MySQL (InnoDB): 使用MVCC,但读操作仍可能被写操作阻塞
- PostgreSQL: 使用MVCC,读操作几乎不会被写操作阻塞
索引性能对比
| 索引类型 | MySQL | PostgreSQL | 性能差异 |
|---|---|---|---|
| B-Tree索引 | ✅ 支持 | ✅ 支持 | 性能相近 |
| Hash索引 | ✅ 支持(Memory引擎) | ✅ 支持 | PostgreSQL更稳定 |
| 全文索引 | ✅ 支持(MyISAM/InnoDB) | ✅ 支持(GIN/GiST) | PostgreSQL更强大 |
| 空间索引 | ✅ 支持(MyISAM) | ✅ 支持(GiST) | PostgreSQL更标准 |
| 部分索引 | ❌ 不支持 | ✅ 支持 | PostgreSQL优势 |
| 表达式索引 | ❌ 不支持 | ✅ 支持 | PostgreSQL优势 |
实际性能测试示例
sql
-- 测试场景:1000万条数据的聚合查询
-- MySQL测试
SELECT department, COUNT(*), AVG(salary)
FROM employees
WHERE hire_date >= '2020-01-01'
GROUP BY department;
-- 执行时间: ~2.5秒
-- PostgreSQL测试(相同数据)
SELECT department, COUNT(*), AVG(salary)
FROM employees
WHERE hire_date >= '2020-01-01'
GROUP BY department;
-- 执行时间: ~1.8秒(并行查询优化)
注意: 实际性能差异取决于具体场景、数据量、硬件配置等因素。
应用场景选择指南
MySQL适合的场景
1. Web应用(特别是中小型)
小到中型 大型 否 是 高并发简单操作 复杂事务 Web应用 数据规模 MySQL 是否需要复杂查询 PostgreSQL 并发要求
原因:
- 简单易用,上手快
- 在简单查询场景下性能优秀
- 社区支持广泛
- 运维工具丰富
典型应用:
- 博客系统
- CMS系统
- 电商网站(中小型)
- 社交网络(中小型)
2. 读写分离场景
- MySQL的主从复制成熟稳定
- 读写分离配置简单
3. 需要多种存储引擎的场景
- 需要内存表(Memory引擎)
- 需要全文索引(MyISAM)
- 需要事务支持(InnoDB)
PostgreSQL适合的场景
1. 复杂查询和分析场景
复杂查询需求 多表JOIN 复杂聚合 窗口函数 CTE递归查询 PostgreSQL
原因:
- 查询优化器更强大
- 支持更多SQL标准特性
- 复杂查询性能更好
典型应用:
- 数据分析系统
- 报表系统
- BI系统
- 数据仓库
2. 需要严格数据完整性的场景
- 金融系统
- 医疗系统
- 政府系统
原因:
- 更严格的类型检查
- 更完善的约束支持
- 更好的ACID保证
3. 需要扩展功能的场景
- GIS应用(PostGIS扩展)
- 全文搜索(内置支持)
- JSON处理(JSONB类型)
- 数组和自定义类型
4. 需要高并发读写的场景
- PostgreSQL的MVCC实现更优秀
- 读操作几乎不阻塞写操作
选择决策树
是 否 是 否 熟悉MySQL 熟悉PostgreSQL 小到中型 大型 是 否 是 否 开始选择数据库 是否需要复杂查询? PostgreSQL 是否需要多种存储引擎? MySQL 团队熟悉度? 数据规模? 是否需要扩展功能? 是否需要高并发读写?
混合使用场景
在实际项目中,可以考虑混合使用:
应用系统 MySQL PostgreSQL 用户数据
订单数据
简单查询 分析数据
报表数据
复杂查询 数据同步
场景:
- MySQL用于OLTP(在线事务处理)
- PostgreSQL用于OLAP(在线分析处理)
- 通过ETL工具同步数据
索引机制深度对比
B-Tree索引对比
MySQL的B-Tree索引
sql
-- MySQL创建B-Tree索引
CREATE INDEX idx_name ON users(name);
CREATE INDEX idx_email ON users(email(50)); -- 前缀索引
-- MySQL的索引特点
-- 1. 支持前缀索引(节省空间)
-- 2. 支持覆盖索引优化
-- 3. InnoDB使用聚簇索引(主键索引)
PostgreSQL的B-Tree索引
sql
-- PostgreSQL创建B-Tree索引
CREATE INDEX idx_name ON users(name);
CREATE INDEX idx_email ON users(email); -- 不支持前缀索引,但可以创建表达式索引
-- PostgreSQL的索引特点
-- 1. 不支持前缀索引,但支持表达式索引
-- 2. 支持部分索引(条件索引)
-- 3. 使用堆表结构(非聚簇索引)
索引类型对比表
| 索引特性 | MySQL | PostgreSQL | 说明 |
|---|---|---|---|
| B-Tree索引 | ✅ | ✅ | 两者都支持,性能相近 |
| 前缀索引 | ✅ | ❌ | MySQL可以只索引前N个字符 |
| 表达式索引 | ❌ | ✅ | PostgreSQL可以索引表达式结果 |
| 部分索引 | ❌ | ✅ | PostgreSQL可以只索引满足条件的行 |
| 唯一索引 | ✅ | ✅ | 两者都支持 |
| 复合索引 | ✅ | ✅ | 两者都支持 |
| 覆盖索引 | ✅ | ✅ | 两者都支持(实现方式不同) |
MySQL特有的索引特性
1. 前缀索引
sql
-- MySQL: 只索引前50个字符
CREATE INDEX idx_content ON articles(content(50));
-- 优势: 节省索引空间
-- 劣势: 不支持ORDER BY和完整匹配
2. 聚簇索引
sql
-- MySQL InnoDB: 主键自动成为聚簇索引
CREATE TABLE users (
id INT PRIMARY KEY, -- 自动成为聚簇索引
name VARCHAR(100)
);
-- 优势: 主键查询非常快(数据就在索引中)
-- 劣势: 主键更新可能导致行移动
PostgreSQL特有的索引特性
1. 表达式索引
sql
-- PostgreSQL: 可以索引表达式结果
CREATE INDEX idx_lower_name ON users(LOWER(name));
-- 查询时可以使用索引
SELECT * FROM users WHERE LOWER(name) = 'john'; -- 可以使用索引
2. 部分索引
sql
-- PostgreSQL: 只索引满足条件的行
CREATE INDEX idx_active_users ON users(name) WHERE is_active = TRUE;
-- 优势: 索引更小,查询更快
-- 适用场景: 只查询活跃用户
3. 多列索引的使用
sql
-- PostgreSQL: 更灵活的索引使用
CREATE INDEX idx_user_order ON orders(user_id, created_at DESC);
-- PostgreSQL可以更灵活地使用多列索引
SELECT * FROM orders WHERE user_id = 1 ORDER BY created_at DESC; -- 可以使用索引
SELECT * FROM orders WHERE user_id = 1 AND created_at > '2024-01-01'; -- 可以使用索引
索引性能对比
创建索引速度
- MySQL: 通常较快(特别是InnoDB的在线DDL)
- PostgreSQL: 较慢,但支持并发创建索引(CREATE INDEX CONCURRENTLY)
索引维护
- MySQL: 自动维护,但可能需要OPTIMIZE TABLE
- PostgreSQL: 自动维护,支持VACUUM和REINDEX
实际使用建议
对于MySQL开发者使用PostgreSQL的索引建议
-
不要期望前缀索引
sql-- MySQL习惯 CREATE INDEX idx_email ON users(email(50)); -- PostgreSQL替代方案 CREATE INDEX idx_email ON users(email); -- 索引完整列 -- 或者使用表达式索引(如果需要) CREATE INDEX idx_email_prefix ON users(SUBSTRING(email, 1, 50)); -
利用部分索引优化
sql-- 如果经常查询活跃用户 CREATE INDEX idx_active_users ON users(name) WHERE is_active = TRUE; -
利用表达式索引
sql-- 如果经常使用LOWER()查询 CREATE INDEX idx_lower_name ON users(LOWER(name));
事务处理对比
ACID特性对比
| ACID特性 | MySQL (InnoDB) | PostgreSQL | 说明 |
|---|---|---|---|
| 原子性 (Atomicity) | ✅ 完全支持 | ✅ 完全支持 | 两者都完全支持 |
| 一致性 (Consistency) | ✅ 支持 | ✅ 支持 | PostgreSQL约束更严格 |
| 隔离性 (Isolation) | ✅ 支持(4个级别) | ✅ 支持(4个级别) | 实现方式不同 |
| 持久性 (Durability) | ✅ 支持 | ✅ 支持 | 两者都支持 |
事务隔离级别对比
MySQL的隔离级别
sql
-- MySQL设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; -- 默认级别
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- MySQL InnoDB默认: REPEATABLE READ
-- 使用Next-Key Locking防止幻读
PostgreSQL的隔离级别
sql
-- PostgreSQL设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -- 实际等同于READ COMMITTED
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 默认级别
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- PostgreSQL默认: READ COMMITTED
-- 使用MVCC实现隔离
默认隔离级别差异
事务隔离级别 READ UNCOMMITTED READ COMMITTED REPEATABLE READ SERIALIZABLE MySQL: 支持 PostgreSQL: 不支持
实际等同于READ COMMITTED MySQL: 支持 PostgreSQL: 默认级别 MySQL: 默认级别 PostgreSQL: 支持 MySQL: 支持 PostgreSQL: 支持
关键差异:
- MySQL默认: REPEATABLE READ(防止幻读)
- PostgreSQL默认: READ COMMITTED(性能更好,但可能出现幻读)
MVCC实现差异
MySQL InnoDB的MVCC
sql
-- MySQL使用undo log实现MVCC
-- 每个事务看到的是事务开始时的数据快照
-- 使用Next-Key Locking防止幻读
PostgreSQL的MVCC
sql
-- PostgreSQL使用多版本行实现MVCC
-- 每个事务看到的是事务开始时的数据快照
-- 通过VACUUM清理旧版本
锁机制对比
MySQL的锁
sql
-- MySQL InnoDB锁类型
-- 1. 共享锁(S锁)
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
-- 2. 排他锁(X锁)
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 3. 意向锁(表级锁)
-- 4. 记录锁、间隙锁、Next-Key锁
PostgreSQL的锁
sql
-- PostgreSQL锁类型
-- 1. 共享锁
SELECT * FROM users WHERE id = 1 FOR SHARE;
-- 2. 排他锁
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 3. 表级锁(ACCESS SHARE, ROW SHARE等)
-- 4. 行级锁(通过MVCC实现,读操作不阻塞写操作)
死锁处理对比
MySQL的死锁处理
sql
-- MySQL自动检测死锁并回滚其中一个事务
-- 返回错误: ERROR 1213 (40001): Deadlock found
PostgreSQL的死锁处理
sql
-- PostgreSQL自动检测死锁并终止其中一个事务
-- 返回错误: ERROR: deadlock detected
事务使用建议
对于MySQL开发者使用PostgreSQL的事务建议
-
注意默认隔离级别差异
sql-- MySQL习惯: REPEATABLE READ防止幻读 -- PostgreSQL默认: READ COMMITTED,可能出现幻读 -- 如果需要防止幻读,显式设置 SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; BEGIN; -- 事务操作 COMMIT; -
利用PostgreSQL的MVCC优势
sql-- PostgreSQL的读操作不会阻塞写操作 -- 可以放心地进行长时间查询,不会影响写入性能 -
注意锁的使用
sql-- MySQL习惯 SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE; -- PostgreSQL等价写法 SELECT * FROM users WHERE id = 1 FOR SHARE;
数据类型差异详解
数值类型对比
| 类型 | MySQL | PostgreSQL | 说明 |
|---|---|---|---|
| TINYINT | ✅ (-128 to 127) | ✅ (SMALLINT) | MySQL有符号/无符号,PostgreSQL只有有符号 |
| SMALLINT | ✅ (-32768 to 32767) | ✅ (-32768 to 32767) | 相同 |
| INT/INTEGER | ✅ | ✅ | 相同 |
| BIGINT | ✅ | ✅ | 相同 |
| DECIMAL/NUMERIC | ✅ | ✅ | PostgreSQL更严格遵循SQL标准 |
| FLOAT | ✅ | ✅ (REAL) | MySQL单精度,PostgreSQL使用REAL |
| DOUBLE | ✅ | ✅ (DOUBLE PRECISION) | 相同 |
关键差异
1. 无符号整数
sql
-- MySQL: 支持无符号整数
CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
age TINYINT UNSIGNED
);
-- PostgreSQL: 不支持无符号整数
CREATE TABLE users (
id SERIAL PRIMARY KEY, -- 只能是有符号
age SMALLINT CHECK (age >= 0) -- 使用CHECK约束模拟
);
2. 布尔类型
sql
-- MySQL: 使用TINYINT(1)
CREATE TABLE users (
is_active TINYINT(1) DEFAULT 1
);
-- PostgreSQL: 真正的BOOLEAN类型
CREATE TABLE users (
is_active BOOLEAN DEFAULT TRUE
);
字符串类型对比
| 类型 | MySQL | PostgreSQL | 说明 |
|---|---|---|---|
| CHAR(n) | ✅ 固定长度 | ✅ 固定长度 | 相同 |
| VARCHAR(n) | ✅ 可变长度 | ✅ 可变长度 | PostgreSQL没有长度限制的TEXT更常用 |
| TEXT | ✅ 大文本 | ✅ 大文本 | PostgreSQL的TEXT没有长度限制 |
| BLOB | ✅ 二进制 | ✅ BYTEA | PostgreSQL使用BYTEA存储二进制 |
关键差异
1. VARCHAR长度限制
sql
-- MySQL: VARCHAR最大65535字节(取决于字符集)
CREATE TABLE users (
name VARCHAR(255)
);
-- PostgreSQL: VARCHAR最大1GB,但通常使用TEXT
CREATE TABLE users (
name VARCHAR(255), -- 可以,但不常用
description TEXT -- 更常用,无长度限制
);
2. 字符串比较
sql
-- MySQL: 默认不区分大小写(取决于字符集)
SELECT * FROM users WHERE name = 'john'; -- 匹配'John'
-- PostgreSQL: 默认区分大小写
SELECT * FROM users WHERE name = 'john'; -- 只匹配'john'
SELECT * FROM users WHERE name ILIKE 'john'; -- 不区分大小写
日期时间类型对比
| 类型 | MySQL | PostgreSQL | 说明 |
|---|---|---|---|
| DATE | ✅ | ✅ | 相同 |
| TIME | ✅ | ✅ | PostgreSQL支持时区 |
| DATETIME | ✅ | ❌ | PostgreSQL使用TIMESTAMP |
| TIMESTAMP | ✅ | ✅ | PostgreSQL功能更强大 |
| YEAR | ✅ | ❌ | PostgreSQL使用SMALLINT |
关键差异
1. DATETIME vs TIMESTAMP
sql
-- MySQL: DATETIME和TIMESTAMP都支持
CREATE TABLE events (
created_at DATETIME, -- 不自动更新
updated_at TIMESTAMP -- 可以自动更新
);
-- PostgreSQL: 只有TIMESTAMP
CREATE TABLE events (
created_at TIMESTAMP, -- 不自动更新
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- 需要显式设置默认值
);
2. 时区支持
sql
-- MySQL: TIMESTAMP自动转换时区,DATETIME不转换
CREATE TABLE events (
local_time DATETIME, -- 不转换时区
utc_time TIMESTAMP -- 自动转换时区
);
-- PostgreSQL: TIMESTAMP和TIMESTAMPTZ
CREATE TABLE events (
local_time TIMESTAMP, -- 不包含时区信息
utc_time TIMESTAMPTZ -- 包含时区信息,自动转换
);
JSON类型对比
| 特性 | MySQL | PostgreSQL | 说明 |
|---|---|---|---|
| JSON类型 | ✅ (5.7+) | ✅ (9.2+) | 两者都支持 |
| JSONB类型 | ❌ | ✅ | PostgreSQL特有,二进制存储 |
| JSON索引 | ✅ | ✅ | PostgreSQL的JSONB索引更强大 |
| JSON查询 | ✅ | ✅ | PostgreSQL的查询语法更丰富 |
关键差异
1. JSON vs JSONB
sql
-- MySQL: 只有JSON类型
CREATE TABLE users (
id INT PRIMARY KEY,
profile JSON
);
-- PostgreSQL: JSON和JSONB
CREATE TABLE users (
id INT PRIMARY KEY,
profile JSON, -- 文本存储,保留格式
data JSONB -- 二进制存储,查询更快,推荐使用
);
-- JSONB优势:
-- 1. 查询性能更好
-- 2. 支持GIN索引
-- 3. 自动去除重复键和空格
2. JSON查询语法
sql
-- MySQL JSON查询
SELECT * FROM users WHERE JSON_EXTRACT(profile, '$.age') > 18;
SELECT * FROM users WHERE profile->>'$.age' > 18; -- 简化语法
-- PostgreSQL JSON查询(更丰富)
SELECT * FROM users WHERE profile->>'age' > '18';
SELECT * FROM users WHERE (profile->>'age')::int > 18;
SELECT * FROM users WHERE profile @> '{"age": 18}'; -- JSONB包含查询
数组类型对比
sql
-- MySQL: 不支持数组类型
-- 需要使用JSON或关联表
-- PostgreSQL: 支持数组类型
CREATE TABLE users (
id INT PRIMARY KEY,
tags TEXT[], -- 文本数组
scores INTEGER[], -- 整数数组
coordinates FLOAT[][] -- 多维数组
);
-- 插入数据
INSERT INTO users (tags, scores) VALUES
(ARRAY['java', 'python'], ARRAY[90, 85]);
-- 查询
SELECT * FROM users WHERE 'java' = ANY(tags);
SELECT * FROM users WHERE array_length(tags, 1) > 2;
枚举类型对比
sql
-- MySQL: ENUM类型
CREATE TABLE users (
status ENUM('active', 'inactive', 'pending')
);
-- PostgreSQL: 使用CHECK约束或自定义类型
-- 方式1: CHECK约束
CREATE TABLE users (
status VARCHAR(20) CHECK (status IN ('active', 'inactive', 'pending'))
);
-- 方式2: 自定义类型(更接近MySQL的ENUM)
CREATE TYPE user_status AS ENUM ('active', 'inactive', 'pending');
CREATE TABLE users (
status user_status
);
数据类型迁移建议
对于MySQL开发者使用PostgreSQL的数据类型建议
-
使用TEXT替代VARCHAR(很大值)
sql-- MySQL习惯 CREATE TABLE articles ( content VARCHAR(65535) ); -- PostgreSQL推荐 CREATE TABLE articles ( content TEXT -- 无长度限制,更灵活 ); -
使用JSONB替代JSON(如果需要查询)
sql-- PostgreSQL推荐 CREATE TABLE users ( profile JSONB -- 查询性能更好 ); -
利用数组类型
sql-- 不需要创建关联表存储多值 CREATE TABLE products ( tags TEXT[] -- 直接存储标签数组 ); -
注意布尔类型
sql-- 使用BOOLEAN而不是TINYINT(1) CREATE TABLE users ( is_active BOOLEAN DEFAULT TRUE );
PostgreSQL核心概念解析
数据库 vs Schema vs Table
MySQL的概念模型
MySQL Server Database 1 Database 2 Table 1 Table 2 Table 3 Table 4
MySQL结构:
- Server: MySQL服务器实例
- Database: 数据库(包含表)
- Table: 表
PostgreSQL的概念模型
PostgreSQL Cluster Database 1 Database 2 Schema: public Schema: app_schema Table 1 Table 2 Table 3 Table 4
PostgreSQL结构:
- Cluster: PostgreSQL集群实例
- Database: 数据库(逻辑隔离)
- Schema: 模式(命名空间,包含表)
- Table: 表
关键概念对比
| 概念 | MySQL | PostgreSQL | 说明 |
|---|---|---|---|
| 数据库 | Database | Database | MySQL的Database ≈ PostgreSQL的Database |
| 模式 | ❌ 无 | ✅ Schema | PostgreSQL有Schema概念 |
| 表 | Table | Table | 相同 |
| 默认Schema | Database本身 | public | PostgreSQL默认使用public schema |
public Schema详解
什么是public?
public是PostgreSQL的默认Schema(模式),类似于MySQL中Database的概念。
sql
-- PostgreSQL中,每个数据库都有一个默认的public schema
-- 当你创建表时,如果没有指定schema,默认创建在public schema中
-- 这两条语句是等价的
CREATE TABLE users (id INT);
CREATE TABLE public.users (id INT);
-- 查询时也等价
SELECT * FROM users;
SELECT * FROM public.users;
为什么需要Schema?
1. 命名空间隔离
sql
-- 可以在不同的schema中使用相同的表名
CREATE SCHEMA app1;
CREATE SCHEMA app2;
CREATE TABLE app1.users (id INT);
CREATE TABLE app2.users (id INT); -- 不会冲突
-- 访问时需要指定schema
SELECT * FROM app1.users;
SELECT * FROM app2.users;
2. 权限管理
sql
-- 可以为不同的schema设置不同的权限
GRANT USAGE ON SCHEMA app1 TO user1;
GRANT SELECT ON ALL TABLES IN SCHEMA app1 TO user1;
3. 逻辑组织
sql
-- 可以将相关的表组织在同一个schema中
CREATE SCHEMA finance;
CREATE SCHEMA hr;
CREATE SCHEMA sales;
-- 每个schema包含相关的表
CREATE TABLE finance.accounts (...);
CREATE TABLE hr.employees (...);
CREATE TABLE sales.orders (...);
Schema vs Database的区别
PostgreSQL Cluster Database: app_db Schema: public Schema: finance Schema: hr Table: users Table: products Table: accounts Table: transactions Table: employees Table: departments
关键区别:
| 特性 | Database | Schema |
|---|---|---|
| 连接隔离 | ✅ 需要单独连接 | ❌ 同一连接内切换 |
| 备份隔离 | ✅ 可以单独备份 | ❌ 随数据库备份 |
| 权限隔离 | ✅ 数据库级权限 | ✅ Schema级权限 |
| 跨库查询 | ❌ 不支持 | ✅ 支持(同一数据库内) |
实际使用示例
MySQL的使用方式
sql
-- MySQL: 直接使用数据库
USE myapp;
CREATE TABLE users (id INT);
SELECT * FROM users;
PostgreSQL的使用方式
sql
-- PostgreSQL: 连接数据库后,默认使用public schema
-- 连接数据库
\c myapp
-- 创建表(默认在public schema)
CREATE TABLE users (id INT);
SELECT * FROM users; -- 等价于 SELECT * FROM public.users;
-- 使用其他schema
CREATE SCHEMA app;
CREATE TABLE app.users (id INT);
SELECT * FROM app.users;
-- 设置搜索路径(类似MySQL的USE)
SET search_path TO app, public;
SELECT * FROM users; -- 现在会先查找app.users
搜索路径(search_path)
PostgreSQL使用search_path来决定查找表的顺序:
sql
-- 查看当前搜索路径
SHOW search_path;
-- 默认: "$user", public
-- 设置搜索路径
SET search_path TO app, public;
-- 现在查询users时,会先查找app.users,如果不存在再查找public.users
SELECT * FROM users;
对于MySQL开发者的建议:
- 大多数情况下,只需要使用
publicschema即可 - 将PostgreSQL的Database理解为MySQL的Database
- 将PostgreSQL的Schema理解为MySQL的Database内的命名空间(虽然MySQL没有这个概念)
常见问题解答
Q1: PostgreSQL的表和库是一个概念吗?
A: 不是。PostgreSQL有三级结构:
- Cluster (集群) → 相当于MySQL的Server
- Database (数据库) → 相当于MySQL的Database
- Schema (模式) → MySQL没有这个概念,但可以理解为Database内的命名空间
- Table (表) → 相同
Q2: public是什么意思?
A : public是PostgreSQL的默认Schema名称。当你创建表时,如果没有指定Schema,表会创建在public Schema中。这类似于MySQL中直接在Database中创建表。
Q3: 为什么要这样设计?
A: Schema设计的好处:
- 命名空间隔离: 可以在同一数据库内使用相同的表名
- 权限管理: 可以按Schema管理权限
- 逻辑组织: 可以将相关表组织在一起
- 多租户: 可以为不同租户使用不同Schema
迁移实战建议
迁移前准备
1. 环境准备
bash
# 安装PostgreSQL
# Windows: 下载安装包
# Linux:
sudo apt-get install postgresql postgresql-contrib
# 创建数据库
createdb myapp_db
# 连接数据库
psql -d myapp_db
2. 数据迁移工具
推荐工具:
- pgLoader: 自动从MySQL迁移到PostgreSQL
- mysqldump + 手动转换: 导出SQL后手动修改
- ETL工具: 如Pentaho、Talend等
3. 使用pgLoader迁移示例
bash
# 安装pgLoader
# Ubuntu/Debian
sudo apt-get install pgloader
# 迁移命令
pgloader mysql://user:password@localhost/mysqldb \
postgresql://user:password@localhost/pgdb
迁移检查清单
开始迁移 1. 数据类型转换 2. SQL语法转换 3. 索引重建 4. 约束检查 5. 函数/存储过程转换 6. 触发器转换 7. 权限设置 8. 性能测试 迁移完成
检查项列表
-
数据类型转换
- TINYINT(1) → BOOLEAN
- UNSIGNED INT → INT + CHECK约束
- DATETIME → TIMESTAMP
- ENUM → CHECK约束或自定义类型
-
SQL语法转换
- 字符串引号(单引号)
- LIMIT语法
- 日期函数
- 字符串函数
-
索引重建
- 前缀索引 → 完整索引或表达式索引
- 聚簇索引 → 非聚簇索引(注意性能影响)
-
约束检查
- 外键约束
- CHECK约束
- 唯一约束
-
函数/存储过程
- MySQL函数 → PostgreSQL函数
- 存储过程语法转换
-
触发器
- 触发器语法转换
- 触发器函数编写
常见迁移问题及解决方案
问题1: AUTO_INCREMENT迁移
sql
-- MySQL
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY
);
-- PostgreSQL解决方案
CREATE TABLE users (
id SERIAL PRIMARY KEY -- 最简单
);
-- 或
CREATE TABLE users (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY -- 更标准
);
问题2: 字符串比较大小写
sql
-- MySQL(不区分大小写)
SELECT * FROM users WHERE name = 'john';
-- PostgreSQL解决方案
SELECT * FROM users WHERE LOWER(name) = LOWER('john');
-- 或创建表达式索引
CREATE INDEX idx_lower_name ON users(LOWER(name));
SELECT * FROM users WHERE LOWER(name) = 'john'; -- 可以使用索引
问题3: GROUP BY严格性
sql
-- MySQL(可能允许)
SELECT department, name, COUNT(*)
FROM users
GROUP BY department;
-- PostgreSQL解决方案
SELECT department, name, COUNT(*)
FROM users
GROUP BY department, name; -- 必须包含所有非聚合列
-- 或使用ANY_VALUE(PostgreSQL 9.5+)
SELECT department, ANY_VALUE(name), COUNT(*)
FROM users
GROUP BY department;
迁移后优化建议
1. 更新应用程序代码
java
// Java示例:更新JDBC连接字符串
// MySQL
String url = "jdbc:mysql://localhost:3306/mydb";
// PostgreSQL
String url = "jdbc:postgresql://localhost:5432/mydb";
2. 更新ORM配置
xml
<!-- MyBatis配置 -->
<!-- MySQL -->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!-- PostgreSQL -->
<property name="driver" value="org.postgresql.Driver"/>
3. 性能优化
- 分析查询计划:使用
EXPLAIN ANALYZE - 创建合适的索引
- 调整PostgreSQL配置参数
总结
核心差异总结
| 方面 | MySQL | PostgreSQL | 建议 |
|---|---|---|---|
| 易用性 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | MySQL更易上手 |
| 功能完整性 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | PostgreSQL功能更丰富 |
| 标准兼容性 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | PostgreSQL更标准 |
| 性能(简单查询) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | MySQL略优 |
| 性能(复杂查询) | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | PostgreSQL更优 |
| 扩展性 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | PostgreSQL更可扩展 |
选择建议
选择MySQL如果:
- 团队熟悉MySQL
- 应用场景简单(Web应用)
- 需要快速上手
- 不需要复杂查询
选择PostgreSQL如果:
- 需要复杂查询和分析
- 需要严格的数据完整性
- 需要扩展功能(GIS、全文搜索等)
- 需要更好的标准兼容性
附录:快速参考表
SQL语法快速对照
| 操作 | MySQL | PostgreSQL |
|---|---|---|
| 字符串拼接 | CONCAT(a, b) |
`a |
| 字符串格式化 | DATE_FORMAT(date, '%Y-%m-%d') |
TO_CHAR(date, 'YYYY-MM-DD') |
| 日期加减 | DATE_ADD(date, INTERVAL 1 DAY) |
date + INTERVAL '1 day' |
| 分页查询 | LIMIT 10 OFFSET 20 或 LIMIT 20, 10 |
LIMIT 10 OFFSET 20 |
| 当前时间 | NOW() |
NOW() 或 CURRENT_TIMESTAMP |
| 不区分大小写 | 默认不区分 | ILIKE 或 LOWER() |
| 自增主键 | AUTO_INCREMENT |
SERIAL 或 IDENTITY |
数据类型快速对照
| MySQL | PostgreSQL | 说明 |
|---|---|---|
TINYINT(1) |
BOOLEAN |
布尔类型 |
INT UNSIGNED |
INT CHECK (col >= 0) |
无符号整数 |
DATETIME |
TIMESTAMP |
日期时间 |
TEXT |
TEXT |
大文本(PostgreSQL无长度限制) |
JSON |
JSONB |
JSON类型(推荐JSONB) |
ENUM |
CHECK约束 或 自定义类型 |
枚举类型 |