半夜两点被电话叫起来,数据库实例怎么也拉不起来,终端里就剩一行红字:"数据库启动时发生报错,致命错误:无法创建信号量"。如果你也遇到过这种场面,第一反应多半是怀疑数据库软件坏了,或者数据文件出了问题。实际上这句话是操作系统在告诉你:数据库向内核申请进程同步资源时被拒绝了,这个资源叫System V信号量。本文就把这条报错从头到尾拆开讲清楚,涉及信号量机制、kernel.sem四个内核参数怎么调、残留信号量怎么清理、容器和systemd环境下为什么改了没生效,以及怎么把这类故障挡在巡检之外。适合DBA、运维工程师,以及自己搭库做开发的读者。
1. "无法创建信号量"背后是操作系统在拒绝:先理解信号量与kernel.sem
1.1 数据库为什么离不开信号量
数据库绝大多数是多进程架构,拿Oracle来举例,启动时会拉起PMON、SMON、DBWR、LGWR等一系列后台进程,这些进程共同操作SGA里的数据块、日志缓冲区、锁结构。如果没有任何同步机制,两个进程同时修改同一块内存,数据早就烂掉了。信号量就是内核提供的一把"令牌",进程在做关键操作前必须拿到令牌,做完再交还,其他人才能进来。
你可以把信号量想象成餐厅门口取号等位:信号量集合就是整排排队通道,信号量数量就是座位/排队号的数量。数据库启动时一次性向内核申请一批信号量,如果系统里已经没有足够名额,内核直接返回失败,数据库实例自然起不来。PostgreSQL、达梦数据库这类产品同样依赖信号量,只是具体调用方式略有差异。所以这条报错不是数据库软件自身故障,而是所在操作系统给不了它想要的资源。
需要额外说一句:数据库死锁发生在应用事务层,是两个会话互相持有对方需要的锁,跟内核信号量是两码事。排查"无法创建信号量"时不要把死锁分析扯进来,方向完全不同。
1.2 kernel.sem四个参数,分别卡在哪个环节
Linux内核用kernel.sem这个参数统一管理System V信号量,它包含四个值,顺序固定,缺一不可:
| 参数 | 全称含义 | 通俗解释 | 常见默认值 |
|---|---|---|---|
| SEMMSL | 每个信号量集合最多能容纳的信号量个数 | 一根队列最多排多少人 | 250 |
| SEMMNS | 整个系统范围内信号量总数上限 | 所有队列加起来最多多少人 | 32000 |
| SEMOPM | 一次semop系统调用最多能同时操作的信号量个数 | 一次最多处理多少个排队号 | 32 |
| SEMMNI | 整个系统范围内信号量集合总数上限 | 系统最多允许开多少条队 | 128 |
"无法创建信号量"并不是一个固定的失败点,这四个参数任何一个命中上限,报错都可能以这个形式出现:
- SEMMNI满了:系统里信号量集合数量太多,新集合创建不出来。
- SEMMNS满了:整个系统的信号量总数被占光,集合里再加信号量就超限。
- SEMMSL太小:每个集合能容纳的信号量个数小于数据库申请的个数。
- SEMOPM太小:数据库的某个操作一次性要操作很多信号量,超过单次上限。
所以遇到报错先别急着套网上现成的kernel.sem值,先看清楚系统到底卡在哪一项。
1.3 一个大实例到底会吃掉多少信号量
以Oracle数据库为例,实例启动时创建信号量集合,集合里信号量的数量大致和processes参数成正比。processes设的是数据库允许的最大进程连接数,每个客户端连接在Oracle里对应一个服务器进程,这些进程都要关联到信号量上。如果processes设成1500,SEMMSL却还是默认的250,数据库连启动阶段都过不去。
这也是为什么很多人在小机上没事、搬到大服务器上就报错的原因——通常DBA习惯于把processes调大应对高并发,却忽略了同机内核的SEMMSL上限。达梦数据库同样遵循类似逻辑,进程模型参考了Oracle设计,信号量需求与配置的最大连接数/工作线程数强相关。PostgreSQL情况稍有不同,它在现代Linux上默认更倾向使用POSIX信号量,对System V信号量需求相对弱一些,但如果你用的是编译期指定SysV信号量、或者系统限制POSIX信号量的场景,同样
