oracle_fdw 是 LightDB 的一个扩展,提供了一个外部数据包装器,用于轻松高效地访问 Oracle 数据库, 包括 WHERE 条件和所需列的下推以及全面的 EXPLAIN 支持。
以下是使用 oracle_fdw 的简单示例。
更详细的信息将在“选项”和“用法”部分中提供。您还应该阅读 LightDB 关于外部数据和相关命令的文档。
为了本示例,假设您可以使用以下命令作为操作系统用户 lightdb
(或启动 LightDB 服务器的任何人)连接:
sqlplus orauser/orapwd@//dbserver.mydomain.com:1521/ORADB
这意味着 Oracle 客户端和环境已设置正确。
我们想要访问一个定义如下的表:
SQL> DESCRIBE oratab Name Null? Type ------------------------------- -------- ------------ ID NOT NULL NUMBER(5) TEXT VARCHAR2(30) FLOATING NOT NULL NUMBER(7,2)
然后将 oracle_fdw 配置为 LightDB 超级用户,如下所示:
ltdb=# CREATE EXTENSION oracle_fdw; ltdb=# CREATE SERVER oradb FOREIGN DATA WRAPPER oracle_fdw OPTIONS (dbserver '//dbserver.mydomain.com:1521/ORADB');
(您可以使用其他命名方法或本地连接,请参阅下面选项 dbserver
的描述。)
只有在真正需要的情况下才使用超级用户是个好主意,因此让我们允许普通用户使用外部服务器(这并非本示例工作所必需,但我建议这样做):
ltdb=# GRANT USAGE ON FOREIGN SERVER oradb TO pguser;
然后,您可以作为 pguser
连接到 LightDB 并定义:
ltdb=> CREATE USER MAPPING FOR pguser SERVER oradb OPTIONS (user 'orauser', password 'orapwd');
(您可以使用外部身份验证来避免存储 Oracle 密码;请参阅下面的说明。)
ltdb=> CREATE FOREIGN TABLE oratab ( id integer OPTIONS (key 'true') NOT NULL, text character varying(30), floating double precision NOT NULL ) SERVER oradb OPTIONS (schema 'ORAUSER', table 'ORATAB');
(请记住表名和模式名 -- 后者是可选的 -- 通常必须大写。)
现在,您可以像使用常规的 LightDB 表一样使用该表。
FUNCTION oracle_fdw_handler() RETURNS fdw_handler FUNCTION oracle_fdw_validator(text[], oid) RETURNS void
这些函数是必要的处理程序和验证器函数,用于创建外部数据包装器。
FOREIGN DATA WRAPPER oracle_fdw HANDLER oracle_fdw_handler VALIDATOR oracle_fdw_validator
扩展自动创建了一个名为 oracle_fdw
的外部数据包装器。
通常这就是您所需要的,然后您可以继续定义外部服务器。您可以创建其他 Oracle 外部数据包装器,
例如如果您需要设置 nls_lang
选项(您可以更改现有的 oracle_fdw
包装器,
但所有修改将在转储/还原后丢失)。
FUNCTION oracle_close_connections() RETURNS void
此函数可用于关闭此会话中的所有打开的 Oracle 连接。有关进一步说明,请参见“用法”部分。
FUNCTION oracle_diag(name DEFAULT NULL) RETURNS text
此函数仅用于诊断目的。
它将返回 oracle_fdw、LightDB 服务器和 Oracle 客户端的版本。如果不带参数或为 NULL 调用, 还将返回用于建立 Oracle 连接的一些环境变量的值。
FUNCTION oracle_execute(server name, stmt text) RETURNS void
此函数可用于在远程 Oracle 服务器上执行任意 SQL 语句。仅适用于不返回结果的语句(通常是 DDL 语句)。
在使用此函数时要小心,因为它可能会干扰 oracle_fdw 的事务管理。请记住,在 Oracle 中运行 DDL 语句会发出隐式 COMMIT。 最好建议您在多语句事务之外使用此函数。
(注意:如果您修改默认的外部数据包装器 oracle_fdw
,则任何更改都将在转储/还原时丢失。
如果要选项持久化,请创建一个新的外部数据包装器。随软件提供的 SQL 脚本中包含可用的 CREATE FOREIGN DATA WRAPPER 语句。)
nls_lang
(可选)
设置 Oracle 的 NLS_LANG 环境变量为此值。
NLS_LANG 格式为“语言_国家.字符集”(例如 AMERICAN_AMERICA.AL32UTF8)。这必须与您的数据库编码匹配。 当此值未设置时,如果 oracle_fdw 可以自动完成正确的操作,它将自动执行操作,并在无法执行时发出警告。 仅在您知道自己在做什么时才设置此值。请参阅“问题”部分。
如果 Lightdb 数据库是用 GBK 编码的,支持使用Oracle_fdw扩展访问 Oracle 数据库。
dbserver
(必填)
远程数据库的 Oracle 数据库连接字符串。
这可以以Oracle支持的任何形式进行,只要您的Oracle客户端进行了相应的配置。
将此设置为空字符串以进行本地(“BEQUEATH”)连接。
isolation_level
(可选,默认为serializable
)
在Oracle数据库中要使用的事务隔离级别。
该值可以为serializable
,read_committed
或read_only
。
请注意,Oracle表可以在单个LightDB语句期间进行多次查询(例如,在嵌套循环连接期间)。为确保不会发生由并发事务的竞争条件引起的不一致,事务隔离级别必须保证读取稳定性。
仅使用Oracle的SERIALIZABLE或READ ONLY隔离级别才能保证这一点。
不幸的是,Oracle的SERIALIZABLE实现相当差,会在意外情况下(例如插入表中)导致序列化错误(ORA-08177)。
使用READ COMMITTED事务可以解决此问题,但是存在不一致的风险
。如果要使用它,请检查您的执行计划,以确定外部扫描是否可以执行多次。
nchar
(布尔型,可选,默认为关闭)
将此选项设置为on
将在Oracle端选择更昂贵的字符转换。如果您正在使用单字节Oracle数据库字符集,但具有包含不能在数据库字符集中表示的字符的NCHAR或NVARCHAR2列,则需要此选项。
将nchar
设置为on
将对性能产生明显影响,并导致UPDATE语句中设置超过2000个字节的字符串时出现ORA-01461错误(如果您的MAX_STRING_SIZE =
EXTENDED,则为16383)。此错误似乎是Oracle的一个bug。
user
(必填)
会话的Oracle用户名。
如果您不想将Oracle凭据存储在LightDB数据库中(一种简单的方法是使用外部密码存储库
),则将其设置为空字符串,以进行外部身份验证。
password
(必填)
Oracle用户的密码。
table
(必填)
Oracle表名。此名称必须与Oracle系统目录中出现的名称完全一致,因此通常只由大写字母组成。
要基于任意Oracle查询定义外部表,请将此选项设置为括在括号中的查询,例如
OPTIONS (table '(SELECT col FROM tab WHERE val = ''string'')')
在这种情况下不要设置schema
选项。
如果在简单查询上定义了外部表,则INSERT、UPDATE和DELETE将能够正常工作;如果要避免这种情况(或者在复杂查询的情况下避免混淆Oracle错误消息),请使用表选项
readonly
。
dblink
(可选)
通过Oracle数据库连接访问表。此名称必须与Oracle系统目录中出现的名称完全一致,因此通常只由大写字母组成。
schema
(可选)
表的模式(或所有者)。有用于访问不属于连接Oracle用户的表。此名称必须与Oracle系统目录中出现的名称完全一致,因此通常只由大写字母组成。
max_long
(可选,默认为“32767”)
Oracle表中任何LONG、LONG RAW和XMLTYPE列的最大长度。可能的值为1到1073741823之间的整数(LightDB中bytea
的最大大小)。这个内存量至少会分配两次,所以大的值将会消耗大量的内存。
如果max_long
小于检索到的最长值的长度,则会收到错误消息ORA-01406: fetched column value was
truncated
。
readonly
(可选,默认为“false”)
只有在该选项设置为no/off/false的表上才允许进行INSERT、UPDATE和DELETE操作。
sample_percent
(可选,默认为“100”)
该选项仅影响ANALYZE处理过程,并可用于在合理的时间内分析非常大的表。
该值必须在0.000001和100之间,并定义了将随机选择计算LightDB表统计信息的Oracle表块的百分比。这是通过在Oracle中使用SAMPLE BLOCK (x)
子句来实现的。
对于使用Oracle查询定义的表,ANALYZE将失败并显示ORA-00933错误,对于使用复杂的Oracle视图定义的表,ANALYZE可能会失败并显示ORA-01446错误。
prefetch
(可选,默认为“200”)
设置在LightDB与Oracle之间进行外部表扫描期间单次往返获取的行数。这是使用Oracle行预取实现的。该值必须在0和10240之间,其中值为0表示禁用预取。
更高的值可以提高性能,但会在LightDB服务器上使用更多的内存。
force_pushdown_where_op
(可选, 默认值为"false")
从 LightDB 24.1 版本开始支持。
涉及字符串比较的过滤条件因为排序规则在 LightDB 和 Oracle 中可能不同, 因此在默认情况下不下推此过滤条件。
通过把 force_pushdown_where_op 选项设置为 "true" 可以忽略排序规则强制下推涉及字符串比较的过滤条件.
下面的例子展示了这个选项的用法和效果:
lightdb@test_o=# CREATE foreign TABLE t1 ( id number(10) NOT NULL, val1 varchar(10), val2 char(10), val3 text ) SERVER oradb OPTIONS (table 'T1'); CREATE FOREIGN TABLE lightdb@test_o=# explain (costs false) select * from t1 where val1 > '1'; QUERY PLAN --------------------------------------------------------------------------------- ---------------------------------- Foreign Scan on t1 Filter: ((val1)::text > '1'::text) Oracle query: SELECT /*c1f143f0d9e74f29fb779e0a2ccfbe91*/ r1."ID", r1."VAL1", r1."VAL2", r1."VAL3" FROM "T1" r1 (3 rows) lightdb@test_o=# alter foreign table t1 OPTIONS (add force_pushdown_where_op 'true'); ALTER FOREIGN TABLE lightdb@test_o=# explain (costs false) select * from t1 where val1 > '1'; QUERY PLAN --------------------------------------------------------------------------------- ---------------------------------------------------------- Foreign Scan on t1 Oracle query: SELECT /*d1d07899c1c6cc550f34054b7ba97384*/ r1."ID", r1."VAL1", r1."VAL2", r1."VAL3" FROM "T1" r1 WHERE (r1."VAL1" > '1') (2 rows) lightdb@test_o=# drop foreign TABLE t1; DROP FOREIGN TABLE lightdb@test_o=# CREATE foreign TABLE t1 ( id number(10) NOT NULL, val1 varchar(10), val2 char(10), val3 text ) SERVER oradb OPTIONS (table 'T1', force_pushdown_where_op 'true'); CREATE FOREIGN TABLE lightdb@test_o=# explain (costs false) select * from t1 where val1 > '1'; QUERY PLAN --------------------------------------------------------------------------------- ---------------------------------------------------------- Foreign Scan on t1 Oracle query: SELECT /*d1d07899c1c6cc550f34054b7ba97384*/ r1."ID", r1."VAL1", r1."VAL2", r1."VAL3" FROM "T1" r1 WHERE (r1."VAL1" > '1') (2 rows)
Oracle用户显然需要CREATE SESSION权限以及选择相关表或视图的权限。
对于EXPLAIN VERBOSE,用户还需要在V$SQL和V$SQL_PLAN上拥有SELECT权限。
oracle_fdw会缓存Oracle连接,因为为每个单独的查询创建Oracle会话的成本较高。当LightDB会话结束时,所有连接都会自动关闭。
函数oracle_close_connections()
可用于关闭所有缓存的Oracle连接。这对于长时间运行的会话非常有用,因为它们不会一直访问外部表,并且希望避免阻塞打开的Oracle连接所需的资源。
不能在修改Oracle数据的事务中调用此函数。
当您定义外部表时,Oracle表的列按其定义顺序映射到LightDB列。
oracle_fdw只会在LightDB查询中实际需要的情况下在Oracle查询中包含这些列。
LightDB表可以比Oracle表具有更多或更少的列。如果它有更多的列,并且这些列被使用,您将收到一个警告并返回NULL值。
如果要进行UPDATE或DELETE操作,请确保所有属于表主键的列的key选项都已设置。否则将导致错误。
必须使用oracle_fdw可以转换的数据类型来定义LightDB列(请参见下面的转换表)。只有在实际使用列时才强制执行此限制,因此您可以为无法转换的数据类型定义“虚拟”列,只要您不访问它们即可(此技巧仅适用于SELECT,而不是在修改外部数据时)。如果Oracle值超出LightDB列的大小(例如,varchar列的长度或最大整数值),则会收到运行时错误。
这些转换由oracle_fdw自动处理:
Oracle type | Possible LightDB types -------------------------+-------------------------------------------------- CHAR | char, varchar, text NCHAR | char, varchar, text VARCHAR | char, varchar, text VARCHAR2 | char, varchar, text, json, oracle.varchar2 NVARCHAR2 | char, varchar, text CLOB | char, varchar, text, json LONG | char, varchar, text RAW | uuid, bytea BLOB | bytea BFILE | bytea (read-only) LONG RAW | bytea NUMBER | numeric, float4, float8, char, varchar, text NUMBER(n,m) | numeric, float4, float8, int2, int4, int8, | boolean, char, varchar, text FLOAT | numeric, float4, float8, char, varchar, text BINARY_FLOAT | numeric, float4, float8, char, varchar, text BINARY_DOUBLE | numeric, float4, float8, char, varchar, text DATE | date, timestamp, timestamptz, char, varchar, text, oracle.date TIMESTAMP | date, timestamp, timestamptz, char, varchar, text TIMESTAMP WITH TIME ZONE | date, timestamp, timestamptz, char, varchar, text TIMESTAMP WITH | date, timestamp, timestamptz, char, varchar, text LOCAL TIME ZONE | INTERVAL YEAR TO MONTH | interval, char, varchar, text INTERVAL DAY TO SECOND | interval, char, varchar, text XMLTYPE | xml, char, varchar, text
如果将NUMBER转换为布尔值,则0表示false
,其他所有值表示true
。
插入或更新XMLTYPE只适用于不超过VARCHAR2数据类型最大长度(4000或32767,取决于MAX_STRING_SIZE
参数)的值。
目前不支持NCLOB,因为Oracle无法自动将其转换为客户端编码。
如果需要超出上述转换的转换,请在Oracle或LightDB中定义适当的视图。
LightDB将使用WHERE子句的所有适用部分作为扫描的过滤器。每当这样的条件可以安全地转换为Oracle SQL时, Oracle_fdw构造的Oracle查询将包含与这些过滤条件相对应的WHERE子句。这个功能也被称为WHERE子句的“下推”, 可以大大减少从Oracle检索的行数,可能使Oracle的优化器选择访问所需表的良好计划。
同样地,ORDER BY子句将在可能的情况下下推到Oracle。请注意,不会将按字符字符串排序的ORDER BY条件下推, 因为LightDB和Oracle中的排序顺序不能保证相同。
为了利用这一点,请尽量使用简单的条件来为外部表进行选择。选择与Oracle类型相对应的LightDB列数据类型, 否则条件无法转换。
表达式now()
,transaction_timestamp()
,
current_timestamp
,current_date
和localtimestamp
会被正确转换。
EXPLAIN的输出将显示所使用的Oracle查询,因此您可以看到哪些条件被翻译为Oracle以及如何翻译。
从 24.1 版本开始,涉及到隐式转换为numeric类型的过滤条件可以下推,下面是一些例子:
lightdb@test_o=# explain (costs false) select * from t1 where val1 > 1; QUERY PLAN --------------------------------------------------------------------------------- ------------------------------------------------------------------------ Foreign Scan on t1 Oracle query: SELECT /*1057989d07288e060655a4377d7d36ef*/ r1."ID", r1."VAL1", r1."VAL2", r1."VAL3" FROM "T1" r1 WHERE (cast(r1."VAL1" as number) > 1) (2 rows) lightdb@test_o=# explain (costs false) select * from t1 where val1::number > 1; QUERY PLAN --------------------------------------------------------------------------------- ------------------------------------------------------------------------ Foreign Scan on t1 Oracle query: SELECT /*1057989d07288e060655a4377d7d36ef*/ r1."ID", r1."VAL1", r1."VAL2", r1."VAL3" FROM "T1" r1 WHERE (cast(r1."VAL1" as number) > 1) (2 rows)
下面的表展示了能被下推的函数,以及在 Oracle 中的对应项
Table E.53. 支持下推的函数
LightDB |
Oracle |
---|---|
abs |
abs |
acos |
acos |
asin |
asin |
atan |
atan |
atan2 |
atan2 |
ceil |
ceil |
ceiling |
ceil |
char_length |
length |
character_length |
length |
concat |
concat |
cos |
cos |
exp |
exp |
initcap |
initcap |
length |
length |
lower |
lower |
lpad |
lpad |
ltrim |
ltrim |
mod |
mod |
octet_length |
lengthb |
position |
instr |
pow |
power |
power |
power |
replace |
replace |
round |
round |
rpad |
rpad |
rtrim |
rtrim |
sign |
sign |
sin |
sin |
sqrt |
sqrt |
strpos |
instr |
substr |
substr |
substring(arg1, arg2, arg3) |
substr(arg1, arg2, arg3) |
tan |
tan |
to_char |
to_char |
to_date |
to_date |
to_number |
to_number |
to_timestamp |
to_timestamp |
translate |
translate |
trunc |
trunc |
upper |
upper |
nvl |
nvl |
date_part(arg1, arg2), arg1 只能是 "'year'", "'month'", "'day'", "'hour'", "'minute'", "'second'", "'timezone_hour'" 或 "'timezone_minute'" |
extract( arg1 FROM arg2) |
now |
(CAST (:now AS TIMESTAMP WITH TIME ZONE)) |
transaction_timestamp |
(CAST (:now AS TIMESTAMP WITH TIME ZONE)) |
btrim(arg)/btrim(arg1, arg2) |
trim(both from arg)/trim(both arg2 from arg1) |
nvl 和 btrim 函数从 24.1 版本开始支持,下面是一些示例:
lightdb@test_o=# explain (costs false) select * from t1 where nvl(val1,' ') = 'abc' order by id; QUERY PLAN --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- --------------------- Foreign Scan on t1 Oracle query: SELECT /*49625285d521d9e84167b4358a1b57b0*/ r1."ID", r1."VAL1", r1."VAL2", r1."VAL3" FROM "T1" r1 WHERE (nvl(r1."VAL1", ' ') = 'abc') ORDER BY r1 ."ID" ASC NULLS LAST (2 rows) lightdb@test_o=# explain (costs false) select * from t1 where trim(val1) = 'abc' order by id; QUERY PLAN --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- ------------------------------- Foreign Scan on t1 Oracle query: SELECT /*ad50eef72115ae7525aaaf08663b014c*/ r1."ID", r1."VAL1", r1."VAL2", r1."VAL3" FROM "T1" r1 WHERE (TRIM(BOTH ' ' FROM r1."VAL1") = 'abc') O RDER BY r1."ID" ASC NULLS LAST (2 rows) lightdb@test_o=# explain (costs false) select * from t1 where trim(LEADING val1) = 'abc' order by id; QUERY PLAN --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- ----------------------- Foreign Scan on t1 Oracle query: SELECT /*e2c065886aa1e3ccb9cc00faa85f9f95*/ r1."ID", r1."VAL1", r1."VAL2", r1."VAL3" FROM "T1" r1 WHERE (ltrim(r1."VAL1", ' ') = 'abc') ORDER BY r1."ID" ASC NULLS LAST (2 rows) lightdb@test_o=# explain (costs false) select * from t1 where trim(TRAILING val1) = 'abc' order by id; QUERY PLAN --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- ----------------------- Foreign Scan on t1 Oracle query: SELECT /*4fc7fba86ff56a97069e1102f988519f*/ r1."ID", r1."VAL1", r1."VAL2", r1."VAL3" FROM "T1" r1 WHERE (rtrim(r1."VAL1", ' ') = 'abc') ORDER BY r1."ID" ASC NULLS LAST (2 rows) lightdb@test_o=# explain (costs false) select * from t1 where nvl(trim(val1),' ') = 'abc' order by id; QUERY PLAN --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- ----------------------------------------- Foreign Scan on t1 Oracle query: SELECT /*77db51933cdfa1dd6edc39f35a00f952*/ r1."ID", r1."VAL1", r1."VAL2", r1."VAL3" FROM "T1" r1 WHERE (nvl(TRIM(BOTH ' ' FROM r1."VAL1"), ' ') = 'abc') ORDER BY r1."ID" ASC NULLS LAST (2 rows)
在 LightDB, trim 表达式被转换为 ltrim, rtrim 和 btrim 函数调用,在24.1版本支持了btrim 后,trim已完全支持下推。
oracle_fdw可以将连接下推到Oracle服务器,也就是两个外部表之间的连接将导致在Oracle端执行连接的单个查询。
只有 inner/left/right/full/semi join 支持下推。 下面是一些示例:
lightdb@test_o=# SELECT oracle_execute( lightdb@test_o(# 'oracle', lightdb@test_o(# E'CREATE TABLE t1(id number(10,0) primary key, val1 varchar(10), val2 char(10), val3 varchar2(10))' lightdb@test_o(# ); oracle_execute ---------------- (1 row) lightdb@test_o=# lightdb@test_o=# SELECT oracle_execute( lightdb@test_o(# 'oracle', lightdb@test_o(# E'CREATE TABLE t2(id number(10,0) primary key, start_date number(10,0), val1 varchar(10), val2 char(10))' lightdb@test_o(# ); oracle_execute ---------------- (1 row) lightdb@test_o=# CREATE foreign TABLE t1 ( lightdb@test_o(# id number(10) OPTIONS (key 'yes') NOT NULL, lightdb@test_o(# val1 varchar(10), lightdb@test_o(# val2 char(10), lightdb@test_o(# val3 text lightdb@test_o(# ) SERVER oracle OPTIONS (table 'T1', force_pushdown_where_op 'true'); 'yes') NOT NULL, start_date number(10), val1 varchar(10), val2 char(10) ) SERVER oracle OPTIONS (table 'T2', force_pushdown_where_op 'true');CREATE FOREIGN TABLE lightdb@test_o=# lightdb@test_o=# CREATE foreign TABLE t2 ( lightdb@test_o(# id number(10) OPTIONS (key 'yes') NOT NULL, lightdb@test_o(# start_date number(10), lightdb@test_o(# val1 varchar(10), lightdb@test_o(# val2 char(10) lightdb@test_o(# ) SERVER oracle OPTIONS (table 'T2', force_pushdown_where_op 'true'); CREATE FOREIGN TABLE lightdb@test_o=# insert into t1 values(1, '1234', '1234','1234'); ; insert into t2 values(1, '20240118', 'abc', '2'); insert into t2 values(2, '20240118', 'qwe', '2'); insert into t2 values(3, '20240119', 'abc', '2'); insert into t2 values(4, '20240119', 'qwe', '3'); insert into t2 values(5, '20240121', 'zxc', '2'); insert into t2 values(6, '20240119', '', '3');INSERT 0 1 lightdb@test_o=# insert into t1 values(2, '2234', '2234','2234'); INSERT 0 1 lightdb@test_o=# insert into t1 values(3, '2', '2','2'); INSERT 0 1 lightdb@test_o=# insert into t1 values(4, '3', '3','3'); INSERT 0 1 lightdb@test_o=# insert into t1 values(5, '4', '3','3'); INSERT 0 1 lightdb@test_o=# insert into t1 values(6, '5', '3','3'); INSERT 0 1 lightdb@test_o=# lightdb@test_o=# insert into t2 values(1, '20240118', 'abc', '2'); INSERT 0 1 lightdb@test_o=# insert into t2 values(2, '20240118', 'qwe', '2'); INSERT 0 1 lightdb@test_o=# insert into t2 values(3, '20240119', 'abc', '2'); INSERT 0 1 lightdb@test_o=# insert into t2 values(4, '20240119', 'qwe', '3'); INSERT 0 1 lightdb@test_o=# insert into t2 values(5, '20240121', 'zxc', '2'); INSERT 0 1 lightdb@test_o=# insert into t2 values(6, '20240119', '', '3'); INSERT 0 1 lightdb@test_o=# create table t1_copy( lightdb@test_o(# id number(10) NOT NULL, lightdb@test_o(# val1 varchar(10), lightdb@test_o(# val2 char(10), lightdb@test_o(# val3 text lightdb@test_o(# ); CREATE TABLE lightdb@test_o=# explain (costs false) insert into t1_copy select * from t1 where exists (select * from t2 where t1.id=t2.id); QUE RY PLAN ------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------ ----- Insert on t1_copy -> Foreign Scan Oracle query: SELECT /*0354f41de5ae3cfec39f466b95199ae0*/ r3."ID", r3."VAL1", r3. "VAL2", r3."VAL3" FROM "T1" r3 WHERE EXISTS (SELECT 1 FROM "T2" r4 WHERE (r3."ID" = r4."ID ") ) (3 rows) lightdb@test_o=# explain (costs false) insert into t1_copy select t1.* from t1 join t2 on t1.id = t2.id; QUERY PLAN ------------------------------------------------------------------------------------------ --------------------------------------------------------------------------- Insert on t1_copy -> Foreign Scan Oracle query: SELECT /*8198ab105b0cf1a88507da8440560abf*/ r3."ID", r3."VAL1", r3. "VAL2", r3."VAL3" FROM ("T1" r3 INNER JOIN "T2" r4 ON (r3."ID" = r4."ID")) (3 rows) lightdb@test_o=# explain (costs false) insert into t1_copy select t1.* from t1 left join t2 on t1.id = t2.id; QUERY PLAN ------------------------------------------------------------------------------------------ -------------------------------------------------------------------------- Insert on t1_copy -> Foreign Scan Oracle query: SELECT /*9590092cd58c52862a9f87d57d5cc7b0*/ r3."ID", r3."VAL1", r3. "VAL2", r3."VAL3" FROM ("T1" r3 LEFT JOIN "T2" r4 ON (r3."ID" = r4."ID")) (3 rows) lightdb@test_o=# explain (costs false) insert into t1_copy select t1.* from t1 right join t2 on t1.id = t2.id; QUERY PLAN ------------------------------------------------------------------------------------------ -------------------------------------------------------------------------- Insert on t1_copy -> Foreign Scan Oracle query: SELECT /*5b0638eb17c55e93456a02170c07ed6f*/ r3."ID", r3."VAL1", r3. "VAL2", r3."VAL3" FROM ("T2" r4 LEFT JOIN "T1" r3 ON (r3."ID" = r4."ID")) (3 rows) lightdb@test_o=# explain (costs false) insert into t1_copy select t1.* from t1 full join t2 on t1.id = t2.id; QUERY PLAN ------------------------------------------------------------------------------------------ -------------------------------------------------------------------------- Insert on t1_copy -> Foreign Scan Oracle query: SELECT /*0175371a65b00ef9b62ca21e1910774f*/ r3."ID", r3."VAL1", r3. "VAL2", r3."VAL3" FROM ("T1" r3 FULL JOIN "T2" r4 ON (r3."ID" = r4."ID")) (3 rows)
在下推连接时,有一些限制:
两个表必须在相同的外部服务器上定义。
三个或更多表之间的连接不会被下推。
连接必须在SELECT语句 或者 INSERT-SELECT语句中。
oracle_fdw必须能够将所有连接条件和WHERE子句下推。
没有连接条件的交叉连接不会被下推。
如果连接被下推,则ORDER BY子句不会被下推。
很重要的是,使用ANALYZE收集了两个外部表的表统计信息,LightDB才能确定最佳的连接策略。
oracle_fdw支持对外部表进行INSERT、UPDATE和DELETE操作。默认情况下(包括从早期的LightDB版本升级的数据库中)允许此操作,
并且可以通过设置readonly
表选项来禁用。
对于UPDATE和DELETE操作,与Oracle表的主键列对应的列必须设置key
列选项。
这些列用于标识外部表中的一行,因此请确保在属于主键的所有列上设置了该选项。
如果在INSERT操作中省略了外部表列,那么该列将设置为在LightDB外部表上定义的DEFAULT子句的值(如果没有DEFAULT子句,则为NULL)。 不使用相应的Oracle列上的DEFAULT子句。如果LightDB外部表不包括Oracle表的所有列,则Oracle DEFAULT子句将用于未包含在外部表定义中的列。
INSERT、UPDATE和DELETE操作中的RETURNING子句被支持,但对于具有Oracle数据类型LONG和LONG RAW的列除外 (Oracle不支持在RETURNING子句中使用这些数据类型)。
支持外部表上的触发器。使用AFTER和FOR EACH ROW定义的触发器要求外部表没有具有Oracle数据类型LONG或LONG RAW的列。 这是因为这样的触发器利用了上面提到的RETURNING子句。
虽然修改外部数据是可行的,但在许多行受到影响时,性能并不是特别好,这是由于(由于外部数据包装器的工作方式) 每一行都必须单独处理。
事务将被转发到Oracle,因此BEGIN、COMMIT、ROLLBACK和SAVEPOINT按预期工作。不支持涉及Oracle的预处理语句。 有关详细信息,请参见内部部分。
由于oracle_fdw默认使用序列化事务,因此修改数据语句可能导致序列化失败:
ORA-08177: can't serialize access for this transaction
如果并发事务修改了表,并且在长时间运行的事务中更有可能发生此类情况。这些错误可以通过它们的SQLSTATE(40001)进行识别。 使用oracle_fdw的应用程序应该重试以此错误失败的事务。
可以使用不同的事务隔离级别,有关讨论,请参见Foreign server options。
LightDB的EXPLAIN命令将显示实际发送给Oracle的查询语句。使用EXPLAIN VERBOSE命令将显示Oracle的执行计划(不适用于Oracle服务器9i或更早版本,请参阅Problems)。
您可以使用ANALYZE命令收集外部表的统计信息。这一功能由oracle_fdw支持。
如果没有统计信息,LightDB无法估计对外部表的查询结果行数,这可能导致选择错误的执行计划。
与普通表不同,LightDB不会自动通过autovacuum守护进程来收集外部表的统计信息,因此在创建外部表之后,以及远程表发生重大变化时,运行ANALYZE命令收集外部表的统计信息尤为重要。
请注意,对Oracle外部表进行分析将导致完整的顺序表扫描。您可以使用表选项sample_percent
,仅使用Oracle表的部分抽样数据以加快速度。
支持使用IMPORT FOREIGN SCHEMA命令批量导入Oracle模式中所有表的表定义。
除了IMPORT FOREIGN SCHEMA的文档之外,还请考虑以下内容:
IMPORT FOREIGN SCHEMA会为在ALL_TAB_COLUMNS中找到的所有对象创建外部表,包括表、视图,但不包括同义词。
下列是IMPORT FOREIGN SCHEMA支持的选项:
case
:控制导入期间表和列名的大小写折叠。
可能的取值包括:
keep
:保持与Oracle中相同的名称,通常为大写。
lower
:将所有表和列名转换为小写。
smart
:仅翻译在Oracle中全部为大写字母的名称(这是默认设置)。
collation
: 用于case
选项的lower
和
smart
的大小写折叠的排序规则。
默认值为default
,即使用数据库的默认排序规则。仅支持pg_catalog
模式中的排序规则。请参阅pg_collation
目录中的collname
值,获取可能的取值列表。
dblink
:访问模式的Oracle数据库链接。
此名称必须与Oracle系统目录中的名称完全相同,通常只包含大写字母。
readonly
:在所有导入表上设置readonly
选项。
有关详细信息,请参阅选项部分。
max_long
:在所有导入表上设置max_long
选项。
有关详细信息,请参阅选项部分。
sample_percent
:在所有导入表上设置sample_percent
选项。
详见选项部分以获取更多细节。
prefetch
:对所有导入的表设置 prefetch
选项。
详见选项部分以获取更多细节。
Oracle 模式名称必须与 Oracle 中完全一致,通常为大写。
由于 LightDB 在处理之前将名称转换为小写,因此必须使用双引号保护模式名称(例如 "SCOTT"
)。
在限制(LIMIT TO)或排除(EXCEPT)子句中的表名必须按照上述大小写转换后在 LightDB 中的表现方式进行编写。
请注意,IMPORT FOREIGN SCHEMA 与 Oracle server 8i 不兼容;详见问题部分以获取更多细节。
oracle_fdw 将 Oracle 会话的 MODULE 设置为 LightDB,ACTION 设置为后端进程号。 这有助于识别 Oracle 会话,并允许您使用 DBMS_MONITOR.SERV_MOD_ACT_TRACE_ENABLE 跟踪它。
oracle_fdw 使用 Oracle 的结果预取以避免不必要的客户端和服务器之间的往返。预取行数可以使用 prefetch
表选项进行配置,默认设置为 200。
oracle_fdw 不使用 PLAN_TABLE 来解释 Oracle 查询(这需要在 Oracle 数据库中创建这样的表), 而是使用库高速缓存中存储的执行计划。为此,需要明确地描述一个 Oracle 查询,这将迫使 Oracle 解析该查询。 难点在于在 V$SQL 中查找语句的 SQL_ID 和 CHILD_NUMBER,因为 SQL_TEXT 列仅包含查询的前 1000 个字节。 因此,oracle_fdw 向查询中添加了一个包含查询文本的 MD5 哈希值的注释。这用于在 V$SQL 中搜索。 实际的执行计划或成本信息从 V$SQL_PLAN 中检索。
oracle_fdw 在 Oracle 端使用事务隔离级别 SERIALIZABLE,这对应于 LightDB 的 REPEATABLE READ。 这是必要的,因为单个 LightDB 语句可能导致多个 Oracle 查询(例如,在嵌套循环连接期间), 并且结果需要保持一致。
不幸的是,Oracle 实现的 SERIALIZABLE 具有某些怪癖;详见问题部分以获取更多细节。
在本地事务提交之前,Oracle 事务立即提交,以便完成的 LightDB 事务保证已完成 Oracle 事务。 然而,即使 Oracle 事务已提交,仍存在 LightDB 事务无法完成的小概率情况。 如果没有使用两阶段事务和事务管理器,这是无法避免的,这已经超出了外部数据包装器可以合理提供的范围。
由于相同的原因,不支持涉及 Oracle 的预处理语句。
存储在 Oracle 数据库中的字符,如果无法转换为 LightDB 数据库编码,Oracle 将默默地替换为 替换字符
,
通常是普通或反向问号。您将不会收到警告或错误消息。
如果您使用 Oracle 不知道的 LightDB 数据库编码(目前有 EUC_CN、EUC_KR、LATIN10、MULE_INTERNAL、WIN874 和 SQL_ASCII), 则无法正确地转换非 ASCII 字符。在这种情况下,您将收到警告,并且字符将被替换为上述替换字符。
您可以设置外部数据包装器的 nls_lang
选项以强制使用某个 Oracle 编码,
但是生成的字符很可能是不正确的,并会导致 LightDB 错误消息。如果您知道自己在做什么,则仅对 SQL_ASCII 编码有用。
请参见选项部分。
Oracle 系统目录 V$SQL 和 V$SQL_PLAN 的定义已经随 Oracle 10.1 更改。 在旧版本的 Oracle 服务器上使用 EXPLAIN VERBOSE 将导致出现错误,例如:
ERROR: error describing query: OCIStmtExecute failed to execute remote query for sql_id DETAIL: ORA-00904: "LAST_ACTIVE_TIME": invalid identifier
目前没有修复这个问题的计划,因为自 2010 年以来,Oracle 9i 已经停止了扩展支持,而且这种功能也不是必要的。
IMPORT FOREIGN SCHEMA 在 Oracle server 8i 中抛出以下错误:
ERROR: error importing foreign schema: OCIStmtExecute failed to execute column query DETAIL: ORA-00904: invalid column name
这是因为 ALL_TAB_COLUMNS 视图缺少列 CHAR_LENGTH,该列在 Oracle 9i 中添加。
在 Oracle 11.2 或更高版本中,使用 oracle_fdw 向新创建的 Oracle 表插入第一行会导致序列化错误。
这是因为 Oracle 具有一个名为延迟 段创建
的功能,它推迟为新表分配存储空间,直到插入第一行。
这会导致可串行化事务出现序列化故障(请参见 Oracle 知识库中的文档 1285464.1)。
这不是一个严重的问题;您可以通过忽略第一个错误或使用 SEGMENT CREATION IMMEDIATE 创建表来解决它。
一种更加严重的问题是,当索引页面与修改可串行化事务同时发生并发插入时,有时可能会导致序列化错误(请参见 Oracle 文档 160593.1)。
Oracle 声称这不是一个 bug,建议的解决方案是重试遇到序列化错误的事务。
这是过去影响 oracle_fdw 的 Oracle Bug 列表。
Bug 2728408 可能会导致 ORA-8177 cannot serialize access for this transaction
,
即使没有尝试修改远程数据。
它可能会发生在 Oracle server 8.1.7.4(安装 one-off patch 2728408)或 Oracle server 9.2(安装 Patch Set 9.2.0.4 或更高版本)。