SQL Server 迁电科金仓:那三周我记下的真事
先说背景,不是 demo。
源端 SQL Server 2017,业务库 650G,220 多张表,存储过程上百个,触发器、游标、表变量满地都是。跑的是制造业 ERP,白天高并发,夜里批处理。割接窗口只给凌晨两点到四点。
目标库是电科金仓 KingbaseES,后面就叫 KES。
我带两个后端、一个 DBA 老张,干了三周。下面按时间线写,想到哪说到哪,不按教科书来。
第一晚就翻车
我一开始特自信,觉得 KDTS 一拖完事。老张在旁边说:“你兼容模式开了没?”
我愣了。没开。
ALTER SYSTEM SET compatible_sqlserver = 'on'; ALTER SYSTEM SET sqlserver_compatible_version = '150'; SELECT sys_restart_db();重启完再跑 KDMS 评估,原来红了一屏的“不兼容”直接消掉大半。TOP、[]、GETDATE()、#temp全认了。老张端着咖啡说:“没穿鞋就跑步。”
从那以后我们组死规矩:迁移评审第一项,查兼容模式。没开,评估报告作废。
顺带说,KES 的系统视图也做了映射,
sys.objects、sys.columns那些,应用里靠元数据扫表结构的代码不用重写,这点挺省心。
建表:强迫症发作
原库订单表:
CREATE TABLE Sales.Orders ( OrderID INT IDENTITY(1,1) PRIMARY KEY, OrderNo NVARCHAR(32) NOT NULL, Amount DECIMAL(18,2) NOT NULL, Status TINYINT NOT NULL DEFAULT 0, OrderDate DATETIME DEFAULT GETDATE() );兼容模式开着,粘过去能跑。但我看到NVARCHAR就手痒,非得改成VARCHAR。KES 默认 UTF-8,中文不需要 N 前缀,留着只会让后来的人困惑。
最后落地的版本:
CREATE TABLE Sales.Orders ( OrderID INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, OrderNo VARCHAR(32) NOT NULL, Amount NUMERIC(18,2) NOT NULL, Status SMALLINT NOT NULL DEFAULT 0, OrderDate TIMESTAMP DEFAULT CURRENT_TIMESTAMP );TINYINT升到SMALLINT,以前有个项目用 byte 映射状态字段,一算溢出,查了半天才定位到。DATETIME换TIMESTAMP,跟应用层 DateTime 对接时毫秒精度不打架。
类型映射我们记了张表,后面新表全照这个来:
SQL Server | KES |
|---|---|
NVARCHAR(n) | VARCHAR(n) |
DATETIME | TIMESTAMP |
DECIMAL(18,2) | NUMERIC(18,2) |
UNIQUEIDENTIFIER | UUID 兼容类型 |
BIT | BOOLEAN |
rowversion 那玩意儿我们一开始建在 test 库报错,后来切到业务库才正常。这种坑文档不会写,得自己撞。
插数据:原脚本一个字没动
INSERT INTO Sales.Orders (CustomerID, OrderNo, Amount, Status) VALUES (1001, 'SO-2603-001', 12800.00, 1), (1001, 'SO-2603-002', 4600.00, 1), (1002, 'SO-2603-003', 23500.00, 2);要手填自增主键就开开关:
SET IDENTITY_INSERT Sales.Orders ON; INSERT INTO Sales.Orders (OrderID, CustomerID, OrderNo, Amount, Status) VALUES (9999, 1005, 'SO-MANUAL-001', 100.00, 1); SET IDENTITY_INSERT Sales.Orders OFF;注意这个开关是会话级的,跟 SQL Server 里行为有点差异。我们批处理脚本里特意显式关回来,不然后面会话串了容易出怪问题。
查询:TOP 居然直接过
SELECT TOP 10 [OrderID], [OrderNo], ISNULL(NULLIF(Status, 0), 9) AS StatusFlag FROM Sales.Orders WHERE OrderDate >= '2026-03-01' ORDER BY Amount DESC;跑出来的时候我还有点不敢信。方括号、ISNULL、NULLIF 全认。
新写的接口我尽量用标准写法LIMIT 10,老代码先冻着。别一口气全改,改多了容易出事,前端那哥们会骂人的。
联表更新,最怕翻车的地方直接过了
SQL Server 里那种UPDATE ... FROM JOIN的写法:
UPDATE o SET o.Status = 2 FROM Sales.Orders o JOIN Sales.Customers c ON o.CustomerID = c.CustomerID WHERE c.City = '杭州' AND o.Status = 1;KES 直接吃。我拿生产里一个两百多行的存储过程试水,里面七八个联表更新全过。那一刻后背汗下来了,真的。
删除要留痕:OUTPUT 救了审计
DELETE FROM Sales.Orders OUTPUT DELETED.OrderNo, DELETED.Amount WHERE Status = 9 AND OrderDate < '2025-01-01';更新时拿前后值:
UPDATE Sales.Orders SET Amount = Amount * 0.9 OUTPUT INSERTED.OrderNo, DELETED.Amount AS OldAmount, INSERTED.Amount AS NewAmount WHERE Status = 1 AND Amount > 20000;老王本来都写好重写方案了,看到这结果把文档删了,说白熬两晚。
分页:同秒数据坑过我
OFFSET FETCH没问题:
SELECT OrderID, OrderNo FROM Sales.Orders ORDER BY OrderDate DESC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY;但深层分页一定要带主键。有次前端翻页第三页开始重复,查了两小时发现是OrderDate有同秒数据,排序不稳定。后来我直接定规矩:分页必须ORDER BY OrderDate DESC, OrderID DESC。前端那哥们翻白眼,但数据确实再没重复过。
日期函数别用DATEADD硬写,换成:
SELECT CURRENT_TIMESTAMP - INTERVAL '7' DAY;临时表更不用管,#tmp、##global随便用,报表那堆存储过程基本零改动。
MERGE:同步作业省了大活
MERGE INTO Sales.Orders AS target USING Staging.OrdersDelta AS source ON target.OrderNo = source.OrderNo WHEN MATCHED THEN UPDATE SET target.Amount = source.Amount WHEN NOT MATCHED THEN INSERT (CustomerID, OrderNo, Amount, Status) VALUES (source.CustomerID, source.OrderNo, source.Amount, source.Status);我扫代码库,MERGE 出现四十多处。本来最怕这玩意儿,结果全过,一处没改。ETL 作业这块省了我们大概五天活。
存储过程:唯一得人工盯的
简单过程不用动。复杂点的,KDMS 标红哪个改哪个。
@@IDENTITY换LASTVAL(),RAISERROR换RAISE EXCEPTION,动态 SQL 用EXECUTE format(...)重写。老张复核了两周,二十几个高风险过程逐个过,这活没捷径。
割接校验别偷懒
行数一致不算完事,核心表一定做 hash:
SELECT OrderID, MD5(OrderNo || '|' || Amount || '|' || Status) AS h FROM Sales.Orders ORDER BY OrderID LIMIT 1000;源端和目标端对 hash,对不上就别切。有次差了 3 行,查出来是源库触发器在迁移窗口里又插了数据,KFS 没追到。重跑增量补齐,虚惊一场。
JDBC 驱动换掉:jdbc:kingbase8://192.168.10.60:54321/appdb。MyBatis 基本没动,有个 .NET 老系统用 Dapper,SQL 一个字没改,集成测试直接绿。
最后
三套系统跑下来,应用层改动平均不到 5%。电科金仓这个兼容模式,确实把 TOP、OUTPUT、临时表、联表更新这些脏活咽下去了。存储过程、触发器、复杂事务还是得人一行行看,但工具已经把最重的活扛了。
迁移这事儿,工具解决 80%,剩下 20% 是工程纪律。谁跟你说“一键零改造”,让他拿 650G ERP 跑一遍再说。