各位好,我是路远。搞了十五年数据库,我最怕听到的话就是"这个很简单"。
周二下午两点,一天里最忙的时候,值班的兄弟在群里喊:接口全断了,报错刷屏。我第一个念头是库崩了,登进去一查,库好端端的,CPU、内存、响应都正常。卡住的地方在门口。新连接挤不进来,客户端报的是 ORA-12516,翻数据库的告警日志,旁边还并排着一句 ORA-00020:
ORA-12516: TNS:listener could not find available handler with matching protocol stack
ORA-00020: maximum number of processes (5000) exceeded
这两句放一起,很多人会当成两个毛病。它俩其实是同一件事的两面。Oracle 的连接为什么这么容易顶到天花板,得从它的连接模型说起。今天不聊怎么杀连接,那是最表层的招。我带你往下拆一层,看清楚一个连接背后,Oracle 到底付了什么代价。
两道关卡:先撞 listener,再撞数据库
一个客户端要连上库,得先过两道关。第一道在监听器,listener 守着 1521 端口,收到连接请求,得给它找个"handler",也就是一个能接手干活的服务器进程。第二道在数据库内部,进程总数不能超过 processes 这个上限。任何一道满了,连接就进不来。
我从主机侧走 OS 认证登进去。这个点应用账号已经进不去了,只能这么进:
sqlplus / as sysdba
进来先看 Oracle 自己那本配额账,v$resource_limit:
sql
SELECT resource_name, current_utilization, max_utilization, limit_value
FROM v$resource_limit
WHERE resource_name IN ('processes','sessions');
RESOURCE_NAME CURRENT_UTIL MAX_UTIL LIMIT_VALUE
------------- ------------ -------- -----------
processes 5000 5000 5000
sessions 5480 5495 5505
processes 和 sessions 双双顶格。这俩数不是随便定的,sessions 的上限是从 processes 推出来的。11g 及更早,口径是 processes 乘 1.1 再加 5;12c 起改成了 1.5 乘 processes 再加 22。这台库走的是老口径,5000 乘 1.1 加 5,正好 5505,跟表里对上。版本不一样,sessions 能放多大差得不小,规划的时候得照手上这版的公式来。
客户端来连,listener 想给它拉一个进程出来,发现数据库这边名额已经用光,报 ORA-00020;listener 拿不到 handler,转头把 ORA-12516 甩给客户端。一个在数据库里记,一个在门口喊,根子是一个。
顺带说一句,这 5000 个名额不是全留给业务的,库自己的后台进程先占掉四五十个,真能分给用户连接的,比 5000 还得再少一截。而这些后台进程一个没少,服务照常转,所以业务看着"库活着",实际早被挡在门外了。这类故障最唬人,里里外外都正常,就门口堵死了。
一个连接一个专用进程,Oracle的"重"在哪
要搞懂名额为什么这么容易满,得先看清 Oracle 的连接长什么样。默认的部署方式叫专用服务器,一个用户连接,对应操作系统里一个实实在在的进程。
我把这些连接进程拎出来数一数:
sql
SELECT COUNT(*) AS user_procs,
ROUND(SUM(pga_alloc_mem)/1024/1024) AS total_pga_mb
FROM v$process p
WHERE p.addr IN (SELECT paddr FROM v$session WHERE type = 'USER');
USER_PROCS TOTAL_PGA_MB
---------- ------------
4950 13120
近五千个进程,PGA 加起来十三个 G 左右。这还只是内存。每个服务器进程,都在操作系统进程表里占一个格子,还各自揣着一份 PGA,会话私有的排序区、哈希区都搁在里头。
所以 Oracle 的连接是"重"的。它不是一个轻量对象,是操作系统层面一个完整的进程。连接数往上走,压的不是数据库一个地方,是整台机器的进程表和内存。进程表要是被吃满,别说数据库,连你 ssh 上去敲命令、crontab 起个定时任务,都可能起不来。我见过一次,DBA 想上去救火,结果连 ssh 都登不进,只能重启机器。
这也解释了另一个老问题:为什么 Oracle 上到一两千连接,大家就开始紧张,而 MySQL 挂几千连接还挺淡定。MySQL 一条连接是一个线程,线程比进程轻得多,内存落点、调度成本都不是一个量级。同样是"连接数",两边的账本根本不是一回事。
processes 是静态参数,现场根本改不动
看到名额满,很多人的第一反应是去把 processes 调大。这条路,在事故现场是堵死的。
我先确认了一下它的脾气:
sql
SELECT name, value, issys_modifiable
FROM v$parameter
WHERE name = 'processes';
NAME VALUE ISSYS_MODIFIABLE
-------- ----- ----------------
processes 5000 FALSE
issys_modifiable 是 FALSE,意思是这个参数改不了实时值。要改,得写进参数文件:
sql
ALTER SYSTEM SET processes = 8000 SCOPE = SPFILE;
SCOPE 必须写 SPFILE,落进参数文件,然后重启实例才认数。事故现场,库正忙着一堆没提交的事务,你不可能为了腾名额去重启它。重启本身,就是另一起生产事故。
这就是 Oracle 独有的死结。MySQL 那边,max_connections 一个 SET GLOBAL 就变了,顶不住临时调大顶一阵,过后再调回来。Oracle 的 processes 你想都别想,静态参数四个字,就是把现场自救的路焊死了。名额一旦满了,你能动的,只有把已经占着名额的人请出去。
Oracle 其实留过后门:Shared Server
说到这儿,得替 Oracle 说句公道话。它不是只会"一个连接一个进程"。早年它就设计过另一套连接模型,叫 Shared Server,老名字叫多线程服务器,MTS。
这套模型里,连接不再各绑一个进程。客户端连的是一个叫 dispatcher 的分发进程,请求丢进 SGA 里一个公共的请求队列,一小撮共享服务器进程从队列里取活干,干完把结果放回响应队列。你在库里看的还是几千个会话,但背后真正干活的进程,可能就几十个。
我查了一下现在这套参数,看它到底启没启:
sql
SELECT name, value FROM v$parameter
WHERE name IN ('shared_servers','dispatchers','max_shared_servers');
NAME VALUE
------------------ ---------------
shared_servers 0
dispatchers (PROTOCOL=TCP)
max_shared_servers 20
shared_servers 是 0,等于这台库压根没启用共享服务器,走的全是专用进程那一套。要是当初把 Shared Server 配起来,几千个连接压下来,进程数也不至于这么难看。
但 Shared Server 不是银弹,它有取舍。连接不独占进程了,省的是进程和内存,代价是响应要过队列,单会话的吞吐会打折,大排序、大哈希这种吃私有大内存的操作,在共享进程里会互相挤。所以它适合的是那种连接数极高、但单个连接大部分时间闲着发慌的场景,比如一堆移动端长连接。真到 OLTP 高吞吐的核心里,大家还是老老实实配专用服务器,把连接治理挪到应用侧的连接池去做。
这条后门,知道的人不多,真正用对的更少。所以连接"重"这事儿,别全赖架构,有一半是当年部署的时候,模型就选偏了。
现场能做的,只有腾名额
回到事故现场。processes 动不了,Shared Server 没开,唯一能松名额的动作,是把占着不放的连接请出去。
我没敢全杀。杀应用连接是有代价的,杀到正在处理的事务,单子就断了。我先跟应用侧确认了业务影响,挑两类下手:确认已经是死连接的,和非核心业务的。批量把 kill 语句生成出来:
sql
SELECT 'ALTER SYSTEM KILL SESSION ''' || sid || ',' || serial# || ''' IMMEDIATE;'
FROM v$session
WHERE username = 'APP_USER' AND status = 'INACTIVE';
生成一条执行一条,inactive 会话往下掉,名额一点点松出来,新连接紧跟着就建上了。同时让应用把连接池上限临时压小,滚动重启一部分实例,从源头把连接总量降下去。两头一起收,十来分钟业务回来了。
这里有个别人容易漏的动作。杀之前,得先搞清楚哪些连接是真死了。应用和库之间隔着防火墙,防火墙对空闲 TCP 连接有超时,时间一到就把连接掐了,可两端不会马上知道。库这边还当它活着,进程僵在那儿,名额白白占着。能在库侧探活清僵尸连接的是 SQLNET.EXPIRE_TIME,配在 sqlnet.ora 里:
# sqlnet.ora
SQLNET.EXPIRE_TIME = 10
配上它,库每隔几分钟主动探一次,探不通的连接直接清掉,不给僵尸占名额。这一条,是 Oracle 连接治理里最容易被忘的一块。
换个库,这套账完全不一样
这单收尾的时候,顺手把这几个库的连接模型摆一块儿看了看,差别比想象中大:
| 维度 | Oracle(专用服务器) | MySQL | 国产库(兼容Oracle) |
|---|---|---|---|
| 连接模型 | 一连接一进程 | 一连接一线程 | 多为进程模型 |
| 上限参数 | processes/sessions | max_connections | 沿用processes/sessions |
| 能否热改 | 否,改后要重启 | 能,SET GLOBAL即生效 | 多数要重启,各家不一 |
| 内存落点 | UGA在PGA,随进程走 | 线程栈,相对轻 | 多随进程 |
| 打满报错 | ORA-00020/ORA-12516 | ERROR 1040 | 类似Oracle |
| 现场自救 | 只能腾名额(杀会话) | 可临时调大上限 | 视参数能否热改而定 |
国产库这边,跟 Oracle 兼容的居多,连接的模型、processes 和 sessions 这套口径基本照搬了过来。会 Oracle 这套,上手不难,但要留意两个地方。一是各家的参数能不能热改不完全一样,有的跟 Oracle 一样得重启;二是进程模型压内存的方式各有各的算法,不能拿 Oracle 的经验直接套。上线前把手册翻明白,别想当然。
复盘:连接的容量账,得按进程算
这单给我最大的教训,说到底就俩字:算账。Oracle 的连接容量,不是应用随手填个连接池大小就能兜住的,得按操作系统能扛多少进程倒着算。
一批应用总共能开多少连接,要把实例数、每个实例的池上限、后台任务、运维登录、突发流量全乘进去,再留两成余量,这就是 processes 该给的数。定完还得回头核两样硬件:内存,每个进程一份 PGA,进程数乘上去不能把物理内存压爆;还有操作系统的进程表上限。
这笔账最怕的,反而是失控的池子。应用一扩容,实例数蹭蹭涨,池子各自为政,谁都不知道加起来到底开了多少连接。200 个实例,每个池子配 30,就是 6000,早把 5000 的 processes 顶穿了,可这个乘法,配置阶段根本没人做过。
避坑清单
下面三条,是我把这套系统盘过之后给自己立的规矩,各位对着自己的环境看看。
应用各实例的连接池上限加起来,必须小于库的 processes,还得留出两成给后台任务、运维登录和突发流量。上线前把这个乘法过一遍,别等高峰被挤爆才发现。
processes 是静态参数,要调就安排维护窗口,改完重启才算数。应急的时候它帮不上忙,应急靠清连接、压应用顶过去。
最后一条,是我这次才彻底想明白的:动 processes 之前,先算内存,别只顾着进程数。早年我给一台库加过 processes,从 1500 加到 3000,进程数是够用了,可内存没跟着加。进程一铺开,PGA 把物理内存吃光,机器开始换页,库反而更慢,业务比加之前还卡。名额的账、内存的账,得一起算,只加一边,等于换个地方翻车。
写在最后
回头看这单,现场操作其实就两下:分清是哪道关卡满了,再一手清连接、一手压应用。真正花时间的,是往下拆清楚 Oracle 的连接为什么这么容易满、为什么满了现场还救不动。
Oracle 这套连接模型,是它那个年代的设计,重、稳、吃资源。放到今天动辄几百上千实例的云场景,processes 改不动、连接一贵就不敢多开,这套静态脾气就成了负担。所以国产库在兼容 Oracle 的同时,怎么让参数更灵活、连接模型更省,是绕不开的一课。
你们那边的 processes,是算出来的还是拍脑袋填的?踩过为扩连接去重启库、结果停机的坑吗?评论区聊聊,互相避坑。
我是路远。数据库这事儿,生产环境说了算。