SQL Server 2008迁移PostgreSQL的EDB迁移工具实战
手头一台Windows Server上还跑着SQL Server 2008业务方突然说要把数据迁到PostgreSQL第一反应多半是“这破库里的存储过程、触发器、自增列、类型映射手工搬得搬到什么时候”。但真别急着写脚本硬啃EnterpriseDB官方其实给了一套免费方案——EDB Migration Toolkit专门干SQL Server、Oracle、MySQL到PostgreSQL迁移的活。我用它实际走完过几次SQL Server 2008到PostgreSQL的迁移包括Windows环境下的完整流程今天把这套玩法掰开揉碎讲一遍包括那些官方文档里不会写的坑。这套工具最大的价值是它不只搬表结构连索引、主外键、视图、函数、存储过程、触发器这类对象都会尝试自动转换。对熟悉T-SQL但没怎么碰过PL/pgSQL的团队来说能省掉大量手写转换的体力活。而且它既可以连库直迁也可以先只生成迁移脚本、人工审查后再执行适合对数据安全要求高的项目。适合谁手里有老旧SQL Server库要往PostgreSQL挪的DBA和开发尤其Windows环境、库里有大量存储过程、需要保留约束和索引的场景。再说说我的建议EDB MTK不是一键无脑全自动它本质上是“半自动人工兜底”。走完一遍迁移报告出来那些标红的对象才是你真正要花时间的部分。所以最稳的路线是先用工具跑通全量结构再用报告逐项修复失败项最后手工做数据校验。下面按我的实际执行顺序展开。1. 工具定位与选型思路为什么先试EDB Migration Toolkit1.1 这个工具能做什么、不能做什么EDB MTK全称EDB Migration Toolkit也有人叫它MTK是EDBEnterpriseDB提供的免费迁移工具官方初衷是降低从商业数据库迁到PostgreSQL体系的门槛。它支持的对象范围比多数人预想的要大表、字段、主键、外键、唯一约束、检查约束、默认值、索引、视图、序列、函数/存储过程、触发器这些都会出现在迁移列表里。但不要把它想象成“只要点一下连同应用代码都给你改好”。它做的是“结构翻译”而非“业务重写”把SQL Server的T-SQL对象翻译成PostgreSQL能认的PL/pgSQL或SQL语句。翻译之后能不能跑通业务逻辑取决于你的存储过程里用了多少SQL Server专有写法。比如T-SQL里的GETDATE()、ISNULL()、TOP n、PRINT这类工具会转成PostgreSQL风格但如果你用了PIVOT、APPLY、WITH XMLNAMESPACES这类高级特性它十有八九会报不支持或转换失败需要你手动重写。它的输出有两种模式脚本模式Generate Script只生成迁移脚本不连目标库执行。适合备份、审查、版本管理。直迁模式Export Data Create Objects直接连目标库建对象、灌数据。我个人的习惯是——第一次跑或对象复杂的库一定先走脚本模式。因为直迁模式如果中途出问题目标库里会残留半成品对象清理起来比重新跑一遍还麻烦。1.2 版本兼容与前置依赖这套工具是用Java写的图形界面程序Windows下要先装好Java运行时JREJDK 8或11都行安装时注意PATH里能找到java命令。EDB官网下载MTK时会让你选对应数据库版本和位数下载包里有安装向导和示例脚本。需要特别留意的版本对应关系SQL Server 2008需要支持的JDBC驱动微软官方提供的sqljdbc 4.2或更高版本都可以注意把sqljdbc4.jar或sqljdbc42.jar放到MTK的lib目录下。目标PostgreSQL建议用10及以上版本低版本虽然能连但像identity列、部分窗口函数这类新特性支持不好。不同MTK版本对SQL Server 2008这种老版本的兼容性有差异如果连接测试报错试着换一个MTK的历史版本别死磕最新版。1.3 选型对比为什么不是手工脚本或其他工具手工写Python/SQL脚本迁移的最大问题是表和数据的搬运好办约束、索引、序列、触发器这些元信息要是手工抄几百个对象时极易遗漏。市面上其他开源工具我也试过比如从SQL Server导成中间格式再导入缺点是数据库专有类型和函数表达式在中间格式里容易失真。EDB MTK的优势在于它长期维护数据库方言转换表尤其对SQL Server到PostgreSQL的映射做得很细比如nvarchar(max)、datetime、bit这些老库常用类型的默认映射基本不用改就能用。这比从零手写一个转换器靠谱得多。不过它也有明显的软肋迁移后如果App层的SQL语句是强绑定SQL Server方言的那工具再厉害也管不到应用代码。所以选型前一定要明确边界——工具只解决数据库侧的对象迁移应用层SQL的兼容必须列入单独的工作项。2. 迁移前的系统盘点与环境准备2.1 源库资产盘点清单很多人一上来就装工具、建连接结果跑到一半发现漏了一堆东西。我自己的习惯是先花小半天把源库摸清楚这个步骤省不掉。建议整理出以下清单库总大小、表数量、单表最大行数超过千万行的表要单独评估。存储过程、函数、触发器、视图、序列的数量。是否有SQL Server Agent作业、SSIS包、复制、链接服务器等外围依赖。是否有自定义CLR类型、全文索引、分区表。其中最容易忽略的是SQL Server Agent作业和SSIS包——这类东西在EDB MTK里根本不会出现但它们往往承载着大量定时数据处理逻辑。迁完库发现报表没数据多半就是Job还在SQL Server上跑、连接串已经指向新库造成的。这部分必须由业务方确认是否另行重建。2.2 目标库初始化参数建议目标PostgreSQL建议单独建一个实例或数据库初始化时把编码设为UTF8这个和SQL Server的排序规则直接关系到后面中文乱码问题。建库语句类似CREATE DATABASE newdb WITH ENCODING UTF8 LC_COLLATE C LC_CTYPE C TEMPLATE template0;template0是为了避免模板库里的排序规则影响LC_COLLATE用C可以规避部分兼容性问题。但如果你对中文排序有特定要求这里就需要斟酌不能盲目照抄。另外PostgreSQL的check_function_bodies参数建议临时关闭因为迁移进来的函数体里可能带有尚未定义好的依赖对象在创建函数时不校验函数体可以避免一连串报错SET check_function_bodies off;等所有对象迁移完再重新开启。2.3 源库权限与网络检查EDB MTK要读取SQL Server的系统视图如sys.objects、sys.columns、sys.types这要求迁移账号至少有VIEW DEFINITION权限最好是db_owner或sysadmin。实际遇到最多的情况是账号能连库、能查数据但工具在读取元数据时报“没有权限查看对象”就是这个权限没给够。网络层面SQL Server 2008默认安装时TCP/IP协议通常没启用Windows防火墙也可能挡着1433端口。EDB MTK是远程连接方式所以需要先在SQL Server Configuration Manager里启用TCP/IP并确保防火墙放行。别小看这一步很多时候工具报“连接失败”不是因为配置问题而是源库根本没开远程连接。3. 使用EDB Migration Toolkit的完整迁移过程3.1 建连接与对象选择工具安装完启动后进入图形界面左右两侧分别配置源库和目标库连接。源库类型选Microsoft SQL Server填服务器地址、端口、实例名、账号密码目标库选PostgreSQL填连接信息。一般建议目标库账号用超级用户或具备建库建表权限的账号避免迁移过程中因为权限不足中断。连接测试通过后进入对象选择页面。这里会列出当前库所有schema对象默认全选。我建议按下面顺序勾选而不是一次性全选直接跑先只选表、序列、索引。再单独迁移视图。最后单独迁移存储过程/函数/触发器。原因很简单表的迁移最成熟、失败率最低先跑通这部分能快速检验配置和类型映射是否正确。存储过程的转换涉及T-SQL到PL/pgSQL的重写哪怕有工具辅助也可能有一批要手工处理。混在一起跑报告里几十个失败项混在一起排查起来体验很差。如果库很大、表很多工具也支持按schema或对象名过滤选择可以分批执行。每批跑完看报告比一次全跑完看几十页失败列表更容易定位问题。3.2 类型映射规则EDB MTK内置了SQL Server到PostgreSQL的默认类型映射但“默认”不代表“万能”。迁移前建议先在工具里过一遍映射表把不符合预期的改掉。以下是我常改的几个映射先看默认对应关系SQL ServerPostgreSQL默认备注intinteger常见无需改bigintbigint常见无需改smallintsmallint常见无需改tinyintsmallintPostgreSQL无tinyint注意值域bitboolean语义接近decimal/numericnumeric精度要核对moneynumeric(19,4)默认映射通常合理datetimetimestamp无时区时间戳datetime2timestamp精度更高注意微秒差异smalldatetimetimestamp注意原数据只到分钟级datedate一致charchar长度保留varcharvarchar长度保留nvarcharvarchar建议确认原库若存生僻字需小心varchar(max)text常见默认nvarchar(max)text常见默认texttext一致ntexttext常见默认imagebytea二进制大对象varbinarybytea二进制uniqueidentifieruuid语义一致xmlxmlPostgreSQL也有xmltimestamprowversion不支持/需改bytea特殊见下文sql_variant不支持需手工改text或拆列两个最容易出问题的类型timestamprowversion。SQL Server里的timestamp不是时间类型而是数据库自动生成的行版本号每次更新自动变。这个类型PostgreSQL没有对应物EDB MTK默认会报错或不转换。我一般的处理办法是把这类列改成bytea并去掉自动更新逻辑由应用写入一个类似xid的版本值。如果业务逻辑强依赖这个列做并发控制迁移前要单独评估方案。nvarchar/nchar。SQL Server上文默认字符集可能是UTF-16或本地编码PostgreSQL用UTF8。如果数据里有特殊字符比如某些GBK编码的汉字、全角符号直接迁过去可能出现长度不一致或截断。稳妥做法是迁移完成后抽几条含特殊字符的记录做比对同时把varchar长度适当放大避免因字符计算宽度的差异导致超长。3.3 执行迁移与生成物选完对象、确认映射后就可以执行。我建议的步骤是先执行“生成脚本”只让工具产出全套迁移SQL脚本。在目标库实例上打开脚本人工快速扫一遍有无明显错误。确认无大问题后再执行数据迁移。MTK生成的脚本是分文件的每个对象一个文件目录结构清晰方便针对单个失败对象重跑。数据迁移阶段可以设置批处理大小Batch Size默认值通常够用但千万级以上大表建议调小到5000~10000行一批不然目标库端内存压力大也容易触发磁盘IO尖峰。数据迁移完成后工具会输出执行报告包含每个对象的成功/失败状态。这里提个醒报告说“成功”不代表业务上一定正确它只代表“SQL语句执行完成了”。比如把GETDATE()转换成了now()执行是成功但如果你原本希望用事务开始时间而不是语句执行时间那业务语义就悄悄变了。所以报告只能作为参考真正的验证还要靠后续的数据校验。4. 迁移后的核心改造T-SQL到PL/pgSQL的语法重建4.1 高频语法差异对照EDB MTK负责“翻译”但翻译质量取决于工具内置规则。实际跑下来大概有七八成存储过程能直接转换成功剩下两三成需要人肉改。这些需要手工处理的点其实很有规律我整理了最常遇到的几组T-SQL写法PL/pgSQL写法GETDATE()now()或clock_timestamp()ISNULL(expr, val)COALESCE(expr, val)SELECT TOP 10 ...SELECT ... LIMIT 10PRINT msgRAISE NOTICE msg[column]方括号column双引号连接字符串ROWCOUNTGET DIAGNOSTICS affected_count ROW_COUNT;EXEC procCALL proc或PERFORM procWAITFOR DELAY 00:00:01pg_sleep(1)CAST(x AS INT)x::integer或CAST(x AS integer)临时表#tempCREATE TEMP TABLE tempIDENTITY(1,1)GENERATED BY DEFAULT AS IDENTITY或SERIALN字符串直接写字符串注意UTF8这里单独说下自增列。SQL Server 2008的IDENTITY列EDB MTK通常会转成PostgreSQL的SERIAL或BIGSERIAL这在功能上没问题。但如果你后续还需要用pg_dump做备份恢复且对自增序列的连续性有要求建议迁移后执行SELECT setval(pg_get_serial_sequence(表名, 列名), (SELECT MAX(列名) FROM 表名));这样能保证序列起点比现有最大ID大否则插入新记录时可能撞主键。这个步骤虽然简单但很容易漏。4.2 存储过程转换实战存储过程往往是最难的。举一个实际遇到的简化例子原来是T-SQLCREATE PROCEDURE GetUserOrders UserId INT, TopN INT 10 AS BEGIN SET NOCOUNT ON; SELECT TOP (TopN) o.OrderId, o.OrderDate, u.UserName FROM Orders o INNER JOIN Users u ON o.UserId u.UserId WHERE o.UserId UserId ORDER BY o.OrderDate DESC; END;EDB MTK大概会转成类似CREATE OR REPLACE FUNCTION GetUserOrders(p_userid integer, p_topn integer DEFAULT 10) RETURNS TABLE(order_id integer, order_date timestamp, user_name varchar) AS $$ BEGIN RETURN QUERY SELECT o.order_id, o.order_date, u.user_name FROM orders o INNER JOIN users u ON o.user_id u.user_id WHERE o.user_id p_userid ORDER BY o.order_date DESC LIMIT p_topn; END; $$ LANGUAGE plpgsql;注意几个变化存储过程变成了函数因为PostgreSQL传统上没有独立的“存储过程”概念11虽然有了PROCEDURE但MTK默认还是转成函数居多。输出参数变成了RETURNS TABLE(...)。过程体里的SELECT必须加上RETURN QUERY才能返回结果集。TOP (TopN)变成了LIMIT p_topn。如果你的业务代码里原来用EXEC GetUserOrders 100来调存储过程迁到PostgreSQL后调用方式也得变。用函数的话SQL里直接SELECT * FROM GetUserOrders(100)。这个应用层改动是避不开的。如果存储过程里用到多条SQL语句的结果集返回比如先查汇总再查明细两个SELECTMTK没法自动生成多个结果集的函数只能你自己拆。有几种常见做法把过程拆成多个函数逐个调用或者用OUT参数返回多个标量/游标。业务复杂时这部分的工作量可能比前面所有表迁移加起来都大务必提前给业务方打预防针。5. 验证策略与常见问题速查5.1 数据一致性验证方法迁移完最怕数据对不上。我的验证习惯分三层第一层是数量校验每个表执行SELECT COUNT(*)源库和目标库比对。可写成一段自动化的脚本循环比对不要人工一张张点。第二层是抽样校验关键业务表按主键抽样把每一行所有字段拼成字符串用MD5比较。可以用SQL做MD5拼接但注意字段顺序、类型转换后的空格问题。更简单的办法是导出成CSV后用diff或Excel比对。如果两张表的数据在迁移过程中个别字符被转坏这层基本能查出来。第三层是应用层冒烟测试让业务方拿一套典型业务用例跑一遍包括新增、修改、删除、报表查询、定时任务。这层能暴露的不只是数据问题还有存储过程和视图的逻辑问题。逻辑问题在对象迁移时可能“执行成功”但跑起来结果不对必须靠业务用例兜底。5.2 常见问题与排查表现象常见原因处理建议工具连不上SQL ServerTCP/IP未启用、防火墙挡1433、账号权限不足在SQL Server Configuration Manager启用TCP/IP放行防火墙确认db_owner权限连接慢或超时源库实例名解析问题、工具用旧JDBC驱动在连接串里指定端口确认sqljdbc版本匹配迁移后的中文乱码目标库编码不是UTF8建库时用UTF8重新迁移数据数据类型不兼容报错timestamprowversion、sql_variant等无对应类型改为bytea或text手工调整映射自增插入冲突序列起点落后于现有数据用setval同步序列存储过程转换失败多用了过多的T-SQL专有语法单独提取存储过程人工改写核心逻辑大表迁移极慢批处理大小不当、目标库未关闭约束调小批大小或先关约束/索引、迁移完再开外键创建失败表中存在不一致的关联数据先迁数据并清理脏数据再加外键还有一个容易忽略的点SQL Server 2008的数据库排序规则可能是大小写敏感的PostgreSQL默认大小写敏感但如果你原来依赖排序规则做中文拼音排序或特殊比较迁完之后排序结果可能和原来不一样。这类业务隐含依赖只有业务方跑真实查询才暴露得出来。5.3 一个不建议省掉的步骤预演迁移如果你有时间预算强烈建议在正式迁移前做一次完整的预演。从连接源库、生成脚本、数据迁移、验证报告整个过程走一遍不比正式迁移省多少事但价值巨大你会提前知道哪些对象是工具处理不了的、哪些存储过程需要重写、哪个大表迁移要多久。预演完你就能给业务方一个相对靠谱的时间估算而不是等到停机窗口里才慌慌张张去处理报错。预演时可以直接把目标库建在本地另一台环境机器上把正式迁移要做的步骤全部跑一遍包括最后的应用层冒烟测试。这个环境甚至可以保留作为回滚后的验证环境。最后分享一个我自己的操作体会EDB MTK最适合发挥价值的场景是“结构复杂但体量中等”的老旧SQL Server库——表几十上百张、存储过程二三十个、约束索引齐全。这种库手写迁移脚本非常痛苦工具能把八成工作自动做完剩下两成人工改。真正要小心的不是工具本身而是那些“看起来迁移成功但其实语义变了”的隐性差异比如时间类型精度、自增列序列、字符串拼接方式。所以不管工具报告多漂亮数据校验和应用测试都是绝对不能省的一步。如果手头正好也有类似迁移任务不妨先拿测试库跑一遍EDB MTK的脚本生成模式你很快就会发现原来让人头疼的T-SQL转PL/pgSQL大部分其实没那么可怕。