开发转兼职DBA(七):不是SQL的锅——从操作系统层面排查数据库问题
数据库慢了,第一反应是查SQL、查执行计划。但有时候SQL没问题,索引也在走,数据库配置也没改——问题在操作系统层。这篇讲两个真实案例:Tomcat权限污染导致系统"假死",和磁盘IO瓶颈拖慢整个数据库。
文章目录
- 开发转兼职DBA(七):不是SQL的锅——从操作系统层面排查数据库问题
- 案例一:系统"挂了",结果不是数据库的问题
- 现象
- 排查过程
- 修复
- 为什么会这样
- 教训
- 案例2:数据库慢了,结果是磁盘的问题
- 现象
- 跳出数据库,看操作系统
- 解决:加大内存
- 为什么加大内存能解决磁盘问题
- 为什么加大内存能解决磁盘问题
- 两层缓存
- 这个案例里发生了什么
- 另一种思路:不扩内存,把SGA加大?
- 一句话总结
- 排查思路:逐层往下追
- 几个常用命令速查
- CPU相关
- 内存相关
- 磁盘IO相关
- 网络相关
- 文件权限相关
- 系列终章总结
案例一:系统"挂了",结果不是数据库的问题
现象
某天上午,用户打电话来:系统打不开了。
登录检查:
- 数据库正常,监听正常,SQL能跑
- 应用服务器ping得通
- 但浏览器访问页面,一直转圈,最终超时
第一反应:数据库是不是锁了?查了一下,没有阻塞会话,没有异常等待。数据库好好的。
那问题在哪?
排查过程
第一步:看应用日志。
tail-200f/opt/tomcat/logs/catalina.out报错:
java.io.FileNotFoundException: /opt/tomcat/logs/localhost.2024-01-15.log (Permission denied) java.io.IOException: Unable to create directory /opt/tomcat/work/Catalina/localhost/_写不进去日志文件。
第二步:查文件权限。
ls-la/opt/tomcat/logs/-rw-r----- 1 root root 12345 Jan 15 09:00 localhost.2024-01-15.log drwxr-x--- 2 root root 4096 Jan 15 09:00 Catalina/所有者全是root。
第三步:查Tomcat是用什么用户启动的。
psaux|greptomcattomcat 12345 2.3 15.4 2345678 654321 ? Sl 09:00 1:23 /usr/bin/java -jar ...现在是用tomcat用户跑的。但日志文件属于root。
第四步:问了一圈,真相大白。
前一天晚上,有人(用root账号)手动重启了Tomcat:
su- /opt/tomcat/bin/startup.shTomcat以root身份启动。运行过程中创建的日志文件、临时文件、编译后的JSP class文件——全部属于root。
第二天早上,自动化的systemd服务以tomcat用户重启了Tomcat。新进程想写日志文件——没有权限。想编译JSP——work目录属于root,写不进去。应用看起来"挂了",但数据库完全正常。
修复
# 停掉Tomcatsystemctl stop tomcat# 把所有文件归属改回tomcat用户chown-Rtomcat:tomcat /opt/tomcat/# 重启systemctl start tomcat一行chown,问题解决。
为什么会这样
Linux的权限模型很简单:
-rw-r----- 1 tomcat tomcat 12345 Jan 15 09:00 catalina.out │├──┤├──┤├──┤ ├──┤ ├──┤ │ │ │ │ │ └── 组名 │ │ │ │ └── 所有者 │ │ │ └── 其他用户的权限(--- = 无权限) │ │ └── 同组用户的权限(r-- = 只读) │ └── 所有者的权限(rw- = 读写) └── 文件类型(- = 普通文件,d = 目录)进程创建新文件时,文件的所有者是进程的运行用户。root启动的进程创建的文件属于root。之后tomcat用户想去写这些文件——权限不够。
教训
- 永远不要用root启动应用服务——不仅权限会乱,安全风险也大
- 用systemd管理服务,在unit文件里指定用户:
[Service] User=tomcat Group=tomcat- 排查"系统挂了"不要只盯着数据库——应用层的问题比数据库层更常见
案例2:数据库慢了,结果是磁盘的问题
现象
某大库,Oracle数据库整体变慢。不是某一条SQL慢,是所有SQL都慢。
第一反应:是不是锁表了?查v$session,没有阻塞。查AWR报告,发现db file sequential read的等待时间比平时高了几倍。
db file sequential read是单块读等待——Oracle从数据文件读一个块到缓冲区的等待。这个等待变长,要么是读的块变多了,要么是每次读的时间变长了。
查执行计划,跟以前一样,没变。查索引,没变。查数据量,没有暴增。
不是SQL的问题。
跳出数据库,看操作系统
第一步:看CPU。
top%Cpu(s): 3.0 us, 1.5 sy, 0.0 ni, 55.0 id, 38.0 wa, 0.0 hi, 2.5 si关键指标:
- us(user):用户态CPU占用,3%——不高
- sy(system):内核态CPU占用,1.5%——不高
- id(idle):空闲,55%——一半多的CPU时间在空闲
- wa(iowait):38%——严重不正常
wa(iowait)是CPU在等磁盘IO完成的时间占比。正常应该接近0。38%意味着CPU超过三分之一的时间在等磁盘。idle只有55%,不是CPU忙,是CPU在等磁盘。
第二步:看磁盘IO。
iostat-x15Device rrqm/s wrqm/s r/s w/s rMB/s wMB/s await %util sda 0.0 12.0 350.0 200.0 2.8 1.6 35.8 98.5关键指标:
- r/s, w/s:每秒读写次数,加起来550次/秒——很多
- await:平均每次IO等待时间,35.8ms——很高
- %util:磁盘利用率,98.5%——磁盘已经饱和
35ms的await意味着什么?Oracle一次单块读需要35ms。一个查询需要读100个块,光等磁盘就要3.5秒。而且这不是个别慢查询的问题——550次/秒的IO请求量,磁盘98.5%利用率,磁盘已经被打满了。
第三步:看内存。
free-gtotal used free shared buff/cache available Mem: 128 95 2 1 31 30 Swap: 8 1 7128G总内存,Oracle的SGA占了约100G,剩余留给操作系统的只有不到30G。其中buff/cache约31G——看起来不小,但这是一个大库,数据文件几百GB甚至上TB,31G的page cache远远覆盖不了热点数据。
128G总内存 ├── Oracle SGA: ~100G(Buffer Cache + Shared Pool + 其他) ├── 进程+系统: ~2G ├── page cache: ~31G └── 空闲: ~2G根因找到了:大库,数据量大,128G内存里Oracle自己吃了100G,留给操作系统page cache的只有30G,缓存不住热点数据,大量读请求直接打到磁盘,磁盘被打满。
解决:加大内存
服务器内存从128G扩到264G。Oracle的SGA配置没动,还是100多G。多出来的136G全部留给操作系统。
扩完之后,top里看到最明显的变化:
- idle从55%升到85%+——CPU空闲时间多了
- wa从38%降到5%以下——CPU不用等磁盘了
free -g的变化:
total used free shared buff/cache available Mem: 264 97 80 1 87 164page cache从31G涨到87G。Oracle的Buffer Cache没变,但操作系统层多出了56G的缓存。Oracle的Buffer Cache没命中的数据块,现在大概率在page cache里能找到——不用访问磁盘了。
iostat的变化:磁盘%util从98%降到30%左右,await从35ms降到个位数。
数据库整体性能恢复。不是改了任何SQL、加了任何索引、调了任何数据库参数——就是加了内存。
为什么加大内存能解决磁盘问题
为什么加大内存能解决磁盘问题
这个问题的本质不是"磁盘太慢",是内存不够大,缓存不住热点数据。
但"缓存"这个词太笼统。内存分配给数据库之后,实际上是两层缓存在配合工作,很多人只看到了一层。
两层缓存
Oracle读一个数据块 ↓ 第一层:Oracle自己的 Buffer Cache(SGA里配的 db_cache_size) ↓ 命中 → 直接返回 ↓ 未命中 第二层:Linux的 page cache(操作系统的 buff/cache) ↓ 命中 → 从内存读,不需要访问磁盘 ↓ 未命中 第三层:磁盘 → 真正的物理IO(慢)第一层:Oracle的Buffer Cache。这是Oracle自己管理的内存区域,配多大由sga_target或db_cache_size决定。Oracle把频繁访问的数据块缓存在这里,内部用LRU算法管理淘汰。Buffer Cache命中率就是第五篇讲的那个指标——低于90%说明不够用。
第二层:Linux的page cache。Oracle的数据文件(.dbf)也是操作系统管理的文件。Oracle向操作系统发起read调用时,Linux先查page cache——如果这个数据块最近被读过,还在内存里,直接返回,不访问磁盘。
这两层缓存的关系:
- Oracle的Buffer Cache是Oracle内部申请的内存,通过
sga_target分配。这部分内存被Oracle进程锁定,Linux不会回收。 - Linux的page cache是操作系统自动管理的空闲内存。Linux会把"没人用的内存"全部拿来缓存磁盘数据。应用不用的内存越多,page cache越大,磁盘IO越少。
内存就这么多,给了Oracle就不能给Linux的page cache,反过来也一样。所以内存规划的核心问题是:Oracle的Buffer Cache和操作系统的page cache,怎么分?
这个案例里发生了什么
128G总内存。Oracle的SGA配了约100G(其中Buffer Cache是大头),操作系统和进程占用几G,剩下的约30G被Linux自动用作page cache。
128G总内存 ├── Oracle SGA: ~100G(Buffer Cache占大部分) ├── 进程+系统: ~3G └── page cache: ~31G大库的数据文件几百GB,100G的Buffer Cache加上31G的page cache,总共约130G的缓存。看似很多,但一个跑了几年的大库,热点数据分散在大量表和索引上,130G的缓存覆盖不了。大量读请求穿透两层缓存直接打到磁盘。
扩到264G之后:
264G总内存 ├── Oracle SGA: ~100G(没变) ├── 进程+系统: ~3G ├── page cache: ~87G(大涨) └── 空闲: ~80G(Linux会逐渐回收用作page cache)Oracle的SGA没变,Buffer Cache还是100G左右。但操作系统的page cache从31G涨到87G。Oracle的Buffer Cache没命中的数据块,在page cache里命中的概率大大增加——很多请求不用访问磁盘了。
磁盘压力骤降。iowait从38%降到5%以下,idle从55%升到85%+。
另一种思路:不扩内存,把SGA加大?
既然缓存不够,把Oracle的Buffer Cache从100G加大到150G,是不是也能解决?
能缓解,但有个问题:Oracle的Buffer Cache只缓存Oracle自己的数据块。操作系统上的其他进程(RMAN备份、日志收集、文件传输)的磁盘IO不受Oracle Buffer Cache保护,还是直接打磁盘。如果这些操作也在产生大量IO,Oracle加大Buffer Cache的作用有限。
而操作系统的page cache是全局的——所有进程的文件IO都受益。Oracle的数据块、备份程序读的文件、日志文件的写入,都能利用page cache减少磁盘IO。
所以实际经验中:不要把所有内存都分配给Oracle的SGA,要留足够的内存给操作系统做page cache。一般建议Oracle SGA占总内存的40%~60%,剩下的留给操作系统。
这个案例里,128G内存给Oracle 100G,SGA占了78%——比例偏高了,留给操作系统的太少。扩到264G后,SGA只占38%,操作系统拿到160G,page cache充足,磁盘压力自然下来了。
这不是数据库调优能解决的问题。执行计划再优化,索引再加,该从磁盘读的块还是要读。瓶颈在磁盘IO,解法在内存规划和两层缓存的配合。
一句话总结
数据库慢了,不一定是SQL的问题。先看iostat和free,确认瓶颈在哪一层。
排查思路:逐层往下追
两个案例放在一起,抽象出一个通用的排查链路:
用户说"系统慢了/挂了" ↓ 第一层:应用层 ├── 应用日志有没有报错?(Tomcat权限问题就是在这一层发现的) ├── 接口响应时间是多少? └── 是所有功能都慢,还是某个功能慢? ↓ 第二层:数据库层 ├── 有没有锁阻塞?(v$session的blocking_session) ├── 有没有慢SQL?(v$sql按elapsed_time排序) ├── 等待事件是什么?(v$system_event) └── 执行计划有没有变化? ↓ 第三层:操作系统层 ├── CPU够不够?(top的us/sy/wa) ├── 内存够不够?(free的available和buff/cache) ├── 磁盘IO是不是瓶颈?(iostat的await和%util) └── 网络通不通?(ping/telnet/netstat) ↓ 第四层:基础设施层 ├── 磁盘是不是快满了?(df -h) ├── 存储后端有没有问题?(SAN/NAS/云盘) └── 有没有硬件故障?(dmesg | grep error)大部分开发者到第二层就停了——查SQL、加索引、调参数。但如果问题在第三层、第四层,在数据库里怎么折腾都没用。
几个常用命令速查
CPU相关
# 整体CPU使用情况top# 每个CPU核心的使用情况mpstat-PALL15# 查哪个进程吃CPUpsaux--sort=-%cpu|head-20重点关注:%wa(iowait)持续>5%说明磁盘是瓶颈。
内存相关
# 内存使用概况free-m# 进程内存排序psaux--sort=-%mem|head-20# 详细内存映射cat/proc/meminfo重点关注:available(真正可用的内存,包含可回收的缓存)、Swap used(swap用了多少,持续增长说明物理内存不够)。
磁盘IO相关
# 磁盘IO统计(每秒刷新,共5次)iostat-x15# 查看哪个进程在疯狂读写磁盘iotop# 磁盘空间df-h# 目录大小du-sh/opt/*重点关注:%util持续>80%说明磁盘饱和,await持续>10ms(SSD>1ms)说明IO延迟高。
网络相关
# 查看网络连接状态netstat-tlnp# 测试数据库端口是否通telnet192.168.1.1001521# 查看网络流量sar-nDEV15# DNS解析nslookupdbserver文件权限相关
# 查看权限ls-la/opt/tomcat/logs/# 递归修改所有者chown-Rtomcat:tomcat /opt/tomcat/# 修改权限chmod755/opt/tomcat/bin/startup.sh# 查看某用户对文件的权限su- tomcat-c"test -w /opt/tomcat/logs/catalina.out && echo 'writable' || echo 'not writable'"系列终章总结
七篇文章,一条线:
(一)只会写SQL ↓ 查询慢了 (二)学会看执行计划,加索引 ↓ 索引也救不了 (三)数据库起不来了,逼着理解WAL和redo ↓ undo也坏了 (四)逼着理解MVCC和undo ↓ 不能老出事才救 (五)学参数、内存、监控、备份 ↓ 换了个项目,数据库换成了MySQL (六)发现原理都一样 ↓ 数据库没问题,系统还是慢 (七)跳出数据库,从操作系统层排查从一个只会写SELECT * FROM的开发者,到能看执行计划优化SQL,到理解WAL和MVCC的底层原理,到能配参数做监控写备份脚本,到跨数据库触类旁通,到能跳出数据库从操作系统层面定位问题。
这就是"开发转兼职DBA"的完整路径。不是因为我想学,是因为小团队没有专职DBA,出了事就是我扛。每次事故逼出一段认知,每段认知沉淀成经验,最终串成一条完整的知识链。
标签:#DBA #运维 #Linux #iostat #top #free #权限 #磁盘IO #page cache #故障排查