从 SQL Server 迁移到 PostgreSQL 不是把数据导过去就能上线那么简单。两种数据库在数据类型、函数名称、存储过程语法、事务行为等多个层面存在显著差异,如果前期评估不足,迁移后容易出现隐式转换错误、性能骤降甚至数据不一致。本文基于一次将约 200GB 业务库从 SQL Server 2016 迁移到 PostgreSQL 14 的实战过程,整理出一套可复用的迁移路径。

一、迁移前评估与对象兼容性盘点
迁移项目的第一步不是立即开始导数据,而是要摸清源库中有哪些对象、哪些类型和哪些特性在 PostgreSQL 中不存在或行为不同。可以先在 SQL Server 中运行一段盘点脚本,统计表、视图、存储过程、函数、触发器、用户自定义类型、同义词等对象的数量,并列出所有特殊数据类型。例如查询用户表数量:
SELECT COUNT(*) AS table_count FROM sys.tables; SELECT COUNT(*) AS proc_count FROM sys.procedures; SELECT COUNT(*) AS view_count FROM sys.views;
接下来要重点检查 SQL Server 专有的数据类型。比如 datetime2、datetimeoffset、uniqueidentifier、money、geography 等,在 PostgreSQL 中需要映射为 timestamp、timestamptz、uuid、numeric、geometry(配合 PostGIS)等。如果源库大量使用 uniqueidentifier 作为主键,迁移后直接使用 PostgreSQL 的 uuid 类型是自然的,但要注意生成方式差异:SQL Server 常用 NEWID(),而 PostgreSQL 可以借助 gen_random_uuid()(需要 pgcrypto 扩展)或应用层生成。盘点时还要关注计算列、默认值、约束命名是否依赖 SQL Server 的特定行为。
存储过程和函数是迁移中工作量最大的部分。SQL Server 使用 T-SQL,而 PostgreSQL 使用 PL/pgSQL,两者虽然都有过程式编程能力,但参数传递、结果集返回、错误处理语法完全不同。如果源库包含大量存储过程,建议先统计行数和复杂度,评估是用自动化工具转换还是人工重写。视图和触发器也要逐个检查,尤其注意触发器在 SQL Server 中默认是语句级,而 PostgreSQL 支持语句级和行级,迁移后触发逻辑的语义可能发生变化。
二、T-SQL 与 PL/pgSQL 的核心差异与改写
语法差异是迁移过程中最先暴露的问题。SQL Server 的 TOP 子句在 PostgreSQL 中对应 LIMIT;ISNULL(expr, default) 对应 COALESCE(expr, default);日期函数 GETDATE() 对应 NOW() 或 CURRENT_TIMESTAMP。下表列出几个常见函数的映射关系:
| SQL Server | PostgreSQL |
|---|---|
GETDATE() | NOW() |
DATEADD(day, 1, col) | col + interval '1 day' |
DATEDIFF(day, a, b) | EXTRACT(day FROM (b - a)) |
ISNULL(a, b) | COALESCE(a, b) |
TOP 10 | LIMIT 10 |
IDENTITY(1,1) | GENERATED ALWAYS AS IDENTITY |
分页查询是应用系统最常用的功能之一。SQL Server 2012 及以上版本支持 OFFSET ... FETCH NEXT,而 PostgreSQL 使用 LIMIT ... OFFSET。例如 SQL Server 中取第 21 到 30 行:
SELECT * FROM orders WHERE order_date >= '2023-01-01' ORDER BY order_id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
对应 PostgreSQL 写法为:
SELECT * FROM orders WHERE order_date >= '2023-01-01' ORDER BY order_id LIMIT 10 OFFSET 20;
存储过程改写是另一大难点。T-SQL 的存储过程可以简单地返回一个结果集,而 PostgreSQL 的函数必须声明返回类型,返回结果集使用 RETURNS SETOF 或 RETURNS TABLE。下面是一个获取最近订单的简单存储过程在两种数据库中的写法对比。
-- SQL Server 存储过程
CREATE PROCEDURE GetRecentOrders
@CustomerId INT
AS
BEGIN
SELECT TOP 10 *
FROM orders
WHERE customer_id = @CustomerId
ORDER BY order_date DESC;
END;
-- PostgreSQL 函数
CREATE OR REPLACE FUNCTION get_recent_orders(p_customer_id INT)
RETURNS SETOF orders
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT *
FROM orders
WHERE customer_id = p_customer_id
ORDER BY order_date DESC
LIMIT 10;
END;
$$;
错误处理方面,T-SQL 使用 TRY...CATCH,而 PL/pgSQL 使用 BEGIN ... EXCEPTION ... END 块。需要注意,PostgreSQL 的 EXCEPTION 块会引入额外的开销,不建议在热点路径中频繁使用。动态 SQL 执行中,SQL Server 的 sp_executesql 在 PostgreSQL 里对应 EXECUTE ... USING,并且必须拼接安全参数,防止 SQL 注入。
三、数据迁移策略与一致性校验
数据迁移方案取决于停机窗口和源库是否允许持续写入。对于允许停机数小时的场景,一次性全量迁移加启动前校验即可;对于要求尽量缩短停机时间的场景,可以采用全量加增量同步的方式。pgloader 是一个支持从 MS SQL Server 直接读取数据的开源工具,能够自动转换部分架构并加载数据。一个基本的 pgloader 命令示例如下:
pgloader sqlserver://user:password@source_host/dbname postgresql://user:password@target_host/dbname
使用 pgloader 时需要通过 ODBC 或 TDS 连接 SQL Server,具体取决于编译时的依赖。它能够自动处理一些类型转换,但存储过程和函数仍然需要手动迁移。如果源库体积较大,建议将全量数据导出为 CSV 文件,再通过 COPY 命令并行加载到 PostgreSQL,这样速度更快且容易控制。对于增量同步,可以借助 SQL Server 的 CDC 或时间戳列,定期抽取变更数据写入 PostgreSQL 的临时表,再通过 upsert 合并。
数据迁移完成后,必须进行一致性校验。最简单的方式是分别统计源库和目标库中每个业务表的行数,并对比特定核心字段的聚合值。例如在 SQL Server 中计算主键的校验和:
SELECT COUNT(*) AS row_cnt, SUM(CAST(order_id AS BIGINT)) AS id_sum FROM orders;
在 PostgreSQL 中执行同样的聚合:
SELECT COUNT(*) AS row_cnt, SUM(order_id::bigint) AS id_sum FROM orders;
行数和聚合值一致只能证明数据没有大量丢失,对于高精度校验,可以使用 md5 哈希或导出 CSV 后对比文件。NULL 值在哈希计算中需要统一处理,否则会得到不同结果。建议在迁移前就确定校验口径,并在测试环境演练一次完整流程。
四、迁移后性能调优与易踩的坑
数据迁移完成后,应用可能会遇到查询变慢的情况。这与查询计划器、索引策略以及统计信息有关。SQL Server 的查询优化器基于成本模型,PostgreSQL 的优化器同样基于成本,但两者的默认配置和统计采样不同。迁移后应立即运行 ANALYZE 命令更新统计信息,并检查关键查询的执行计划。PostgreSQL 使用 EXPLAIN ANALYZE 可以同时看到预估和实际成本。
索引方面,SQL Server 支持创建包含非键列的覆盖索引,例如 CREATE INDEX ... INCLUDE (col1, col2)。PostgreSQL 从 11 版本开始也支持 INCLUDE 子句,语义相近。但注意,PostgreSQL 的 B-tree 索引默认不存储表数据,只有索引字段可用,因此迁移后需要重新评估哪些查询依赖覆盖索引。如果 SQL Server 中有很多堆表(没有聚集索引),迁移到 PostgreSQL 后建议为每张表指定合适的主键或创建聚集索引,以提高范围扫描效率。
事务隔离级别差异也容易导致行为变化。SQL Server 默认 READ COMMITTED 使用锁来避免脏读,而 PostgreSQL 在 READ COMMITTED 下使用 MVCC 快照,读操作不会阻塞写操作,但一个事务内多次读取同一行可能看到不同版本。如果应用依赖 SQL Server 的可重复读语义,需要将事务级别调整为 REPEATABLE READ 或使用显式锁。另外,PostgreSQL 区分大小写,而 SQL Server 默认不区分,这会影响 WHERE name = 'Alice' 的匹配结果,需要使用 citext 扩展或 lower() 索引来保持兼容。
最后是数据库参数调优。PostgreSQL 默认的 shared_buffers、work_mem、maintenance_work_mem 等参数比较保守,迁移完成后应根据服务器内存和负载情况调整。例如 work_mem 影响排序和哈希操作的性能,如果设置过小,大查询会频繁溢出到磁盘。建议在测试环境使用 pgbench 或真实业务压力进行调优,再应用到生产环境。
从 SQL Server 迁移到 PostgreSQL 是一项系统工程,需要兼顾架构转换、代码改写、数据校验和性能验证。提前做好兼容性评估,设计合理的迁移窗口,并充分测试每一类数据库对象,才能把风险降到最低。上述经验来自一个生产库的成功迁移,你可以根据自身环境调整细节。

