跨库查一张表,DB Link 往往最省事:不搬数据、不做同步,一条 SQL 读到远端。但它把网络、认证、事务、权限绑在一条链接上,任何一环没配对,报错都指向别处。下面先讲清它做了什么、有哪些类型,再给一套可照抄的创建过程。
一、DB Link 是什么
数据库链接(Database Link)是 Oracle 在一个数据库实例中访问另一个实例对象的机制。通过它,可以对远端表做查询、插入、更新、删除,语法上只是在对象名后加 @链接名。
它不复制数据,也不缓存结果。每次引用都是真实的远程调用:本地解析、按链接定义连远端、远端认证执行、结果回传。后面所有性能和故障问题都由这一点解释。
1.1 一次跨库查询的完整路径
以 select count(*) from as_user_info@asuser_as_user_info 为例:
- 本地会话解析 SQL,识别出对象带
@,取出链接定义里的连接描述。 - 按连接描述里的
HOST、PORT、SERVICE_NAME发起 TCP 连接。连接描述可以写在链接定义里,也可以只写一个 TNS 别名,由tnsnames.ora或 LDAP 解析。 - 远端监听接手,把连接交给对应服务;远端用链接里指定的账号密码认证,建立会话。
- SQL 被送到远端执行。绝大多数情况下,过滤、聚合、排序都在远端完成,只把结果集传回本地。
- 如果语句里同时有本地表和远端表,本地要做 join,这时才会有大量数据在网络上往返。
第 4 步是关键:count(*) 只回传一行,代价很小;select * 加本地 join 则可能把远端整张表拉过来。跨库 SQL 慢,先看执行计划在哪一端跑。
1.2 链接名、schema 与对象解析
select * from as_user_info@link 里,as_user_info 按远端账号的默认 schema 解析。链接用 CONNECT TO asuser 建立,远端会话的 schema 就是 ASUSER,不写 schema 也能查到。
改用 CONNECT TO CURRENT_USER 后,远端会话的 schema 变成当前登录用户,同一条 SQL 就报 ORA-00942。这时必须写全 ASUSER.AS_USER_INFO@link,或在远端建同义词。
1.3 优化器看到的是什么
本地 CBO 对远端表的统计信息通常一无所知,只能按默认选择率估算。两张表各自不大,join 却选了嵌套循环、驱动顺序反了,这类问题在 DB Link 场景很常见。
可控的做法有三种:把过滤条件写进远端视图;用 /*+ driving_site(别名) */ 把整条语句推到远端;或放弃实时 join 改成定时抽取。第三种最土,但数据量大时往往最稳。
1.4 事务与两阶段提交
本地对远端表做 DML 后提交,Oracle 会用两阶段提交协调本地和远端。正常时感觉不到差别,网络中断时就可能出现 in-doubt 事务:一端已提交、另一端结果未知,需要 DBA 介入。
所以 DB Link 适合读多写少。业务要求强一致的跨库写时,先评估网络质量和故障恢复流程,不要默认它像单库事务一样工作。
二、三种类型与三种身份
2.1 私有、公有、全局
| 类型 | 创建语法 | 可见范围 |
|---|---|---|
| 私有 | CREATE DATABASE LINK | 只有创建者能使用 |
| 公有 | CREATE PUBLIC DATABASE LINK | 库内所有用户都能使用 |
| 全局 | CREATE DATABASE LINK ... CONNECT TO CURRENT_USER | 由目录服务(如 Oracle Names)定义,网络内多个数据库可用 |
私有链接建在用户 schema 下,跟着用户走,权限边界清晰,生产优先用。公有链接省事,但任何能连库的人都能借用其中的远端账号,等于把远端权限放大到全库。
三种类型的实际差别不在语法,而在“谁看得见”。同一句 select * from t@link,用私有链接必须由 owner 执行;换成公有链接,任何用户都能执行,用的还是链接里那个远端账号的身份。这就是公有链接在生产环境要严格管控的原因。
全局链接依赖目录服务,普通单机环境基本用不到。它的经典定义在 Oracle Names 服务器上,而 Oracle Names 早已不是主流,实际项目更常见的是 LDAP 或干脆不用。
2.2 固定用户、连接用户、当前用户
链接定义里 CONNECT TO 的写法决定了用谁的身份连远端:
CONNECT TO asuser IDENTIFIED BY "密码" 是固定用户。密码存在数据字典里,DBA_DB_LINKS 不显示,但有高权限的账号仍可能取到。行为确定,不依赖本地登录用户。
固定用户最常见,代价是密码轮换麻烦:远端改密后,所有引用该链接的库都要重建链接,否则下次连接报 ORA-01017。改密流程要把 DB Link 列进影响面清单。
CONNECT TO CURRENT_USER 用当前会话的用户名去远端认证,要求两端有同名同密码的账号。它避免在链接里存密码,但账号必须两端同步。
完全不写 CONNECT TO 时默认用本地登录用户去远端认证,同样要求两端账号一致。这种方式最易被忽略:创建时不报错,查询时才报 ORA-01017。
2.3 权限
建链接和用链接是两套权限,很多人只记住了前者。
创建链接需要 CREATE DATABASE LINK;建公有链接需要 CREATE PUBLIC DATABASE LINK,通常只有 DBA 有。这只是“建”的权限。
“用”的权限在远端校验:远端账号必须对目标表有 SELECT/INSERT/UPDATE/DELETE。本地用户即使能写 select * from tab@link,远端没授权照样报 ORA-00942 或 ORA-01031。
还有一层容易漏:私有链接只有 owner 能用;公有链接所有人可见,但远端对象权限仍按链接里保存的账号算。
审计时可以用这两条查清现状:
select owner, db_link, username, host, created
from dba_db_links
order by owner, db_link;
select * from v$dblink;
v$dblink 是当前实例已打开的链接会话,能看到 LOGON_TIME 和 IN_TRANSACTION;排查“谁在用链接”时比 DBA_DB_LINKS 有用。
三、创建前的检查
3.1 网络通不通
先在源端服务器确认能连到目标端监听端口,不要只在客户端测:
tnsping assdb
nc -vz 192.168.61.131 1526
tnsping 走 TNS 解析加监听握手,能通说明别名和网络都没问题;nc 只测 TCP,可区分网络不通和 TNS 配错。
只测通还不够。跨机房或跨云的网络,TCP 能连上但 RTT 高、丢包重传多,DB Link 上每条 SQL 都要付这个延迟。先量:
ping -c 20 192.168.61.131
平均 RTT 超过 10ms 时,不适合在链接上做逐行处理(如 insert ... select 一百万行),改成远端聚合或批量抽取。
3.2 服务名对不对
到目标端确认监听注册的服务名,不要凭记忆写:
lsnrctl status
select name, pdb from v$services order by name;
SERVICE_NAME 写错时报 ORA-12514(监听不认识这个服务),而不是“连不上”。RAC 下还要确认连的是 SCAN 还是某个 VIP。
PDB 环境要注意:v$services 在 CDB 根能看到所有 PDB 的服务,只有 con_id 对应的才是要连的。用 show con_name 确认当前容器,再对照服务列表:
show con_name
select name, con_id, pdb from v$services order by con_id;
PDB 刚创建、监听里还没有它的服务时,先在 PDB 里执行 alter system register;,再看 lsnrctl status。
3.3 两端账号与权限
目标用户要存在、有配额、默认表空间正确。PDB 环境下尤其注意:用户建在哪个容器,链接就必须连到那个容器的服务名,连到 CDB 根看不到 PDB 用户的表。
源端用户要有 CREATE DATABASE LINK;建公有链接还要 CREATE PUBLIC DATABASE LINK,并且想清楚谁会用它。
四、完整实操
环境:源端 192.168.61.132,CDB 为 GACDB,PDB 为 GADB,用户 GAUSER;目标端 192.168.61.131,CDB 为 ASSCDB,PDB 为 ASSDB,用户 ASUSER。需求是源端 GAUSER 通过 DB Link 访问目标端 ASUSER.AS_USER_INFO。

4.1 目标端:建表空间和用户
sqlplus / as sysdba
alter session set container=ASSDB;
CREATE tablespace ASUSER_TBS datafile '/oracle/app/db/oradata/ASSCDB/assdb/asuser_tbs.dbf' SIZE 200M autoextend ON;
CREATE USER ASUSER identified BY "AS#0312#dba" DEFAULT tablespace ASUSER_TBS;
GRANT CONNECT,resource,unlimited tablespace TO ASUSER;
sqlpuls 是笔误,实际命令是 sqlplus。alter session set container 只在连到 CDB 根容器时有效,直接用 PDB 服务名登录不需要。密码加双引号是为了保留大小写并明确边界;不加引号时 Oracle 对大小写的处理和加引号不同,建链接时密码对不上就报 ORA-01017。

4.2 目标端:建表、索引、造数据
CREATE TABLE ASUSER.AS_USER_INFO (
user_id number(10) PRIMARY KEY,
user_name varchar2(50) NOT NULL,
email varchar2(100),
create_time date DEFAULT sysdate,
status number(1) DEFAULT 1
);
-- 为 user_name 创建索引(提升查询效率)
CREATE INDEX idx_as_user_info_name ON ASUSER.AS_USER_INFO(user_name);

DECLARE
v_max_rows NUMBER := 100000;
BEGIN
FOR i IN 1..v_max_rows LOOP
INSERT INTO asuser.as_user_info (user_id, user_name, email)
VALUES (
i,
'user_' || LPAD(i, 6, '0'),
'user_' || LPAD(i, 6, '0') || '@test.com'
);
-- 每 1000 条提交一次,避免 UNDO 日志过大
IF MOD(i, 1000) = 0 THEN
COMMIT;
END IF;
END LOOP;
COMMIT;
END;
/

分 100 次提交是为了控制 UNDO 和日志增长。数据量再大时这段 PL/SQL 会很慢,改用 insert /*+ append */ ... select ... from dual connect by level <= 100000 或外部表。
4.3 源端:建用户并授权
sqlplus / as sysdba
alter session set container=GADB;
CREATE tablespace GAUSER_TBS datafile '/oracle/app/db/oradata/GACDB/gadb/gauser01.dbf' SIZE 200M autoextend ON;
CREATE USER GAUSER identified BY "your_password" DEFAULT tablespace GAUSER_TBS;
GRANT CONNECT,resource,unlimited tablespace TO gauser;
GRANT CREATE database link TO gauser;

这里只授了 CREATE DATABASE LINK,建出来的是 GAUSER 的私有链接。确实需要公有链接时,把权限换成 CREATE PUBLIC DATABASE LINK,并确认远端账号的权限范围可接受。
4.4 源端:创建 DB Link
创建之前先确认当前用户在正确的容器里、有建链接的权限。私有链接属于创建它的用户,可见范围跟着容器走:在 PDB 里建,只在那个 PDB 可见;在根容器用公共用户建,各 PDB 才可能用到。拿不准时,建完到目标容器里查一次 DBA_DB_LINKS 验证。
show con_name
select * from session_privs where privilege like '%DATABASE LINK%';
第二条查不到任何行,说明当前用户没有建链接的权限,先授权再继续。
sqlplus 'gauser/"your_password"@GADB'
CREATE database link asuser_as_user_info
CONNECT TO asuser identified BY "AS#0312#dba"
USING '(DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.61.131)(PORT = 1526)))(CONNECT_DATA = (SERVICE_NAME = assdb)))';
这一行若写成 sqlplus "gauser/"your_password"@GADB',引号不配对,直接复制会报错。上面改成单引号包整串,让 SQL*Plus 解析里面的双引号密码。
链接名建议写成“远端用户 + 对象或用途”,别用 link1、test,半年后没人知道它连的是哪。
USING 直接写完整连接描述,不依赖本地 tnsnames.ora。换机器不用改 TNS,但地址变了要逐个改链接;链接多、地址常变时改用 TNS 别名:
CREATE database link asuser_as_user_info
CONNECT TO asuser identified BY "AS#0312#dba"
USING 'ASSDB';
两种写法各有代价。硬编码描述在目标端换 IP 时要重建所有链接;用别名则依赖源端 tnsnames.ora 的维护,别名写错时报 ORA-12154,看起来像网络问题。跨机房切换、VIP 漂移的场景,别名更容易集中改一处。
4.5 源端:验证
SELECT * FROM dual@asuser_as_user_info;
SELECT count(*) FROM AS_USER_INFO@asuser_as_user_info;

第一条验证链接能连上,第二条验证远端对象权限。两条都过才算完整;只有第一条过、第二条报 ORA-00942,问题在远端授权。
想确认实际连到哪个实例,可以查:
select instance_name, host_name from v$instance@asuser_as_user_info;
4.6 源端:建同义词
不想每条 SQL 都写 @链接名,可以建同义词:
-- 如果 gauser 没有创建同义词权限,则使用 sys 账号赋予
grant create synonym to gauser;
create synonym ASUSER_AS_USER_INFO FOR AS_USER_INFO@asuser_as_user_info;
-- 可以通过同义词进行访问了
select count(*) from ASUSER_AS_USER_INFO;


同义词只是语法糖,不改变权限模型:查询仍用链接里保存的远端账号执行。它也容易让人误以为是本地表,命名最好带 _LINK 或远端系统前缀。
五、跨库 SQL 怎么写才不慢
同一个需求,写法不同,性能可能差两个数量级。下面三条规则按收益从高到低排。
过滤条件尽量留在远端。 在本地写 where 不等于在远端过滤。优化器可能把整表拉回本地再筛,尤其是远端统计信息缺失时。把条件固化到远端视图里,本地只做 select * from 视图@link,执行计划就稳定了:
-- 远端执行
create or replace view asuser.v_user_info_active as
select user_id, user_name, email, create_time
from asuser.as_user_info
where status = 1;
需要 join 时考虑 driving_site。 提示让整条语句在远端执行,只回传最终结果:
select /*+ driving_site(a) */ a.user_id, b.dept_name
from asuser.as_user_info@asuser_as_user_info a,
local_dept b
where a.user_id = b.user_id;
这个提示不是万能的。远端要能访问本地表才行,否则语句根本推不过去。真正的跨库 join 仍然会分两段执行。
批量操作改成分批。insert into local_tab select * from remote_tab@link 会在一个事务里锁住两端,回滚段和 UNDO 都吃不消。改成按主键范围分批,每批提交:
insert into local_tab
select * from remote_tab@link
where id between 1 and 10000;
commit;
每批大小按网络 RTT 和 UNDO 容量调,没有固定值。批太小,网络往返次数多;批太大,UNDO 撑不住。
六、日常维护
链接建好只是开始,还有三件事要定期做。
验证可用性。 不要等业务报错才发现链接断了。放一条轻量检查进监控,每分钟跑一次:
select 1 from dual@asuser_as_user_info;
这条 SQL 不碰业务表,只验证网络、监听、认证。失败时告警,比业务方打电话快。
查看链接会话。 远端实例上能看到链接过来的会话,定位慢 SQL 时从这里入手:
select s.sid, s.serial#, s.username, s.program, s.machine, s.status
from v$session s
where s.username = 'ASUSER'
and s.program like '%ORACLE%';
program 带 ORACLE 且 machine 是源端主机名的,基本就是 DB Link 会话。
删除链接。 不再使用的链接要显式删掉,避免权限残留:
drop database link asuser_as_user_info;
公有链接用 drop public database link 链接名;。删除前确认没有同义词、视图、存储过程依赖它,否则编译会失败。
select owner, name, type
from dba_dependencies
where referenced_name = 'ASUSER_AS_USER_INFO';
七、常见报错怎么定位
ORA-02019: connection description for remote database not found。链接名拼错,或 USING 里的 TNS 别名解析不到。先 select * from dba_db_links where db_link like '%关键字%' 确认名字,再 tnsping 确认别名。
ORA-12154: TNS:could not resolve the connect identifier specified。USING 用了别名但 tnsnames.ora 里没有,或者客户端读的不是你以为的那个 tnsnames.ora(TNS_ADMIN 指错)。
ORA-12514: TNS:listener does not currently know of service requested。网络通了、监听也通了,但服务名不对,或 PDB 没注册到监听。到目标端 lsnrctl status 看服务列表。
ORA-01017: invalid username/password; logon denied。密码大小写、特殊字符没加引号、两端账号不一致(用 CURRENT_USER 时)。注意 11g 之后密码默认区分大小写。
ORA-00942: table or view does not exist。链接是通的,但远端账号对表没权限,或者表在另一个 PDB/schema 里。用 select * from tab@link 看远端账号能看到什么。
ORA-02063: preceding line from 链接名。这不是独立错误,只是告诉你“上面那条错来自哪个链接”,真正的错误码在它前一行。
八、生产环境的几条建议
链接密码存在数据字典中,DBA_DB_LINKS 不直接显示,但有 DBA 权限的账号仍可能获取。给链接单独建远端账号,只授 SELECT 和必要的 INSERT/UPDATE,并限制来源 IP。
跨库大查询尽量在远端完成过滤和聚合。可在远端建视图封装过滤条件,本地只查视图,比本地写复杂 join 更稳定。
分布式事务出问题时,DBA_2PC_PENDING 会有 in-doubt 记录,要有处理预案。定期清理 DBA_DB_LINKS 里不用的旧链接,既满足权限要求,也少一类排错干扰。
勘误与待核对
sqlpuls / as sysdba为笔误,文中按sqlplus给出。sqlplus "gauser/"your_password"@GADB'引号不配对,文中改为单引号包裹,需在实际环境验证。- 全局链接“在 Oracle Names 服务器上定义”是传统说法,Oracle Names 已非主流,实际是否可用取决于是否部署了目录服务。
- 示例中的 IP、端口 1526、服务名 assdb/gadb、密码均为实验环境值,不要直接用于生产。
- 若目标端 PDB 未注册到监听,需先确认
local_listener与lsnrctl status中的服务名。
原文首发于微信公众号「止水研习社」。
文章评论