MySQL与PostgreSQL深度对比

概述

为什么需要了解PostgreSQL?

作为MySQL开发者,你可能已经习惯了MySQL的简洁和易用性。但在某些场景下,PostgreSQL提供了更强大的功能和更好的标准兼容性。本文将从MySQL开发者的视角,帮助你理解两者的差异,避免常见的"坑"。

快速对比表

特性 MySQL PostgreSQL
类型 关系型数据库 关系型数据库(支持对象关系)
许可证 GPL/商业许可 PostgreSQL License(类似BSD)
SQL标准 部分支持 高度兼容SQL标准
存储引擎 多种(InnoDB、MyISAM等) 单一存储引擎
ACID支持 InnoDB支持 完全支持
并发控制 MVCC(InnoDB) MVCC(多版本并发控制)
扩展性 有限 高度可扩展(支持自定义函数、类型等)

设计理念对比

MySQL的设计理念

MySQL的设计哲学是**"简单、快速、可靠"**:

  1. 易用性优先: 默认配置即可快速上手
  2. 性能优先: 在简单场景下性能优异
  3. 灵活性: 提供多种存储引擎选择
  4. Web应用友好: 为Web应用场景优化

PostgreSQL的设计理念

PostgreSQL的设计哲学是**"功能完整、标准兼容、可扩展"**:

  1. 标准兼容: 严格遵循SQL标准
  2. 功能丰富: 内置大量高级特性
  3. 可扩展性: 支持自定义函数、类型、操作符
  4. 数据完整性: 严格的类型检查和约束

架构差异

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');

迁移建议:

  • 注意大小写敏感性差异
  • 使用ILIKELOWER()/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的索引建议
  1. 不要期望前缀索引

    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));
  2. 利用部分索引优化

    sql 复制代码
    -- 如果经常查询活跃用户
    CREATE INDEX idx_active_users ON users(name) WHERE is_active = TRUE;
  3. 利用表达式索引

    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的事务建议
  1. 注意默认隔离级别差异

    sql 复制代码
    -- MySQL习惯: REPEATABLE READ防止幻读
    -- PostgreSQL默认: READ COMMITTED,可能出现幻读
    
    -- 如果需要防止幻读,显式设置
    SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
    BEGIN;
    -- 事务操作
    COMMIT;
  2. 利用PostgreSQL的MVCC优势

    sql 复制代码
    -- PostgreSQL的读操作不会阻塞写操作
    -- 可以放心地进行长时间查询,不会影响写入性能
  3. 注意锁的使用

    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的数据类型建议
  1. 使用TEXT替代VARCHAR(很大值)

    sql 复制代码
    -- MySQL习惯
    CREATE TABLE articles (
        content VARCHAR(65535)
    );
    
    -- PostgreSQL推荐
    CREATE TABLE articles (
        content TEXT  -- 无长度限制,更灵活
    );
  2. 使用JSONB替代JSON(如果需要查询)

    sql 复制代码
    -- PostgreSQL推荐
    CREATE TABLE users (
        profile JSONB  -- 查询性能更好
    );
  3. 利用数组类型

    sql 复制代码
    -- 不需要创建关联表存储多值
    CREATE TABLE products (
        tags TEXT[]  -- 直接存储标签数组
    );
  4. 注意布尔类型

    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开发者的建议:

  • 大多数情况下,只需要使用public schema即可
  • 将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设计的好处:

  1. 命名空间隔离: 可以在同一数据库内使用相同的表名
  2. 权限管理: 可以按Schema管理权限
  3. 逻辑组织: 可以将相关表组织在一起
  4. 多租户: 可以为不同租户使用不同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. 性能测试 迁移完成

检查项列表
  1. 数据类型转换

    • TINYINT(1) → BOOLEAN
    • UNSIGNED INT → INT + CHECK约束
    • DATETIME → TIMESTAMP
    • ENUM → CHECK约束或自定义类型
  2. SQL语法转换

    • 字符串引号(单引号)
    • LIMIT语法
    • 日期函数
    • 字符串函数
  3. 索引重建

    • 前缀索引 → 完整索引或表达式索引
    • 聚簇索引 → 非聚簇索引(注意性能影响)
  4. 约束检查

    • 外键约束
    • CHECK约束
    • 唯一约束
  5. 函数/存储过程

    • MySQL函数 → PostgreSQL函数
    • 存储过程语法转换
  6. 触发器

    • 触发器语法转换
    • 触发器函数编写

常见迁移问题及解决方案

问题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 20LIMIT 20, 10 LIMIT 10 OFFSET 20
当前时间 NOW() NOW()CURRENT_TIMESTAMP
不区分大小写 默认不区分 ILIKELOWER()
自增主键 AUTO_INCREMENT SERIALIDENTITY

数据类型快速对照

MySQL PostgreSQL 说明
TINYINT(1) BOOLEAN 布尔类型
INT UNSIGNED INT CHECK (col >= 0) 无符号整数
DATETIME TIMESTAMP 日期时间
TEXT TEXT 大文本(PostgreSQL无长度限制)
JSON JSONB JSON类型(推荐JSONB)
ENUM CHECK约束自定义类型 枚举类型

相关推荐
JIngJaneIL2 小时前
停车场管理|停车预约管理|基于Springboot+的停车场管理系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·notepad++·停车场管理|
钮钴禄·爱因斯晨3 小时前
Python常见的文件操作
android·数据库·python
CPU NULL3 小时前
Redis相关知识点总结
java·数据库·spring boot·redis·缓存
懒羊羊不懒@3 小时前
【MySQL | 进阶】存储引擎
数据库·mysql
YUJIANYUE3 小时前
查立得PHP+Mysql影院选座式教室座位预定系统 v1.0
开发语言·mysql·php
BD_Marathon4 小时前
Hive初始化元数据库时报错:Unknown version specified for initialization: 3.1.0
数据库·hive·hadoop
ArabySide4 小时前
【Spring Boot】事务的回滚、传播与常见问题
数据库·spring boot
q***57504 小时前
Redis服务安装自启动(Windows版)
数据库·windows·redis
Databend4 小时前
DATA AI Databend Meetup 2025上海站邀您共话未来
数据库