E.35. oracle_fdw

E.35.1. Cookbook
E.35.2. Objects created by the extension
E.35.3. 选项
E.35.4. 用法
E.35.5. 内部结构
E.35.6. 存在的问题

oracle_fdw 是 LightDB 的一个扩展,提供了一个外部数据包装器,用于轻松高效地访问 Oracle 数据库, 包括 WHERE 条件和所需列的下推以及全面的 EXPLAIN 支持。

E.35.1. Cookbook

以下是使用 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 表一样使用该表。

E.35.2. Objects created by the extension

            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。 最好建议您在多语句事务之外使用此函数。

E.35.3. 选项

E.35.3.1. 外部数据包装器选项

(注意:如果您修改默认的外部数据包装器 oracle_fdw,则任何更改都将在转储/还原时丢失。 如果要选项持久化,请创建一个新的外部数据包装器。随软件提供的 SQL 脚本中包含可用的 CREATE FOREIGN DATA WRAPPER 语句。)

  • nls_lang(可选)

    设置 Oracle 的 NLS_LANG 环境变量为此值。

    NLS_LANG 格式为“语言_国家.字符集”(例如 AMERICAN_AMERICA.AL32UTF8)。这必须与您的数据库编码匹配。 当此值未设置时,如果 oracle_fdw 可以自动完成正确的操作,它将自动执行操作,并在无法执行时发出警告。 仅在您知道自己在做什么时才设置此值。请参阅“问题”部分。

    如果 Lightdb 数据库是用 GBK 编码的,支持使用Oracle_fdw扩展访问 Oracle 数据库。

E.35.3.2. 外部服务器选项

  • dbserver(必填)

    远程数据库的 Oracle 数据库连接字符串。

    这可以以Oracle支持的任何形式进行,只要您的Oracle客户端进行了相应的配置。

    将此设置为空字符串以进行本地(“BEQUEATH”)连接。

  • isolation_level(可选,默认为serializable

    在Oracle数据库中要使用的事务隔离级别。

    该值可以为serializableread_committedread_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。

E.35.3.3. 用户映射选项

  • user(必填)

    会话的Oracle用户名。

    如果您不想将Oracle凭据存储在LightDB数据库中(一种简单的方法是使用外部密码存储库),则将其设置为空字符串,以进行外部身份验证。

  • password(必填)

    Oracle用户的密码。

E.35.3.4. 外部表选项

  • 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)
    
                            

E.35.4. 用法

E.35.4.1. Oracle权限

Oracle用户显然需要CREATE SESSION权限以及选择相关表或视图的权限。

对于EXPLAIN VERBOSE,用户还需要在V$SQL和V$SQL_PLAN上拥有SELECT权限。

E.35.4.2. 连接

oracle_fdw会缓存Oracle连接,因为为每个单独的查询创建Oracle会话的成本较高。当LightDB会话结束时,所有连接都会自动关闭。

函数oracle_close_connections() 可用于关闭所有缓存的Oracle连接。这对于长时间运行的会话非常有用,因为它们不会一直访问外部表,并且希望避免阻塞打开的Oracle连接所需的资源。

不能在修改Oracle数据的事务中调用此函数。

E.35.4.3. 列

当您定义外部表时,Oracle表的列按其定义顺序映射到LightDB列。

oracle_fdw只会在LightDB查询中实际需要的情况下在Oracle查询中包含这些列。

LightDB表可以比Oracle表具有更多或更少的列。如果它有更多的列,并且这些列被使用,您将收到一个警告并返回NULL值。

如果要进行UPDATE或DELETE操作,请确保所有属于表主键的列的key选项都已设置。否则将导致错误。

E.35.4.4. Oracle权限

必须使用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中定义适当的视图。

E.35.4.5. WHERE条件和ORDER BY子句

LightDB将使用WHERE子句的所有适用部分作为扫描的过滤器。每当这样的条件可以安全地转换为Oracle SQL时, Oracle_fdw构造的Oracle查询将包含与这些过滤条件相对应的WHERE子句。这个功能也被称为WHERE子句的“下推”, 可以大大减少从Oracle检索的行数,可能使Oracle的优化器选择访问所需表的良好计划。

同样地,ORDER BY子句将在可能的情况下下推到Oracle。请注意,不会将按字符字符串排序的ORDER BY条件下推, 因为LightDB和Oracle中的排序顺序不能保证相同。

为了利用这一点,请尽量使用简单的条件来为外部表进行选择。选择与Oracle类型相对应的LightDB列数据类型, 否则条件无法转换。

表达式now()transaction_timestamp()current_timestampcurrent_datelocaltimestamp 会被正确转换。

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)
              
                

E.35.4.5.1. 能被下推的函数

下面的表展示了能被下推的函数,以及在 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已完全支持下推。

E.35.4.6. 外部表之间的连接

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才能确定最佳的连接策略。

E.35.4.7. 修改外部数据

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。

E.35.4.8. EXPLAIN

LightDB的EXPLAIN命令将显示实际发送给Oracle的查询语句。使用EXPLAIN VERBOSE命令将显示Oracle的执行计划(不适用于Oracle服务器9i或更早版本,请参阅Problems)。

E.35.4.9. ANALYZE

您可以使用ANALYZE命令收集外部表的统计信息。这一功能由oracle_fdw支持。

如果没有统计信息,LightDB无法估计对外部表的查询结果行数,这可能导致选择错误的执行计划。

与普通表不同,LightDB不会自动通过autovacuum守护进程来收集外部表的统计信息,因此在创建外部表之后,以及远程表发生重大变化时,运行ANALYZE命令收集外部表的统计信息尤为重要。

请注意,对Oracle外部表进行分析将导致完整的顺序表扫描。您可以使用表选项sample_percent,仅使用Oracle表的部分抽样数据以加快速度。

E.35.4.10. IMPORT FOREIGN SCHEMA的支持

支持使用IMPORT FOREIGN SCHEMA命令批量导入Oracle模式中所有表的表定义。

除了IMPORT FOREIGN SCHEMA的文档之外,还请考虑以下内容:

  • IMPORT FOREIGN SCHEMA会为在ALL_TAB_COLUMNS中找到的所有对象创建外部表,包括表、视图,但不包括同义词。

  • 下列是IMPORT FOREIGN SCHEMA支持的选项:

    • case:控制导入期间表和列名的大小写折叠。

      可能的取值包括:

      • keep:保持与Oracle中相同的名称,通常为大写。

      • lower:将所有表和列名转换为小写。

      • smart:仅翻译在Oracle中全部为大写字母的名称(这是默认设置)。

    • collation: 用于case选项的lowersmart的大小写折叠的排序规则。

      默认值为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 不兼容;详见问题部分以获取更多细节。

E.35.5. 内部结构

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 的预处理语句。

E.35.6. 存在的问题

E.35.6.1. 编码

存储在 Oracle 数据库中的字符,如果无法转换为 LightDB 数据库编码,Oracle 将默默地替换为 替换字符, 通常是普通或反向问号。您将不会收到警告或错误消息。

如果您使用 Oracle 不知道的 LightDB 数据库编码(目前有 EUC_CN、EUC_KR、LATIN10、MULE_INTERNAL、WIN874 和 SQL_ASCII), 则无法正确地转换非 ASCII 字符。在这种情况下,您将收到警告,并且字符将被替换为上述替换字符。

您可以设置外部数据包装器的 nls_lang 选项以强制使用某个 Oracle 编码, 但是生成的字符很可能是不正确的,并会导致 LightDB 错误消息。如果您知道自己在做什么,则仅对 SQL_ASCII 编码有用。 请参见选项部分。

E.35.6.2. 旧版 Oracle 中的功能受限

  • 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 中添加。

E.35.6.3. 序列化错误

在 Oracle 11.2 或更高版本中,使用 oracle_fdw 向新创建的 Oracle 表插入第一行会导致序列化错误。

这是因为 Oracle 具有一个名为延迟 段创建 的功能,它推迟为新表分配存储空间,直到插入第一行。 这会导致可串行化事务出现序列化故障(请参见 Oracle 知识库中的文档 1285464.1)。

这不是一个严重的问题;您可以通过忽略第一个错误或使用 SEGMENT CREATION IMMEDIATE 创建表来解决它。

一种更加严重的问题是,当索引页面与修改可串行化事务同时发生并发插入时,有时可能会导致序列化错误(请参见 Oracle 文档 160593.1)。

Oracle 声称这不是一个 bug,建议的解决方案是重试遇到序列化错误的事务。

E.35.6.4. 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 或更高版本)。