SQL 类故障

执行计划

SELECT * FROM SYSOBJECTS;

1   #NSET2: [0, 1282, 396]
2     #PRJT2: [0, 1282, 396]; exp_num(17), is_atom(FALSE)
3       #CSCN2: [0, 1282, 396]; SYSINDEXSYSOBJECTS(SYSOBJECTS as SYSOBJECTS)

常见操作符解读

执行计划的每行即为一个计划节点,主要包含三部分信息。
第一部分 NEST2、PRJT2、CSCN2 为操作符及数据库具体执行了什么操作。
第二部分的三元组为该计划节点的执行代价,具体含义为[代价,记录行数,字节数]。
第三部分为操作符的补充信息。
例如:第三个计划节点:操作符是 CSCN2 即全表扫描,代价估算是 0 ms,扫描的记录行数是 1282 行,输出字节数是 396 个。

各计划节点的执行顺序为:缩进越多的越先执行,同样缩进的上面的先执行,下面的后执行,上下的优先级高于内外。

NSET:结果集收集

EXPLAIN SELECT * FROM T1;

1   #NSET2: [1, 10000, 156]
2     #PRJT2: [1, 10000, 156]; exp_num(5), is_atom(FALSE)
3       #CSCN2: [1, 10000, 156]; INDEX33556710(T1

用于结果集收集的操作符,一般是查询计划的顶层节点,优化工作中无需对该操作符过多关注,一般没有优化空间。

PRJT:投影

EXPLAIN SELECT * FROM T1;

1   #NSET2: [1, 10000, 156]
2     #PRJT2: [1, 10000, 156]; exp_num(5), is_atom(FALSE)
3       #CSCN2: [1, 10000, 156]; INDEX33556710(T1

关系的【投影】 (project) 运算,用于选择表达式项的计算。广泛用于查询,排序,函数索引创建等。优化工作中无需对该操作符过多关注,一般没有优化空间。

SLCT:选择

EXPLAIN SELECT * FROM T1 WHERE C2=TEST;

1   #NSET2: [1, 250, 156]
2     #PRJT2: [1, 250, 156]; exp_num(5), is_atom(FALSE)
3       #SLCT2: [1, 250, 156]; T1.C2 = TEST
4         #CSCN2: [1, 10000, 156]; INDEX33556717(T1)

关系的【选择】运算,用于查询条件的过滤。可比较返回结果集与代价估算中是否接近,如相差较大可考虑收集统计信息。若该过滤条件过滤性较好,可考虑在条件列增加索引。

AAGR:简单聚集

EXPLAIN SELECT COUNT(*) FROM T1 WHERE C1 = 10;

1   #NSET2: [0, 1, 4]
2     #PRJT2: [0, 1, 4]; exp_num(1), is_atom(FALSE)
3       #AAGR2: [0, 1, 4]; grp_num(0), sfun_num(1)
4         #SSEK2: [0, 1, 4]; scan_type(ASC), IDX_C1_T1(T1), scan_range[10,10]

用于没有 GROUP BY 的 COUNT、SUM、AGE、MAX、MIN 等聚集函数的计算。

SAGR:快速聚集

EXPLAIN SELECT MAX(C1) FROM T1;

1   #NSET2: [1, 1, 0]
2     #PRJT2: [1, 1, 0]; exp_num(1), is_atom(FALSE)
3       #FAGR2: [1, 1, 0]; sfun_num(1)

用于没有过滤条件时从表或索引快速获取 MAX、MIN、COUNT 值。

HAGR:HASH 分组聚集

EXPLAIN SELECT COUNT(*) FROM T1 GROUP BY C2;

1   #NSET2: [1, 100, 48]
2     #PRJT2: [1, 100, 48]; exp_num(1), is_atom(FALSE)
3       #HAGR2: [1, 100, 48]; grp_num(1), sfun_num(1)
4         #CSCN2: [1, 10000, 48]; INDEX33556717(T1)

用于分组列没有索引只能走全表扫描的分组聚集,该示例中 C2 列没有创建索引。

SAGR:流分组聚集

EXPLAIN SELECT COUNT(*) FROM T1 GROUP BY C1;

1   #NSET2: [1, 100, 4]
2     #PRJT2: [1, 100, 4]; exp_num(1), is_atom(FALSE)
3       #SAGR2: [1, 100, 4]; grp_num(1), sfun_num(1)
4         #SSCN: [1, 10000, 4]; IDX_C1_T1(T1)

用于分组列是有序的情况下,可以使用流分组聚集,C1 列上已经创建了索引,SAGR2 性能优于 HAGR2。

BLKUP:二次扫描 (回表)

EXPLAIN SELECT * FROM T1 WHERE C1=10;

1   #NSET2: [0, 1, 156]
2     #PRJT2: [0, 1, 156]; exp_num(5), is_atom(FALSE)
3       #BLKUP2: [0, 1, 156]; IDX_C1_T1(T1)
4         #SSEK2: [0, 1, 156]; scan_type(ASC), IDX_C1_T1(T1), scan_range[10,10]

先使用二级索引索引定位 rowid,再根据表的主键、聚集索引、rowid 等信息获取数据行中其它列。

CSCN:全表扫描

EXPLAIN SELECT * FROM T1;

1   #NSET2: [1, 10000, 156]
2     #PRJT2: [1, 10000, 156]; exp_num(5), is_atom(FALSE)
3       #CSCN2: [1, 10000, 156]; INDEX33556710(T1)

CSCN2 是 CLUSTER INDEX SCAN 的缩写即通过聚集索引扫描全表,全表扫描是最简单的查询,如果没有选择谓词,或者没有索引可以利用,则系统一般只能做全表扫描。全表扫描 I/O 开销较大,在一个高并发的系统中应尽量避免全表扫描。

SSEK、CSEK、SSCN:索引扫描Copy

-- 创建所需索引
CREATE CLUSTER INDEX IDX_C1_T2  ON T2(C1);
CREATE  INDEX IDX_C1_C2_T1  ON T1(C1,C2);
 
EXPLAIN SELECT * FROM T1 WHERE C1=10;

1   #NSET2: [0, 1, 156]
2     #PRJT2: [0, 1, 156]; exp_num(5), is_atom(FALSE)
3       #BLKUP2: [0, 1, 156]; IDX_C1_T1(T1)
4         #SSEK2: [0, 1, 156]; scan_type(ASC), IDX_C1_T1(T1), scan_range[10,10]
 

SSEK2 是二级索引扫描即先扫描索引,再通过主键、聚集索引、rowid 等信息去扫描表。

EXPLAIN SELECT * FROM T2 WHERE C1=10;

1   #NSET2: [0, 250, 156]
2     #PRJT2: [0, 250, 156]; exp_num(5), is_atom(FALSE)
3       #CSEK2: [0, 250, 156]; scan_type(ASC), IDX_C1_T2(T2), scan_range[10,10]

CSEK2 是聚集索引扫描只需要扫描索引,不需要扫描表,即无需 BLKUP 操作,如果 BLKUP 开销较大时,可考虑创建聚集索引。

EXPLAIN SELECT C1,C2 FROM T1;

1   #NSET2: [1, 10000, 60]
2     #PRJT2: [1, 10000, 60]; exp_num(3), is_atom(FALSE)
3       #SSCN: [1, 10000, 60]; IDX_C1_C2_T1(T1)
 

SSCN 是索引全扫描,不需要扫描表。

NEST LOOP:嵌套循环连接
嵌套循环连接最基础的连接方式,将一张表(驱动表)的每一个值与另一张表(被驱动表)的所有值拼接,形成一个大结果集,再从大结果集中过滤出满足条件的行。驱动表的行数就是循环的次数,将在很大程度上影响执行效率。

连接列是否有索引,都可以走 NEST LOOP,但没有索引,执行效率会很差,语句如下所示:


select /*+use_nl(t1,t2)*/* from t1 inner join t2 on t1.c1=t2.c1 where t1.c2=A;
1   #NSET2: [281359, 24502, 104]
2     #PRJT2: [281359, 24502, 104]; exp_num(4), is_atom(FALSE)
3       #NEST LOOP INNER JOIN2: [281359, 24502, 104]
4         #SLCT2: [5, 250, 52]; T1.C2 = A
5           #CSCN: [5, 10000, 52]; INDEX33555947(T1)
5         #CSCN: [5, 10000, 52]; INDEX33555948(T2)

可针对 T1 和 T2 的连接列创建索引,并收集统计信息,语句如下所示:

Copy
CREATE INDEX IDX_T1_C2  ON T1(C2);
CREATE INDEX IDX_T2_C1  ON T2(C1);
DBMS_STATS.GATHER_INDEX_STATS(USER,'IDX_T1_C2');
DBMS_STATS.GATHER_INDEX_STATS(USER,'IDX_T2_C1');

再次查看执行计划可看出效率明显改善,代价有显著下降,语句如下所示:

select /*+use_nl(t1,t2)*/* from t1 inner join t2 on t1.c1=t2.c1 where t1.c2=A;
1   #NSET2: [1, 17151, 104]
2     #PRJT2: [1, 17151, 104]; exp_num(4), is_atom(FALSE)
3       #NEST LOOP INDEX JOIN2: [1, 17151, 104]
4         #BLKUP2: [0, 175, 52]; IDX_T1_C2(T1)
5           #SSEK: [0, 175, 52]; scan_type(ASC), IDX_T1_C2(T1), scan_range[A,A]
6         #BLKUP2: [0, 175, 52]; IDX_T2_C1(T2)
7           #SSEK: [0, 175, 52]; scan_type(ASC), IDX_T2_C1(T2), scan_range[T1.C1,T1.C1]

适用场景:

驱动表有很好的过滤条件
表连接条件能使用索引
结果集比较小
HASH JOIN:哈希连接
哈希连接是在没有索引或索引无法使用情况下大多数连接的处理方式。哈希连接使用关联列去重后结果集较小的表做成 HASH 表,另一张表的连接列在 HASH 后向 HASH 表进行匹配,这种情况下匹配速度极快,主要开销在于对连接表的全表扫描以及 HASH 运算。

select * from t1 inner join t2 on t1.c1=t2.c1 where t1.c2=A;
1   #NSET2: [12, 24502, 104]
2     #PRJT2: [12, 24502, 104]; exp_num(4), is_atom(FALSE)
3       #HASH2 INNER JOIN: [12, 24502, 104]; KEY_NUM(1);
4         #SLCT2: [5, 250, 52]; T1.C2 = A
5           #CSCN: [5, 10000, 52]; INDEX33555947(T1)
5         #CSCN: [5, 10000, 52]; INDEX33555948(T2)

哈希连接比较消耗内存如果系统有很多这种连接时,需调整以下 3 个参数:

参数名说明
HJ_BUF_GLOBAL_SIZEHASH 连接操作符的数据总缓存大小 ()>=HJ_BUF_SIZE),系统级参数,以兆为单位。有效值范围(10~500000)
HJ_BUF_SIZE单个哈希连接操作符的数据总缓存大小,以兆为单位。有效值范围(2~100000)
HJ_BLK_SIZE哈希连接操作符每次分配缓存 (BLK) 大小,以兆为单位,必须小于 HJ_BUF_SIZE。有效值范围(1~50)

MERGE JOIN:归并排序连接
归并排序连接需要两张表的连接列都有索引,对两张表扫描索引后按照索引顺序进行归并。

Copy
-- 对连接列创建索引
CREATE INDEX IDX_T1_C1C2 ON T1(C1,C2);
 
sselect /*+use_merge(t1 t2)*/t1.c1,t2.c1 from t1 inner join t2 on t1.c1=t2.c1 where t2.c2=b;
1   #NSET2: [12, 24502, 104]
2     #PRJT2: [12, 24502, 104]; exp_num(4), is_atom(FALSE)
3       #MERGE INNER JOIN: [12, 24502, 104]; KEY_NUM(1);
4         #SSCN: [4, 10000, 4]; IDX_T1_C1C2(T1)
5         #BLKUP2: [5, 10000, 52]; IDX_T2_C1(T2)
5           #SSCN: [5, 10000, 52]; IDX_T2_C1(T2)

通过 ET 优化单条 SQL

ET 工具是 DM 数据库自带的 SQL 性能分析工具,能够统计 SQL 语句执行过程中每个操作符的实际开销,为 SQL 优化提供依据以及指导。本章节主要介绍 ET 工具的配置方法及使用方式。

ET 功能的开启
ET 功能默认关闭,可通过配置 INI 参数中的 ENABLE_MONITOR=1、MONITOR_SQL_EXEC=1 开启该功能。

–两个参数均为动态参数,可直接调用系统函数进行修改
SP_SET_PARA_VALUE(1,‘ENABLE_MONITOR’,1);
SP_SET_PARA_VALUE(1,‘MONITOR_SQL_EXEC’,1);

–关闭 ET
SP_SET_PARA_VALUE(1,‘ENABLE_MONITOR’,0);
SP_SET_PARA_VALUE(1,‘MONITOR_SQL_EXEC’,0);

注意
ET 功能的开启将对数据库整体性能造成一定影响,优化工作结束后尽量关闭该功能以提升数据库整体运行效率。
ET 查看方式
执行 SQL 语句后,客户端会返回 SQL 语句的执行号。鼠标单机执行号即可查看 SQL 语句对应的 ET 结果。
CALL ET(55);

ET 结果说明

OP: 操作符
TIME(us): 时间开销,单位为微秒
PERCENT: 执行时间占总时间百分比
RANK: 执行时间耗时排序
SEQ: 执行计划节点号
N_ENTER: 进入次数
以 SORT3 操作符为例,时间开销为 2.8ms,占总执行时间的 59.13%,可作为优化的重点对象。对 T2 表的 C1 字段建二级索引,消除排序操作符。

统计信息

SP_CREATE_SYSTEM_PACKAGES (1,‘DBMS_STATS’); --如果还未创建过系统包。请先调用系统过程创建系统包
dbms_stats.table_stats_show(‘SYSDBA’,‘CF’); --查看表sysdba.cf的统计信息
dbms_stats.column_stats_show(‘SYSDBA’,‘CF’,‘FDMCHXM’); --查看表某列的统计信息
dbms_stats.index_stats_show(‘SYSDBA’,‘I_CF_FDMCHXM’); --查看模式下索引的统计信息

  call sp_db_stat_init(); --库级别的统计信息收集(包括所有用户、模式的表及表上索引的信息)
  call sp_tab_index_stat_init('SYSDBA','CF');--收集CF表下的所有索引的统计信息,上面新建索引没有收集统计信息时不走索引,收集完该索引或者该索引的列后,就走索引了
  call sp_col_stat_init('SYSDBA','CF','FDMCHXM');--收集表中某一列的统计信息
  call sp_tab_col_stat_init('SYSDBA','CF');--收集某张表上所有列的统计信息

  call sp_stat_on_table_cols('SYSDBA','CF',90);--收集某张表上所有列的统计信息,并指定采样率,采样率范围0-100
  call sp_tab_stat_init('SYSDBA','CF'); --收集指定的表的统计信息
  call sp_index_stat_init('SYSDBA','I_CF_FDMCHXM'); --收集指定的索引的统计信息

  call sp_index_stat_deinit('SYSDBA','I_CF_FDMCHXM'); --删除模式下指定的索引统计信息
  call sp_col_stat_deinit('SYSDBA','CF','FDMCHXM'); --删除指定列的统计信息
  call sp_tab_col_stat_deinit('SYSDBA','CF'); --删除指定表上所有的统计信息
  call sp_tab_stat_deinit('SYSDBA','CF'); --删除指定表的统计信息

1、dbms_stat包
2、stat 100-采样率 on 表(列名);
3、sp_index_stat_init(可以指定采样率)

1.数据库在导入大量数据之后,请立即更新统计信息
2.数据库数据分布方式不太变动的时候,请不要更新他们的统计信息(可以维持计划稳定)
3.对于时间列,尤其是有具体参数的时间列(分布不均的方式),我们需要每天都及时更新这些列的统计信息

方法1

可以对表自身收集
可以对sql 语句涉及到的对象收集,—— 当sql 很复杂的时候,很舒服!
但是,他使用的自适应的采样比例,不可以控制。——系统内嵌的算法,数据量在什么级别的时候,用多大的采样比例,不可以指定。

– 针对表自身的
SP_TAB_STAT_INIT ( ‘SYSDBA’ , ‘TEST’ );
SP_TAB_STAT_DEINIT

– 针对索引的
SP_INDEX_STAT_INIT ( ‘SYSDBA’ , ‘IDX_TEST_V1’ );
SP_INDEX_STAT_DEINIT

– 针对全库的(慎用,库比较大的话,每个一时半会,别想跑完)
SP_COL_STAT_INIT ( ‘SYSDBA’ , ‘TEST’ , ‘V2’ );
SP_DB_STAT_DEINIT

– 针对列的
SP_COL_STAT_INIT
SP_COL_STAT_DEINIT

– 针对sql 语句的
SP_SQL_STAT_INIT ( ‘select * from test a ,test b where a.v1=b.v2’ );

方法2

对于收集列的统计信息的时候,最常用的一个方法。尤其是在刚建立完索引的时候。
直接 在索引的语句 on 前面 加个 stat 100 选中后面部分执行就可以,很方便。—— 懒人。。。
可以指定列收集,既是它的优点,也是他的缺点。

Stat 命令,主要是我们用在表的列上,比较方便:
Stat 100 on test(v1);
stat 100 on sysdba.test(v1);

方法3

用的也很多,可以对整个表的索引列,指定采样比例收集,或者对整个表的全部列,指定采样比例收集。
一个命令,对全表涉及列收集,是它的优点,也是它的缺点,因为有时候,我们不需要收集某些列嘛,表大的时候,收集很浪费时间的。
当然,可以指定采样比例收集整个模式,也是它的优点,但是不建议,只因为他中断了怎么办,再次执行又是从头再来。

DBMS_STATS System_Packages.pdf 手册(系统包)
DBMS_STATS.GATHER_SCHEMA_STATS(‘USERNAME’,100,TRUE,‘FOR ALL INDEXED COLUMNS SIZE AUTO’);
DBMS_STATS.GATHER_SCHEMA_STATS(‘USERNAME’,100,TRUE,‘FOR ALL COLUMNS SIZE AUTO’);
BMS_STATS.GATHER_TABLE_STATS(‘USERNAME’,‘TABLENAME’,null,100,TRUE,‘FOR ALL COLUMNS SIZE AUTO’);

查询锁阻塞

–已经执行超过2s的活动SQL

select * from (
SELECT sess_id,sql_text,datediff(ss,last_recv_time,sysdate) Y_EXETIME,
       SF_GET_SESSION_SQL(SESS_ID) fullsql,clnt_ip
 FROM V$SESSIONS WHERE STATE='ACTIVE')
 where Y_EXETIME>=2;

–锁查询

select o.name,l.* from v$lock l,sysobjects o where l.table_id=o.id and blocked=1;

–阻塞查询

with locks as(
  select o.name,l.*,s.sess_id,s.sql_text,s.clnt_ip,s.last_send_time  from v$lock l,sysobjects o,v$sessions s
  where l.table_id=o.id and l.trx_id=s.trx_id ),
 lock_tr as (   select trx_id wt_trxid,row_idx blk_trxid from locks where blocked=1),
 res as(    select sysdate stattime,t1.name,t1.sess_id wt_sessid,s.wt_trxid,
     t2.sess_id blk_sessid,s.blk_trxid,t2.clnt_ip,SF_GET_SESSION_SQL(t1.sess_id) fulsql,
     datediff(ss,t1.last_send_time,sysdate) ss,t1.sql_text wt_sql  from lock_tr s,locks t1,locks t2
    where t1.ltype='OBJECT'  and t1.table_id<>0   and t2.ltype='OBJECT'  and t2.table_id<>0
     and s.wt_trxid=t1.trx_id  and s.blk_trxid=t2.trx_id)
select distinct wt_sql,clnt_ip,ss,wt_trxid,blk_trxid  from res;

经常用到的两个查询被锁住对象和解锁脚本:
–查看表上的锁是哪个会话导致的

select a.*,b.NAME,c.SESS_ID from v$lock a
left join sysobjects b on b.ID=a.TABLE_ID
left join v$sessions c on a.TRX_ID=c.TRX_ID;

–kill对应的session
sp_close_session(sess_id);

实例中已执行未提交的 SQL 查询

SELECT t1.sql_text, t1.state, t1.trx_id
  FROM v$sessions t1, v$trx t2
 WHERE t1.trx_id = t2.id AND t1.state = 'IDLE' AND t2.status = 'ACTIVE';

有事务未提交的表查询

SELECT b.object_name, c.sess_id, a.*
  FROM v$lock a, dba_objects b, v$sessions c
 WHERE a.table_id = b.object_id AND ltype = 'OBJECT' AND a.trx_id = c.trx_id;

长时间的 SQL 查询

SELECT t1.sql_text, t1.state, t1.trx_id
  FROM v$sessions t1, v$trx t2
 WHERE t1.trx_id = t2.id AND t1.state = 'IDLE' AND t2.status = 'ACTIVE';

作业状态

select * from sysjob.sysjobs;
select * from sysjob.sysjobhistories;
select * from sysjob.sysjobschedules;
select * from sysjob.sysjobsteps;

数据库实例主要通过接口发送的 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 v$lock 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,导致发生内存写溢出、越界、空指针操作等引起的异常,这种情况一般是由于某些语句引起。我们可以进行如下处理:

通过 dmrdc 工具对异常生成的 core 文件进行信息读取,读取后会得到一个数据文件,输出文件的格式为[线程号] SQL 语句内容。
通过 gdb 工具打开异常时生成的 core 文件。
通过 info thread 命令,找到出现异常的线程,线程对应的 LWP 后跟的数字即为异常线程号。
通过 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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值