为什么又双叒要迁移数据库
很多团队从 SQL Server 迁到 PostgreSQL,原因无外乎:授权成本、跨平台(Linux 原生友好)、开源生态、云厂商支持。但迁移最难的不是「把数据搬过去」,而是两套方言的语法差异——一个写惯了 TOP 和 GETDATE() 的人,第一次写 PG 会被 LIMIT 和 NOW() 之外的一堆细节绊倒。
本文按「高频差异 → 迁移踩坑」梳理,帮你少踩坑。
一、自增主键:IDENTITY vs SERIAL / GENERATED
-- SQL Server
CREATE TABLE users (
id INT IDENTITY(1,1) PRIMARY KEY,
name NVARCHAR(100)
);
INSERT INTO users(name) VALUES('张三'); -- 不用管 id
-- PostgreSQL(推荐 GENERATED AS IDENTITY,语义最贴近 IDENTITY)
CREATE TABLE users (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(100)
);
-- 老写法 SERIAL 也可用,但 GENERATED 更符合标准、可控更强
二、分页:TOP vs LIMIT/OFFSET
-- SQL Server
SELECT TOP 10 * FROM orders ORDER BY id DESC;
-- PostgreSQL
SELECT * FROM orders ORDER BY id DESC LIMIT 10 OFFSET 20;
深分页性能上两者都要注意,大数据量建议用「游标分页」(基于上一页最后 id)。
三、字符串与日期函数对照
| 场景 | SQL Server | PostgreSQL |
|---|---|---|
| 当前时间 | GETDATE() / SYSDATETIME() |
NOW() / CURRENT_TIMESTAMP |
| 字符串拼接 | 'a' + 'b'(注意 NULL 变 NULL) |
'a' || 'b' |
| 判空合并 | ISNULL(a,b) |
COALESCE(a,b) |
| substring | SUBSTRING(s,1,3) |
SUBSTRING(s,1,3)(同) |
| 类型转换 | CONVERT(VARCHAR, x) |
x::VARCHAR 或 CAST(x AS VARCHAR) |
| 获取日期部分 | DATEPART(yy, d) |
EXTRACT(YEAR FROM d) |
四、存储过程与匿名块
SQL Server 的 CREATE PROCEDURE 与 PG 的 CREATE FUNCTION/PROCEDURE 语义相近,但语法细节差异大。PG 更强调函数返回类型(RETURNS TABLE 等)。大量依赖存储过程的老系统,这块是迁移工作量大头。
-- PostgreSQL 函数示例
CREATE FUNCTION get_active_users() RETURNS TABLE(id INT, name VARCHAR) AS $$
BEGIN
RETURN QUERY SELECT u.id, u.name FROM users u WHERE u.active = true;
END;
$$ LANGUAGE plpgsql;
五、其他高频坑
- 标识符大小写:PG 默认把未加引号的标识符转小写,SQL Server 不敏感。迁移后注意表名/列名大小写。
- NULL 与空字符串:PG 区分
''和NULL,SQL Server 的''在某些比较里接近 NULL,逻辑要重写。 - 临时表:SQL Server
#temp与会话绑定;PG 用CREATE TEMPORARY TABLE,注意生命周期。 - 锁与隔离级别:两者默认隔离级别不同(SQL Server 读已提交快照 vs PG 读已提交),高并发逻辑需复核。
六、迁移工具与流程建议
- 结构迁移:用
pgloader、SQL Server Migration Assistant (SSMA)或ora2pg思路的工具做 schema 转换,但务必人工校对。 - 数据迁移:先小表后大表,大表用分批/并行;迁移期间源库只读或双写。
- 回归验证:行数、聚合值、抽样字段逐一比对,别只信「跑完了」。
- 灰度切换:先读流量切 PG,观察稳定后再切写。
小结
SQL Server → PostgreSQL 的难点集中在方言差异和存储过程重写,而非数据本身。把本文的差异表贴在迁移 wiki 上,配合自动化比对,绝大多数坑都能在测试阶段解决,避免在生产炸雷。




