导读:DBA的大部分工作都是围绕着对数据库的维护而展开的,常规的日常维护更是占了绝大多数。本节将围绕日常维护中最常见的三个案例展开讲解,与大家分享排查此类问题的思路。

作者:叶桦 徐浩 张梦颖 应以峰

来源:大数据DT(ID:hzdashuju)

01 TX锁处理

TX锁,也称事务锁或行级锁,是控制数据库并发访问的一项重要技术,也是数据完整性和一致性的重要保证。本文不会过多阐述锁的类型和具体原理,而是重点讲解在生产环境中遇到锁的时候,如何快速查找源头并进行查杀。

有经验的DBA在遇到TX锁时,第一反应就是查询v$lock和v$session视图,定位LMODE和REQUEST类型互斥的会话并进行查杀。然而,随着数据库版本不断地迭代更新,v$session视图的内容越来越丰富,可以直接使用blocking_session、blocking_instance、final_blocking_instance和final_blocking_session字段进行定位。

对于锁层次的排查可以重复查询v$session来确定,但如果锁层次有100层,那么通过人工遍历100次的方式,显然过于低效,不适用于生产环境。

下面就来介绍本节的主角:Oracle的SYS_CONNECT_BY_PATH函数。

自Oracle 9i开始,DBA就可以使用SYS_CONNECT_BY_PATH函数将父节点到当前行的内容以“路径”或层次的形式显示出来。该功能刚好符合我们递归查找锁层次的需求,在这里,笔者模拟了锁环境,可以使用如下语句查询锁信息:

SQL> select a.inst_id,a.process,a.sid,a.serial#,a.sql_id,a.event,a.status,a.program,a.machine,connect_by_isleaf as isleaf,sys_connect_by_path(a.SID || '@' || a.inst_id, ' <- ') tree,level as tree_levelfrom gv$session astart with a.blocking_session is not null
connect by (a.sid || '@' || a.inst_id) = prior(a.blocking_session || '@' || a.blocking_instance);
<!--省略部分列-->
INST_ID PROCESS SID  SERIAL# EVENT                         STATUS  ISLEAF TREE            TREE_LEVEL
------- ------- ---- ------- ----------------------------- ------- ------ --------------- ---------1    7663   17    6749 enq: TX - row lock contention ACTIVE       0 <- 17@1                 11    6198   25    9989 SQL*Net message from client   INACTIVE     1 <- 17@1 <- 25@1         21    6310   28   23199 enq: TX - row lock contention ACTIVE       0 <- 28@1                 11    6198   25    9989 SQL*Net message from client   INACTIVE     1 <- 28@1 <- 25@1         2

下面对代码段中的部分参数进行说明。

  • INST_ID:会话所在的节点号。

  • PROCESS:客户端进程号,与v$process中的spid不是同一个。

  • SID、SERIAL#、SQL_ID、STATUS、PROGRAM、MACHINE:会话信息。

  • ISLEAF:是否为源头,0代表否,1代表是。

  • TREE:树形结构,锁的层次,例如,<- 152@2 <- 153@2 <- 161@1,从左到右依次表示为节点2的会话152被节点2的会话153堵塞,而节点2的会话153又被节点1的会话161堵塞。所以节点1的会话161是锁的源头。

  • TREE_LEVEL:树形层次。

锁源头的查杀方法有两种,说明如下。

1)通过ISLEAF进行筛选,直接查杀锁源头,语句如下:

SQL> select 'alter system kill session ''' || sid || '' || ',' || serial# || ',@' ||inst_id || ''' immediate;' db_kill_sessionfrom (select a.inst_id,a.process,a.sid,a.serial#,a.sql_id,a.event,a.status,a.program,a.machine,connect_by_isleaf as isleaf,sys_connect_by_path(a.SID || '@' || a.inst_id, ' <- ') tree,level as tree_levelfrom gv$session astart with a.blocking_session is not nullconnect by (a.sid || '@' || a.inst_id) = prior(a.blocking_session || '@' || a.blocking_instance))where isleaf = 1order by tree_level asc;
KILL_SESSION
---------------------------------------------------
alter system kill session '161,5579,@1' immediate;
alter system kill session '161,5579,@1' immediate;SQL> select inst_id, 'kill -9 ' || spid os_kill_sessionfrom (select p.inst_id,p.spid,a.sid,a.serial#,a.sql_id,a.event,a.status,a.program,a.machine,connect_by_isleaf as isleaf,sys_connect_by_path(a.SID || '@' || a.inst_id, ' <- ') tree,level as tree_levelfrom gv$session a, gv$process pwhere a.inst_id = p.inst_idand a.paddr = p.addrstart with a.blocking_session is not nullconnect by (a.sid || '@' || a.inst_id) = prior(a.blocking_session || '@' || a.blocking_instance))where isleaf = 1order by tree_level asc; INST_ID OS_KILL_SESSION
---------- --------------------------------1 kill -9 30049

2)借助v$session中的final_blocking_instance和final_blocking_session定位锁源头,语句如下:

SQL> select 'alter system kill session ''' || ss.sid || '' || ',' || ss.serial# || ',@' ||ss.inst_id || ''' immediate;' db_kill_sessionfrom gv$session s, gv$session sswhere s.final_blocking_session is not nulland s.final_blocking_instance = ss.inst_idand s.final_blocking_session = ss.sidand s.sid <> ss.sid
DB_KILL_SESSION
--------------------------------------------------
alter system kill session '161,5579,@1' immediate;
alter system kill session '161,5579,@1' immediate;SQL> select p.inst_id, 'kill -9 ' || p.spid os_kill_sessionfrom gv$session s, gv$session ss, gv$process pwhere s.final_blocking_session is not nulland s.final_blocking_instance = ss.inst_idand s.final_blocking_session = ss.sidand ss.paddr = p.addrand ss.inst_id = p.inst_idand s.sid <> ss.sidINST_ID OS_KILL_SESSION
---------- --------------------------------1 kill -9 30049

执行拼接生成的语句,即可杀掉锁的源头。

想必大家都遇到过在数据库层面发起“alter system kill session”(数据库层杀掉会话,不加immediate关键字)时,经常会出现资源无法及时释放、会话一直处于killed状态的情况。

如果这个会话是锁的源头,那么除了等待PMON(进程监视器)来清理之外,再没有更好的办法了,而在操作系统层面杀掉进程的方式,基本上是百试百灵。

使用系统命令“kill -9”杀死进程,系统向该process进程发出sigkill,sigkill信号直接发送给init进程,终止process进程。这种方式直接终止了Oracle 会话中对应的操作进程,资源也可以直接释放。

下面就来重点讲解“alter system kill session”的过程,以及在“alter system kill session”杀掉会话之后,为何会查不到处于killed状态的会话所对应的系统进程spid。

“alter system kill session”(不加immediate关键字)杀掉会话可分为两种场景进行讨论:会话状态分别是active和inactive。

使用此命令杀掉处于active状态的会话时,过程可以简单概括如下:

会话在收到kill信号后进行回滚,此过程不可被中断,直至过程完成,该会话会接收到“ORA-00028: your session has been killed”信息,PMON清理会话,释放资源。如果1分钟过后,上述动作还未完成,则该会话将被标记为killed状态,若会话拥有的资源未释放,则等待PMON进程清理会话。

使用此命令杀掉处于inactive状态的会话时,过程可以简单概括如下:

会话在收到kill信号后被标记为killed状态,会话拥有的资源未释放,等待PMON进程清理会话。如果会话再次发出查询信号,会话就会接收到“ORA-00028: your session has been killed”信息,PMON清理会话,释放资源。

接下来模拟不加immediate参数,杀掉会话后状态被标记为killed,操作系统查不到进程的实验场景,过程如下:

SQL> select username,sid,serial#,paddr,server,status from v$session where username = 'SCOTT';USERNAME     SID    SERIAL# PADDR            SERVER    STATUS
---------- ----- ---------- ---------------- --------- --------
SCOTT         17       6733 00000000A34C7040 DEDICATED INACTIVE
SCOTT        158       9177 00000000A34D4998 DEDICATED INACTIVESQL> select b.sid,b.serial#,c.spid,b.status from v$session b,v$process c where b.paddr = c.addr and b.sid in (17,158);SID    SERIAL# SPID      STATUS
---- ---------- --------- --------17       6733 23883     INACTIVE158       9177 24120     INACTIVE

手动杀掉这两个会话的命令如下:

SQL> alter system kill session '17,6733';
SQL> alter system kill session '158,9177';

再次查询这两个会话的状态,命令及结果如下:

SQL> select username,sid,serial#,paddr,server,status from v$session where username = 'SCOTT';
USERNAME    SID    SERIAL# PADDR            SERVER    STATUS
---------- ---- ---------- ---------------- --------- --------
SCOTT        17       6733 00000000A3551F18 PSEUDO    KILLED
SCOTT       158       9177 00000000A3551F18 PSEUDO    KILLED

从代码中我们可以发现,当两个会话的状态为killed时,会话的paddr指向同一地址00000000A3551F18(虚拟地址),此地址在操作系统层面并无对应的spid,这就是当会话的状态变为killed之后,使用以下语句查不到spid的原因,查询示例代码如下所示:

SQL> select b.sid,b.serial#,c.spid,b.status from v$session b,v$process c where b.paddr = c.addr and b.sid in (17,158);
no rows selected

此时,我们就可以使用前文的查询语句,查杀并清理会话,命令及结果如下:

SQL> select 'alter system kill session ''' || c.sid || '' || ',' || c.serial# || '''immediate;' kill_session from v$session c where status='KILLED';
KILL_SESSION
-----------------------------------------------
alter system kill session '17,6733' immediate;
alter system kill session '158,9177' immediate;

因此,在查杀会话时,可以考虑直接使用“alter system kill session 'sid,serial#' immediate”命令快速清理会话。需要注意的是,在查杀会话之前一定要再三确认信息,千万不要误杀了系统核心进程。

02 高峰期谨慎编译业务对象

想必大家都遇到过这样的情况,在业务高峰期如果编译存储过程、函数或视图,就会导致大量使用该对象的会话堵塞,自身也将处于挂起状态,后台等待事件为“library cache pin”。

在日常运维中,“library cache”相关等待较为常见,主要分为“library cache lock”或“library cache pin”,前者维护“library object handle”上的并发访问,后者维护“library object handle”下对应heap的并发访问,lock管理并发,pin管理一致性。

当我们编译存储过程、函数或视图的时候,Oracle就会在这些对象的handle上获得一个“library cache lock”,然后在这些对象的heap上获得pin,这样就能保证在编译的时候其他进程不会来更改这些对象。

有了以上的理论基础,当高峰期编译对象出现会话堵塞的问题时,我们应该如何处理呢?这里就会用到基表DBA_KGLLOCK,其包含如下两个字段。

  • kgllkuse字段:“Address of the user session that holds the lock or pin”,主要用于记录持有lock或pin的用户地址。

  • kgllkhdl字段:“Address of the handle for the KGL object”,主要用于记录handle的 对象地址。

故障发生时,首先查看后台等待事件,命令及输出具体如下:

SQL> select inst_id,sid, event, p1,p1text,p1raw,p2,p2text,p2raw from gv$session where wait_class<>'Idle';INST_ID  SID EVENT              P1 P1TEXT                   P1RAW
------- ---- ------------------ -------------------------   ---------------- 1   33 library cache pin  2081944584 handle address   000000007C17F408 

根据等待事件“library cache pin”获取“p1 handle address 000000007C17F408”。

关联视图“dba_kgllock dk,v$session”获取锁信息,命令及输出如下:

SQL> select s.sid,s.sql_id,s.event,dk.* from dba_kgllock dk,v$session s where s.saddr = dk.KGLLKUSE and KGLLKHDL='000000007C17F408';SID SQL_ID       EVENT         KGLLKUSE            KGLLKHDL         KGLLKMOD KGLLKREQ KGLL
--- ------------ ------------- ------------------- ---------------- -------- -------- ----
33  087rrdjwc2act library cache pin 00000000A92FC040 000000007C17F408        3         0  Lock
33  087rrdjwc2act library cache pin 00000000A92FC040 000000007C17F408        0         3  Pin

从以上返回结果中可以看出,我们并没有找到pin的持有者,KGLLKREQ表示当前会话需要申请的锁模式,KGLLKMOD表示当前系统中持有的锁模式,由于该系统为RAC,各节点之间的内存结构不同,handle地址不能公用,因此我们需要定位出owner和object_name在其他节点持有pin的会话。命令及输出如下:

SQL> select ADDR,INDX,INST_ID,KGLHDADR,KGLNAOWN,KGLNAOBJ from x$kglob where KGLHDADR='000000007C17F408';ADDR             INDX INST_ID KGLHDADR         KGLNAOWN   KGLNAOBJ
---------------- ---- ------- ---------------- ---------- ---------
00007FE9B0B45850 4979       1 000000007C17F408 SYS        DUMMY

其中,x$kglob为“library cache object”对象的视图。

RAC节点2根据object_name查找对应的handle地址信息,命令及输出如下:

SQL> select ADDR,INDX,INST_ID,KGLHDADR,KGLNAOWN,KGLNAOBJ from x$kglob where KGLNAOBJ='DUMMY'ADDR             INDX INST_ID KGLHDADR         KGLNAOWN  KGLNAOBJ
---------------- ---- ------- ---------------- --------- ---------
00007F987B1D8ED0 4150       2 00000000AA193870 SYS       DUMMY

查看锁的持有情况,命令及输出如下:

SQL> select s.sid,s.sql_id,s.event,dk.* from dba_kgllock dk,v$session s where s.saddr = dk.KGLLKUSE and KGLLKHDL='00000000AA193870';SID SQL_ID        EVENT             KGLLKUSE         KGLLKHDL         KGLLKMOD KGLLKREQ KGLL
--- ------------  ----------------- ---------------- ---------------- -------- -------- ----
424 d4wnj5j8y1mq7 PL/SQL lock timer 00000000A9787DA0 00000000AA193870        1        0 Lock
424 d4wnj5j8y1mq7 PL/SQL lock timer 00000000A9787DA0 00000000AA193870        2        0 Pin

最终定位节点2上的会话424持有的模式为2(即共享模式)的锁,堵塞了KGLLKREQ 3排它锁的申请,为了能够顺利编译,我们只需要杀掉节点2上的会话424即可。

03 数据误删恢复

在笔者多年的工作经历中,时常会遇到数据被随意篡改或删除的情况,那么在没有备份的情况下又该如何恢复数据呢。

对于drop操作(删除整个表,包括结构和数据),如果没有使用purge参数,那么我们可以使用回收站进行恢复,而对于truncate操作(只删除数据,不删除表的结构),则需要使用非常规的恢复方法,这些不在本书的讨论范围之内,本节将以delete为例演示数据被误删后的恢复。

1. 利用undo闪回查询

根据undo信息,利用前镜像,可以把表置于一个删除前的时间点或SCN(System Change Number),从而找回数据。具体命令如下:

SQL> select * from emp as of timestamp to_timestamp('2019-11-05 08:00:00', 'YYYY-MM-DD HH:MI:SS');

但是此方法会受限于undo_retention的配置,默认情况下,undo_retention的值为900秒,即在删除数据900秒之后,undo中的数据会过期。

但如果业务比较繁忙,在undo表空间不足的情况下,即使镜像没有过期,数据也还是会被覆盖。若此时查询就会收到“ORA-08180: no snapshot found based on specified time”的报错信息。

2. logminer挖掘

数据库所有DML(数据操纵语言)的操作都会记录在redo日志中,只要归档文件还存在,那么所有DML的记录都可以找回,使用方法如下。

1)确定DML时间点日志信息,命令如下:

SQL> select t.THREAD#, t.SEQUENCE#, t.NAMEfrom v$archived_log twhere t.FIRST_TIME >=to_date('2019-11-05 10:24:30', 'yyyy-mm-dd hh24:mi:ss')and t.NEXT_TIME <=to_date('2019-11-05 14:00:30', 'yyyy-mm-dd hh24:mi:ss');THREAD#  SEQUENCE# NAME
---------- ---------- --------------------------------------------------1          2 /app_target/easdb_dg/arch/1_2_1023532682.dbf1          1 /app_target/easdb_dg/arch/1_1_1023532682.dbf1          3 /app_target/easdb_dg/arch/1_3_1023532682.dbf

2)安装logminer安装包,默认系统自带该安装包,安装命令如下:

SQL> @$ORACLE_HOME/rdbms/admin/dbmslm.sql
Package created.
Grant succeeded.
Synonym created.

3)添加挖掘日志,添加命令如下:

SQL> execute dbms_logmnr.add_logfile(logfilename=>'/app_target/easdb_dg/arch/1_2_1023532682.dbf',options=>dbms_logmnr.new);
<!--继续添加-->
SQL> execute dbms_logmnr.add_logfile(logfilename=>'/app_target/easdb_dg/arch/1_1_1023532682.dbf',options=>dbms_logmnr.addfile);
SQL> execute dbms_logmnr.add_logfile(logfilename=>'/app_target/easdb_dg/arch/1_3_1023532682.dbf',options=>dbms_logmnr.addfile);
  • 注意:第一个添加日志选项是new,后续添加选项是addfile。

4)开启logminer,命令如下:

SQL> execute dbms_logmnr.start_logmnr(Options => DBMS_LOGMNR.DICT_FROM_ONLINE_CATALOG);

5)查询v$logmnr_contents视图获取挖掘信息,命令如下:

SQL> select sql_redo from v$logmnr_contents where SEG_OWNER='SCOTT';
<!--sql_redo用于记录当时DML的操作记录-->
SQL> select sql_undo from v$logmnr_contents where SEG_OWNER='SCOTT';
<!--若是误操作回退,则可以使用sql_undo,执行还原操作-->

最终,我们可以根据sql_undo进行DML误操作恢复。

关于作者:叶桦,Oracle OCM,MySQL认证专家,超10年乙方数据库维护经验,美创科技运维服务团队负责人。具备丰富的行业经验与技术积累,所服务的对象包括大型运营商、金融机构、政府机关以及制造业等多个行业客户,对于数据库技术具有深刻的理解。精通Oracle和MySQL数据库内核原理、架构规划和调优诊断,擅长Shell和Python自动化运维开发。

徐浩,美创科技运维部经理,Oracle、MySQL、云数据库高级认证专家。拥有8年以上的数据库领域从业经验,TB级高并发数据库与中大型项目的管理经验。对于分布式高可用架构和性能调优有着丰富的实战经验,擅长故障诊断及数据灾难挽救,服务的行业包括运营商、制造业、金融、医疗、政府等。目前,主要负责Oracle、MySQL、阿里云等技术的研究和运维管理,以及数据库智能运维平台的设计开发等工作。

本文摘编自《DBA攻坚指南:左手Oracle,右手MySQL》,经出版方授权发布。

延伸阅读《DBA攻坚指南》

点击上图了解及购买

转载请联系微信:DoctorData

推荐语:本书是资深Oracle、MySQL技术专家呕心沥血之作,积作者多年的经验结晶和实践经验,也是目前市场上为数不多Oracle和MySQL相结合的数据库技术书籍。

划重点????

干货直达????

  • 史上最全!用Pandas读取CSV,看这篇就够了

  • 既然中台建设这么重要,为什么还有那么多人反对?

  • 在中国,找个身高1米7年入20万的老公,到底有多难?

  • 女生学不好数理化,真的吗?

更多精彩????

在公众号对话框输入以下关键词

查看更多优质内容!

PPT | 读书 | 书单 | 硬核 | 干货 讲明白 | 神操作

大数据 | 云计算 | 数据库 | Python | 爬虫 | 可视化

AI | 人工智能 | 机器学习 | 深度学习 | NLP

5G | 中台 | 用户画像 1024 | 数学 | 算法 数字孪生

据统计,99%的大咖都关注了这个公众号

????

3个最常见案例详解DBA日常维护相关推荐

  1. 代码检查规则:Java语言案例详解

    本节课程为<代码检查规则:Java语言案例详解>, 通常情况下Java的代码检查规则可以分为以下十类: 接下来,让我们具体来看看每个分类的内容. 一.源文件规范 该类规范主要从文件名.文件 ...

  2. python编程入门与案例详解-quot;Python小屋”免费资源汇总(截至2018年11月28日)...

    原标题:"Python小屋"免费资源汇总(截至2018年11月28日) 为方便广大Python爱好者查阅和学习,特整理汇总微信公众号"Python小屋"开通29 ...

  3. php 倒置,PHP依赖倒置案例详解

    这次给大家带来PHP依赖倒置案例详解,PHP依赖倒置的注意事项有哪些,下面就是实战案例,一起来看一下. 什么是依赖倒置呢?简单地讲就是将依赖关系倒置为依赖接口,具体概念如下: 1.上层模块不应该依赖于 ...

  4. html css主题,HTML+CSS=无限可能——案例详解:我的POI主题作品

    原标题:HTML+CSS=无限可能--案例详解:我的POI主题作品 在微博发了这个作业,收到很多好评,实在太开心! 斌叔让我来投稿,那今天就以这个网页为案例,把制作过程中我的方法和经验分享给大家. 作 ...

  5. 视频教程-React Hooks 案例详解(React 进阶必备)-其他

    React Hooks 案例详解(React 进阶必备) Leo 1978年8月生,河北石家庄人. 曾任职中软股份. 计算科学导师: 刘坤起博士. 开发的<电商分销系统>获得淘宝Top10 ...

  6. python代码案例详解-我用Python抓取了7000 多本电子书案例详解

    安装 安装很简单,只要执行: pip install requests-html 就可以了. 分析页面结构 通过浏览器审查元素可以发现这个电子书网站是用 WordPress 搭建的,首页列表元素很简单 ...

  7. python代码案例详解-第7.20节 案例详解:Python抽象类之真实子类

    第7.20节 案例详解:Python抽象类之真实子类 上节介绍了Python抽象基类相关概念,并介绍了抽象基类实现真实子类的步骤和语法,本节结合一个案例进一步详细介绍. 一. 案例说明 本节定义了图形 ...

  8. java同步方法完成案例_Java同步代码块和同步方法原理与应用案例详解

    本文实例讲述了java同步代码块和同步方法.分享给大家供大家参考,具体如下: 一 点睛 所谓原子性WOmoad:一段代码要么执行,要么不执行,不存在执行一部分被中断的情况.言外之意是这段代码就像原子一 ...

  9. 《微信小程序:开发入门及案例详解》—— 3.4 小结

    本节书摘来自华章出版社<微信小程序:开发入门及案例详解>一 书中的第3章,第3.4节,作者李骏 边思,更多章节内容可以访问云栖社区"华章计算机"公众号查看. 3.4 小 ...

最新文章

  1. c# 监视目录下的文件变化
  2. 最好的方法,是为你们量身定制!
  3. Git 版本对比 idea图形化版本
  4. OpenCV调用YOLOv4进行目标检测
  5. “以毒攻毒”?阿里将上线“二哈”防骚扰电话应用程序
  6. 两个not exists_分享两个冷门但又超实用的 Vim 使用技巧!
  7. ​不容错过的 13 个 JavaScript 实用技巧!
  8. OpenSSL生成RSA公私钥(java)
  9. Oracle常用函数系列之一:字符函数(1)
  10. java日历数据_JAVA 常用数据类型 之日历类
  11. 视频转换器怎样将QSV格式转换MP4视频
  12. 谈医药行业网站的运营模式分析
  13. 极速office(Word)怎么删除背景颜色
  14. 搜狗输入法不错,附带的进程需要一个个把exe文件重命名
  15. 人力资源管理案例选读【2】
  16. cvpr 深度估计_近两年 CVPR ICCV ECCV 相机位姿估计、视觉定位、SLAM相关论文汇总...
  17. RT_Thread中rtconfig.h解析
  18. dw cs6 html建站点,dreamweaver cs6网页中制作锚点链接的教程方法
  19. QT实现图片的滚轮缩放、框选放大、拖拽移动
  20. 计算机与网络之间是红叉,雨林木风win7系统网络连接出现红叉无法上网的解决办法...

热门文章

  1. Qt文档阅读笔记-Qt Quick Controls - Wearable Demo实例解析
  2. Qt文档阅读笔记-Qt Core的官方解析
  3. C/C++ | Qt 实现爬虫功能,爬取CSDN博客文章
  4. 大三软件工程小项目-小技术集合-Qt状态栏设置
  5. php公司面试反问,php面试坑集锦
  6. c语言链表常错,C语言链表,哪里错了?
  7. python单词词典_python:单词和词形词典
  8. 电脑键盘功能介绍_Excel应用041:全能电脑抽奖神器(功能介绍)(原创作品) ?...
  9. android输入框圆形,GitHub - mylhyl/Android-CircleDialog: 仿IOS圆角对话框、进度条、列表框、输入框,ad广告框,支持横竖屏切换...
  10. python中小数_比较python中的小数