UDB-TDS 迁移 SQL Server 实践
前言
从传统的 SQL Server 数据库迁移可能非常耗时且需耗费大量资源,任何迁移都涉及三个主要步骤:移动架构、迁移数据和修改客户端应用程序。正如下图中我们所见:迁移数据库时,通常需要完成更多的工作,包括重写与数据库交互的应用程序代码,将 T-SQL 代码迁移到 PL/pgSQL 中,这是复杂、耗时且有风险的。
UDB-TDS 是 UDBTX 兼容版本的一项新功能,可以理解 Microsoft SQL Server 专有的 SQL 语言 T-SQL,并支持相同的通信协议,因此,修改 SQL Server 上运行的应用程序并将其移动到 UDBTX 所需的工作量将减少,从而可实现更快、风险更低且更具成本效益的迁移。
UDB-TDS 通过支持 UDBTX 的 Microsoft SQL Server 数据类型、语法和函数来支持 T-SQL 和 SQL Server 行为。但请注意,UDB-TDS 并不提供对 T-SQL 的100%完整支持,仍然有一些差异和限制,某些情况下需要做手工的代码转换。
本文将列举并演示一些高频及常见的典型代码转换案例,帮助您更高效快速地完成迁移工作。
环境准备
在开始我们的演示之前,假设在您的工作环境,已有一个准备迁移的 SQL Server 源库,那么除此之外,您还需要设置好以下相关的组件:
udbtxtds_compass
这是一个SQL Server 迁移到 UDB-TDS 的语法评估工具,它能在Windows和Linux平台下运行,需要 Java 环境支持,当前的版本是 v2024-04
UDB-TDS
您可以根据官方文档说明来操作,只需简单几步即可创建一个 UDB-TDS 集群环境。配置过程中需要注意的就是数据库迁移模式的选择,还有如果有中文数据的话那么在排序规则中请选择“chinese_prc_ci_as”
到目前为止,一个包含SQL Server源和UDB-TDS目标以及迁移评估工具的环境已经准备好。接下来,请参考本文的内容,您只需要花短短的几分钟就能生成一个UDB-TDS迁移评估报告。
代码转换
转换估计
udbtxtds_compass 工具生成的评估报告是评估迁移工作内容和工作量的指引,您可以根据其中列出的需要修改的项目,逐一编写 SQL 代码转换内容。
评估报告的Summary 章节列出了迁移 SQL Server 源到 UDB-TDS 目标的 T-SQL 的语法特性兼容统计,包括支持、不支持、语义审查、手动审查及可忽略项。其中最关键的是不支持特性的内容,这些含有不支持特性的 SQL 语句,如果不作修改,在 UDB-TDS 环境中大部分执行会报错:“‘???’ is not currently supported in UDB-TDS”,而其他的一些 SQL 语句虽然没有报错,但不会真正生效。
在评估报告中我们可以查看这些不支持特性的 SQL 分类统计,下图中的评估报告列出了每一类不支持特性的 SQL 语句,它显示了我们的案例所用的 DDL 脚本在 Babelfish 中不支持的特性主要有对表增加约束语句,Merge 语句、修改数据库、修改角色、执行某些系统存储过程等。
转换原则
UDB-TDS 为 UDBTX 数据库集群提供了一个额外的端点,使其能够了解 SQL Server 线路级协议和常用的 SQL Server 语句。迁移之后,您仍然可以使用相同的 T-SQL 开发工具和驱动,连接到TDS端口完成相关的开发。您也可以使用原生UDBTX 连接在 UDBTX 这一端做开发,再从 T-SQL 这端进行调用。这一种兼容模式,能帮助我们解决大部分的UDB-TDS 对 T-SQL 的兼容性问题。
选择转换模式:如上所述,对于部分不支持的 SQL 语句,我们可以选择在 T-SQL 中进行改写,也可以在 UDBTX 中修改再从 T-SQL 中调用。转换的原则是根据应用的连接开发模式而定,例如 .net 应用连接到 TDS 端开发,那么首选转换模式就是在T-SQL中进行转换。如果在T-SQL这端无法改写或存在修改后的性能问题,那么可以尝试在 UDBTX 中进行修改。
代码可读性:对于要修改的SQL语句,可能有好几种的改写方法。简单、高效、可读性好永远都是首选。例如,大部分情况下,使用Case语句比使用..Then更容易理解。
简单代码转换
归于这一类的代码转换,其特点就是修改简单,但其数量常常在评估报告中列出的所有不支持特性的 SQL 语句中占绝大部分。此类代码转换工作一般而言只需屏蔽相关选项、注释整条语句或简单修改即可。如此修改的原因,是缘于 UDBTX 和 SQL Server 的两者间的特性差异或 UDB-TDS 的限制。SQL Server 中的某些选项或操作,在 UDB-TDS 不支持且不会对功能执行有影响,可以直接忽略。虽然这类 SQL 语句改写简单,但能达到了相同的效果。
演示之前,让我们看看接下来都会使用到两张表的结构:
create table dept(
deptno int NOT NULL PRIMARY KEY,
dname varchar(14),
loc varchar(13)
)
create table employees (
empno int NOT NULL PRIMARY KEY,
ename varchar(10),
job varchar(9),
mgr int,
hiredate datetime,
sal money,
comm money,
deptno int
)
ALTER TABLE..CHECK CONSTRAINT
原语句
ALTER TABLE [dbo].[employees] WITH CHECK ADD CONSTRAINT [FK_DEPT] FOREIGN KEY([deptno])
REFERENCES [dbo].[dept] ([deptno])
GO
ALTER TABLE [dbo].[Employees] CHECK CONSTRAINT [FK_DEPT]
GO
修改后语句
ALTER TABLE [dbo].[employees] ADD CONSTRAINT [FK_DEPT] FOREIGN KEY([deptno])
REFERENCES [dbo].[dept] ([deptno])
GO
说明:
在 UDB-TDS 中不支持 CHECK CONSTRAINT 语句启用表的约束,ALTER 表添加约束后会自动启用约束
在 UDB-TDS 中添加约束时不支持 WITH CHECK/NOHECK 选项对已有数据进行约束检查
这种不支持的 ALTER TABLE 特性的语句是迁移过程中最常见的,一般是修改后在 UDB-TDS 上新建表和约束,再导入表的数据,表的约束会自动检查导入的数据,保证数据约束有效
ALTER ROLE..
原语句
ALTER ROLE [???] ADD MEMBER [NT AUTHORITY\SYSTEM]
GO
修改后语句(注释掉)
/* ALTER ROLE [???] ADD MEMBER [NT AUTHORITY\SYSTEM]
GO */
说明:
目前 UDB-TDS 只支持用户数据库中的 dbo 用户,您不能创建具有较低权限的用户,例如对某些表的只读权限
大部分此类语句都是用户以操作系统权限登陆 SQL Server源后倒出的 DDL 语句,可以直接注释屏蔽语句
ALTER DATABASE..
原语句
ALTER DATABASE [???] SET RECOVERY FULL
GO
修改后语句
/* ALTER DATABASE [???] SET RECOVERY FULL
GO */
说明:
UDB-TDS 不支持ALTER DATABASE 语法,Aurora PostgreSQL是一个全托管型数据库,会限制一些数据库修改语句,这些语句可以直接注释屏蔽
ALTER AUTHORIZATION ON object
原语句
ALTER AUTHORIZATION ON [dbo].[employees] TO SCHEMA OWNER
GO
修改后语句
/* ALTER AUTHORIZATION ON [dbo].[employees] TO SCHEMA OWNER
GO */
说明:
UDB-TDS 不支持AUTHORIZATION的创建、修改和删除,可以直接注释屏蔽
EXEC sys.sp_addextendedproperty
原语句
EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'编号' ,
@level0type=N'SCHEMA',@level0name=N'dbo',
@level1type=N'TABLE',@level1name=N'dept',
@level2type=N'COLUMN',@level2name=N'deptno'
GO
修改后语句(在 UDBTX 端修改)
COMMENT ON COLUMN dept.deptno IS '编号';
说明:
UDB-TDS 不支持使用系统存储过程 sp_addextendedproperty 为字段增加说明,可以直接注释屏蔽此 SQL 语句,并连接到 UDBTX 端使用comment增加字段说明
OBJECTPROPERTY
原语句
select name from sysobjects where objectproperty(id, N'IsTable') = 1 and name not like N'#%%' order by name
select * from sysobjects where id = object_id(N'temp_tableSpaceInfo') AND objectproperty(id, N'IsUserTable') = 1
修改后语句
select name from sysobjects where xtype in ('U','IT','S') and name not like N'#%%' order by name
select * from sysobjects where id = object_id(N'temp_tableSpaceInfo') AND xtype='U'
说明:
UDB-TDS 不支持内置的元数据函数 OBJECTPROPERTYEX,可根据SQL语义进行适当改写
SET ROWCOUNT
原语句
CREATE PROCEDURE [dbo].[P_Rowcount]
@id int
as
set nocount on
set rowcount @id
begin
select * from employees order by empno
end
GO
修改后语句
CREATE PROCEDURE [dbo].[P_Rowcount]
@id int
as
set nocount on
begin
select top (@id) * from employees order by empno
end
GO
第二种修改
CREATE PROCEDURE [dbo].[P_Rowcount]
@id int
as
set nocount on
begin
select * from employees order by empno offset 0 rows fetch first @id rows only;
end
GO
说明:
UDB-TDS 不支持 SET ROWCOUNT 语句来返回指定的行数,可根据 SQL 语义进行适当改写。从示例中我们看到可以有多种的改写方法,在业务复杂的场景下应从代码的可读性和性能影响方面做选择
CURRENT OF
原语句
CREATE PROCEDURE [dbo].[P_CurrentOf] AS
BEGIN
DECLARE @empno int
DECLARE NoResponce CURSOR FOR
SELECT empno FROM employees;
OPEN NoResponce;
FETCH NEXT FROM NoResponce INTO @empno;
DELETE FROM employees WHERE CURRENT OF NoResponce;
END
GO
修改后语句
CREATE PROCEDURE [dbo].[P_CurrentOf] AS
BEGIN
DECLARE @empno int
DECLARE NoResponce CURSOR FOR
SELECT empno FROM employees;
OPEN NoResponce;
FETCH NEXT FROM NoResponce INTO @empno;
DELETE FROM employees WHERE empno = @empno;
END
GO
备注:
Where Current Of 语句允许您更新或者是删除最后由 cursor 取的记录,Babelfish 不支持 Current Of 语句,可根据 SQL 语句上下文语义选取变量
IDENTITY
原语句
SELECT IDENTITY(INT,1,1) AS rowid,* INTO #tmp
FROM employees
ORDER BY empno
修改后语句
SELECT row_number() over () as rowid, * INTO #tmp
FROM employees
ORDER BY empno
说明:
UDB-TDS 不支持 IDENTITY 函数,用于在带有 INTO 子句的 SELECT 语句中将标识列插入到新表中,可使用row_number() over ()方式改写
复杂代码转换
相对于前面介绍简单代码转换,接下来的这些 SQL 语句会复杂一些,修改内容也比较多。同时,您还需要仔细地审查SQL 语句中上下文之间的关系,以确保修改后的语句和原语句执行得到相同的效果。
MERGE
在这个案例演示之前,创建两张 MERGE 使用的源表和目标表
create table source
(
id int not null primary key ,
country varchar(20) null,
city varchar(20)
);
insert into source
(id, country, city)
VALUES
(1, 'RUSSIA', 'MOSCOW'),
(2, 'FRANCE', 'PARIS'),
(3, 'ENGLAND', 'LONDON'),
(4, 'USA', 'NEW YORK'),
(5, 'GERMANY', 'BERLIN'),
(6, 'BRAZIL', 'BRASILIA');
create table target
(
id int not null primary key ,
country varchar(20) null,
city varchar(20)
);
insert into target
(id, country, city)
VALUES
(1, 'JAPAN', 'TOKYO'),
(4, 'USA', 'DENVER'),
(7, 'CHINA', 'BEI JING');
原语句
MERGE INTO target AS C2
USING source AS C1
ON C2.id = C1.id
WHEN MATCHED
THEN UPDATE
SET
C2.country = C1.country,
C2.city = c1.city
WHEN NOT MATCHED
THEN INSERT (id, country, city)
VALUES (C1.id, C1.country, C1.city);
修改后语句
begin
update target set country = C1.country, city = C1.city from (select id, country, city from source) C1 where target.id = C1.id;
insert into target (id, country, city) select * from source as C1 where not exists (select id from target where id = C1.id);
end
go
第二种修改(在UDBTX端修改)
with upsert as
(update target c2 set country=c1.country, city=c1.city
from source c1 where c1.id=c2.id
RETURNING c2.*
)
insert into target select a.id, a.country, a.city
from source a where a.id not in (select b.id from upsert b);
说明:
MERGE 是常用的一种数据合并更新语句,Babelfish 不支持 MERGE 语句,一般来说可根据 SQL 语义在 T-SQL 中拆分成多个 DML 语句,也可以在 PostgreSQL 端进行等价的改写。
UDBTX 目前还不支持 MERGE 语句,可以使用 UPSET 或 CONFLICT 语句实现,INSERT ON CONFLICT 的执行开销要小于UPDATE 语句
FULLTEXT 全文搜索
UDB-TDS 不支持 SQL Server的全文搜索,不支持以下的语句及系统存储过程
CREATE、ALTER、DROP FULLTEXT CATALOG
CREATE、ALTER、DROP FULLTEXT INDEX
CREATE、ALTER、DROP FULLTEXT STOPLIST
exec sp_fulltext_database 'enable';
UDBTX 增加了对 pg_bigm 扩展程序的支持。pg_bigm 扩展程序在 UDB-TDS 中提供有全文搜索功能。此扩展程序允许用户创建 2-gram(双组),以提高全文搜索速度。以下案例演示如何在UDBTX端通过扩展启用全文搜索功能
set search_path=dbo;
create extension pg_bigm;
CREATE TABLE fulltext_doc (doc text);
INSERT INTO fulltext_doc VALUES('Babelfish助力SQL迁移 成本优化');
INSERT INTO fulltext_doc VALUES('Babelfish助力SQL迁移 性能优化');
INSERT INTO fulltext_doc VALUES('Babelfish助力SQL迁移 提升使用体验');
INSERT INTO fulltext_doc VALUES('Babelfish助力SQL迁移 中提供 2-gram 全文搜索功能的工具');
INSERT INTO fulltext_doc VALUES('Babelfish助力SQL迁移 中提供 3-gram 全文搜索功能的工具');
CREATE INDEX fulltext_doc_idx ON fulltext_doc USING gin (doc gin_bigm_ops);
alter table fulltext_doc owner to dbo;
全文搜索设置成功后可以在 TDS 端口通过 T-SQL 调用
SWITCHOFFSET
原语句
SELECT CAST(SWITCHOFFSET(TODATETIMEOFFSET(SYSUTCDATETIME(),’+00:00’),’+08:00’) AS DATETIME)
修改后语句
CREATE OR REPLACE FUNCTION dbo.f_get_cst()
RETURNS sys.datetime AS $$
BEGIN
RETURN cast(timezone('Asia/Shanghai',now()) as sys.datetime);
END;
$$ LANGUAGE plpgsql;
说明:
UDB-TDS 不支持SWITCHOFFSET和TODATETIMEOFFSET之类的时区偏移量内置函数,可以在UDBTX端创建自定义函数并在TDS端口通过T-SQL调用来实现相同功能
XML方法
在本案例演示之前,创建一张和 xml 解析相关的表
create table t_xml_test (
id int,
country nvarchar(max),
industry nvarchar(max)
);
insert t_xml_test values(1, 'China', 'Manufacturing and foreign trade business');
insert t_xml_test values(2, 'USA', 'Financial and Bioindustry');
insert t_xml_test values(3, 'Russia', 'Resource export');
原语句
create procedure p_xml_test
@xml xml
as
begin
set nocount on
select * from t_xml_test
where id in (select imgXML.Item.value('id[1]','int') from @xml.nodes('/root/country') as imgXML(Item));
set nocount off
end
go
修改后语句(首先在 UDBTX 端创建自定义函数解析 XML)
CREATE OR REPLACE FUNCTION xmlQueryID(in_xml xml)
RETURNS TABLE (id text)
AS $$
DECLARE
BEGIN
RETURN QUERY
select * from (
WITH xmldata(data) AS (VALUES (in_xml::xml))
SELECT xmltable.*
FROM XMLTABLE('/root/country' PASSING (SELECT data FROM xmldata) COLUMNS id text)) as foo;
END;
$$ LANGUAGE plpgsql;
连接 TDS 端口在 T-SQL 中修改 SQL 语句并调用 UDBTX 端创建的自定义函数
create procedure p_xml_test
@xml xml
as
begin
set nocount on
select * from t_xml_test
where id in (select * from xmlQueryID(@xml)) ;
set nocount off
end
go
在 T-SQL 中调用存储过程测试,查询结果显示 xml 解析正常,数据显示正确
说明:
UDB-TDS 不支持解析 XML 数据的方法,包括 VALUES、XML.NODES 和其他方法,可以在 UDBTX 端创建自定义函数并在 TDS 端口通过 T-SQL 调用来完成 XML 数据的解析工作
总结
通过前面的案例介绍,我们为您展示了使用 UDB-TDS 迁移 SQL Server时一些最常见的不支持特性 SQL 的转换方法。当前,UDB-TDS续向前发展,版本在不断更新。每个新的版本都会添加一些重要的功能,包括增加语法的兼容和 SQLServer 原生功能的支持。建议您在规划和实施 SQL Server 迁移时经常检查 UDB-TDS 的特性支持说明,使用最新的特性支持来完成代码的转换。