一张6亿条数据表引发的事故

业务人员告诉我某系统磁盘IO持续高达300MB/s,系统平台为AIX,遂 topas 查看果然如此。

用下面脚本到Oracle数据库中看了一下:

SELECT Disk_Reads DiskReads, Executions, SQL_ID, SQL_Text SQLText,
   SQL_FullText SQLFullText
FROM
(
   SELECT Disk_Reads, Executions, SQL_ID, LTRIM(SQL_Text) SQL_Text,
      SQL_FullText, Operation, Options,
      Row_Number() OVER
         (Partition By sql_text ORDER BY Disk_Reads * Executions DESC)
         KeepHighSQL
   FROM
   (
       SELECT Avg(Disk_Reads) OVER (Partition By sql_text) Disk_Reads,
          Max(Executions) OVER (Partition By sql_text) Executions,
          t.SQL_ID, sql_text, sql_fulltext, p.operation,p.options
       FROM v$sql t, v$sql_plan p
       WHERE t.hash_value=p.hash_value AND p.operation='TABLE ACCESS'
       AND p.options='FULL' AND p.object_owner NOT IN ('SYS','SYSTEM')
       AND t.Executions > 1
   )
   ORDER BY DISK_READS * EXECUTIONS DESC
)
WHERE KeepHighSQL = 1
AND rownum <=5;

这里当时没有保存记录,总之跟后来AWR收集的是一样的sql,如下所示。

可以看到第一个sql的物理读非常高。遂看了一下执行计划

[email protected]>select * from table(dbms_xplan.display_cursor('54043712',null,'advanced'));

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
HASH_VALUE  54043712, child number 0
------------------------------------
SELECT D.MODEL_ID, D.OBJ_ID, D.OBJ_TYPE, D.DATA_TYPE, D.DATA_DATE, D.DATA_FROM_DATE, D.DATA_TO_DATE,
D.DATA_FLAG  FROM zbdba1 D, zbdba2 C  WHERE D.MODEL_ID = C.MODEL_ID AND C.COLLECT_ID =
:COLLECT_ID        AND D.DATA_DATE = :DATA_DATE AND D.DATA_TYPE = :DATA_TYPE AND D.VALUE_FLAG = 0

Plan hash value: 1780662521

-------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name                     | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                          |       |       |     9 (100)|          |       |       |
|   1 |  NESTED LOOPS                |                          |     1 |    61 |     9   (0)| 00:00:01 |       |       |
|   2 |   PARTITION RANGE SINGLE     |                          |     1 |    51 |     8   (0)| 00:00:01 |   KEY |   KEY |
|*  3 |    TABLE ACCESS FULL         | zbdba1 |     1 |    51 |     8   (0)| 00:00:01 |   KEY |   KEY |
|*  4 |   TABLE ACCESS BY INDEX ROWID| zbdba2             |     1 |    10 |     1   (0)| 00:00:01 |       |       |
|*  5 |    INDEX UNIQUE SCAN         | PK_zbdba2          |     1 |       |     0   (0)|          |       |       |
-------------------------------------------------------------------------------------------------------------------------

Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------

   1 - SEL$1
   3 - SEL$1 / [email protected]$1
   4 - SEL$1 / [email protected]$1
   5 - SEL$1 / [email protected]$1

Outline Data
-------------

  /*+
      BEGIN_OUTLINE_DATA
      IGNORE_OPTIM_EMBEDDED_HINTS
      OPTIMIZER_FEATURES_ENABLE('10.2.0.5')
      ALL_ROWS
      OUTLINE_LEAF(@"SEL$1")
      FULL(@"SEL$1" "D"@"SEL$1")
      INDEX_RS_ASC(@"SEL$1" "C"@"SEL$1" ("zbdba2"."MODEL_ID"))
      LEADING(@"SEL$1" "D"@"SEL$1" "C"@"SEL$1")
      USE_NL(@"SEL$1" "C"@"SEL$1")
      END_OUTLINE_DATA
  */

Peeked Binds (identified by position):
--------------------------------------

   1 - :COLLECT_ID (VARCHAR2(30), CSID=852): '70350'
   2 - :DATA_DATE (DATE): 06/01/15 00:00:00
   3 - :DATA_TYPE (VARCHAR2(30), CSID=852): '02'

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - filter(("D"."DATA_TYPE"=:DATA_TYPE AND "D"."DATA_DATE"=:DATA_DATE AND "D"."VALUE_FLAG"=0))
   4 - filter("C"."COLLECT_ID"=TO_NUMBER(:COLLECT_ID))
   5 - access("D"."MODEL_ID"="C"."MODEL_ID")

Column Projection Information (identified by operation id):
-----------------------------------------------------------

   1 - "D"."MODEL_ID"[NUMBER,22], "D"."OBJ_ID"[NUMBER,22], "D"."OBJ_TYPE"[VARCHAR2,2],
       "D"."DATA_TYPE"[VARCHAR2,2], "D"."DATA_DATE"[DATE,7], "D"."DATA_FROM_DATE"[DATE,7], "D"."DATA_TO_DATE"[DATE,7],
       "D"."DATA_FLAG"[NUMBER,22]
   2 - "D"."MODEL_ID"[NUMBER,22], "D"."OBJ_ID"[NUMBER,22], "D"."OBJ_TYPE"[VARCHAR2,2],
       "D"."DATA_TYPE"[VARCHAR2,2], "D"."DATA_DATE"[DATE,7], "D"."DATA_FROM_DATE"[DATE,7], "D"."DATA_TO_DATE"[DATE,7],
       "D"."DATA_FLAG"[NUMBER,22]
   3 - "D"."MODEL_ID"[NUMBER,22], "D"."OBJ_ID"[NUMBER,22], "D"."OBJ_TYPE"[VARCHAR2,2],
       "D"."DATA_TYPE"[VARCHAR2,2], "D"."DATA_DATE"[DATE,7], "D"."DATA_FROM_DATE"[DATE,7], "D"."DATA_TO_DATE"[DATE,7],
       "D"."DATA_FLAG"[NUMBER,22]
   5 - "C".ROWID[ROWID,10]

HASH_VALUE  54043712, child number 1

SELECT D.MODEL_ID, D.OBJ_ID, D.OBJ_TYPE, D.DATA_TYPE, D.DATA_DATE, D.DATA_FROM_DATE, D.DATA_TO_DATE,
D.DATA_FLAG  FROM zbdba1 D, zbdba2 C  WHERE D.MODEL_ID = C.MODEL_ID AND C.COLLECT_ID =
:COLLECT_ID        AND D.DATA_DATE = :DATA_DATE AND D.DATA_TYPE = :DATA_TYPE AND D.VALUE_FLAG = 0

NOTE: cannot fetch plan for HASH_VALUE: 54043712, CHILD_NUMBER: 1
      Please verify value of HASH_VALUE and CHILD_NUMBER;
      It could also be that the plan is no longer in cursor cache (check v$sql_plan)

81 rows selected.

查看表行数:

[email protected]>select num_rows,last_analyzed from dba_tables where table_name='zbdba1';

  NUM_ROWS LAST_ANALYZE
---------- ------------
659764063 31-JUL-15

[email protected]>select num_rows,last_analyzed from dba_tables where table_name='zbdba2';

  NUM_ROWS LAST_ANALYZE
---------- ------------
     76513 14-JUL-15
explan plan for 该sql查看执行计划:
[email protected]>select * from table(dbms_xplan.display());

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 2057366878

----------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                          | Name                        | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                   |                             |     3 |   183 |    11   (0)| 00:00:01 |       |       |
|   1 |  TABLE ACCESS BY GLOBAL INDEX ROWID| zbdba1    |     1 |    51 |     4   (0)| 00:00:01 | ROWID | ROWID |
|   2 |   NESTED LOOPS                     |                             |     3 |   183 |    11   (0)| 00:00:01 |       |       |
|   3 |    TABLE ACCESS BY INDEX ROWID     | zbdba2                |     2 |    20 |     4   (0)| 00:00:01 |       |       |
|*  4 |     INDEX RANGE SCAN               | IDX_zbdba2COLLECTFLAG |     2 |       |     2   (0)| 00:00:01 |       |       |
|*  5 |    INDEX RANGE SCAN                | IDX_C_COLLECT_MD_001        |     1 |       |     3   (0)| 00:00:01 |       |       |
----------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - access("C"."COLLECT_ID"=TO_NUMBER(:COLLECT_ID))
   5 - access("D"."MODEL_ID"="C"."MODEL_ID" AND "D"."DATA_DATE"=:DATA_DATE AND "D"."DATA_TYPE"=:DATA_TYPE AND
              "D"."VALUE_FLAG"=0)

19 rows selected.

发现走了索引,但是当时为什么没有走索引呢。并且在二节点走了全表扫描,一节点走的是索引范围扫描。

该oracle数据库的版本为10.2.0.5,在11g之前没有引入ACS(Adaptive Cursor Sharing),所以这里CBO在第一次进行硬解析的时候才会窥视变量的值,并且生成执行计划,之后一直使用相同的执行计划。

这里我猜想,在2节点。第一次使用绑定变量的时候,CBO认为应该使用全表扫描效率更高,所以在以后一直使用该执行计划。然而在一节点,第一次使用绑定变量的时候,CBO认为走范围扫描效率更高。所以这里导致1节点和2节点的执行计划不一样。

找到原因了,就好办了。业务人员怕数据库负载过大导致宕机,遂叫我把该sql的相关进程(发现30个进程)全部kill掉。kill完进程后磁盘IO瞬间降到50MB。

还没有完,改sql以后还会有这种选择。我们怎么去避免?

既然了解了CBO的做法,那就触发它再一次去执行一次硬解析获得正确的执行计划。有如下4种方法:

1、alter system flush shared_pool(想跪就跑这个)

2、对相关表做DDL操作

3、重新收集统计信息

4、dbms_shared_pool.purge

前面三种方案对生产系统都影响比较大,所以利用第四种方法。

SQL> select address,hash_value,executionsfrom v$sql where hash_value=54043712

ADDRESS          HASH_VALUE EXECUTIONS

---------------- ---------- ---------- -----------
0000040229F039E0 54043712          1           

SQL> alter session set events '5614566 trace name context forever';

SQL> exec dbms_shared_pool.purge('0000040229F039E0,54043712','C');

重新利用合适的绑定变量跑出正确的执行计划即可。

如何永久保持不变呢?

加hint,强制走索引。

explain plan for SELECT /*+ index(zbdba1,IDX_C_COLLECT_MD_001)* / D.MODEL_ID,
       D.OBJ_ID,
       D.OBJ_TYPE,
       D.DATA_TYPE,
       D.DATA_DATE,
       D.DATA_FROM_DATE,
       D.DATA_TO_DATE,
       D.DATA_FLAG
  FROM EIC2.zbdba1 D, EIC2.zbdba2 C
WHERE D.MODEL_ID = C.MODEL_ID
   AND C.COLLECT_ID = '70350'
   AND D.DATA_DATE = to_date('06/01/15','MM/DD/YY')
   AND D.DATA_TYPE = '02'
   AND D.VALUE_FLAG = 0;

如果以后该索引有变化,也将会失效。

当然优化没有一劳永逸的事情,针对大小超过50GB,数据量高达6亿条的表还是要定期检查它相关sql的执行计划。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-05 22:02:13

一张6亿条数据表引发的事故的相关文章

亿条数据在PHP中实现Mysql数据库分表100张

当数据量猛增的时候,大家都会选择库表散列等等方式去优化数据读写速度.笔者做了一个简单的尝试,1亿条数据,分100张表.具体实现过程如下: 首先创建100张表: 1 $i=0; 2 while($i<=99){ 3 echo "$newNumber \r\n"; 4 $sql="CREATE TABLE `code_".$i."` ( 5  `full_code` char(10) NOT NULL, 6  `create_time` int(10) 

oralce 超过1亿条数据的数据库表清理实践

2018-08-18 16:58 无腿鸟 阅读(331) 评论(0) 编辑 收藏 问题:当一个表的数据量超过一亿条,要删除其中的5000w条,如何处理. 如果直接使用delete语句,会涉及到到大量的磁盘IO,并产生大量的数据库日志,效率很低,删除速度慢,可能导致事务中断,甚至有服务器硬盘空间撑爆的可能. 本文提供的思路是先将数据表需要保留的数据不带索引导出,然后导入一个新表中 ,对新表重建索引后将老表.新表进行重命名,这样就完成了删除操作,效率有了很大提升. 主要分为三步,1.数据导出2. 数

1.3万亿条数据查询如何做到毫秒级响应?

关注微信公众号"程序员黄小斜",选择"置顶或者星标" 一起成为更好的自己! ![](https://img2018.cnblogs.com/blog/1813797/201912/1813797-20191230133159470-930879899.jpg) 作者:孙晓光 出处:http://itindex.net/ 知乎,在古典中文中意为"你知道吗?",它是中国的 Quora,一个问答网站,其中各种问题由用户社区创建,回答,编辑和组织. 作为

记录数过亿条的表数据维护-数据删除

当一张表数据很大的时候,由于数据删除的时候时间会很长,事务很大,所需的undo段将会比较大,未提交的话,undo段数据会受到保护,这将影响其它事务的操作-执行时间会变长或者挂起,所以删除大表数据的时候尽量将大事务切分成小事务去做,下面的存储过程是删除表数据时没10万行一提交. declare cursor cur is select rowid from tab1 where   xx<nnn order by rowid; type rowid_table_type is  table  of

mysql 造1亿条记录的单表--大数据表

读写文件 背景及木:现有数据1000w单表,为压力测试准备1亿条数据. 步骤: 1.将1000w条记录,除id外都导入到多个文件中: //DELIMITER DROP PROCEDURE if EXISTS createManyTable; create PROCEDURE createManyTable() BEGIN DECLARE i int; DECLARE fileName VARCHAR(30); set i=1; while i<251 DO SET fileName=CONCAT

MySQL 快速构造一亿条记录的表

  在上一次朋友问我如何快速构造一亿条记录的表后,我理出了实行的办法,见:http://blog.csdn.net/mchdba/article/details/52938114,但是因为录入一亿表需要2个小时,所以还是感觉速度慢了些,那有没有啥办法能加快这一步骤呢?   1.建一张通用的用户表 建用户表没有啥变化,还是和上次一样. USE test; CREATE TABLE `UC_USER` ( `ID` BIGINT (20), `USER_NAME` VARCHAR (400), `U

MySQL 如何准备一亿条记录的表来测试

曾经一个朋友问我如何快速的在线往一个大表里面添加一个字段或者修改一个字段的长度,mysql版本是5.6,所以就准备在测试环境准备一个一亿条记录的表,然后来实际测试下到底哪种方式比较快,先来开始准备一亿条记录的表.   我线上有上亿条记录的表,但是很多网上朋友都没有,那么我这里就实践了一条办法,来实现自己构造一亿条数据记录的表.实现思路就是先建一张通用的20个字段左右的用户表,然后写一个存储过程,不停的往这个表里面写数据,while循环写上一亿次,这样就形成了一张一亿条记录的表出来.     1.

【PPT&amp;视频】《陈新河:万亿元大数据产业新生态》——央视网大数据名人讲堂之大数据产业系列

[PPT&视频]<陈新河:万亿元大数据产业新生态>--央视网大数据名人讲堂之大数据产业系列 原创 2016-07-16 陈新河 软件定义世界(SDX) 热门下载(点击标题即可阅读) ?[下载]2015中国数据分析师行业峰会精彩PPT下载(共计21个文件) 因微信限制,部分图不能显示出来,高清完整版全文请扫描二维码,见每篇文章底部专栏 <陈新河:万亿元大数据产业新生态>--央视网大数据名人讲堂之大数据产业系列 嘉宾介绍 陈新河   中关村大数据产业联盟副秘书长 Talking

QTreeView处理大量数据(使用1000万条数据,每次都只是部分刷新)

如何使QTreeView快速显示1000万条数据,并且内存占用量少呢?这个问题困扰我很久,在网上找了好多相关资料,都没有找到合理的解决方案,今天在这里把我的解决方案提供给朋友们,供大家相互学习. 我开始使用的QTreeWidget 控件来显示我的数据,发现该控件在显示10000行以下的数据还可以应付的过来,但超过10000条,就明显感觉到屏幕刷新就会有卡的现象,而且占据内存很大,虽然操作起来简单方便,但灵活性没有QTreeView强大.因为我要显示的数据量是非常大的,甚至过1000万,因此,采用