当然是吓瘸了
Friday目前就职于一家外企IT公司,总部是在美国,Friday在中国。得益于时差的关系,东西两边的工作能进行24小时相对无缝的切换协作。Friday每天的日常就是早上到公司,先回复下对面美国同事的相关邮件,然后开始一天的开发任务,下班前将当天的进度和问题发回总部确认,如果有必要的话,下班后等美国的同事上班发起一轮电话会议。
今天,本又是个平平无奇的一天,Friday来到公司开始一天的日常。打开邮件,美国同事要求Friday帮忙删除一张表的某条索引,因为在此时中国的时间是业务每天的低峰时刻,几乎没有什么流量。“常规操作”,Friday心道。很快他便写好了删除的命令,打开sqldeveloper进行了一波操作,“唔...为什么删了这么久”,索引删除了,但是Friday感觉有点不对,这本是一张不大的表,理论上秒秒钟就可以操作完成了,仔细一看后,Friday心凉了半截 -- 他删错索引了!再仔细检查后,他意识到自己麻烦大了-- 这个误删的索引的表有近80亿条数据(难怪删除花了不少时间,按照一般的方式,重建肯定要花很久的时间。好在此时是业务低谷,否则肯定各种疯狂告警和被投诉了。
Friday决定先死马当活马医,立刻进行索引重建,但是情况并不乐观。通常情况下对oracle的表建立索引的时候并不需要考虑效率问题,这个通常情况指的是相应的表数据在百万级以下。一旦数据量大到千万级,亿级甚至更大的时候,就不能不考虑新建索引的效率问题,因为当表在建立索引的时候,会产生锁阻塞新数据的更新,如果索引不能很快地建立完毕,会对生产环境造成影响。而Friday现在面临的问题是,重建的速度太慢以致于从进度上看预计需要几十个小时才能完成,而再过8个小时就是美国那边的上班时间,业务的流量高峰也会相应到来,想到这里Friday感觉自己有点窒息了。“一定有办法的”,Friday定了定神开始想对策。
“1. 正在执行的重建索引继续让它执行,如果所有办法失效,这就是最后的保障。”
“2. 调查一下索引删除的影响范围,应该不至于整个系统不可用。” Friday迅速过了一下代码,果然这张表只在一个统计类的功能里用到了,不属于核心功能范围,即使暂时一段时间不可用应该也影响不大。(真的吗? 下文分析)
“3. 马上找优化重建索引效率的办法,争取在4小时内重建完成”
“4. 要不要报告下领导...算了先瞒着...不行了再说” (呵呵...)
计算机的世界是一套严密的逻辑体系,凡事都是有迹可循,因果关系贯穿始终。一旦掌握原理,就可以扮演上帝进行法则的干预。想要优化建索引的效率,首先看创建索引到底做了些什么操作。
- 把索引key的数据读到内存
如果数据没在db_cache 中,这时候很容易有大量的db file scatter read wait
- 对索引key的数据作排序
sort_area_size 或者pga_aggregate_target 不够大的情况下,需要做 disk sort, 会有大量的driect path read/write , 另外,消耗大量CPU Time
- 创建新的index segment ,把排过序的索引数据写到所创建的index segment 里面.
如果index 很大,有时会有redo log 相关等待
那么,只要对这几个方向进行相应的优化,就有可能加速整体的效率。经过一番研究,Friday得出结论: 增大pga,增大temp tablepace, 并且用nologging或并行选项。
救火方案有了,看着依旧在龟速重建的进度,Friday决定直接用新的方案进行尝试,毕竟不会更差了,而且也没有环境和时间进行模拟。还好这次幸运女神站在了Friday这边,优化后只用了20分钟就将索引重建完毕,一切又回到了正轨。
Friday这一天过得惊心动魄,他事后进行复盘觉得自己实在是犯了一个非常低级的错误,以致于差点就长跪不起,今后一定要仔细检查,避免再犯。
然而我们更深入地进行思考,事情其实并没有这么简单。
Friday 作为一个开发人员,为何对数据库有如此高的权限,以至于DBA都没有什么存在感
一个“危险”的DDL操作完全没有被审核的操作,DML那就更不用说了。
-
故障发生后,Friday并没有第一时间上报,而是主观凭自己的经验进行了评估,并企图自己修复掩盖失误,虽然最后的结果是成功了,但并不足取。原因有几:
- 由于经验、技术能力和视野所限,开发并不一定能正确评估风险。比如:Friday认为这个故障只是影响了部分小众功能,问题不大,但是实际上,只要有足够的流量,没有索引的慢查询是足以拖死整个系统的,因为慢查询会占用数据库的连接,不能及时释放,当连接数用完后,其他正常的功能就无法连接数据库,导致整体不可用。其二,Friday的视野只是局限在自己的代码上,也许上报leader后他会发现,这个表还被其他工程甚至其他团队共用,而这其中的风险并没有评估到
- 能独立解决问题是能力强的表现,但是面对线上故障,群策群力更有助于问题的解决。如果有专业DBA的帮助,可能会更快,更低风险地解决故障。而如果因为对自己能力的估计过于乐观进行冒失操作,可能会引起更大的风险和故障。
故障的发生有时候不是一人或是一事的得失,这是一个系统性的问题。一个良好的系统应该是可以规避风险,甚至自愈的。Friday 可能是个救火能手,但是对于系统上层建筑的机制完善此刻还没有足够的意识。
附 oracle大表加速索引建立的实操手册
1. PGA设置
hash_area_size: 这个参数控制每个会话的hash内存空间有多大。它也可以在会话级和实例级被修改。默认值是sort area空间大小的两倍 sort_area_size: 因为排序通常是在PGA中进行的,需要防止因空间或内存不足导致需要disk排序。
alter session set workarea_size_policy=manual;
alter session set hash_area_size=100000;
alter session set sort_area_size=2000000000; -- 在系统可用内存足够的情况下,最大可以到2G
question
- 什么是PGA
- 什么是SGA
2. 增加temp表空间
因为大表的数据量比较大,导致建索引时需要的temp表空间也比较大,一般来说接近索引的大小,没把握的情况下可以估算一下索引的大小先:
另外在建索引的过程中也可以随时监控表空间的使用情况,一旦发现temp表空间不够,可以随时加大
set serveroutput on
declare
v_ddl varchar(1024);
v_used_bytes number;
v_alloc_bytes number;
begin
dbms_space.create_index_cost(
ddl =>' create index ids_t on user(userid)',used_bytes=>v_used_bytes,alloc_bytes =>v_alloc_bytes);
dbms_output.put_line('used_bytes='||v_used_bytes||' bytes'||' alloc_bytes='|| v_alloc_bytes || ' bytes');
end;
/
3. 使用并行参数
关于利用并行度创建索引,前提是多个CPU,单CPU下用并行度创建索引,可能会造成资源的争用。理论上来说8个CPU, 可以用parallel 6 ,最多占用6个CPU,另外留下两个CPU供其他进程使用。 查看CPU核数的方法有很多, 最简单地就是用下面这个sql直接查
SELECT * FROM v$osstat where stat_name='NUM_CPUS';
4. 使用nologging
nologging, 绝对应该使用,能减少大量redo log,使速度大幅上升。
于是一个比较标准的并行nologging建索引语句就出炉了。对于生产环境,保险的办法是再加上online参数,避免建索引时的锁对dml产生阻塞。
CREATE INDEX table_idx ON table (col ) NOLOGGING PARALLEL 6;
Note
对于一个比较大的操作,oracle可能会有等待事件发生 首先可以通过sql developer查看等待时间的信息,得到等待时间的p1,p2,p3。然后通过下面的sql转换p1,p2得到具体等待的object。
select owner,segment_name,segment_typefrom dba_extentswhere file_id = &P1 and &P2 between block_id and block_id + blocks -1;
监控oracle 大事务的执行进度
set linesize 400;
set pagesize 400;
set long 4000;
col sql_fulltext format a100;
col opname format a15;
SELECT se.sid,
opname,
TRUNC (sofar / totalwork * 100, 2) pct_work,
elapsed_seconds elapsed,
ROUND (elapsed_seconds * (totalwork - sofar) / sofar) remain_time,
sql_fulltext
FROM v$session_longops sl, v$sqlarea sa, v$session se
WHERE sl.sql_hash_value = sa.hash_value
AND sl.sid = se.sid
AND sofar != totalwork
ORDER BY start_time;