上周又被拉去看一个问题PL/SQL Developer 里点登录弹出一行ORA-12504: TNS:listener was not given the SERVICE_NAME in CONNECT_DATA。坐在旁边的人已经试了半小时——重装客户端、改密码、甚至怀疑远程库挂了。我扫了一眼他的 tnsnames.oraCONNECT_DATA段里只有一行(SERVER DEDICATED)SERVICE_NAME那一行压根儿不存在。这个报错的定位成本其实极低前提是你知道它在说什么。它属于 Oracle 网络连接里客户端把连接描述符构造错了这一类问题的典型代表跟账号密码、权限、数据库是否启动统统无关。适合看这篇的人有三类本地库换远程库、第一次自己配 tnsnames.ora 的开发刚装完 Instant Client、从同事那里拷了一份配置却连不上的运维以及手写连接串写顺手了、在 JDBC 或脚本里翻车的后端。下面把我处理这类问题的完整思路摊开讲包括报错文本怎么读、几个高频现场长什么样、怎么用一条tnsping把范围缩到最小以及几个配置文件里肉眼看不见的坑。顺带说一句标题末尾那个2019大概率是发帖时随手粘上的年份或版本记号跟报错本身没有关系排查时别被它带偏。1. 先把报错文本拆开ORA-12504 到底在抱怨什么1.1 逐词翻译这行英文完整文本是ORA-12504: TNS:listener was not given the SERVICE_NAME in CONNECT_DATA。三个关键词TNSTransparent Network SubstrateOracle 自己的网络层协议栈客户端和数据库之间所有连接都走它。CONNECT_DATA连接描述符connect descriptor里的一个段落专门用来表达我要连哪个库。它和ADDRESS地址段说明主机和端口在哪是平级的一个完整的描述符就是DESCRIPTION包着ADDRESS和CONNECT_DATA两块。SERVICE_NAMECONNECT_DATA里用来指定服务名的键。所以整句话的直译是客户端发过去的CONNECT_DATA里没有给SERVICE_NAME这一项监听器无法判断你想连哪个服务于是拒绝握手。这里有个非常重要的推论错误发生在握手的第一阶段还没走到用户名密码校验。很多人看到连不上第一反应是是不是密码改了是不是账号被锁了是不是没给权限——在 12504 面前这些怀疑全是白费力气。监听器连你想连哪个库都还不知道根本没轮到认证环节。同理也不用去查表空间、会话数、白名单问题的全部战场就在客户端构造出来的那一小段字符串里。1.2 责任在客户端不在服务器另一个容易走偏的方向是数据库是不是出问题了。12504 这个错误码是监听器返回给客户端的但触发它的原因是客户端送来的内容不完整。换句话说服务器那边一切正常监听器在正常工作、正常拒绝。你去重启数据库、重启监听器一点用都没有。我见过最典型的误判现场有人在服务器上lsnrctl stop再start来回折腾了三遍然后开始怀疑防火墙。其实只要在客户端机器上把CONNECT_DATA补全一秒钟就好了。判断依据很简单——报错文本里明确写了 was not given是被动语态是你没给我不是我给不了你。1.3 从 SID 到 SERVICE_NAME 的历史包袱要理解为什么这个坑这么容易踩得知道 Oracle 网络配置的一段演进。Oracle 8i 及更早客户端连接主要靠CONNECT_DATA里的SID实例标识符监听器拿到 SID 后去找对应的实例。8.1 之后引入了服务名机制一个服务可以对应多个实例RAC 场景客户端通过SERVICE_NAME表达我要连哪个逻辑服务由监听器转发到具体实例。再往后动态服务注册成为主流——实例启动后由 PMON 进程主动向监听器报到把service_name登记上去缺省大约一分钟内完成也可以手动催。问题就出在新旧混杂上。网上大量的老教程、老博客、老项目里的配置文件写的还是(SID ORCL)。照着抄过来在新环境里就会撞上各种错误。这里要特别澄清一个高频误解只写 SID 不会报 12504。只写 SID 而监听器不认识报的是ORA-12505写了 SERVICE_NAME 但监听器没注册这个服务报的是ORA-12514。12504 的特殊之处在于——它对应的是CONNECT_DATA里连SERVICE_NAME这个键都没有CONNECT_DATA段要么是空的要么只剩SERVER DEDICATED这种无关项。把这一点记牢后面所有排查都会变得非常干脆你不需要猜你只需要去看客户端到底发了什么。2. 三个高频现场我遇到的 12504 基本都长这样2.1 现场一tnsnames.ora 里的 CONNECT_DATA 被写残了这是绝对的主流占了八成以上。一份能用的配置长这样ORCLPDB (DESCRIPTION (ADDRESS (PROTOCOL TCP)(HOST 10.0.0.21)(PORT 1521)) (CONNECT_DATA (SERVER DEDICATED) (SERVICE_NAME orclpdb1) ) )出问题的版本往往只差一行ORCLPDB (DESCRIPTION (ADDRESS (PROTOCOL TCP)(HOST 10.0.0.21)(PORT 1521)) (CONNECT_DATA (SERVER DEDICATED) ) )就这一行(SERVICE_NAME orclpdb1)没了症状就是 12504。丢这一行的原因五花八门我按遇到频率排个序从别的条目复制过来改地址改完 HOST 和 PORT 就收工忘了把服务名一起换掉或者干脆漏掉。手动删注释把某一行当成注释删了结果删的是SERVICE_NAME。tnsnames.ora 里注释是用#开头的别凭感觉删。括号不配对。少一个右括号Oracle 的解析器会静默地把整段CONNECT_DATA丢掉剩下的描述符里自然就没有服务名。这种最隐蔽因为文件看起来有内容。写了空值(SERVICE_NAME )键在、值为空效果和没写基本一样。键名写错写成(SID_NAME orcl)、(SERVICENAME orcl)、(SERVICE orcl)。Oracle 不认识这些会被忽略掉然后照样报 12504。判断办法很朴素把这份CONNECT_DATA抄进记事本数左括号和右括号的数量是否相等再逐个字母核对SERVICE_NAME的拼写。别嫌笨我确实靠数括号解决过不止一次。2.2 现场二PL/SQL Developer 登录框里的模式选错了PL/SQL Developer 的登录界面Database那一栏是个下拉框列出的是当前 OCI 能解析到的 TNS 别名。有些人下拉里找不到目标库就直接手打字符串进去问题就从这里开始。常见的三种错法输入方式实际效果报错下拉选别名别名内容完整正常连接无Database手打10.0.0.21:1521/orclpdb1被当作别名去解析找不到ORA-12154只填主机10.0.0.21服务名留空描述符里没有服务名ORA-12504别名本身CONNECT_DATA就残缺描述符里没有服务名ORA-12504第三行和第四行的报错一样但根因完全不同一个是客户端没给服务名一个是配置文件里就没写。区分方法就是第 4 节要讲的tnsping。另外还有个更隐蔽的层次PL/SQL Developer 自身不解析 tnsnames.ora它通过 OCI 动态库调用 Oracle 客户端真正读配置的是那个客户端。所以PL/SQL Developer 用哪个 OCI就决定了它读哪份 tnsnames.ora。在多套客户端共存的机器上比如同时装了完整客户端和 Instant Client或者 32 位和 64 位并存改了一份配置却没生效八成是改错了文件。首选项里的Oracle Home和OCI library这两个路径必须和 PL/SQL Developer 自身的位数对齐——32 位的 PL/SQL Developer 只能加载 32 位的 OCI装错位数时一般会直接启动失败不会报 12504但值得一并检查。2.3 现场三手写连接串少写了服务名命令行和代码里的连接串是另一个重灾区。EZConnect 的语法是//主机:端口/服务名很多人为了省事只写到端口# 错误只有主机和端口没有服务名 sqlplus scott/tiger//10.0.0.21:1521 # 正确斜杠后面跟服务名 sqlplus scott/tiger//10.0.0.21:1521/orclpdb1注意这里有两个长得极像、含义完全不同的分隔符混淆是家常便饭# 冒号形式最后一段是 SID老语法 sqlplus scott/tiger10.0.0.21:1521:ORCL # 斜杠形式最后一段是 SERVICE_NAME新语法 sqlplus scott/tiger//10.0.0.21:1521/orclpdb1JDBC 里同理jdbc:oracle:thin://host:port/service和jdbc:oracle:thin:host:port:SID是两套写法混着拼就会出现前面写得像新版、后面给的是 SID的情况。更常见的是配置文件里服务名被环境变量占位符替换后变成了空串——这个坑特别阴配置文件看着没错运行时拼出来的串少了一截。Python 的 oracledb、.NET 的Data Source描述符、各种连接池的url字段都要按同样逻辑检查一遍。3. 一张表把五个 TNS 报错分清楚这几个错误码长得像、含义完全不同混在一起猜会浪费大量时间。我在排查时习惯先把报错归到下面这张表的某一行再决定往哪个方向走。报错码完整含义问题出在哪一层第一手动作ORA-12154could not resolve the connect identifier客户端名字解析失败别名根本没找到查 tnsnames.ora 路径、别名拼写ORA-12504listener was not given the SERVICE_NAME in CONNECT_DATA别名找到了但CONNECT_DATA里没有SERVICE_NAMEtnsping看解析出的描述符原文ORA-12505listener does not currently know of SID given in connect descriptor给了 SID监听器不识这个实例查是否该用服务名、实例是否注册ORA-12514listener does not currently know of service requested给了服务名但监听器没登记这个服务服务器上lsnrctl status看服务列表ORA-12541no listener端口不通或监听器没起查端口、防火墙、lsnrctl start这张表里最值得记住的一对是 12154 和 12504前者说明客户端根本没找到你写的别名配置文件的路径问题、拼写问题后者说明别名找到了、文件也读了只是里面内容不完整。这两个错误的方向完全相反一个是文件没读到一个是文件读到了但写残了。区分清楚能省掉一半的瞎折腾。另外补一句12504 和 12505 的边界也要拎清。12505 的文本里写的是 SID given in connect descriptor意思是客户端给了 SID、监听器不认识。如果你在CONNECT_DATA里同时写了SID和SERVICE_NAMEOracle 的行为是优先看SERVICE_NAME如果只写 SID就走 12505 这条路。所以看到 12504可以直接下结论SERVICE_NAME这个键不存在。4. 排查链路从客户端到监听器一段一段验我给团队里的新人讲过很多遍这套流程它的价值在于每一步都能把范围砍掉一半不会出现改了一堆东西也不知道是哪一处起作用了的情况。4.1 第一步tnsping 打印出的描述符原文比你想象的有用tnsping ORCLPDB正常输出大概是这样TNS Ping Utility for 64-bit Windows: Version 19.0.0.0.0 - Production on 12-6月-2024 10:22:31 Used TNSNAMES adapter to resolve the alias Attempting to contact (DESCRIPTION (ADDRESS (PROTOCOL TCP)(HOST 10.0.0.21)(PORT 1521)) (CONNECT_DATA (SERVER DEDICATED) (SERVICE_NAME orclpdb1))) OK (12 msec)这里有两条信息很多人的目光只落在最后那个 OK 上其实是浪费了。第一条是Used TNSNAMES adapter to resolve the alias。这句话说明 tnsping 确实是通过 tnsnames.ora 解析出来的别名而不是走了别的适配器比如 LDAP、EZConnect。如果这句话变成了别的说明你改的那份文件根本不是它读的那份。第二条是Attempting to contact后面那一长串这是解析后完整展开的连接描述符。有没有SERVICE_NAME一眼就能看见。我现在的习惯是遇到 12504 先跑一次 tnsping输出里缺什么直接就能定位到配置文件的具体位置比打开文件一行行找快得多。但 tnsping 有个必须知道的局限它返回 OK 只代表监听器那个端口通、TCP 握手成功不代表服务名是对的。tnsping 本质上是 ping 监听器不是 ping 数据库。服务名写错成orclpdb999tnsping 照样 OK真连的时候报 12514。所以千万别把 tnsping OK 当成配置没问题的证据——它只能证明地址段是通的。4.2 第二步在服务器上看监听器登记了哪些服务lsnrctl status关键是Services Summary这一段Services Summary... Service orclpdb1 has 1 instance(s). Instance orcl, status READY, has 1 handler(s) for this service... Service orclXDB has 1 instance(s). Instance orcl, status READY, has 1 handler(s) for this service... The command completed successfully拿这里列出来的服务名和客户端配置里的SERVICE_NAME逐字比对。注意两件事一是大小写。Oracle 内部登记服务名时通常存成大写形式连接时一般大小写不敏感但我在实际环境里确实遇到过因大小写不一致而排查很久的情况所以习惯上让客户端的写法和服务端显示保持完全一致省事。二是服务名根本没出现在列表里。这时候问题不在客户端配置而在服务注册。可能是实例还没起来可能是 PDB 没 open也可能是动态注册还没完成。命令lsnrctl services能看到更细的 handler 信息配合使用。还有个绝招监听器的日志文件listener.log在$ORACLE_BASE/diag/tnslsnr/...或老的$ORACLE_HOME/network/log下会记录每一个连接请求进来的原始CONNECT_DATA。客户端到底发了什么白纸黑字写在那儿这是最硬的一手证据比任何猜测都可靠。日志级别够的话一行CONNECT_DATA(COMMAND...)里缺不缺服务名一眼就清楚。4.3 第三步反过来在库里确认我该用哪个服务名不少 12504 的根因其实是——客户端配置是对的只是服务名这个概念本身用错了对象。常见的场景是库升级到多租户架构之后ORCL是容器库业务数据在一个叫ORCLPDB1的插件库里连容器库连得上连业务库就必须用插件库自己的服务名。在能连上的会话里执行-- 看当前实例的信息 select instance_name, host_name, status from v$instance; -- 看这个库对外提供了哪些服务12c 及以上还能看到属于哪个容器 select name, pdb, con_id from v$services order by name; -- 看初始化参数里显式配置的服务名 show parameter service_namesv$services这个视图是重点它列出来的就是监听器上应该能看到的服务名集合。两边一对照该写什么就清楚了。如果service_names是空的说明用的是默认值通常是数据库名或域名形式的组合这时候更要以v$services的输出为准。4.4 第四步确认客户端到底在读哪一份 tnsnames.ora这一步是给改了半天没反应的人准备的。同一台机器上存在多份 tnsnames.ora 是常态完整客户端一份、Instant Client 一份、某个工具自带的又一份。查找顺序大致是环境变量TNS_ADMIN指定的目录优先没有的话用ORACLE_HOME/network/admin。# Windows echo %TNS_ADMIN% echo %ORACLE_HOME% where sqlplus # Linux / macOS echo $TNS_ADMIN echo $ORACLE_HOME which sqlplus最干脆的对照实验在同一台机器上用命令行 sqlplus 连同一个别名。sqlplus scott/tigerORCLPDB如果 sqlplus 连得上、PL/SQL Developer 连不上那问题 100% 不在 tnsnames.ora 的内容上而在 PL/SQL Developer 加载的 OCI 和 ORACLE_HOME 上方向立刻收窄。反过来如果两边都报 12504那就是配置内容本身的问题安心回去改文件。这个对照实验我每次都会做花十秒省半小时。4.5 第五步改完之后按清单验一遍改配置文件这种事最怕的是改对了但没生效。我的验证顺序固定保存文件确认编码是纯 ASCII 或 ANSI不是带 BOM 的 UTF-8。tnsping 别名看输出里的描述符有没有SERVICE_NAME值对不对。sqlplus 用户名/密码别名能进到 SQL 提示符。再打开 PL/SQL Developer 登录。如果第 3 步能过、第 4 步不能过回头查第 4.4 步的 OCI 路径。顺序别乱。先验命令行、再验图形工具能把配置问题和工具配置问题彻底分开。5. 各种客户端的正确写法直接抄配置类的东西抄对比理解快。下面这几段是我自己常用、也在多个环境验证过的写法把主机、端口、服务名换成你自己的即可。5.1 tnsnames.ora 通用模板# 别名建议全大写方便在工具下拉框里找 PROD_PDB (DESCRIPTION (ADDRESS_LIST (ADDRESS (PROTOCOL TCP)(HOST 10.0.0.21)(PORT 1521)) ) (CONNECT_DATA (SERVER DEDICATED) (SERVICE_NAME orclpdb1) ) )几个书写约定值得坚持ADDRESS_LIST外面那层括号加上多地址容灾时直接往里加ADDRESS就行CONNECT_DATA里SERVER DEDICATED写上明确用专用服务器模式缩进统一两格出问题时肉眼扫一遍就能看出层级对不对。别名用大写是因为不少工具的下拉框按字典序排大写小写混在一起会显得很乱。5.2 EZConnect临时连一下最快的办法# 基本形式 sqlplus scott/tiger//10.0.0.21:1521/orclpdb1 # 加连接超时和重试参数排查网络问题时很有用 sqlplus scott/tiger//10.0.0.21:1521/orclpdb1?connect_timeout10EZConnect 的好处是不依赖 tnsnames.ora改完立刻生效适合验证到底是配置问题还是网络问题。如果 EZConnect 能连、别名连不上那问题一定在别名配置里如果两边都连不上往网络和服务名方向查。5.3 JDBC 与其他语言客户端// 正确双斜杠 斜杠分隔服务名 String url jdbc:oracle:thin://10.0.0.21:1521/orclpdb1; // 错误示范只有主机端口服务名为空 String url jdbc:oracle:thin://10.0.0.21:1521/; // 老语法冒号后面是 SID不是服务名别混用 String url jdbc:oracle:thin:10.0.0.21:1521:ORCL;# Python oracledb / cx_Oracle 的两种写法 dsn 10.0.0.21:1521/orclpdb1 # 简写 dsn_full (DESCRIPTION(ADDRESS(PROTOCOLTCP)(HOST10.0.0.21)(PORT1521)) (CONNECT_DATA(SERVICE_NAMEorclpdb1))).NET 的 Data Source 直接写完整描述符最稳避免拼接出错 Data Source(DESCRIPTION(ADDRESS(PROTOCOLTCP)(HOST10.0.0.21)(PORT1521))(CONNECT_DATA(SERVICE_NAMEorclpdb1)));User Idscott;Passwordtiger;代码里的连接串有个特有风险服务名常常来自配置文件或环境变量。生产环境里最常见的事故是变量没注入拼出来的串以斜杠结尾然后报 12504。上线前把拼好的最终字符串打一次日志比盯着模板代码检查有效得多。6. 改配置之外几个容易被忽略的坑6.1 动态注册的时序库刚起别急着下结论服务是实例启动后由 PMON 进程主动注册到监听器上的这个过程不是瞬时的。如果监听器和数据库同时重启很可能出现监听器已经起来了、服务还没登记的窗口期这时候连上去可能报 12505 或 12514。有人在重启后立刻连撞上这个窗口然后开始怀疑配置写错了白折腾一通。确认和止损的办法-- 手动催一次注册不用重启监听器 alter system register;执行完等几秒再试。另外还要注意监听器启动时的PLSExtProc之类默认条目那些跟你的业务服务无关别被它们误导。6.2 静态注册能解决什么、不能解决什么listener.ora里可以配静态注册把实例信息硬编码进去SID_LIST_LISTENER (SID_LIST (SID_DESC (GLOBAL_DBNAME orclpdb1) (ORACLE_HOME /u01/app/oracle/product/19.0.0/dbhome_1) (SID_NAME orcl) ) ) LISTENER (DESCRIPTION_LIST (DESCRIPTION (ADDRESS (PROTOCOL TCP)(HOST 10.0.0.21)(PORT 1521)) ) )注意GLOBAL_DBNAME这里写的是服务名SID_NAME写的是实例名两者不是一回事。静态注册的用处是实例还没启动时监听器上也存在这个服务条目某些运维场景比如远程启动实例需要它。但要明确一点静态注册解决不了 12504。因为 12504 的根因是客户端压根没发服务名服务器端登记得再全也没用。有人看到 12504 就去改 listener.ora方向从一开始就错了——这是我想强调的第二个高频误判第一个是把 12504 当密码问题。改完listener.ora记得要lsnrctl reload才生效直接status是看不到变化的。6.3 插件库的服务名别拿容器库的凑数多租户环境里v$services会列出一堆名字有的是容器库的有的是插件库的还有 XDB 之类的组件服务。业务数据在插件库里就必须用插件库自己的服务名。插件库没 OPEN 的时候它的服务不会出现在监听器上这时候连接会报 12514 而不是 12504——但如果你在配置里把服务名写成了空或者写错了键名就会退化回 12504。排查时先看插件库状态select name, open_mode from v$pdbs;READ WRITE才是正常打开状态MOUNTED状态下连不进去。6.4 全角字符、BOM 和看不见的零宽空格这个坑纯粹是复制粘贴带来的。从网页、聊天工具、Word 文档里拷一段配置很容易带上全角括号、全角等号、中文逗号或者行首行尾一个看不见的零宽空格。这些字符在某些编辑器里根本看不出来但 Oracle 的解析器不认识会直接把对应的键丢掉——结果就是CONNECT_DATA里少了SERVICE_NAME报 12504。我的处理习惯配置文件只用纯 ASCII 保存记事本另存为时编码选 ANSI不要选 UTF-8 带 BOM。从网上抄来的片段先粘到纯文本编辑器里过一遍再拷进正式文件。怀疑有隐形字符时用十六进制方式看一眼文件头几个字节或者给每一行末尾做一个trim处理的对比。别名不要用中文、不要带空格纯字母数字加下划线最稳。这些做法听起来琐碎但只要遇到过一次文件看起来完全正确却连不上的情况你就会把这些习惯固化下来。7. 一份可直接套用的修复模板与自查清单把上面的内容压缩成一个可以照着走的流程。先按模板改配置你的别名 (DESCRIPTION (ADDRESS (PROTOCOL TCP)(HOST 你的主机IP)(PORT 1521)) (CONNECT_DATA (SERVER DEDICATED) (SERVICE_NAME 你的服务名) ) )然后按顺序过下面的清单每一步都能明确地排除掉一部分可能步骤动作观察点结论1echo %TNS_ADMIN%与where sqlplus路径是否指向你改的那份文件不一致就改路径或改文件2tnsping 别名描述符里有没有SERVICE_NAME没有就是配置文件写残了3服务器上lsnrctl statusServices Summary里有没有该服务名没有就是注册问题4库里查v$services服务名是否真实存在不存在就换名5sqlplus 用户/密码别名能否进 SQL能进说明配置没问题6PL/SQL Developer 登录首选项里 OCI 路径第 5 步能过这里不过就是工具配置实际操作中第 2 步和第 3 步能解决九成以上的问题大部分情况到第 2 步就结束了。真正需要往下走的往往是环境里存在多套客户端、或者服务名用错了对象。最后分享一个我自己的小习惯遇到 TNS 类报错先别打开任何配置文件先在命令行敲一次tnsping。十秒钟把别名有没有解析到描述符里有什么这两件事看清楚再决定往哪儿走。这个顺序反过来——先翻配置文件、一页一页找——看起来更努力实际上很容易在错误的方向上花掉一整个下午。改配置这种事证据永远比直觉靠谱。