1. 故障定位先别急着翻日志,先圈定现象边界
做GBase 8c数据库运维这些年,我遇到最多的故障,根子都出在操作系统层。数据库进程跑在操作系统上,CPU怎么分配、内存够不够、磁盘写得动写不动、网络稳不稳,全都受系统层控制。业务侧看到的“连接失败”“实例重启”“主备切换”,数据库日志里往往只有一句笼统的FATAL或ERROR,真正的诱因早被淹没在系统日志里了。所以拿到故障单,我的习惯是先别急着翻数据库日志,先把现象边界圈出来,再决定从哪里下手。
1.1 为什么数据库故障一半以上能在OS层找到线索
GBase 8c在单个节点上不是只有一个孤零零的进程,它有一组核心进程在协作,进程之间要使用共享内存、信号量、文件锁这些OS级别的资源,同时还要依赖本地文件系统持续写入WAL日志和数据文件。这意味着OS任何一个资源分配异常,最终都会传导到数据库行为上。
举个例子,磁盘写延迟正常是2毫秒,某天突然涨到200毫秒,数据库的checkpoint和WAL刷盘一下就被拖慢,事务提交耗时跟着成倍上涨。业务侧不会说“你的磁盘慢”,只会说“数据库卡了”。再比如,内核网络监听队列溢出,数据库监听端口来不及accept新连接,应用侧看到的就是“Connection reset”或者“Connection refused”。这些现象只看数据库日志,会得到一堆“could not send data to client”“terminating connection due to administrator command”这种间接结果,很容易被带偏。
所以我一直把证据分成三类:数据库日志、OS日志、OS资源指标。三份证据必须落在同一个时间点上,不能各看各的。数据库日志里有checkpoint报错,那就去dmesg和sar里找同一时间段的磁盘I/O记录,对上才算定位完成。光凭一句报错下结论,后面大概率要返工。
1.2 先回答四个问题再动手
故障发生时最怕的就是看到报错就猜原因。我现在要求自己先回答四个问题,答案写在纸上再开始查。
第一,故障从什么时间开始,持续了多久?这个时间要精确到秒,因为数据库日志和OS日志都是按时间顺序记录的,只有先把时间段锁定,才能在两套日志里拉出同一段内容做交叉比对。很多DBA上来就看最后一屏日志,这是不对的,故障可能是半小时前就开始的,滚动日志早把关键信息冲掉了。
第二,最近有没有变更?数据库参数调整、应用发布、扩缩容、内核补丁、硬件维护、网络割接,全都算。别看这个问题老生常谈,真实案例里至少有一半问题是因为某个“小变更”没同步记录。比如有人顺手改了一个内核参数,或者底层存储做了固件升级,结果数据库第二天开始抖动。变更信息不完整,定位方向就容易完全跑偏。
第三,影响范围是什么?是单节点故障还是整个集群异常?只影响某个业务库还是全部库?这个信息能把排查范围缩小一大半。整个集群都出现连接超时,第一怀疑对象是网络层和公共依赖;只有单个节点出问题,重点看这个节点的CPU、内存、磁盘和系统日志。
第四,应用侧报什么错?不要只盯着数据库日志。应用报“timeout”和报“connection refused”对应的OS层原因完全不同。前者多半是资源紧张或网络延迟,后者很可能是监听队列溢出或端口根本没起来。应用侧的报错文本措辞差异很大,值得原样记录下来。
这四件事理清楚,我才会打开终端,按固定命令流去采集现场信息。后面几个章节,就是我在GBase 8c维护工作中反复使用、确认有效的定位方法和命令组合。
2. 四大OS资源的定位方法与实战经验
2.1 CPU:先分清用户态、内核态、等待和steal
CPU排查最容易犯的错,就是只看整机load。load高可能来自CPU繁忙,也可能来自磁盘等待、内存换页,甚至是不可中断进程堆积。光看一个数字,根本区分不了是哪一种。所以第一步是跑top,按1键看每个核的使用率,同时看us、sy、wa、st、hi、si这几项。
us高,说明是业务程序在跑,要尽快定位到具体进程和SQL;sy高,说明系统调用频繁,可能是锁竞争、网络包处理、文件系统操作;wa高,说明CPU在等磁盘,瓶颈其实在存储;st高,说明这台机器是虚拟机,宿主机上的其它虚拟机正在争抢CPU资源。st这个指标在物理机上很罕见,但云上环境非常常见,而且最容易误导人。
有一次客户报告某个节点查询变慢,我上去看到整机load有30,第一反应是SQL问题。细看top才发现st已经占到40%,也就是说有40%的CPU时间根本不在我们这台机器上执行,而是被宿主机调度走了。数据库自身消耗的CPU并不高,单纯调SQL没有意义。最后协调平台方把实例迁移到资源充足的宿主机,故障立刻消失。这件事之后,我只要看到云上数据库性能问题,都会先看st再聊SQL。
定位到确实是用户态CPU高后,下一步要找到具体线程。推荐用pidstat -t -p <数据库主进程pid> 1,把进程内各线程的CPU使用率打出来,然后去GBase 8c的会话视图里把os线程id和会话id对应起来。这个对应关系一旦建立,是哪个会话在烧CPU就一目了然。复杂查询调优、vacuum(清理)任务调度,都离不开这一步。我自己的经验是,遇到CPU问题先在进程树里“捞”线程,能省下大量在SQL日志里大海捞针的时间。
2.2 内存:free看到的只是表象
free -h输出里,buff/cache经常显示占用几十个G,新同学看到就会紧张,以为内存不够了。其实page cache是内核用来缓存文件和目录数据的,内存不足时可以自动回收。真正要关注的是available列,这才是应用实际可用的内存。如果available长期很低,swap又被大量使用,就要小心了。
数据库场景最担心的内存问题是OOM Killer误伤进程。GBase 8c这类数据库进程内存占用大,在系统内存紧张时很容易被内核选中杀掉。判断方法很直接:先执行dmesg -T | grep -i oom,看内核日志里有没有“Out of memory: Kill process”。如果有,往上翻几行,能看到当时是谁触发了OOM、谁被杀了、各进程当时的内存占用是多少。
这里有个常见误解:OOM杀的并不一定是内存占用最大的进程,而是内核根据oom_score算出来的“性价比牺牲品”。所以有时候你会看到某个不起眼的进程被杀,真正的元凶却是旁边那个疯狂吃内存的进程。解决思路不是单纯调大内存,而是要控制整体内存水位。我规划GBase 8c内存时,会预留20%到30%的OS空闲内存给page cache和系统进程,shared_buffers这类数据库参数再大,也不能把整机内存吃干。同时保留适量swap,我习惯设成物理内存0.5到1倍,用于缓解瞬时峰值,而不是指望它扛长期压力。
还有一类内存问题不太好查:内存缓慢泄漏。具体表现是系统可用内存一天比一天少,但单个进程看不出明显异常。排查时要靠监控趋势,把free -h和ps aux输出按小时记录,连续观察几天。定位到具体进程后,再看这个进程是数据库内部的还是外部程序。数据库内部的,多和连接数、缓存命中率变化有关;外部的,要单独处理那个程序的资源占用。这类问题不常见,但一旦出现就非常隐蔽,没有趋势数据很难抓住。
2.3 磁盘和文件系统:空间只是最低要求
磁盘故障要分三层看:空间、inode、I/O能力。很多监控只看磁盘使用率,结果数据目录所在分区还剩不少GB,数据库却写不进去了,一查是inode耗尽。所以df -h和df -i必须一起看,两个都正常才算空间充足。
I/O能力判断要靠iostat -x 1的输出。我重点看await、util、svctm这三项。await是I/O请求从发出到完成的时间,它高说明请求在队列里等着;util接近100%只能说明设备一直在工作,不代表“慢”;svctm是实际服务时间。如果await高但svctm不高,说明请求在排队,瓶颈可能是请求数量太多或队列深度不合理,不是磁盘物理速度不够。
GBase 8c对磁盘的依赖,核心在WAL日志和数据文件。数据目录、WAL目录、归档目录、日志目录,我建议分开规划,放在不同磁盘或至少不同分区。不为别的,就为故障时能隔离。归档目录是最容易爆的:开启归档但归档失败时,日志会持续堆积,直到分区写满,数据库可能直接hang住,或者备节点被踢出集群。我碰到过一次非常典型的:归档目录占用100%,数据库日志里刷满了“could not archive log file”,但业务侧表现却是“节点失联”,排查了好几个小时才发现是磁盘满。这件事之后,我把磁盘监控指标扩展到了每个挂载点,不再只盯根分区和数据分区。
文件系统层面,Linux环境给数据库用XFS通常比ext4稳,尤其是并发写和多线程场景。挂载参数我建议至少加上noatime,减少读操作对atime的更新;其它优化项不要照搬网上的文章,必须结合具体存储和内核版本做验证。还有一点要记住:文件系统变成只读时,错误提示已经非常明确,但很多人的第一反应是重启机器,这个动作非常危险。应该先确认根因,通常和I/O错误、坏道、底层存储链路抖动有关,再决定修复文件系统还是更换存储。
2.4 网络:连接被拒不一定是数据库不背锅
GBase 8c的主备节点和分布式节点之间,靠网络通信做心跳和日志同步。网络层故障在数据库日志里通常表现为“connection timeout”“connection reset”“heartbeat failed”这类模糊信息。不要一看到这些就怀疑数据库配置,网络本身也是头号嫌疑对象。
排查网络问题,我习惯先跑ss -lntp查看监听端口状态,重点看Recv-Q和Send-Q。Recv-Q如果持续不为0并且一直有大值,说明连接已经到了内核,但数据库进程还没来得及accept,也就是监听队列溢出。GBase 8c监听队列容量由内核参数net.core.somaxconn和程序listen设置共同决定,常见做法是把somaxconn调大。还有一个更隐蔽的坑:如果tcp_abort_on_overflow开启,内核会直接发RST包,应用侧表现为“Connection reset”,但数据库日志干净得很。
再往前走一步,要看TCP层的丢包和重传。用netstat -s能看到总的TCP重传次数,配合ss -s看当前socket状态。如果重传持续增长,通常意味着链路不稳,可能是交换机丢包、带宽打满,甚至网卡故障。分布式场景下还要检查节点间ping延迟和吞吐,不能只看连通性。延迟抖动对日志同步的影响比丢包更隐蔽,因为它看起来像“数据库处理慢了”。
网络参数调整要克制,不能把所有队列调大就完事。somaxconn调太大,连接堆积在内核队列里不可见,问题反而更难排查。我一般只在看到确凿的队列溢出证据时才调整,每次只改一个参数,改完立即用ss观察效果。这里也提一句:分布式集群内部节点通信,建议单独规划网络,至少不要和业务流量混在一个拥塞链路上,不然高峰期互相抢带宽,数据库日志同步迟滞会引发一连串误判。
3. 故障现场的一整套命令流,建议直接背下来
3.1 入场先抓三样:时间、上下文、内核日志
故障发生时最重要的事情是固定现场,而不是急着分析。我实际操作时,会按下面的顺序把信息先存下来,整个过程用不了两分钟,后面定位全靠这份底稿。
date +%F_%T # 记录当前时间,确认和数据库日志时间一致 uptime # 负载全貌 top -H -b -n 1 > top_H.txt # 进程和线程级CPU快照 free -h # 内存全貌 df -hT # 各挂载点空间 df -i # 各挂载点inode iostat -x 1 3 # 连续3秒的磁盘I/O详情 sar -q -u -r -b -n DEV -n TCP 1 3 # 系统历史统计 dmesg -T | tail -200 # 内核日志尾部 tail -200 /var/log/messages # 系统日志尾部这套命令流的顺序有讲究。先跑进程级快照,是因为故障现场稍纵即逝,CPU最高的那个线程可能几秒后就消失了;再跑连续采样,是为了看动态趋势;最后看内核日志和系统日志,是因为日志内容量大,要先了解上下文再读,不然很容易被无关条目淹没。我见过不少人到现场直接tail /var/log/messages,翻了几百行全是无关记录,反而错过了和数据库日志交叉验证的黄金机会。
如果故障已经过去了,现场快照没抓到怎么办?只能靠sysstat的历史数据。sar服务如果一直开着,可以回看故障时段内的CPU、内存、磁盘、网络指标,虽然看不到进程级细节,但定位到资源维度完全够用。这也是我坚持长期部署sysstat的原因,平时看起来没什么存在感,关键时候能救命。
3.2 数据库侧日志怎么和OS侧交叉验证
GBase 8c的运行日志通常在数据目录下的log或pg_log子目录,里面记录启动参数、运行状态、错误信息,还有慢查询和备份恢复日志。故障定位时我最常用的是运行日志,重点关注FATAL、PANIC、ERROR,以及“could not”这类带系统调用的描述。
拿到数据库日志后,第一步不是分析内容,而是把时间对齐。OS日志用系统时间,数据库日志用本地时间,两边必须确认时区一致。我曾经处理过一个案例,数据库日志显示14:00:05发生PANIC,OS日志同一时刻是14:00:04的磁盘I/O error,偏差1秒,这是因为两边日志精度不同,不算问题;但如果时区不一致,再聪明的分析都没用。所以部署阶段就应该配好NTP,统一时区。
时间对齐后,第二步是找交集。比如数据库日志里出现“could not write to file”,我在同时间段的内核日志里又看到SCSI层的“I/O error”,两条线索一拼,根因基本清楚了。反过来,如果数据库日志报连接中断,但网络监控和内核日志都没异常,那就要把注意力拉回到数据库自身,比如连接池、会话数限制、慢事务阻塞。
还有一类现场必须重视:进程级崩溃。如果GBase 8c核心进程被信号杀掉,OS可能留下coredump文件。建议提前在系统配置里设置好core文件路径,比如kernel.core_pattern指向固定目录,崩溃时自动落盘。没有coredump的情况下,也要去/var/log/messages里找信号来源。“signal 9”通常对应OOM,“signal 11”对应段错误,这两个信号能把问题大的方向区分出来。
3.3 内核参数的快速核对清单
数据库部署文档里通常会给出内核参数建议,但很多环境是后来改造的,参数已经被改得和初始状态完全不同。我建议把下面这张表存下来,遇到疑难故障就逐项核对。
| 参数 | 常见参考值 | 影响 |
|---|---|---|
| vm.swappiness | 10左右,不建议直接改0 | 控制匿名内存和page cache回收偏好,影响内存抖动 |
| vm.overcommit_memory | 0或部署文档指定值 | 影响大内存请求是否被接受,设置不当会触发OOM或启动失败 |
| vm.max_map_count | 65530或更大 | 影响进程可映射内存区域数,数据库线程并发多时易踩 |
| fs.file-max | 按连接预算放大 | 限制系统总文件句柄数,太小会报too many open files |
| net.core.somaxconn | 1024或更大 | 影响监听队列容量,太小导致连接被拒绝 |
| net.ipv4.tcp_syncookies | 1 | 缓解SYN Flood,对正常业务影响小 |
| kernel.sem | 按部署文档 | 影响信号量数量,太小会导致启动新进程失败 |
| kernel.shmmax | 足够大 | 影响共享内存段上限,与shared_buffers配置相关 |
这里要强调的不是“抄参数”,而是“排查时先看这些参数现在是什么”。我遇到过一些环境的参数和官方要求完全相反,数据库还能跑,只是跑得很难受。比如kernel.sem设置过小,数据库在会话量上来后会启动新进程失败,日志里不断出现“could not create semaphores”。这类问题表面看像权限错误,实际是内核参数不对,只看应用层永远找不到根。
另外不要忽略用户资源限制。在gbase用户下执行ulimit -a,重点看open files、max user processes和stack size。进程数限制太小,连接池扩容时fork子进程会失败;文件句柄限制太小,高并发下会出现大量“too many open files”。修改这类限制要同时改/etc/security/limits.conf和systemd服务配置,只改一边不换另一边,重启后照样踩坑。
还有一个常见优化项是透明大页THP。很多数据库在THP开启时会出现性能抖动,因为内存页合并和分配行为不稳定,建议在内核启动参数里加上transparent_hugepage=never,并确认生效。这个操作一般在系统初始化时就要做掉,免得业务上线后再折腾。
4. 三个高频故障现场复盘:从现象到根因
4.1 场景一:归档日志写满磁盘,集群节点反复被踢
这个场景我印象很深。某系统是一主两备部署,某天半夜开始主备节点频繁切换,业务断断续续,数据库日志里不断出现备节点连接超时、心跳失败的记录。现场同学第一反应是网络问题,查了链路却一切正常。
我上去后没有立刻查网络,先跑df -hT,一眼看到某个挂载点使用率100%。再查数据库日志,发现归档进程从几个小时前就开始报“could not archive log file”,WAL日志越积越多,最终把磁盘彻底写满。磁盘满之后,主节点无法继续接收备节点的同步请求,连接超时被误判为节点故障,触发了剔除和切换。
处理分三步走:第一步清理过期归档,恢复可用空间;第二步确认主备数据一致性,把集群状态恢复正常;第三步把归档目录迁移到独立磁盘,给磁盘使用率加监控阈值,超过85%就告警。事后复盘最值得记下来的教训是,不能只监控根分区和数据目录,归档目录这种容易被遗忘的挂载点,恰恰是故障高发区。磁盘空间告警必须覆盖所有挂载点,而且要按数据文件、日志、归档、临时文件分别设置预警阈值。
4.2 场景二:内存配置不当引发OOM误杀
另一个高频场景是实例频繁重启。某个节点运行一段时间后,业务查询突然大面积报错,数据库日志里有“server process was terminated by signal 9”,随后整个实例自动重启。单看这句话,很多人会怀疑数据库进程崩溃,但我翻了dmesg之后看到了“Out of memory: Kill process”,确认是OOM误杀。
为什么会被误杀?当时的系统里除了数据库,还跑着几个批处理任务,内存占用经常冲高。数据库的shared_buffers配置非常大,几乎把物理内存的可调空间都吃掉了,swap又只有几个G,内存一紧张,内核只能选择杀掉某个进程来释放空间。数据库进程因为内存占用大,在OOM打分里排在最前面,就成了牺牲品。
调整方案不是简单加内存,而是三方面同时着手:一是把数据库内存参数调到合理水位,给OS和page cache留出余量;二是限制批处理任务的内存占用,并且错峰执行;三是保留适量swap,防止瞬时峰值触发内核干预。如果核心库确实很重要,还可以调整进程的oom_score_adj,让内核在万不得已时优先杀别的进程。但这是把双刃剑,要配合监控,否则可能掩盖真实内存压力。
这个案例给所有DBA的提醒是:数据库内存参数不是越大越好,OS必须有自己的呼吸空间。我现在习惯在部署前先画一张内存预算表,把数据库固定开销、连接并发开销、OS预留、page cache目标占用全部列出来,再反推shared_buffers等参数的值,而不是随手填个“物理内存的70%”。
4.3 场景三:文件系统异常变成只读,数据库写入全部失败
还有一种故障最直接,也最容易让人慌神:文件系统变成只读。某次应用反馈节点数据写不进去了,数据库日志里刷出“ERROR: read-only file system”。我先执行mount,看到对应磁盘状态已经是ro,说明内核检测到I/O错误后,自动把文件系统切成了只读保护模式。
只读状态下,数据库还能对外提供读服务,但任何写入操作都会失败,这比直接宕机更隐蔽。现场第一反应不是重启,而是先把系统日志和内核日志完整保存下来,因为一旦重启,有些错误痕迹可能被冲掉。接着查看/var/log/messages,发现文件系统底层报了一串I/O错误,初步判断是存储链路抖动或磁盘坏道。协调底层存储修复后,再尝试把文件系统重新挂载为rw,同时做一致性检查。
这类问题要避免一个危险操作:在文件系统处于只读状态时强制重启主机。如果底层有硬件损坏,重启后文件系统可能无法完整挂载,反而扩大故障范围。正确流程是先隔离业务,再备份日志,再修复底层硬件,最后恢复文件系统。另外,只读状态要第一时间告警,不能等业务反馈才发现。文件系统的“只读”和“空间满”是两种完全不同的问题,前者通常对应硬件或链路故障,后者是容量管理,监控上要分别覆盖。
5. 避坑清单和长期监控
5.1 我踩过的几个坑
做OS层故障定位这些年,我也掉过不少坑,有些至今想起都觉得不该犯。第一个坑是只看数据库日志、不看系统日志。有一次数据库会话大量中断,我翻了一晚上慢查询和锁等待,第二天偶然打开/var/log/messages,才发现网络重传已经持续了好久。从那以后我给自己立了个规矩:数据库日志里只要出现“connection”“could not”“terminated”这类字眼,必须同时看系统日志。
第二个坑是故障发生时没有保留现场。早期处理过一次磁盘满的故障,第一反应是赶紧清理空间恢复业务,结果把确凿的现场证据都删了,后面做根因复盘时啥都拿不出来。现在我的习惯是先跑前面那套命令流,把输出重定向到带时间戳的文件,哪怕和业务恢复没关系,也要留着。
第三个坑是误把iowait当成CPU瓶颈。服务器load高,我就去加CPU,结果没过多久又打回原形。后来用iostat仔细看,发现磁盘util长期100%,SQL慢的根因在存储。给CPU加资源只会让应用更快地堆积到磁盘队列上,问题反而更难解。这类误判在监控数据不完整的时候特别容易发生,所以现在我把top和iostat配合着看,先分清瓶颈在哪一层。
第四个坑是对内核参数直接照抄网上的“优化大全”。有一次我把vm.swappiness直接改成0,结果数据库在某些内存压力场景下表现更差。后来在测试环境仔细对比,才发现我们那套存储和业务模型并不适合这个设置。现在碰到任何内核参数,我都会先在测试环境压一遍,确认没有副作用再上生产。
5.2 值得长期部署的监控与应急准备
经验再多,也比不上一套可靠的监控。我给生产环境长期部署的几样东西,这里一并分享。
首先是sysstat,也就是sar的源码包。它的价值不只是实时看指标,而是“事后回放”。把定时任务配置好,保留至少7天历史数据,故障过去之后依然可以回看每一分钟的资源指标。对数据库这样的长期服务来说,这份历史数据是定位疑难杂症的底牌。
其次是日志时间同步。OS日志、数据库日志、应用日志、网络设备日志,最好都统一到NTP源,时区保持一致。很多时间轴对齐问题其实部署阶段就能避开,而不是故障时靠人肉换算。
第三是黄金指标监控清单,我通常会盯这几个维度:
| 维度 | 核心指标 |
|---|---|
| CPU | 使用率、load、steal、用户态/内核态占比 |
| 内存 | 可用内存、swap使用率、OOM事件、page cache回收情况 |
| 磁盘 | 每个挂载点空间、inode、await、util、只读状态 |
| 网络 | TCP重传、连接队列溢出、连接数、丢包率 |
| 进程 | 数据库主进程是否存在、核心线程状态、进程数限制 |
第四是应急脚本。我会准备一个一键采集脚本,把前面第3节那套命令流的输出打包成一个带时间戳的压缩包,同时把数据库日志最近2小时的错误行抽出来。故障发生时只要执行一条命令,所有现场信息就自动留档。这套脚本平时不起眼,真正救场时比任何分析工具都管用。
最后是coredump配置。提前设置好内核的core_pattern,确保数据库进程崩溃时能留下完整的内存映像,同时检查ulimit -c是否为unlimited。有了coredump,很多“进程神秘消失”的问题才有机会定位到具体代码路径,而不是靠猜。
做了这么多年数据库相关工作,我最深的体会是:故障定位不靠灵光一现,靠的是规范流程和长期积累。OS层问题对数据库影响巨大,可它恰恰是数据库运维里最容易被标准化处理的部分。把命令流、日志对齐、参数核对、监控指标变成肌肉记忆,遇到故障时就能多一分从容。我自己的习惯是,每次处理完一类OS故障,都会把现场关键输出和解决方案归档到本地,再用一段话写清时间线。回头再看,这种记录比任何复盘文档都管用,下次遇到同类问题,直接按关键词翻出来,能少走很多弯路。