欢迎光临
我们一直在努力

开发转兼职DBA(七):不是SQL的锅——从操作系统层面排查数据库问题

开发转兼职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是用什么用户启动的。

ps aux | grep tomcat

tomcat 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.sh

Tomcat以root身份启动。运行过程中创建的日志文件、临时文件、编译后的JSP class文件——全部属于root。

第二天早上,自动化的systemd服务以tomcat用户重启了Tomcat。新进程想写日志文件——没有权限。想编译JSP——work目录属于root,写不进去。应用看起来"挂了",但数据库完全正常。

修复

# 停掉Tomcat
systemctl stop tomcat

# 把所有文件归属改回tomcat用户
chown -R tomcat: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 -x 1 5

    Device 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 -g

    total used free shared buff/cache available
    Mem: 128 95 2 1 31 30
    Swap: 8 1 7

    128G总内存,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 164

    page 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 -P ALL 1 5

    # 查哪个进程吃CPU
    ps aux –sort=-%cpu | head -20

    重点关注:%wa(iowait)持续>5%说明磁盘是瓶颈。

    内存相关

    # 内存使用概况
    free -m

    # 进程内存排序
    ps aux –sort=-%mem | head -20

    # 详细内存映射
    cat /proc/meminfo

    重点关注:available(真正可用的内存,包含可回收的缓存)、Swap used(swap用了多少,持续增长说明物理内存不够)。

    磁盘IO相关

    # 磁盘IO统计(每秒刷新,共5次)
    iostat -x 1 5

    # 查看哪个进程在疯狂读写磁盘
    iotop

    # 磁盘空间
    df -h

    # 目录大小
    du -sh /opt/*

    重点关注:%util持续>80%说明磁盘饱和,await持续>10ms(SSD>1ms)说明IO延迟高。

    网络相关

    # 查看网络连接状态
    netstat -tlnp

    # 测试数据库端口是否通
    telnet 192.168.1.100 1521

    # 查看网络流量
    sar -n DEV 1 5

    # DNS解析
    nslookup dbserver

    文件权限相关

    # 查看权限
    ls -la /opt/tomcat/logs/

    # 递归修改所有者
    chown -R tomcat:tomcat /opt/tomcat/

    # 修改权限
    chmod 755 /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 #故障排查

    赞(0)
    未经允许不得转载:171主机测评 » 开发转兼职DBA(七):不是SQL的锅——从操作系统层面排查数据库问题
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址