SQL 类故障

数据库实例主要通过接口发送的 SQL 语句与客户端之间进行交互,我们在执行语句的时候可能会碰到以下几种异常的情况:

  • 结果集异常
  • 执行长期没有返回数据
  • 执行过程中数据库进程异常

结果集异常

首先我们要明确标定出结果集异常的语句,可以通过 LOGCOMMIT 日志、V$SQL_HISTORY 视图等找到该语句。

一般情况下,结果集异常大部分是由于优化器对查询语句的错误改写或者优化导致的,我们尽量保证提取到的语句和实际执行的语句是严格一致、绑定参数相同、数据库参数一致,这样可以保证我们进行验证时的语句执行计划和出错时一致。

在计划一致的情况下,如果可以重现查询结果集异常的情况,我们可以尝试对异常的查询语句进行裁剪,也可以参照当前计划,有目的修改通过 SQL 语句摘除计划中的某些操作符,一步步的得到结果集出错的最精简语句。

如果知识储备充足(查询计划相关),可以通过修改参数 /HINT/ 改写语句等方式,调整问题操作符为等价的其他操作符,看是否可以规避问题,如果不能通过等价修改的方式绕过该问题,可以及时整理重现用例将问题反馈至服务中心。

另外一些特殊情况的结果集异常可能是因为某些信息没有及时更新或者错误使用导致,包括但不限于:

  • 查询涉及到全文索引没有更新。
  • 查询涉及到的物化视图没有更新。
  • 错误的使用确定性函数标记了不确定函数。
  • 非一致读备机查询数据存在延迟。

执行长时间没有返回数据

碰到这种情况,我们大致有几个排查的方向:

  • 所执行语句本身存在性能问题,需要进行优化调整执行计划。
  • 执行过程中某些对象/资源发生等待,导致执行语句长时间等待不能返回。
  • 其他异常情况。

性能问题

  • 确认语句处于活动状态,语句如下:
SELECT * FROM v$sessions WHERE state='ACTIVE'
AND dbms_lob.substr(sf_get_session_sql(sess_id)) LIKE '%语句片段%'
  • 查询等待事务:
SELECT * FROM v$trxwait WHERE id = 上述语句得到结果得 TRX_ID
注意

查询不到结果,说明该语句没有发生事务性等待。如果发现语句活动,且没有事务性等待,则大概率是该语句自身执行存在效率问题,需要对执行计划进行调整,具体得方式可以参考查询优化相关章节。

对象/资源/事务等待导致长时间没有返回结果

  • 如果存在长时间没有返回结果得语句,首先通过 V$SESSIONS 确认语句处于活动状态,语句如下:
SELECT * FROM v$sessions WHERE state='ACTIVE'
AND dbms_lob.substr(sf_get_session_sql(sess_id)) LIKE '%语句片段%'
  • 明确事务等待导致语句没有正常执行结束,语句如下:
SELECT * FROM v$trxwait WHERE id = 上述语句得到结果得 TRX_ID

V$TRXWAIT 查询得到结果的 WAIT_FOR_ID 字段标记的事务即为当前语句正在等待的事务。

我们通过查询 SELECT * FROM v$sessions WHERE trx_id = 查询到的 WAIT_FOR_ID,可以得到当前事务所在等待的会话的一些信息,一般来说,都是因为某些会话或者客户端忘记进行提交或者回滚操作,后续就一直空闲了,导致其他的事务由于跟该事务存在一些事务上的依赖关系发生等待,针对这种情况,我们需要在确认安全的情况针对这个阻塞源头会话进行操作,关闭客户端 /CLOSE 会话/发送提交或者回滚命令等。

另外一种等待不会通过 V​TRXWAIT 查询得到结果,比如某些表发生 DDL 操作过程中,其他的会话尝试对该表进行查询,由于字典对象发生变化,所以发生字典对象的等待,这种阻塞可以通过查询 `SELECT * FROM vlock WHERE blocked = 1` 来进行确认,查询结果中可以对相关锁对象的持有事务来查询 V$SESSIONS,来确认阻塞来源。

注意

一般情况下这种 DDL 导致的等待会有一个显示的等待时间,由 dm.ini 参数 DDL_WAIT_TIME 来进行控制,默认是 10,也就是说如果等待 10 秒钟后,阻塞源头的 DDL 还没有执行完释放资源,会抛出锁超时错误。

其他一些特殊情况下发生的等待事件

  • 查询中由于存在临时表 /SORT/HASH JOIN/HAGR 等操作导致使用的临时表空间,INI 参数又限制了 TEMP 表空间大小,在临时表空间没有被其他会话使用完并释放时发生等待。
  • 主备、读写分离等环境运行过程中,由于备机自身或者配置相关的原因(备机 IO 出现异常、主备网络异常、数据延迟达到配置的主备最大延迟)等,导致主机上运行一些需要刷 REDO 日志的语句发生等待,这种等待需要备机上的这些现象缓解之后才会解开。
  • 大表发生 DELETE 量数据、TRUNCATE 后,对该表的查询或者 DML 操作缓慢,这个时由于大批量删除数据之后,由于这些数据都只是被标记删除,在一定事件后会由回收站进行统一的清理操作,清理过程中需要对数据页进行修改,而涉及的数据页又非常多,所以导致执行速度缓慢。
  • 数据库自身 BUG 导致的死锁,正常情况下,如果存在某些事务互相等待的情况,数据库会主动抛出死锁报错,但是如果时数据库内部自身 BUG,导致自身的一些临界区资源出现死锁,是不能自动处理的,一般我们最后考虑这种情况。如果发生长时间没有响应并排除了上述的原因后,我们可以通过 pstack/gdb 工具抓取数据库实时运行堆栈信息,通过服务中心人员对堆栈进行分析,确认是否数据库本身 BUG。

执行过程中数据库进程异常

这种异常我们也分为以下几类:

  • 运行过程中数据库发生 halt。
  • 运行过程中数据库段错误。
  • 运行过程中发生 PAGE FAULT。
  • 运行过程中发生线程污染 (TAINED)。
  • 运行过程中发生 OOM 错误。

以上错误,我们都可以通过查看进程,来发现运行过程中数据库进程没有了(配置了自动拉起的情况下是进程号发生变化)。

对于 halt 类型错误,我们可以通过查看数据库的运行日志,搜索 halt,如果发现存在 halt 字样的日志内容,会在 halt 之前又具体的 halt 原因说明,这个是数据自己保证数据安全的一种方式,当检测到一些严重异常时,采取自杀的方式来保全数据,防止更进一步的异常。

对于其他几种类型的错误,我们从数据库运行日志中一般不会发现明显的信息,都是运行日志刷新过程中突然中断了,这些错误我们可以通过查询操作系统日志(一般是 /var/log/messages*),然后搜索异常前的 dmserver 的进程号或者搜索 DM 相关的信息,根据查询到的信息不同的类型进行不同的处理。

  • Segfault

需要说明的是,halt 后操作系统日志中也会是 segfault 类型的记录,因为 halt 时时通过主动进行除 0 操作引发的异常,如果不是 halt,发生 segfault 一般都是由于数据库自身 BUG,导致发生内存写溢出、越界、空指针操作等引起的异常,这种情况一般是由于某些语句引起。我们可以进行如下处理:

  1. 通过 dmrdc 工具对异常生成的 core 文件进行信息读取,读取后会得到一个数据文件,输出文件的格式为 [线程号] SQL 语句内容
  2. 通过 gdb 工具打开异常时生成的 core 文件。
  3. 通过 info thread 命令,找到出现异常的线程,线程对应的 LWP 后跟的数字即为异常线程号。
  4. 通过 GDB 找到的异常线程号去 dmrdc 的输出文件中进行搜索,得到的语句就是导致异常的语句,如果语句可以稳定重现,可以将 gdb 该 core 文件的 bt 输出内容、相关语句发送到服务中心进行 BUG 确认。
  • Page fault

一般 PAGE FUALT 后会跟有具体的错误码,code: xx,在 LINUX 内核中定义了这些报错对应的内容。

如果 code 是 0,一般是由于数据库运行中没有办法申请到需要使用的内存导致,这种时候需要考虑修改 INI 相关配置,调整数据库的内存使用量。其他几种 code 或者是 code 的组合(比如 6 = 4 + 2 表示存在两种错误),基本是发生内存读写越界引起。

  • Tained

如果操作系统日志中出现了 DM 相关线程 tained 相关的信息,一般是由于第三方软件(主要是杀毒软件或者安全软件),对 DM 的相关线程进行污染,导致数据库的一些线程被异常中止,不同类型的线程被中止可能导致不同的结果,而且是不可预期的,这种情况下需要及时调整现场的环境,让相关软件尽量不影响数据库进程。

  • OOM 错误

这种也是比较常见的错误,OOM 操作系统本身对于自己的一种保护机制,对占用大量内存的进程,如果满足一定条件,就会被操作系统中止,腾出空余内存,如果频繁发生数据库进程被 OOM kill,则需要调整数据库内存相关参数,操作系统内存相关参数等,防止数据库进程被频繁 OOM。

微信扫码
分享文档
扫一扫
联系客服