前言
前几天上午在对数据库的一张表进行操作的时候, 由于这张表是按照时间的一张统计表, 正好到那天没有测试数据了, 于是我想将表中所有的时间, 统一更新到后一个月, 于是对 80w 条数据的更新开始了. 整个过程曲折的一批. 同时学到了很多知识, 在此进行记录. 希望对大家有帮助.
首先是大批量更新, 由于数据已经进行了分区, 开始对分区进行分析, 然后大批量操作死锁, 对死锁的解决, 最后存储过程来解决数据的大批量插入.
曲折的过程开始
由于测试数据到 21 号就没了, 21 号一上去, 发现开发的功能, 都没有数据了, 图表也都空了. 查询原因发现测试数据没了. 于是打算开始造数据. 此时数据库已经有 80 多 w 的数据, 当时想着将所有数据的 collect_time 时间字段向后推迟一个月, 即可. 当时也没想优化问题. 于是写下 sql.
update i_people_collect set collect_time = collect_time+30
此 sql 将表的所有时间向后推迟一个月. 于是开始执行.
此时报错: ORA-14402: 更新分区关键字列将导致分区的更改.
于是发现此表的 collect_time 列进行了分区处理.
我们可以先开启表的行移动来允许对分区字段的 update 操作. sql 如下
alter table xxx enable row movement;
之后再执行 update 发现可以执行, 执行完毕后, 记得关闭行移动.
alter table xxx disable row movement;
回到刚才我们执行 update 语句, 预计会慢, 但是发现执行了 20 分钟还没有结束. 于是怀疑报错了. 就强行终止. 但是此时终止也不好使了.. 大概是占用资源太多, 不好释放.
于是强行关掉 pl/sql. 重新登录. 这里我们先分析一下, 执行 update 操作为什么会这么慢.
分区表某一行更新时, 如果更新的是分区列, 并且更新后的列值不属于原来的这个分区, 如果开启了这个选项, 就会把这行从这个分区中 delete 掉, 并加到更 新后所属的分区. 相当于一个隐式的 delete+insert, 但是不会触发 insert/delete 触发器. 如果没有开启这个选项, 就会在更新时报错 ORA-14402;
这一操作产生影响的特殊之处在于这是个 DML 操作, 是和 online transaction 密切相关. 对于这样一个 UPDATE, 实际上分为 3 步: 先从原有分区将数据删除; 将原数据转移到新分区上; 更新数据.
其影响就在于以下几个方面:
一 个 UPDATE 被分解为 DELET,INSERT,UPDATE 三个操作, 增加了性能负担. 其中, DELETE 的查询条件与原 UPDATE 的查询条件相 同, 新的 UPDATE 的查询条件是基于 INSERT 生成的新的 ROWID, 相应的 Redo Log,Undo Log 会增加;
如果 Update 语句还涉及到了 Local Index 的字段的话, 新, 旧 2 个分区上的 Local Index 都要被更新.
由于我们更新的是 collect_time 列. collect_time 列又正好是分区列. 于是就产生了上面的这种情况. 造成执行速度十分的缓慢.
原因分析完毕. 继续说接下来发生的问题.
重新连接到 PL/Sql 后, 对刚才的表进行查询, 发现一直执行 sql, 并不返回结果. 于是考虑刚才的 sql 还在执行的问题.
通过 pl/sql 的工具, 会话, 发现刚才的会话仍然存在, 没有断开连接. 这就坑爹了啊. 通过会话来对连接强制结束. 发现还是不能操作刚才的表. 于是考虑了一下, 可能是表发生了死锁.
于是执行查询哪些表产生了死锁的 sql, 如下
select b.owner,b.object_name,a.session_id,a.locked_mode from v$locked_object a,dba_objects b where b.object_id = a.object_id;
通过结果发现, 刚才的表果然已经被锁定了.
继续向下看是哪个用户的哪个进程造成的死锁
-- 查看那个用户那个进程照成死锁
select b.username,b.sid,b.serial#,logon_time from v$locked_object a,v$session b where a.session_id = b.sid order by b.logon_time;
-- 查看连接的进程
SELECT sid, serial#, username, oSUSEr FROM v$session;
-- 查出锁定表的 sid, serial#,os_user_name, machine_name, terminal, 锁的 type,mode
- SELECT s.sid, s.serial#, s.username, s.schemaname, s.osuser, s.process, s.machine,
- s.terminal, s.logon_time, l.type
- FROM v$session s, v$lock l
- WHERE s.sid = l.sid
- AND s.username IS NOT NULL
- ORDER BY sid;
此时通过这些查询, 我们已经能够定位是哪个进程导致了锁表的产生. 同时获取到了进程的 sid 以及 serial.
执行中断进程的 sql,
alter system kill session'210,11562';
讲道理, 此时已经进行了进程的结束, 但是发现表还是在锁着的. 于是可能是查看一下造成死锁的这一进程的状态.
select saddr, sid, serial#, paddr, username, status, machine from v$session where username is not null
通过 status 发现锁定的进程的状态已经改变为 KILLED, 这种状态可能导致长时间的未释放资源, PMON 并没有对其进行清除, 等了很久仍然是锁表状态.
于是可能需要操作系统级别的对进程进行清除.
我们查询出会话进程在操作系统中的进程 id.
select a.spid,b.sid,b.serial#,b.username,b.status from v$process a,v$session b where a.addr=b.paddr ;
我们进入 Linux 后台. 通过 kill -9 spid, 此时执行后, 发现表已经解锁了, 死锁结束. 呼~ 不容易.
接下来问题又来了, 我们如何继续更新数据呢. 最终决定实用存储过程来进行增加数据.
create or replace procedure aaa(startdate in date, days in number) as
-- 生成的数据包含 startdate 当天
- i number;
- begin
- i := 0;
- while i < days loop
- insert into aaa1
- select sec_pkid.nextval,startdate + i,
- '字段名','字段名','字段名','字段名'
- from aaa2 t where collect_time = to_date('2018-11-09','yyyy-mm-dd');
- i := i+1;
- commit;
- end loop;
- end aaa;
来源: http://www.linuxidc.com/Linux/2019-01/156490.htm