亚马逊AWS官方博客
写给开发者的迁移指南:如何将 Oracle 与 SQL Server 代码转换为 PostgreSQL
Original URL: https://amazonaws-china.com/cn/blogs/database/code-conversion-challenges-while-migrating-from-oracle-or-microsoft-sql-server-to-postgresql/
核子可乐译
PostgreSQL已经成为当下最流行的开源关系数据库系统之一。当客户从Oracle及微软SQL Server等商业数据库向外迁移时,PostgreSQL已经成为一大首选替代方案。目前,AWS为大家提供两种PostgreSQL托管选项:Amazon RDS与Amazon Aurora。
除了提供托管PostgreSQL服务之外,AWS还准备了一系列用于协助迁移的工具与资源。AWS Schema Conversion Tool(SCT)就是一款免费AWS工具,可帮助您转换现有schema,且同时支持多个源数据库与目标数据库。AWS Database Migration Service(数据库迁移服务,简称DMS)则用于在异构数据库与同构数据库之间完成数据的传输与连接复制。当然,AWS还提供迁移指导手册,其中包含关于商业数据库以及开源数据库(包括PostgreSQL)之间的大量功能映射说明。
在今天的文章中,我们将介绍从PL/SQL转换为PL/pgSQL的技巧与最佳实践,希望帮助大家在顺利将代码转换为PostgreSQL形式的同时,获取良好的数据库运行性能。本文主要面向从事数据库迁移的开发人员,并要求您预先掌握关于数据库及PL/SQL的基础知识。
性能考量
本节主要探讨一系列在从商业或传统数据库(例如SQL Server及Oracle)向PostgreSQL迁移时,可能对数据库性能造成影响的具体因素。虽然大部分数据库中包含类似的对象,但其中仍有部分对象可能在迁移之后影响到系统的运作方式。本节将向大家介绍如何通过调整存储流程、函数以及SQL语句获得更好的性能。
数据类型
为了避免不必要的返工,在正式启动迁移项目之前,大家需要将目标数据库中的数据类型与源系统正确映射起来。下表总结了从Oracle到SQL Server、再到PostgreSQL的一系列常见数据类型映射。
Oracle | PostgreSQL | SQL Server | 备注 |
Number | Small Integer | Tinyint / Smallint | 通常用于对数值受限的表进行查找。 |
Number | Integer / Bigint | Integer / Bigint | |
Number | Double Precision / Float / Numeric |
Double Precision / Float / Numeric |
对于金融等需要在应用内保存高精度值的使用场景,您可以将其配置为数字/小数。而在其他场景下,使用双精度或浮点数即可。 |
Varchar | Char(n) Varchar(n) Varchar Text Character varying |
Nchar Nvarchar Ntext |
|
Timestamp(6) | Timestamp without timezone | DateTime2(p) DateTime |
|
Clob | Text | ||
Blob | Raw | ||
Bytea | Binary, Image, VarBinary | ||
Boolean | Boolean | Bit | |
XML | XML | XML |
为什么要使用smallint/integer/bigint,而不直接使用数字?
要在数据库中获取最佳性能,选择最适合的数据类型非常重要。
如果您的表列中最多只能包含四位数字,那么具有2字节(smallint)的列数据类型就足以完成任务,意味着我们不必将其定义为4字节(整数/实数)、8字节(bigint/双精度)或者可变字节(数字)等更占资源的数据类型。
数值是一种可以容纳13万1千个数位的复杂类型,主要用于表示货币金额及其他少数需要极高精度的数量。但与整数类型或者浮点类型相比,数字的运算符处理速度很慢,因此计算速度也相当缓慢。
在下表的示例当中,我们可以看到在分别使用smallint/int/bigint建立无索引非精确列时,表整体大小发生的变化。
数据类型 | 大小 | 外部大小 | 插入值 |
numericsize | 16 KB | 8192 bytes | 插入numericsize值 (1234678) |
smallintsize | 8192 bytes | 0 bytes | 插入numericsize值(1234) |
intsize | 8192 bytes | 0 bytes | 插入numericsize值(123457) |
bigintsize | 8192 bytes | 0 bytes | 插入numericsize值(123486) |
下表使用与上表相同的信息,但包含索引。在此表中,大小指的是表的总体大小,外部大小则代表相关对象(例如索引)的大小。
数据类型 | 大小 | 外部大小 |
numericsize | 32 KB | 24 KB |
smallintsize | 24 KB | 16 KB |
intsize | 24 KB | 16 KB |
bigintsize | 24 KB | 16 KB |
AWS SCT在不了解实际数据大小的情况下,也能够将数字与表中的数字数据类型映射起来。这款工具还提供选项,帮助用户在转换过程中配置/映射正确的数据类型。
存储过程与函数
PostgreSQL 10及较早版本并不支持存储过程。Oracle与SQL Server中的所有存储过程与函数都将被映射为PostgreSQL中的函数。但从版本11开始,PostgreSQL也引入了存储过程支持,其基本原理与Oracle类似。
PostgreSQL支持三种波动函数类别,您需要在迁移当中根据函数特性指定适当的类别,即:Volatile
、Stable
与Immutable
。正确标记函数类别,有望给我们的数据库性能带来显著提升。
Volatile
下面是一条示例函数,用以说明执行Volatile
函数需要花费多长时间。
执行以下函数即可查看执行成本。
Stable
类型表示该函数无法修改数据库。此外,Stable还表示在单一表扫描操作当中,它对于相同的参数值将始终返回相同的结果,但具体结果可能会在不同SQL语句之间有所区别。如果需要创建一条结果取决于数据库查找或者参数变量(例如当前时区)的函数,那么Stable类型往往是理想的选择。current_timestamp
函数家族就是其中的典型代表,它们的值在事务执行过程中始终保持不变。
下面是一条示例函数,用以显示执行Stable函数需要花费多长时间。
执行以下函数即可查看执行成本。
Immutable
下面是一条示例函数,用以显示执行Immutable函数需要花费多长时间。
执行以下函数即可查看执行成本。
对各函数执行的测试结果表明,这些函数的基本功能完全相同,但其中Immutable函数的执行时长最短。这是因为Immutable类别允许优化程序在查询调用期间通过常量参数对该函数进行预评估。
视图与查询中的函数调用
许多应用程序都会使用包含函数调用的视图与查询。如上一节所述,在PostgreSQL当中,函数调用有可能占用大量资源,特别是在未能正确设置函数volatility类别的情况下。此外,函数调用本身也会增加相应的查询成本。
为此,我们需要根据函数功能为函数选择适当类别。如果您的函数更适合Immutable
或者Stable
条件,那么正确设置以取代默认的Volatile
将带来一定性能优势。
以下示例代码,为一条包含Volatile
函数调用的查询。
其中的getDeptname()
函数被标记为volatile。该查询的总运行时长为2秒886毫秒。
下面来看包含Stable
函数调用的查询示例。
其中的getDeptname()
函数被标记为stable,其总运行时长为2秒644毫秒。
以下示例代码将函数替换为功能。
函数逻辑被成功转换至新查询内,而总运行时长仅为933毫秒。
Exception优化
PostgreSQL允许用户使用Exception
与Raise
语句捕捉并触发错误的功能。这项功能虽然具有现实意义,但也要付出一定代价。Raise
语句会在PL/pgSQL函数的执行过程中引发错误与异常。在默认情况下,PL/pgSQL函数内部发生的任何错误都会导致执行中止以及变更回滚。为了从错误中正常恢复,PL/pgSQL可以使用Exception
子句捕捉具体错误。要实现这项功能,我们需要保证PostgreSQL在输入还有异常处理的代码段之前保存事务状态。这项操作会占用大量资源,因此间接增加了运行成本。
为了避免这部分成本,我们的建议是:要么在应用程序端捕捉异常,要么确保提前进行必要验证、使得函数永远不会发生异常。
以下代码示例展示了在函数调用中纳入异常,会给数据库性能造成怎样的影响。
如果大家无法在无异常状况下进行验证,那么异常将不可避免。在以上示例中,我们可以检查诊断结果以跟踪是否存在需要关注的变更。另外,如果可能,请尽量不要使用异常处理机制。
无需提取操作的计数器
不少应用程序需要进行游标循环并获取计数,才能完成记录内容的提取工作。由于提取操作会在无对应记录时返回null,因此最好能用提取状态来替代声明两项变量的传统计数检查方法。如此一来,我们可以避免声明额外变量并对其进行检查,从而减少需要执行的语句数量并获得更好的性能。具体请参见以下代码示例。
我们也可以按照以下步骤重新编写上述代码,其中使用游标本体进行迭代并利用游标状态中断/退出循环,这就有效避免了引入两个新的变量。
检查EXISTS,而非直接计数
在旧版应用程序当中编写SQL查询时,我们首先需要查找匹配的记录数,而后才能应用所需的业务逻辑。如果表中包含数十亿条记录,那么获取记录数量往往需要占用大量资源。
以下代码示例演示了如何先检查行数,而后更新数据。
此查询的总运行时长为163毫秒。
我们也可以重新编写代码以检查一列——而非一整行,这样可以节约成本并提高性能。具体请参见以下代码示例。
这条查询的总运行时长为104毫秒。
在DML语句后记录计数结果
在大多数旧版应用程序中,我们可以通过记录计数结果来判断数据操作语句是否引发了变更。在PostgreSQL中,这部分信息被保留在统计信息当中,用户可以随时检索以避免在操作之后对值进行计数。我们可以使用诊断程序检索受影响的行数,具体如以下代码示例所示。
模式匹配与搜索
从表中检索数据时,常见的做法是将通配符%
或 _
与LIKE
表达式配合使用(在进行非敏感搜索时也可以使用ILIKE
)。如果通配符位于给定schema的开头,那么即使存在索引,查询规划程序也无法使用该索引。在这种情况下,我们就必须使用顺序扫描,而这是一项相当耗时的操作。为了在处理数百万条记录时获得良好性能,并保证查询规划程序正常使用可用的索引,大家需要在谓词的中间或结尾处(而非开头)使用通配符,从而强制引导规划程序使用索引。
除了LIKE
表达式之外,大家也可以使用pg_trgm
模块/扩展进行模式匹配。其中pg_trgm
模块将为我们提供用于确定字母数字文本相似性的函数与运算符,同时提供支持相似字符串快速搜索的索引运算符类。关于更多详细信息,请参阅PostgreSQL网站上发布的pg_tram说明文档。
在Oracle、SQL Server以及PostgreSQL之间进行映射转换
本节主要介绍在Oracle、SQL Server以及PostgreSQL数据库中编写SQL语句方面的不同之处。
默认FROM子句
在Oracle当中,FROM
子句具有强制性,因此只能在代码中使用Select 1 from Dual
;。而在PostgreSQL与SQL当中,大家可以选择使用代码Select 1
;。
生成值集合
通过指定开始数字与结束数字,我们可以生成一个值集合。
在Oracle中,我们不需要起始数字,但可以提供结束数字。具体代码示例如下。
在使用起始与结束数字时,使用以下代码。
在PostgreSQL中,使用以下代码。
在SQL Server当中,使用以下代码。
联接(+)运算符
在Oracle中,左联接运算的实现需使用以下代码。
要实现右联接,使用以下代码。
若需了解更多详细信息,请参阅Oracle数据库网站上的SQL新手指南(第五部分):联接。
PostgreSQL与SQL Server上并不存在“+”这种可对表进行左右联接的功能;相反,二者使用以下两项查询。
将类型作为函数参数
在SQL Server中,我们可以使用Type
数据类型传递多条记录。要在PostgreSQL中实现相同的效果,大家可以通过JSON格式或者数组形式将该类型视为JSON或者文本数据类型。在以下示例代码中,JSON格式的文本数据类型就包含有多条记录。您可以将其插入临时表,并在后续代码中执行进一步处理。
Oracle
以下代码所示,为多条记录如何在Oracle的varchar数据类型中实现传递。
SQL Server
以下代码所示,为多条记录如何在SQL Server的表类型中实现传递。
PostgreSQL
以下代码所示,为多条记录如何在PostgreSQL中实现与之前Oracle及SQL Server相同的传递效果。
Pivoting转换
在PostgreSQL当中,pivoting功能无法直接启用,需要额外扩展提供支持。tablefunc
扩展带来的crosstab
函数可用于创建数据pivot表,其功能与SQL Server及Oracle类似。以下是Oracle、SQL Server以及PostgreSQL中的pvioting功能代码。
Oracle
使用以下代码在Oracle中实现pivoting功能。
SQL Server
使用以下代码在SQL Server中实现pivoting功能。
PostgreSQL
使用以下代码在PostgreSQL中实现pivoting功能。
对数组进行unpivoting
PostgreSQL同样无法直接提供Unpivot
函数。在将SQL Server或者Oracle转换为PostgreSQL时,unpivot函数会被映射为一个数组。具体请参见以下代码示例。
Oracle
使用以下代码示例在Oracle中实现unpivoting功能。
SQL Server
使用以下代码示例在SQL Server中实现unpivoting功能。
PostgreSQL
使用以下代码示例在PostgreSQL中实现unpivoting功能。
从单一函数处返回多个结果集
SQL Server可以将多条结果按多行形式直接返回。大家可以使用游标在PostgreSQL与Oracle中实现相同的效果,如以下示例所示。
Oracle
使用以下代码在Oracle中通过单一过程返回多个结果集。
SQL Server
使用以下代码在SQL Server中通过单一过程返回多个结果集。SQL Server不需要使用额外其他参数。
使用以下代码在SQL ServerL中通过单一过程返回多个结果集。
PostgreSQL
使用以下代码在PostgreSQL中通过单一过程返回多个结果集。
要在PostgreSQL中执行此过程,请输入以下代码。
带别名的内联查询
PostgreSQL语义可将内联视图称为Subselect
或者Subquery
。Oracle支持在内部语句中省略别名,PostgreSQL与SQL Server则要求必须使用别名。
Oracle
使用以下代码在Oracle中执行内联查询演示。
SQL Server与PostgreSQL
在Oracle中编写的示例内联查询若要在SQL Server及PostgreSQL中运行,则必须使用别名。
数据顺序
将数据从Oracle或SQL Server迁移至PostgreSQL之后,数据的检索顺序也可能发生改变。这种改变可能是受到了插入顺序、列数据类型及具体数值、或者排序规则的影响。
为了保证数据顺序的正确性,我们需要确定业务需求并在查询当中采用Order by
子句以匹配数据内容。
dblink与外部数据包装器
dblink是一项负责在同构与异构数据库间实现通信的功能。截至本文撰写之时,Amazon RDS与Aurora PostgreSQL还不提供异构支持,但已经能够支持跨PostgreSQL数据库间的通信。
跨同构数据库通信
PostgreSQL可利用dblink与外部数据包装器(FDW)实现跨数据库间的正常通信。在本节中,我们将具体聊聊dblink与FDW的使用方法。
使用外部数据包装器
- 使用以下代码创建该扩展。
- 使用以下代码创建服务器并接入外部数据库。
- 使用以下代码创建用户映射,借此访问外部数据库中的表。
- 为每一位需要通过FDW通信的用户创建用户映射。
- 将全部外部表导入本地schema,这样我们就能像访问常规表那样访问外部表中的数据。以下为从外部数据库及schema执行表导入的示例代码。
跨异构数据库通信
PostgreSQL不支持跨数据库通信。在实现跨数据库的异构通信方面,Amazon Aurora PostgreSQL确实存在一定局限,但大家可以在源环境(例如Oracle或者SQL Server)上建立指向目标(PostgreSQL)的dblink
,而后对数据执行pull或push操作。
若需了解更多详细信息,请参阅 在Compose PostgreSQL上进行跨数据库查询。
使用dblink为外部数据库表创建视图
若需了解更多详细信息,请参阅外部数据包装器与postgres_fdw说明文档。
选项一:在SQL语句之内提供目标数据库的访问细节
在这种选项中,每一次主机或连接的细节信息发生变化,我们都需要反复在主机连接与数据库凭证处做出多项对应调整。
选项二:对访问细节进行拆分,并使用连接对象
在这种选项中,主机与连接细节在同一个位置进行定义,并使用连接名称实现跨数据库连接。
使用dblink进行函数调用
以下代码为来自外部PostgreSQL数据库的函数,运行后将返回一个整数。
以下代码为来自外部PostgreSQL数据库的函数,运行后将返回一个表类型。
从一组数字中找出最大与最小值
在面向PostgreSQL进行迁移时,我们可能需要找出最大与最小值。PostgreSQL中包含一项最大/最小值查找函数,具体参见以下示例代码。
考虑使用自联接以实现更新
当在select语句中的from
子句内使用相同的源表(正在更新的表)时,PostgreSQL与SQL Server的具体更新机制将有所区别。与SQL Server不同,PostgreSQL中from
子句的第二次引用将独立于第一次引用,且变更将被应用于整个表。
以下示例代码,用于更新部门1中员工的薪水。
此函数在SQL Server的起效方式并无区别;但在迁移之后,同一SQL语句的更新范围将由当前部门扩展至整个表。这是由于PostgreSQL会假定两个employee
表彼此独立,这与SQL Server完全不同。
要更新单一部门中的数据,应将DML转换为以下代码。
如果使用Oracle,则将DML转换为以下代码。
总结
本文从商业数据库到PostgreSQL的迁移场景出发,向开发者朋友们分享了一些技巧与最佳实践。本文的重点在于介绍迁移过程中需要面对的种种决策,以及决策结果给数据库性能造成怎样的影响。在迁移过程中,请牢记这些性能方面的影响因素,这将帮助大家提前避免随后可能因迁移出现的种种性能问题。
如果您对本文还有任何疑问或者建议,请在下方评论中分享您的看法。