Oracle如何从异库获取数据(HTTP API调用)

本文介绍了在Oracle数据库中不使用DBLink的情况下,通过创建HTTP服务和UTL_HTTP函数调用实现数据获取和处理的方法,包括GET和POST请求,以及如何处理大字段类型和权限管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

常见的处理方法也许是创建创建database link,但dblink会对数据库性能造成影响(且会产生事务),而且不能传输大字段类型(如clob,bclob);

在不使用dblink的情况下如何实现呢?其实可以使用http请求实现,首先搭建一个http服务(post请求/get请求)返回需要的数据(返回数据类型可以是xml/json字符串,便于Oracle(xmltable/jsontable)解析),然后在Oracle语句块中请求对应服务进行操作便可。

HTTP GET方式:

CREATE OR REPLACE FUNCTION FN_HTTP_GET (v_url  VARCHAR2)
RETURN VARCHAR2
AS
BEGIN
  DECLARE
  req UTL_HTTP.REQ;
  resp UTL_HTTP.RESP;
  v_line VARCHAR2 ( 4000 );
  v_text VARCHAR2 ( 4000 );
  BEGIN
    v_text := '';
    BEGIN
      req := UTL_HTTP.BEGIN_REQUEST ( url => v_url, method => 'GET' );
      UTL_HTTP.SET_BODY_CHARSET('UTF-8');
      UTL_HTTP.SET_HEADER(req, 'Content-Type', 'application/x-www-form-urlencoded');
      resp := UTL_HTTP.GET_RESPONSE ( req );
      LOOP
      UTL_HTTP.READ_LINE ( resp, v_line, TRUE );
      v_text := v_text || v_line;
      END LOOP;
      UTL_HTTP.END_RESPONSE( resp );
      UTL_HTTP.END_REQUEST( req );
      EXCEPTION
        WHEN UTL_HTTP.END_OF_BODY THEN
        UTL_HTTP.END_RESPONSE ( resp );
        WHEN OTHERS THEN
        UTL_HTTP.END_RESPONSE(resp);
        UTL_HTTP.END_REQUEST(req);
    END;
    return v_text;
  END;
END;

 HTTP POST方式:

CREATE OR REPLACE FUNCTION FN_HTTP_POST (v_url  VARCHAR2, v_body  VARCHAR2, v_body_type VARCHAR2)
RETURN VARCHAR2
AS
BEGIN
	DECLARE
	req UTL_HTTP.REQ;
	resp UTL_HTTP.RESP;
	v_line VARCHAR2 ( 4000 );
	v_text VARCHAR2 ( 4000 );
	BEGIN
		v_text := '';
		BEGIN
			req := UTL_HTTP.BEGIN_REQUEST ( url => v_url, method => 'POST' );
			UTL_HTTP.SET_BODY_CHARSET('UTF-8');
			UTL_HTTP.SET_HEADER(req, 'Content-Type', v_body_type);
			utl_http.set_header(req, 'Content-Length',lengthb(v_body));
		  utl_http.write_text(req, v_body);
      resp := UTL_HTTP.GET_RESPONSE ( req );      
      LOOP
      UTL_HTTP.READ_LINE ( resp, v_line, TRUE );
      v_text := v_text || v_line;
      END LOOP;

      UTL_HTTP.END_RESPONSE( resp );
      UTL_HTTP.END_REQUEST( req );
      EXCEPTION
        WHEN UTL_HTTP.END_OF_BODY THEN
        UTL_HTTP.END_RESPONSE ( resp );
        WHEN OTHERS THEN
        UTL_HTTP.END_RESPONSE(resp);
        UTL_HTTP.END_REQUEST(req);
    END;
    return v_text;
  END;
END;
CREATE OR REPLACE 
FUNCTION HTTP_REQUEST(
v_url  VARCHAR2,--请求地址
v_param  VARCHAR2,--POST 相当于表单提交
v_body  clob,--POST body中的参数
v_type  varchar2--类型
)return varchar2 is
    req UTL_HTTP.REQ;
    resp UTL_HTTP.RESP;
    v_line VARCHAR2 (4000);
    v_text VARCHAR2 (4000);
    v_param_length NUMBER ;
    v_body_length NUMBER;
  buffer       VARCHAR2(32767);
  offset       NUMBER := 1;
  amount       NUMBER := 32767;
begin
    IF V_TYPE='GET' THEN  
          v_text := '';
          req := UTL_HTTP.BEGIN_REQUEST ( url => v_url, method => 'GET' );
          UTL_HTTP.SET_BODY_CHARSET('UTF-8');
          UTL_HTTP.SET_HEADER(req, 'Content-Type', 'application/x-www-form-urlencoded');
          resp := UTL_HTTP.GET_RESPONSE ( req );
          UTL_HTTP.READ_LINE ( resp, v_line, TRUE );
          v_text := v_text || v_line;
          UTL_HTTP.END_RESPONSE( resp );  
        
    ELSIF V_TYPE='POST_FORM' THEN
          v_param_length  := LENGTHB(v_param);
          req := UTL_HTTP.BEGIN_REQUEST (url=> v_url, method => 'POST');

          UTL_HTTP.SET_BODY_CHARSET('UTF-8');
          UTL_HTTP.SET_HEADER (r      =>  req,
                               name   =>  'Content-Type',
                               VALUE  =>  'application/x-www-form-urlencoded');
          UTL_HTTP.SET_HEADER (r      =>   req,
                               name   =>   'Content-Length',
                               VALUE  =>   v_param_length);
          UTL_HTTP.WRITE_RAW (r    => req,
                              data => UTL_RAW.CAST_TO_RAW(v_param)); 
          resp := UTL_HTTP.GET_RESPONSE(req);
          UTL_HTTP.READ_LINE(resp, v_text, TRUE);
          UTL_HTTP.END_RESPONSE(resp);
        ELSE
          v_body_length  := dbms_lob.getlength(v_body);
          req := UTL_HTTP.BEGIN_REQUEST (url=> v_url, method => 'POST');

          UTL_HTTP.SET_BODY_CHARSET('UTF-8');
          UTL_HTTP.SET_HEADER (r      =>  req,
                               name   =>  'Content-Type',
                               VALUE  =>  'application/json;charset=utf-8');
          UTL_HTTP.SET_HEADER (r      =>   req,
                               name   =>   'Content-Length',
                               VALUE  =>   v_body_length);
                    --分段输出请求参数,这个header设置很重要
                    UTL_HTTP.SET_HEADER (req, 'Transfer-Encoding', 'chunked');
                    --分段输出请求参数,避免内容超出长度限制
          WHILE (offset < v_body_length) LOOP
                        dbms_lob.read(v_body, amount, offset, buffer);
                        UTL_HTTP.WRITE_RAW(r => req, data => UTL_RAW.CAST_TO_RAW(buffer));
                        offset := offset + amount;
                    END LOOP;
                    resp := UTL_HTTP.GET_RESPONSE(req);

          UTL_HTTP.READ_LINE(resp, v_text, TRUE);
          UTL_HTTP.END_RESPONSE(resp);
    END IF;
    return v_text;
EXCEPTION
            WHEN UTL_HTTP.END_OF_BODY THEN
            UTL_HTTP.END_RESPONSE ( resp );
            WHEN OTHERS THEN
            UTL_HTTP.END_RESPONSE(resp);
            UTL_HTTP.END_REQUEST(req);
end;

网络正常,单http调用失败,无法调用时,可能是ACL控制未开启。

select * from dba_network_acls;
Select * From dba_network_acl_privileges ;

dba_network_acls为assign_acl维护进去的
dba_network_acl_privileges为create_acl和add_privilege维护进去的

如果dba_network_acl_privileges中有可用的记录,那么我们可以跳过create_acl这一步,直接使用存在的xml文件来新增权限;如果没有,那么我们就先创建一个acl

--添加acl和权限控制,若已存在访问控制文件(ACL),可不用再创建
  begin
    dbms_network_acl_admin.create_acl (       -- 创建访问控制文件(ACL)
    acl         => 'utl_http.xml',          -- 文件名称
    description => 'HTTP Access',           -- 描述
    principal   => 'YBZNFK',             -- 授权或者取消授权账号,大小写敏感
    is_grant    => TRUE,                    -- 授权还是取消授权
    privilege   => 'connect',               -- 授权或者取消授权的权限列表
    start_date  => null,                    -- 起始日期
    end_date    => null                     -- 结束日期
    );
   
--acl中给用户增加一个resolve的权限
    dbms_network_acl_admin.add_privilege (    -- 添加访问权限列表项
    acl        => 'utl_http.xml',           -- 刚才创建的acl名称 
    principal  => 'YBZNFK',                    -- 授权或取消授权用户
    is_grant   => TRUE,                     -- 与上同 
    privilege  => 'resolve',                -- 权限列表
    start_date => null,                     
    end_date   => null
    );
   
--acl中添加要访问的目标网址(ip和域名均可),可以指定端口范围
    dbms_network_acl_admin.assign_acl (       -- 该段命令意思是允许访问acl名为utl_http.xml下授权的用户,使用oracle网络访问包,所允许访问的目的主机,及其端口范围。
    acl        => 'utl_http.xml',
    host       => '*',           -- 访问接口服务的ip地址或者域名,*代表所有
    lower_port => null,                     -- 允许访问的起始端口号
    upper_port => Null                      -- 允许访问的截止端口号
    );
    commit;
  end;

当响应结果比较大时,可使用如下逻辑处理输出:

begin
 loop
   utl_http.read_raw( resp, raw_buf );
   dbms_lob.append( v_rblob, to_blob(raw_buf) );
 end loop;
 exception
 when utl_http.END_OF_BODY
 then
     null;
 end;

包案例:

CREATE OR REPLACE PACKAGE CM.pkg_scene_http_option AS
  /***
  * create user: 杨江兵
  * date:*
  * http工具包
  * ***/
  /*HTTP_REQUEST
  * @pharms:
  *      :
  * */
  FUNCTION HTTP_REQUEST(v_url   VARCHAR2, --请求地址
                        v_param VARCHAR2, --POST 相当于表单提交
                        v_body  clob, --POST body中的参数
                        v_type  varchar2 --类型
                        ) return varchar2;
  /*
  * @pharms:
  *      :
  *结果返回大文本
  * */
  FUNCTION HTTP_REQUEST_BLOB(v_url   VARCHAR2, --请求地址
                        v_param VARCHAR2, --POST 相当于表单提交
                        v_body  clob, --POST body中的参数
                        v_type  varchar2 --类型
                        ) return blob ;
  /*
  * @pharms:
  *      :
  *结果返回大文本
  * */
  FUNCTION HTTP_REQUEST_CLOB(v_url   VARCHAR2, --请求地址
                        v_param VARCHAR2, --POST 相当于表单提交
                        v_body  clob, --POST body中的参数
                        v_type  varchar2 --类型
                        ) return clob ;
END pkg_scene_http_option;
/
CREATE OR REPLACE PACKAGE body CM.pkg_scene_http_option AS
  /***
  * create user: 杨江兵
  * date: *
  * http工具包
  * ***/
  /*
  * @pharms:
  *      :
  * */
  FUNCTION HTTP_REQUEST(v_url   VARCHAR2, --请求地址
                        v_param VARCHAR2, --POST 相当于表单提交
                        v_body  clob, --POST body中的参数
                        v_type  varchar2 --类型
                        ) return varchar2 is
    req            UTL_HTTP.REQ;
    resp           UTL_HTTP.RESP;
    v_line         VARCHAR2(4000);
    v_text         VARCHAR2(4000);
    v_param_length NUMBER;
    v_body_length  NUMBER;
    buffer         VARCHAR2(32767);
    offset         NUMBER := 1;
    amount         NUMBER := 32767;
  begin
    IF V_TYPE = 'GET' THEN
      v_text := '';
      req    := UTL_HTTP.BEGIN_REQUEST(url => v_url, method => 'GET');
      UTL_HTTP.SET_BODY_CHARSET('UTF-8');
      UTL_HTTP.SET_HEADER(req,
                          'Content-Type',
                          'application/x-www-form-urlencoded');
      resp := UTL_HTTP.GET_RESPONSE(req);
      UTL_HTTP.READ_LINE(resp, v_line, TRUE);
      v_text := v_text || v_line;
      UTL_HTTP.END_RESPONSE(resp);

    ELSIF V_TYPE = 'POST_FORM' THEN
      v_param_length := LENGTHB(v_param);
      req            := UTL_HTTP.BEGIN_REQUEST(url    => v_url,
                                               method => 'POST');

      UTL_HTTP.SET_BODY_CHARSET('UTF-8');
      UTL_HTTP.SET_HEADER(r     => req,
                          name  => 'Content-Type',
                          VALUE => 'application/x-www-form-urlencoded');
      UTL_HTTP.SET_HEADER(r     => req,
                          name  => 'Content-Length',
                          VALUE => v_param_length);
      UTL_HTTP.WRITE_RAW(r => req, data => UTL_RAW.CAST_TO_RAW(v_param));
      resp := UTL_HTTP.GET_RESPONSE(req);
      UTL_HTTP.READ_LINE(resp, v_text, TRUE);
      UTL_HTTP.END_RESPONSE(resp);
    ELSE
      v_body_length := dbms_lob.getlength(v_body);
      req           := UTL_HTTP.BEGIN_REQUEST(url    => v_url,
                                              method => 'POST');

      UTL_HTTP.SET_BODY_CHARSET('UTF-8');
      UTL_HTTP.SET_HEADER(r     => req,
                          name  => 'Content-Type',
                          VALUE => 'application/json;charset=utf-8');
      UTL_HTTP.SET_HEADER(r     => req,
                          name  => 'Content-Length',
                          VALUE => v_body_length);
      --分段输出请求参数,这个header设置很重要
      UTL_HTTP.SET_HEADER(req, 'Transfer-Encoding', 'chunked');
      --分段输出请求参数,避免内容超出长度限制
      WHILE (offset < v_body_length) LOOP
        dbms_lob.read(v_body, amount, offset, buffer);
        UTL_HTTP.WRITE_RAW(r => req, data => UTL_RAW.CAST_TO_RAW(buffer));
        offset := offset + amount;
      END LOOP;
      resp := UTL_HTTP.GET_RESPONSE(req);

      UTL_HTTP.READ_LINE(resp, v_text, TRUE);
      
      UTL_HTTP.END_RESPONSE(resp);
    END IF;
    return v_text;
  EXCEPTION
    WHEN UTL_HTTP.END_OF_BODY THEN
      UTL_HTTP.END_RESPONSE(resp);
      return sqlerrm;
    WHEN OTHERS THEN
      UTL_HTTP.END_RESPONSE(resp);
      UTL_HTTP.END_REQUEST(req);
      return sqlerrm;
  end;
 
  /*
  * @pharms:
  *      :
  *结果返回大文本
  * */
  FUNCTION HTTP_REQUEST_BLOB(v_url   VARCHAR2, --请求地址
                        v_param VARCHAR2, --POST 相当于表单提交
                        v_body  clob, --POST body中的参数
                        v_type  varchar2 --类型
                        ) return blob is
    req            UTL_HTTP.REQ;
    resp           UTL_HTTP.RESP;
    v_rblob        blob;
    l_raw_data      RAW(32767); -- 用于存储原始字节流
    out_buffer     VARCHAR2(32767);
    v_param_length NUMBER;
    v_body_length  NUMBER;
    buffer         VARCHAR2(32767);
    offset         NUMBER := 1;
    amount         NUMBER := 32767;
  begin
    dbms_lob.createtemporary(v_rblob, true);
    IF V_TYPE = 'GET' THEN
      req    := UTL_HTTP.BEGIN_REQUEST(url => v_url, method => 'GET');
      UTL_HTTP.SET_BODY_CHARSET('UTF-8');
      UTL_HTTP.SET_HEADER(req,
                          'Content-Type',
                          'application/x-www-form-urlencoded');
      resp := UTL_HTTP.GET_RESPONSE(req);
      --响应结果设置字符集
      utl_http.set_body_charset(r=> resp, charset=>'UTF-8');
       begin

         UTL_HTTP.READ_RAW(resp, l_raw_data, amount);
          -- 关键步骤:将读取到的 UTF-8 编码的 RAW 数据转换为字符串,并追加到 CLOB 中
          -- 使用 UTL_I18N.RAW_TO_CHAR 指定源编码为 'UTF8'
          --out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
           -- 尝试不同的字符集名称
          /*BEGIN
            -- 首先尝试 UTF8(最常用)
            out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
          EXCEPTION
            WHEN OTHERS THEN
              BEGIN
                -- 如果UTF8失败,尝试AL32UTF8(Oracle的标准UTF-8名称)
                out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'AL32UTF8');
              EXCEPTION
                WHEN OTHERS THEN
                  BEGIN
                    -- 如果还失败,尝试WE8ISO8859P1(ISO-8859-1)
                    out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'WE8ISO8859P1');
                  EXCEPTION
                    WHEN OTHERS THEN
                      -- 如果所有字符集都失败,使用默认转换(可能产生乱码但不会报错)
                      out_buffer := UTL_RAW.CAST_TO_VARCHAR2(l_raw_data);
                  END;
              END;
          END;*/
          --编码一致,则使用直接读取文本
          UTL_HTTP.READ_TEXT(resp, out_buffer, LENGTH(out_buffer));
          DBMS_LOB.WRITEAPPEND(v_rblob, LENGTH(out_buffer), out_buffer);
        exception
          when utl_http.END_OF_BODY
         then
           null;
        end;
      UTL_HTTP.END_RESPONSE(resp);

    ELSIF V_TYPE = 'POST_FORM' THEN
      v_param_length := LENGTHB(v_param);
      req            := UTL_HTTP.BEGIN_REQUEST(url    => v_url,
                                               method => 'POST');

      UTL_HTTP.SET_BODY_CHARSET('UTF-8');
      UTL_HTTP.SET_HEADER(r     => req,
                          name  => 'Content-Type',
                          VALUE => 'application/x-www-form-urlencoded');
      UTL_HTTP.SET_HEADER(r     => req,
                          name  => 'Content-Length',
                          VALUE => v_param_length);
      UTL_HTTP.WRITE_RAW(r => req, data => UTL_RAW.CAST_TO_RAW(v_param));
      resp := UTL_HTTP.GET_RESPONSE(req);
      utl_http.set_body_charset(r=> resp, charset=>'UTF-8');
      begin
         UTL_HTTP.READ_RAW(resp, l_raw_data, amount);
          -- 关键步骤:将读取到的 UTF-8 编码的 RAW 数据转换为字符串,并追加到 CLOB 中
          -- 使用 UTL_I18N.RAW_TO_CHAR 指定源编码为 'UTF8'
          --out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
           -- 尝试不同的字符集名称
          /*BEGIN
            -- 首先尝试 UTF8(最常用)
            out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
          EXCEPTION
            WHEN OTHERS THEN
              BEGIN
                -- 如果UTF8失败,尝试AL32UTF8(Oracle的标准UTF-8名称)
                out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'AL32UTF8');
              EXCEPTION
                WHEN OTHERS THEN
                  BEGIN
                    -- 如果还失败,尝试WE8ISO8859P1(ISO-8859-1)
                    out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'WE8ISO8859P1');
                  EXCEPTION
                    WHEN OTHERS THEN
                      -- 如果所有字符集都失败,使用默认转换(可能产生乱码但不会报错)
                      out_buffer := UTL_RAW.CAST_TO_VARCHAR2(l_raw_data);
                  END;
              END;
          END;*/
          --编码一致,则使用直接读取文本
          UTL_HTTP.READ_TEXT(resp, out_buffer, LENGTH(out_buffer));
          DBMS_LOB.WRITEAPPEND(v_rblob, LENGTH(out_buffer), out_buffer);
        exception
          when utl_http.END_OF_BODY
         then
           null;
        end;
      UTL_HTTP.END_RESPONSE(resp);
    ELSE
      v_body_length := dbms_lob.getlength(v_body);
      req           := UTL_HTTP.BEGIN_REQUEST(url    => v_url,
                                              method => 'POST');

      UTL_HTTP.SET_BODY_CHARSET('UTF-8');
      UTL_HTTP.SET_HEADER(r     => req,
                          name  => 'Content-Type',
                          VALUE => 'application/json;charset=utf-8');
      UTL_HTTP.SET_HEADER(r     => req,
                          name  => 'Content-Length',
                          VALUE => v_body_length);
      --分段输出请求参数,这个header设置很重要
      UTL_HTTP.SET_HEADER(req, 'Transfer-Encoding', 'chunked');
      --分段输出请求参数,避免内容超出长度限制
      WHILE (offset < v_body_length) LOOP
        dbms_lob.read(v_body, amount, offset, buffer);
        UTL_HTTP.WRITE_RAW(r => req, data => UTL_RAW.CAST_TO_RAW(buffer));
        offset := offset + amount;
      END LOOP;
      resp := UTL_HTTP.GET_RESPONSE(req);
      utl_http.set_body_charset(r=> resp, charset=>'UTF-8');
       begin
         /*loop
           utl_http.read_raw( resp, l_raw_data );
           dbms_lob.append( v_rblob, to_blob(l_raw_data) );
          end loop;*/
          UTL_HTTP.READ_RAW(resp, l_raw_data, amount);
          -- 关键步骤:将读取到的 UTF-8 编码的 RAW 数据转换为字符串,并追加到 CLOB 中
          -- 使用 UTL_I18N.RAW_TO_CHAR 指定源编码为 'UTF8'
          --out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
           -- 尝试不同的字符集名称
          /*BEGIN
            -- 首先尝试 UTF8(最常用)
            out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
          EXCEPTION
            WHEN OTHERS THEN
              BEGIN
                -- 如果UTF8失败,尝试AL32UTF8(Oracle的标准UTF-8名称)
                out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'AL32UTF8');
              EXCEPTION
                WHEN OTHERS THEN
                  BEGIN
                    -- 如果还失败,尝试WE8ISO8859P1(ISO-8859-1)
                    out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'WE8ISO8859P1');
                  EXCEPTION
                    WHEN OTHERS THEN
                      -- 如果所有字符集都失败,使用默认转换(可能产生乱码但不会报错)
                      out_buffer := UTL_RAW.CAST_TO_VARCHAR2(l_raw_data);
                  END;
              END;
          END;*/
          --编码一致,则使用直接读取文本
          UTL_HTTP.READ_TEXT(resp, out_buffer, LENGTH(out_buffer));
          DBMS_LOB.WRITEAPPEND(v_rblob, LENGTH(out_buffer), out_buffer);
        exception
          when utl_http.END_OF_BODY
         then
           null;
        end;
      UTL_HTTP.END_RESPONSE(resp);
    END IF;
    return v_rblob;
  EXCEPTION
    WHEN UTL_HTTP.END_OF_BODY THEN
      UTL_HTTP.END_RESPONSE(resp);
      return to_blob(sqlerrm);
    WHEN OTHERS THEN
      UTL_HTTP.END_RESPONSE(resp);
      UTL_HTTP.END_REQUEST(req);
      return to_blob(sqlerrm);
  end;
  /*
  * @pharms:
  *      :
  *结果返回大文本
  * */
  FUNCTION HTTP_REQUEST_CLOB(v_url   VARCHAR2, --请求地址
                        v_param VARCHAR2, --POST 相当于表单提交
                        v_body  clob, --POST body中的参数
                        v_type  varchar2 --类型
                        ) return clob is
    req            UTL_HTTP.REQ;
    resp           UTL_HTTP.RESP;
    v_rclob        clob;
    v_param_length NUMBER;
    v_body_length  NUMBER;
    l_raw_data      RAW(32767); -- 用于存储原始字节流
    buffer         VARCHAR2(32767);
    out_buffer     VARCHAR2(32767);
    offset         NUMBER := 1;
    amount         NUMBER := 32767;
  begin
    -- 初始化 CLOB
    DBMS_LOB.CREATETEMPORARY(v_rclob, TRUE);
    IF V_TYPE = 'GET' THEN
      req    := UTL_HTTP.BEGIN_REQUEST(url => v_url, method => 'GET');
      UTL_HTTP.SET_BODY_CHARSET('UTF-8');
      UTL_HTTP.SET_HEADER(req,
                          'Content-Type',
                          'application/x-www-form-urlencoded');
      resp := UTL_HTTP.GET_RESPONSE(req);
      --响应结果设置字符集
      utl_http.set_body_charset(r=> resp, charset=>'UTF-8');
      --读取响应,注意:这里读取的是 RAW 数据
      BEGIN       
        LOOP
          UTL_HTTP.READ_RAW(resp, l_raw_data, amount);
          -- 关键步骤:将读取到的 UTF-8 编码的 RAW 数据转换为字符串,并追加到 CLOB 中
          -- 使用 UTL_I18N.RAW_TO_CHAR 指定源编码为 'UTF8'
          --out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
           -- 尝试不同的字符集名称
          /*BEGIN
            -- 首先尝试 UTF8(最常用)
            out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
          EXCEPTION
            WHEN OTHERS THEN
              BEGIN
                -- 如果UTF8失败,尝试AL32UTF8(Oracle的标准UTF-8名称)
                out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'AL32UTF8');
              EXCEPTION
                WHEN OTHERS THEN
                  BEGIN
                    -- 如果还失败,尝试WE8ISO8859P1(ISO-8859-1)
                    out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'WE8ISO8859P1');
                  EXCEPTION
                    WHEN OTHERS THEN
                      -- 如果所有字符集都失败,使用默认转换(可能产生乱码但不会报错)
                      out_buffer := UTL_RAW.CAST_TO_VARCHAR2(l_raw_data);
                  END;
              END;
          END;*/
          --编码一致,则使用直接读取文本
          UTL_HTTP.READ_TEXT(resp, out_buffer, LENGTH(out_buffer));
          DBMS_LOB.WRITEAPPEND(v_rclob, LENGTH(out_buffer), out_buffer);
        END LOOP;
      EXCEPTION
          WHEN UTL_HTTP.END_OF_BODY THEN
              NULL;
      END;

      UTL_HTTP.END_RESPONSE(resp);

    ELSIF V_TYPE = 'POST_FORM' THEN
      v_param_length := LENGTHB(v_param);
      req            := UTL_HTTP.BEGIN_REQUEST(url    => v_url,
                                               method => 'POST');

      UTL_HTTP.SET_BODY_CHARSET('UTF-8');
      UTL_HTTP.SET_HEADER(r     => req,
                          name  => 'Content-Type',
                          VALUE => 'application/x-www-form-urlencoded');
      UTL_HTTP.SET_HEADER(r     => req,
                          name  => 'Content-Length',
                          VALUE => v_param_length);
      UTL_HTTP.WRITE_RAW(r => req, data => UTL_RAW.CAST_TO_RAW(v_param));
      resp := UTL_HTTP.GET_RESPONSE(req);
      utl_http.set_body_charset(r=> resp, charset=>'UTF-8');
      BEGIN
        LOOP
          UTL_HTTP.READ_RAW(resp, l_raw_data, amount);
          -- 关键步骤:将读取到的 UTF-8 编码的 RAW 数据转换为字符串,并追加到 CLOB 中
          -- 使用 UTL_I18N.RAW_TO_CHAR 指定源编码为 'UTF8'
          --out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
           -- 尝试不同的字符集名称
          /*BEGIN
            -- 首先尝试 UTF8(最常用)
            out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
          EXCEPTION
            WHEN OTHERS THEN
              BEGIN
                -- 如果UTF8失败,尝试AL32UTF8(Oracle的标准UTF-8名称)
                out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'AL32UTF8');
              EXCEPTION
                WHEN OTHERS THEN
                  BEGIN
                    -- 如果还失败,尝试WE8ISO8859P1(ISO-8859-1)
                    out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'WE8ISO8859P1');
                  EXCEPTION
                    WHEN OTHERS THEN
                      -- 如果所有字符集都失败,使用默认转换(可能产生乱码但不会报错)
                      out_buffer := UTL_RAW.CAST_TO_VARCHAR2(l_raw_data);
                  END;
              END;
          END;*/
          --编码一致,则使用直接读取文本
          UTL_HTTP.READ_TEXT(resp, out_buffer, LENGTH(out_buffer));
          DBMS_LOB.WRITEAPPEND(v_rclob, LENGTH(out_buffer), out_buffer);
        END LOOP;        
      EXCEPTION
          WHEN UTL_HTTP.END_OF_BODY THEN
              NULL;
      END;
      UTL_HTTP.END_RESPONSE(resp);
    ELSE
      v_body_length := dbms_lob.getlength(v_body);
      req           := UTL_HTTP.BEGIN_REQUEST(url    => v_url,
                                              method => 'POST');

      UTL_HTTP.SET_BODY_CHARSET('UTF-8');
      UTL_HTTP.SET_HEADER(r     => req,
                          name  => 'Content-Type',
                          VALUE => 'application/json;charset=utf-8');
      UTL_HTTP.SET_HEADER(r     => req,
                          name  => 'Content-Length',
                          VALUE => v_body_length);
      --分段输出请求参数,这个header设置很重要
      UTL_HTTP.SET_HEADER(req, 'Transfer-Encoding', 'chunked');
      --分段输出请求参数,避免内容超出长度限制
      WHILE (offset < v_body_length) LOOP
        dbms_lob.read(v_body, amount, offset, buffer);
        UTL_HTTP.WRITE_RAW(r => req, data => UTL_RAW.CAST_TO_RAW(buffer));
        offset := offset + amount;
      END LOOP;
      resp := UTL_HTTP.GET_RESPONSE(req);
      utl_http.set_body_charset(r=> resp, charset=>'UTF-8');
      BEGIN
        LOOP
          UTL_HTTP.READ_RAW(resp, l_raw_data, amount);
          --字符集不一致导致乱码时尝试 关键步骤:将读取到的 UTF-8 编码的 RAW 数据转换为字符串,并追加到 CLOB 中
          -- 使用 UTL_I18N.RAW_TO_CHAR 指定源编码为 'UTF8'
          -- 明确指定源编码为 ZHS16GBK
          --out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'ZHS16GBK');
          --原样输出
          --out_buffer := UTL_RAW.CAST_TO_VARCHAR2(l_raw_data);
           -- 尝试不同的字符集名称
          /*BEGIN
            -- 首先尝试 UTF8(最常用)
            out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
          EXCEPTION
            WHEN OTHERS THEN
              BEGIN
                -- 如果UTF8失败,尝试AL32UTF8(Oracle的标准UTF-8名称)
                out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'AL32UTF8');
              EXCEPTION
                WHEN OTHERS THEN
                  BEGIN
                    -- 如果还失败,尝试WE8ISO8859P1(ISO-8859-1)
                    out_buffer := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'WE8ISO8859P1');
                  EXCEPTION
                    WHEN OTHERS THEN
                      -- 如果所有字符集都失败,使用默认转换(可能产生乱码但不会报错)
                      out_buffer := UTL_RAW.CAST_TO_VARCHAR2(l_raw_data);
                  END;
              END;
          END;*/
          --编码一致,则使用直接读取文本
          UTL_HTTP.READ_TEXT(resp, out_buffer, LENGTH(out_buffer));
          DBMS_LOB.WRITEAPPEND(v_rclob, LENGTH(out_buffer), out_buffer);
        END LOOP;
      EXCEPTION
          WHEN UTL_HTTP.END_OF_BODY THEN
              NULL;
      END;
      UTL_HTTP.END_RESPONSE(resp);
    END IF;
    -- 释放临时 LOB
    --DBMS_LOB.FREETEMPORARY(v_rclob);
    return v_rclob;
  EXCEPTION
    WHEN UTL_HTTP.END_OF_BODY THEN
      -- 释放临时 LOB
      --DBMS_LOB.FREETEMPORARY(v_rclob);
      UTL_HTTP.END_RESPONSE(resp);
      return sqlerrm;
    WHEN OTHERS THEN
      -- 释放临时 LOB
      --DBMS_LOB.FREETEMPORARY(v_rclob);
      UTL_HTTP.END_RESPONSE(resp);
      UTL_HTTP.END_REQUEST(req);
      return sqlerrm;
  end;
END pkg_scene_http_option;
/

UTL_HTTP.READ_RAW与UTL_HTTP.READ_TEXT核心区别对比

特性UTL_HTTP.READ_RAWUTL_HTTP.READ_TEXT
返回类型RAW(原始字节流)VARCHAR2(字符串)
字符集处理不进行任何字符集转换自动进行字符集转换
数据完整性保持原始字节完整性可能因字符集转换丢失数据
使用场景二进制数据、字符集不确定时文本数据、字符集明确且匹配时
性能稍高(无转换开销)稍低(有转换开销)
错误风险低(需要手动处理字符集)高(自动转换可能出错)

详细解释

1. UTL_HTTP.READ_RAW

  • 返回原始字节流,不进行任何字符集解释

  • 相当于获取HTTP响应的"二进制副本"

  • 需要开发者手动处理字符集转换

sql

DECLARE
  l_raw_data RAW(32767);
BEGIN
  UTL_HTTP.READ_RAW(l_http_response, l_raw_data, 1000);
  -- 此时 l_raw_data 包含的是原始字节,需要手动转换
  l_text := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
END;

2. UTL_HTTP.READ_TEXT

  • 返回已转换的字符串,自动进行字符集转换

  • 转换基于NLS设置或HTTP响应头中的字符集信息

  • 如果字符集不匹配,可能产生乱码

sql

DECLARE
  l_text_data VARCHAR2(32767);
BEGIN
  UTL_HTTP.READ_TEXT(l_http_response, l_text_data, 1000);
  -- 此时 l_text_data 已经是转换后的字符串
END;

使用场景推荐

使用 UTL_HTTP.READ_RAW 的场景:

  1. 字符集不确定或可能不匹配时

    sql

    -- 安全读取,避免自动转换导致的乱码
    UTL_HTTP.READ_RAW(l_response, l_raw_data, l_amount);
    l_text := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8'); -- 明确指定源编码
  2. 处理二进制数据

    sql

    -- 下载图片、PDF、ZIP等二进制文件
    UTL_HTTP.READ_RAW(l_response, l_file_data, l_amount);
    INSERT INTO files_table (file_content) VALUES (l_file_data);
  3. 需要精确控制字符集转换时

    sql

    -- 源数据是GBK,数据库是AL32UTF8
    UTL_HTTP.READ_RAW(l_response, l_raw_data, l_amount);
    l_text := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'ZHS16GBK'); -- 明确转换
  4. 处理多种编码的HTTP服务时

    sql

    -- 不同接口返回不同编码
    UTL_HTTP.READ_RAW(l_response, l_raw_data, l_amount);
    -- 根据Content-Type头动态选择编码进行转换

使用 UTL_HTTP.READ_TEXT 的场景:

  1. 字符集明确且匹配时

    sql

    -- 确认接口返回UTF-8,数据库也是UTF-8
    UTL_HTTP.SET_BODY_CHARSET(l_response, 'UTF-8');
    UTL_HTTP.READ_TEXT(l_response, l_text_data, l_amount);
  2. 处理纯文本且编码简单时

    sql

    -- 处理ASCII或简单英文文本
    UTL_HTTP.READ_TEXT(l_response, l_text_data, l_amount);
  3. 快速原型开发时

    sql

    -- 开发测试阶段,快速获取文本内容
    UTL_HTTP.READ_TEXT(l_response, l_text_data, l_amount);
    DBMS_OUTPUT.PUT_LINE(l_text_data);

实战示例对比

示例1:处理中文文本(推荐使用READ_RAW)

sql

-- 方法1:使用READ_RAW(安全)
DECLARE
  l_response UTL_HTTP.RESP;
  l_raw_data RAW(32767);
  l_text     VARCHAR2(32767);
BEGIN
  l_response := UTL_HTTP.GET_RESPONSE(...);
  
  LOOP
    UTL_HTTP.READ_RAW(l_response, l_raw_data, 1000);
    -- 明确指定源编码为UTF-8,目标为数据库字符集
    l_text := UTL_I18N.RAW_TO_CHAR(l_raw_data, 'UTF8');
    DBMS_OUTPUT.PUT_LINE(l_text);
  END LOOP;
END;
/

-- 方法2:使用READ_TEXT(风险较高)
DECLARE
  l_response UTL_HTTP.RESP;
  l_text_data VARCHAR2(32767);
BEGIN
  l_response := UTL_HTTP.GET_RESPONSE(...);
  UTL_HTTP.SET_BODY_CHARSET(l_response, 'UTF-8'); -- 需要正确设置
  
  LOOP
    UTL_HTTP.READ_TEXT(l_response, l_text_data, 1000);
    DBMS_OUTPUT.PUT_LINE(l_text_data); -- 可能乱码
  END LOOP;
END;
/

示例2:下载二进制文件(必须使用READ_RAW)

sql

-- 下载图片文件
DECLARE
  l_response UTL_HTTP.RESP;
  l_raw_data RAW(32767);
  l_file_blob BLOB;
BEGIN
  l_response := UTL_HTTP.GET_RESPONSE('http://example.com/image.jpg');
  
  DBMS_LOB.CREATETEMPORARY(l_file_blob, TRUE);
  
  LOOP
    UTL_HTTP.READ_RAW(l_response, l_raw_data, 32767);
    -- 将RAW数据存入BLOB
    DBMS_LOB.WRITEAPPEND(l_file_blob, UTL_RAW.LENGTH(l_raw_data), l_raw_data);
  END LOOP;
  
  -- 保存到数据库
  INSERT INTO images (image_name, image_data) 
  VALUES ('downloaded.jpg', l_file_blob);
  
  DBMS_LOB.FREETEMPORARY(l_file_blob);
END;
/

字符集处理机制

READ_TEXT的自动转换过程:

  1. 检查HTTP响应头的Content-Type中的charset参数

  2. 如果没有指定,使用NLS设置或默认字符集

  3. 将字节流转换为数据库字符集

READ_RAW的手动控制过程:

  1. 获取原始字节流

  2. 开发者根据需要调用UTL_I18N.RAW_TO_CHAR()进行转换

  3. 完全控制源编码和目标编码

总结建议

情况推荐使用原因
中文内容处理READ_RAW避免自动转换乱码
二进制文件下载READ_RAW必须使用原始字节流
字符集不确定READ_RAW手动控制更安全
纯英文文本READ_TEXT简单高效
编码明确匹配READ_TEXT方便快捷
开发调试READ_TEXT快速查看内容
<think>我们正在讨论Kettle跨数据库数据同步的配置方法。根据用户提供的信息,Kettle(也称为Pentaho Data Integration)是一款强大的ETL工具,支持多种数据源之间的数据抽取、转换和加载。 用户引用中提到了使用Kettle进行跨数据同步,并给出了一个配置数据库连接的示例(引用[2])。此外,引用[1]提到了一些Kettle任务开发的基本概念,如转换(Transformation)和作业(Job),以及任务配置表和日志表。 配置跨数据库数据同步的基本步骤包括: 1. 配置数据库连接:为源数据库和目标数据库分别创建连接。 2. 设计转换(Transformation):使用输入步骤(如表输入)从源数据库读取数据,使用输出步骤(如表输出、插入/更新)将数据写入目标数据库。 3. 设计作业(Job):如果需要定时执行,可以创建一个作业,在作业中添加转换,并设置定时调度(如使用crontab)。 4. 设置日志记录:将任务执行情况记录到日志表中(如引用[1]中提到的etl_task_log表)。 下面我们将详细说明这些步骤。 ### 步骤1:配置数据库连接 在Kettle中,我们可以在“转换”或“作业”中定义数据库连接。通常,我们会在“主对象树”的“数据库连接”中创建连接。对于每个数据库连接,需要提供以下信息: - 连接名称:自定义的连接名称。 - 连接类型:选择数据库类型(如MySQL, Oracle等)。 - 连接方式:通常选择“Native (JDBC)”。 - 主机名:数据库服务器的IP地址或主机名。 - 数据库名称:要连接的数据库名。 - 端口号:数据库监听端口。 - 用户名和密码:访问数据库的凭据。 例如,配置一个MySQL数据库连接,如引用[2]所示,我们可以按照以下参数设置: 连接名称:MYSQL8_DB 类型:MySQL 主机名:192.168.197.18 数据库名:test_data 端口:3306 用户名:admin 密码:123456 注意:在URL中还可以添加额外的参数,如字符集、时区等(如引用[2]中的URL参数)。 ### 步骤2:设计转换 转换是Kettle中处理数据流的核心。一个典型的跨同步转换包括以下步骤: - **表输入**:从源数据库读取数据。可以编写SQL查询,也可以直接选择表。 - **字段选择**(可选):如果需要对字段进行重命名、类型转换等操作,可以使用“选择/改名值”步骤。 - **表输出**或**插入/更新**:将数据写入目标数据库。 - 如果目标表需要先清空再写入,可以使用“表输出”步骤(配合“执行SQL脚本”步骤来清空表)。 - 如果目标表需要根据关键字更新已有记录,则使用“插入/更新”步骤。 示例转换设计: 1. 拖拽一个“表输入”步骤,配置源数据库连接,并输入SQL查询(如`SELECT * FROM source_table`)。 2. 拖拽一个“插入/更新”步骤(或“表输出”步骤),配置目标数据库连接,选择目标表。 - 在“插入/更新”步骤中,需要设置用于比较的关键字字段(通常是主键),并设置更新字段的映射。 ### 步骤3:设计作业 作业用于控制转换的执行顺序、设置执行条件、定时任务等。例如: - 创建一个作业,添加“START”步骤,然后添加“转换”步骤(指向我们设计的转换)。 - 如果需要定时执行,可以设置作业的调度参数,或者使用外部调度工具(如crontab)调用Kettle的作业执行命令(如`pan.sh`或`kitchen.sh`)。 ### 步骤4:设置日志记录 为了记录任务执行情况,可以在作业中添加“写日志”步骤,或者将日志写入数据库表(如引用[1]中的etl_task_log表)。通常,我们可以: - 在作业开始时,向日志表插入一条开始记录。 - 在作业结束时(无论成功或失败),更新日志记录的状态和结束时间。 引用[1]中提到两个表: - `etl_task_config`:存储任务配置,包括任务编码、执行时间、数据同步的开始和结束时间。 - `etl_task_log`:存储任务每次运行的日志。 我们可以在作业中使用“执行SQL脚本”步骤来操作这些日志表。 ### 示例:简单的跨同步转换 下面是一个简单的转换示例,将数据从MySQL的一个表同步到另一个MySQL数据库的表中(假设两个数据库结构相同,且目标表已存在)。 1. **表输入**(源数据库): - 连接:选择源数据库连接(如MYSQL_SOURCE) - SQL:`SELECT * FROM my_table;` 2. **插入/更新**(目标数据库): - 连接:选择目标数据库连接(如MYSQL_TARGET) - 目标表:`my_table` - 用来查询的关键字:选择主键字段(如`id`),并设置“比较操作符”为“=”。 - 字段映射:将输入字段映射到目标表字段(通常自动匹配)。 注意:如果目标表不存在,需要先创建表结构,或者使用“表输出”步骤(它会自动创建表,但需要确保字段类型正确)。 ### 使用crontab定时执行 在Linux系统中,我们可以使用crontab来定时执行Kettle作业。例如: 1. 将作业保存为`my_job.kjb`。 2. 编写一个shell脚本(如`run_job.sh`)来执行作业: ```bash #!/bin/bash /path/to/data-integration/kitchen.sh -file=/path/to/my_job.kjb ``` 3. 使用`crontab -e`添加定时任务,例如每天凌晨1点执行: ``` 0 1 * * * /path/to/run_job.sh ``` ### 注意事项 - **字符编码**:确保源数据库和目标数据库的字符编码设置一致,避免乱码。 - **事务处理**:根据目标数据库的特性,可能需要调整事务提交的大小(如在“表输出”步骤中设置提交记录数)。 - **错误处理**:在转换中设置错误处理步骤,将错误记录输出到文件或表中,以便后续排查。 - **性能优化**:对于大数据量的同步,可以考虑使用“批量加载”方式(如MySQL的LOAD DATA INFILE)或调整JDBC参数(如使用rewriteBatchedStatements=true)。 通过以上步骤,我们可以配置Kettle实现跨数据库的数据同步。 §§相关问题§§ 1. 在Kettle中如何处理跨数据库同步时数据类型不一致的问题? 2. 如何优化Kettle转换以提高大数据量同步的性能? 3. Kettle支持哪些类型的数据库?是否支持非关系型数据库?</think>### Kettle跨数据库数据同步配置方法 Kettle(Pentaho Data Integration)支持跨数据库数据同步的核心在于**数据库连接配置**和**数据流设计**。以下是详细配置步骤: --- #### **1. 配置数据库连接** 在Kettle中需分别配置源和目标的JDBC连接: ```properties # 源数据库配置示例(MySQL) type=javax.sql.DataSource driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://source_host:3306/source_db?useSSL=false&serverTimezone=UTC user=source_user password=source_pwd # 目标数据库示例(PostgreSQL) type=javax.sql.DataSource driver=org.postgresql.Driver url=jdbc:postgresql://target_host:5432/target_db user=target_user password=target_pwd ``` **关键点**: - 驱动需提前放入Kettle的`lib`目录 - 不同数据库需匹配对应驱动(如Oracle需`ojdbc.jar`) - 跨云数据库时需配置白名单和SSL --- #### **2. 设计数据流(Transformation)** 使用**表输入**→**字段转换**→**表输出**流程: 1. **表输入**:从源读取数据 ```sql SELECT * FROM source_table WHERE update_time > '${LAST_SYNC_TIME}' ``` 2. **字段映射**:使用`选择/改名值`步骤处理字段差 - 类型转换(如MySQL的`DATETIME`转Oracle的`DATE`) - 字段重命名(`src_col → tgt_col`) 3. **表输出**:写入目标 - 选择"批量插入"提升性能 - 设置提交记录数(建议500-2000) - 主键冲突时用`插入/更新`步骤 --- #### **3. 配置定时任务(Job调度)** - **Kettle作业层**: - 添加`START`→`Transformation`→`成功`流程 - 在`Transformation`步骤引用设计好的转换文件 - **系统级调度**: ```bash # Linux crontab示例(每天2点执行) 0 2 * * * /opt/kettle/pan.sh -file=/path/sync.ktr ``` - Windows可用任务计划程序 --- #### **4. 日志与监控** 在目标创建日志表(参考引用[^1]): ```sql CREATE TABLE etl_task_log ( task_id BIGINT, start_time TIMESTAMP, end_time TIMESTAMP, rows_processed INT, status VARCHAR(10) -- SUCCESS/FAILED ); ``` 在Kettle作业中使用`执行SQL脚本`步骤记录日志。 --- #### **5. 性能优化技巧** 1. **增量同步**: - 在`表输入`中使用时间戳或增量ID - 通过变量`${LAST_SYNC_TIME}`传递参数 2. **连接池配置**: - 在`database.properties`中设置`max_active=20` 3. **大表分片**: ```sql -- 表输入中使用分页查询 SELECT * FROM big_table LIMIT 100000 OFFSET ${ROWNUM} ``` --- #### **注意事项** - **字符集问题**:在JDBC URL中强制指定编码 ```diff jdbc:mysql://...?characterEncoding=utf8 ``` - **事务隔离**:目标建议设置`autocommit=false` - **网络延迟**:跨云同步时测试传输速率,必要时压缩数据 > 通过以上步骤可实现MySQL→PostgreSQL、Oracle→SQL Server等跨同步。实际案例显示,单表百万数据同步可在10分钟内完成[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值