达梦服务端只有一个进程 dmserver,里面很多线程:执行 SQL 的、写日志的、做检查点的、在等锁的。其中一个线程出问题,整个实例都可能没了。堆栈就是每个线程“怎么一层层调用到当前位置”的记录。
日志能看到 停库前后打印了什么。堆栈看的是 每个线程当时执行到哪一层函数。Core 文件则是进程退出那一瞬间的内存镜像,事后用 gdb 打开。当数据库突然连不上、进程消失、CPU 很高却没有任何结果返回时,就要用堆栈说明。
达梦 自带 dmrdc,可以从 Core 找到当时还在跑的 SQL 。
调用栈是一层层函数调用。gdb 里每一层叫一帧:
#0 aagr2_exec_after_fetch ()
#1 ......
#11 clone ()
#0 是最里面、正在执行的函数。数字越大越靠近线程入口。达梦线程栈底经常是 clone(),表示这个线程是从进程里拉起来的。
习惯先看 #0 现在停在哪,再从下面往上扫,看是谁把它调进来的。
info threads 里会有 LWP xxxx。可以把它当成这个线程在操作系统里的编号。dmrdc 抠出来的 SQL 前面也常带这个号。堆栈和 SQL 要对上,靠的就是它。达梦是单进程多线程,一份 Core 里会有几十上百条栈,不能只看一条。
先定性,再定位,能处理就处理,处理不了再构造重现、把栈交给研发。
实例还能登录时,先看网络、内存、CPU、磁盘,再查 V$SESSIONS、V$LOCK、V$TRX。有会话卡住,先关连接:
SELECT SESS_ID, THRD_ID, STATE, SQL_TEXT
FROM V$SESSIONS;
-- 确认后再关,未提交事务会回滚
SP_CLOSE_SESSION(sess_id);
进程在但完全没响应、关会话也没用,才用信号把 Core 留下:
ps -ef | grep -v grep | grep dmserver
kill -SIGSEGV <PID>
kill -11 和这条是一类信号。不要用 kill -9,进程来不及按这套流程写 Core。副作用是再启动可能很慢,也有起不来的情况。
ps 已经看不到 dmserver 了,就去翻运行日志和 Core。没有 Core,再去跟踪日志和 dmesg。
服务端 Core 分成两类
栈上常能看到 dm_sys_halt、dm_sys_halt_low()。原因以运行日志 Halt 附近为准。文档举过 redo 环装不下大事务:
Redo log try flush over space
优先看事务有多大、日志相关参数。
非法地址、空指针之类。日志往往打着打着突然没了。这类经常能抠出正在执行的 SQL,但 SQL 在场不等于它就是原因,还跟当时的数据、REDO、UNDO、缓冲区有关。
gdb 打开 Core 时,开头那行信号:
Program terminated with signal SIGSEGV, Segmentation fault.
Program terminated with signal SIGABRT, Aborted.
SIGSEGV 是非法访存,培训取栈用的就是它。SIGABRT 多半是进程自己 abort,更要对着日志看。自己在实验机 kill -11 做出来的 Core,只能练流程,不能当成产品缺陷。
先看这几项:
ulimit -c
cat /proc/<pid>/limits
cat /proc/sys/kernel/core_pattern
gdb -version
ulimit -c 为 0 就是不许生成。limits.conf 里可以给 dmdba 放开:
dmdba soft core unlimited
dmdba hard core unlimited
终端里执行 ulimit -c unlimited 只对当前会话有效。用 systemd 拉起的服务,还要在服务里设 LimitCORE,实际生效看 /proc/<pid>/limits。
Core 上限如果小于当时 dmserver 的虚拟内存,文件是截断的。core_pattern 如果是下面这种,文件不在当前目录:
/dmcore/core-%e-%t-%p-%u.core
%e 进程名,%p 是 PID。
gdb 必须用出事那一套安装里的 dmserver,版本、补丁都要对:
gdb /dmdbms/bin/dmserver /dmcore/core-dmserver-22976.core
实践中个人认为更多是 RELEASE,就走 dmrdc;有 symbols 包再按说明加载。
运行日志看停库前后。Core 是当时内存。gdb 打出每个线程的栈。dmrdc 给出当时的 SQL。用 LWP 把后两样对上。
实例日志按时间搜 [ERROR]、[FATAL]、Halt、signal。有 Halt,原因听日志,栈用来对调用链。只有类似下面这句,就主要靠 Core:
sigterm_handler receive signal 11
dmesg 或 /var/log/messages 里如果是 OOM killer 杀掉 dmserver,先查内存和系统限制。
gdb $DM_HOME/bin/dmserver /path/to/core
进去之后:
set pagination off
info threads
bt
* 标出来的是当前线程,多数时候就是崩溃那条。bt 和 where 一样。
线程很多,把全部栈导进文件:
set logging file /tmp/all_bt.txt
set logging on
thread apply all bt
set logging off
quit
也可以不进交互:
gdb -batch \
-ex "set pagination off" \
-ex "thread apply all bt" \
$DM_HOME/bin/dmserver /path/to/core \
> /tmp/all_bt.txt
全线程栈里大量 pthread_cond_wait、pthread_cond_timedwait 很常见,线程在等任务。真正要看的是带 * 的、日志里点名的 dm_sql_thd,以及能和 dmrdc 的 LWP 对上的那些。
比如锁等待现场,某个 LWP 可能接近:
Thread 106 (LWP 23555):
#0 pthread_cond_timedwait ()
#1 os_event2_wait_timeout_low ()
#2 trx4_waiting_timeout ()
#3 trx4_waiting_interval ()
#4 trx4_waiting ()
#5 nupd2_exec_clu_update_check_rec_visible ()
...
#9 nupd2_exec_update ()
...
#17 uthr_db_main_for_sess ()
trx4_waiting 在等事务,nupd2_exec_update 在做更新。还不能下结论,得看 dmrdc 里 23555 对应哪条 SQL。
info threads
thread 1
bt
栈里如果有 ntsk_process_exec、ntsk_process_prepare、ntsk_process_prepare_and_exec:
frame 5
set print elements 0
p sess->sqls
如果有 vm_run_pln:
frame 8
p pln->sqlstr
set print elements 0 是防止 SQL 太长被截断。帧号按自己 bt 的实际输出改。
cd $DM_HOME/bin
./dmrdc SFILE=<core文件名>
结果一般在 core 同目录,文件名是 核心文件名_tmp。也可以指定输出:
./dmrdc sfile=/dmcore/core.31820 dfile=/tmp/core_31820.txt
参数大小写看本机帮助。Analysing: 当前偏移/文件总大小 是进度。
输出里可能是这种形式:
!#%&*^$@[23555]:UPDATE T_CORE_LOCK SET C1 = C1 + 1 WHERE ID = 1;
!#%&*^$@[23384]:UPDATE T_CORE_LOCK SET C1 = C1 WHERE ID = 1;
方括号里的就是 LWP。回到 gdb 里找 LWP 23555,SQL 和栈就能对上。同一会话更早的语句,用跟踪日志里的 sess、trxid 往上翻。
官方也写了:抠出来的 SQL 只说明它当时在,不等于根因。最好在同版本测试库上再跑一遍。
内核日志里找到线程号,比如 dm_sql_thd、9190,跟踪日志搜:
thrd:9190
取宕机前最后一条。这要求故障前跟踪日志是开的(SVR_LOG、SQL_TRACE_MASK)。
运行中取栈有把库打挂的风险。
pstack <dmserver的PID> >> /tmp/dmserver_pstack.txt
某个会话对应的线程:
SELECT SESS_ID, THRD_ID, STATE, SQL_TEXT FROM V$SESSIONS;
pstack <THRD_ID> >> /tmp/one_thread.txt
pstack 依赖 gdb。attach 会把目标卡住,用完要 detach:
gdb
(gdb) attach <PID>
(gdb) info threads
(gdb) thread apply all bt
(gdb) detach
(gdb) quit
进程已经没了就只能看 Core 和日志。
造一个锁等待
会话 A:
DROP TABLE IF EXISTS T_CORE_LOCK;
CREATE TABLE T_CORE_LOCK(ID INT PRIMARY KEY, C1 INT);
INSERT INTO T_CORE_LOCK VALUES(1, 100);
COMMIT;
UPDATE T_CORE_LOCK SET C1 = C1 WHERE ID = 1;
-- 先不 COMMIT
会话 B:
UPDATE T_CORE_LOCK SET C1 = C1 + 1 WHERE ID = 1;
-- 会一直等 A
然后:
ps -ef | grep -v grep | grep dmserver
kill -SIGSEGV <1360>
cat /proc/sys/kernel/core_pattern
ls -l /dmcore # 按实际路径
gdb $DM_HOME/bin/dmserver /path/to/core
# set pagination off
# info threads
# thread apply all bt
cd $DM_HOME/bin
./dmrdc SFILE=/path/to/core
以下为上机截图:
文章
阅读量
获赞
