merge语句导致的CPU使用率过高的优化(二) (r7笔记第9天)
之前分享过一篇关于merge语句导致的CPU使用率过高优化的案例。http://blog.itpub.net/23718752/viewspace-1819471/ 后续的跟进没有补充,也“秀”一张图,红色的火焰是原来的系统负载,右边的部分是最近的逻辑读情况,不过惭愧的是,这个不是优化的效果,因为应用的高峰期 已经处理完了,后面的sql调用频率极低,所以感觉不到任何的压力。所以通过这个图也可以看出,给一张差别巨大的图也不一定是系统优化的效果,也可能是其 它外在因素。
那么既然要说跟进,后面的情节才够真实和现实,开发同学找到语句,修改花了些时间,今天突然联系到我,说已经修改完成了。我也从v$sql中抓取了几条语句,发现执行计划已经改变。
感觉这件事情就要告一段落,但是开发的同事过了一会找到我说,他们在应用端发现日志中出现了ORA-00001的错误。
### Cause: java.sql.SQLException: ORA-00001: unique constraint (AXXXX.OPENPLATFORM_USER) violated
这个问题着实在意料之外,他们反馈出现问题后,立即回退了代码,但是日志保留了下来,让我看看是什么问题。找开发要绑定变量的值,貌似还比较困难,那就算了,自己分析吧。
比如还是简单模拟这个错误。
CREATE TABLE TEST(ID NUMBER,NAME VARCHAR(100));
ALTER TABLE TEST MODIFY(ID UNIQUE);
INSERT INTO TEST VALUES(100,'BB_NOT_MATCHED');
INSERT INTO TEST VALUES(1000,'BBB_NOT_MATCHED');
SQL> select*from test;
ID NAME
---------- --------------------
1000 BB_NOT_MATCHED
100 BBB_NOT_MATCHED
我们已经插入了两条值,这个时候来尝试一下,update是否生效
SQL> MERGE INTO TEST t
USING (SELECT ID from TEST where ID=1000 union select -1 id from dual ) tw
ON (tw.ID=T.ID)
WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED'
where ID= 1000
WHEN NOT MATCHED THEN INSERT(ID, NAME) VALUES(1000,'BBB_NOT_MATCHED') ;
MERGE INTO TEST t
*
ERROR at line 1:
ORA-00001: unique constraint (TEST.SYS_C0011234) violated
这个错误还是有些奇怪,本来预计的update变成了insert,结果还违反了唯一性约束。
来看看最初始版本的执行情况。
SQL> MERGE INTO TEST t
USING (SELECT count(*) CNT from TEST where ID=1000 ) tw
ON (tw.CNT>0)
WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED' where ID=1000
WHEN NOT MATCHED THEN INSERT(ID,NAME) VALUES(1000,'BBB_NOT_MATCHED');
1 row merged.
发现确实是做了update,把id=1000的行,name列修改成了AAA_MATCHED
SQL> select *from test;
ID NAME
---------- --------------------
1000 AAA_MATCHED
100 BBB_NOT_MATCHED
手工把数据改回来,继续测试。
SQL> update test set name='BBB_NOT_MATCHED' where id=1000;
1 row updated.
SQL> select *from test;
ID NAME
---------- --------------------
1000 BBB_NOT_MATCHED
100 BBB_NOT_MATCHED
这个时候开发的同学突然给我反馈说,他们看如果把union all的字句取消就不报错了。原来他们也在debug,我告诉他们,不报错不代表没错,还是需要搞明白最根本的原因。
MERGE INTO TEST t
USING (SELECT ID from TEST where ID=1000 ) tw
ON (tw.ID=T.ID)
WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED'
where ID= 1000
WHEN NOT MATCHED THEN INSERT(ID, NAME) VALUES(1000,'BBB_NOT_MATCHED') ;
SQL> select *from test;
ID NAME
---------- --------------------
1000 AAA_MATCHED
100 BBB_NOT_MATCHED
这个时候update确实能够正常执行,似乎也是预期的结果,那么做一条insert,看看效果。
MERGE INTO TEST t
USING (SELECT ID from TEST where ID=2000 ) tw
ON (tw.ID=T.ID)
WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED'
where ID= 2000
WHEN NOT MATCHED THEN INSERT(ID, NAME) VALUES(2000,'BBB_NOT_MATCHED') ;
0 rows merged.
可以看到,id=2000的行没有插入数据。这个我觉得也就是为什么开发的同学没有选用这个方法的根本原因。但是似乎他们没有找到更好的方法,
那么继续改进,就是我上次分享的,加入union all的部分。
MERGE INTO TEST t
USING (SELECT ID from TEST where ID=2000 union all select -999 from dual ) tw
ON (tw.ID=T.ID)
WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED'
where ID= 2000
WHEN NOT MATCHED THEN INSERT(ID, NAME) VALUES(2000,'BBB_NOT_MATCHED') ;
1 row merged.
SQL> select *from test;
ID NAME
---------- --------------------
1000 AAA_MATCHED
100 BBB_NOT_MATCHED
2000 BBB_NOT_MATCHED
这个时候问题来了,insert可以了,但是update有问题了。
SQL> MERGE INTO TEST t
2 USING (SELECT ID from TEST where ID=2000 union all select -999 from dual ) tw
3 ON (tw.ID=T.ID )
4 WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED'
5 where ID= 2000
6 WHEN NOT MATCHED THEN INSERT(ID, NAME) VALUES(2000,'BBB_NOT_MATCHED') ;
MERGE INTO TEST t
*
ERROR at line 1:
ORA-00001: unique constraint (TEST.SYS_C0011234) violated
如果你看晕了,我来整理一下思路。
###加入union all的方案
不存在id=2000 可以insert
已存在id=2000 update 报ora-00001
### 去掉union all子句
不存在id=2000 可以插入
已存在id=2000 update无法执行
那么来一个动态的条件,可以不?
SQL> MERGE INTO TEST t
USING (SELECT ID from TEST where ID=2000 union all select -999 from dual) tw
ON (tw.ID=T.ID or tw.id=-999)
WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED'
where ID= 2000
WHEN NOT MATCHED THEN INSERT(ID, NAME) VALUES(2000,'BBB_NOT_MATCHED') ;
MERGE INTO TEST t
*
ERROR at line 1:
ORA-30926: unable to get a stable set of rows in the source tables
所以这些思路都不同,但是根据id来决定Inert,update也算一个常规问题,吃完晚饭继续琢磨,总算找到了一个合适的方法。
SQL> MERGE INTO TEST t
USING (SELECT 2000 id FROM dual ) tw
ON (tw.ID=T.ID )
WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED'
where ID= 2000
WHEN NOT MATCHED THEN INSERT(ID, NAME) VALUES(2000,'BBB_NOT_MATCHED') ;
1 row merged.
这种情况下条件是唯一性匹配的,匹配与否就很清晰了。
当前数据情况如下:
SQL> select *from test;
ID NAME
---------- --------------------
1000 AAA_MATCHED
100 BBB_NOT_MATCHED
2000 AAA_MATCHED
插入一条新数据
SQL> MERGE INTO TEST t
USING (SELECT 3000 id FROM dual ) tw
ON (tw.ID=T.ID )
WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED'
where ID= 3000
WHEN NOT MATCHED THEN INSERT(ID, NAME) VALUES(3000,'BBB_NOT_MATCHED') ;
1 row merged.
SQL> select *from test where id=3000;
ID NAME
---------- --------------------
3000 BBB_NOT_MATCHED
更新一条记录
SQL> MERGE INTO TEST t
USING (SELECT 3000 id FROM dual ) tw
ON (tw.ID=T.ID )
WHEN MATCHED THEN UPDATE SET t.NAME='AAA_MATCHED'
where ID= 3000
WHEN NOT MATCHED THEN INSERT(ID, NAME) VALUES(3000,'BBB_NOT_MATCHED') ;
SQL> select *from test where id=3000;
ID NAME
---------- --------------------
3000 AAA_MATCHED
改进后的执行计划如下:
Execution Plan
----------------------------------------------------------
Plan hash value: 3869333021
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | MERGE STATEMENT | | 1 | 78 | 3 (0)| 00:00:01 |
| 1 | MERGE | TEST | | | | |
| 2 | VIEW | | | | | |
| 3 | NESTED LOOPS OUTER | | 1 | 79 | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS FULL | DUAL | 1 | 2 | 2 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| TEST | 1 | 77 | 1 (0)| 00:00:01 |
|* 6 | INDEX UNIQUE SCAN | SYS_C0011234 | 1 | | 0 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
6 - access("T"."ID"(+)=CASE WHEN (ROWID IS NOT NULL) THEN 3000 ELSE 3000 END )
1 row merged.
所以正式环境的语句也是类似的思路。
MERGE INTO UC_OPENPLATFORM_USER t
USING (SELECT :1 USER_ID,:2 PLATFORM from DUAL ) tw
ON (tw.USER_ID=T.USER_ID and tw.PLATFORM=t.PLATFORM)
WHEN MATCHED THEN UPDATE SET t.NAME=:3, t.UPDATE_DATE=SYSDATE
where USER_ID=:4 and PLATFORM=:5
WHEN NOT MATCHED THEN INSERT(USER_ID, PLATFORM, NAME,
CREATE_DATE, UPDATE_DATE) VALUES(:6, :7, :8, SYSDATE, SYSDATE)
改动之后还是需要再部署测试,相信大体就没有问题了。
通过这个案例可以发现,很多优化的时候从执行计划等情况确实有了很大的提升,一些瓶颈也得到了解决,但是还是要更周密的测试,别修复了一个错,引来更多的问题。而且sql上线也要评估,进行验收测试。尽可能把问题都解决在沟通层面,不用那么多的邮件来标注,说明。
- Elasticsearch Javascript API增删改查
- Oracle二三事之 Oracle SPARC SuperCluster的九大技术优势
- 两个 viewports 的故事-第二部分
- 通过 JS 实现简单的拖拽功能并且可以在特定元素上禁止拖拽
- AngularJS 技术总结
- 《linux c编程指南》学习手记5
- AngularJS API之bootstrap启动
- 通过 JS 判断页面是否有滚动条的简单方法
- Log4j官方文档翻译(六、日志的级别)
- AngularJS API之isXXX()
- 《linux c编程指南》学习手记4
- Kibana中doc与search策略的区别
- jQuery 图片查看插件 Magnify 开发简介(仿 Windows 照片查看器)
- Log4j官方文档翻译(五、日志输出的方法)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 关于权限的一些想法
- AB153x API----captouch驱动程序
- Hacking with iOS: SwiftUI Edition - 愿望清单项目(二)
- C#设备处理类操作
- x509数字证书导入-然后删除自身
- winform总结4> 工欲善其事,必先利其器之xml校验
- 如何利用SNMP实现网络攻击缓解?
- winform总结5> winform程序开发注意事项
- mvc文件上传支持批量上传,拖拽以及预览,文件内容校验
- .net Core 1.0.1 下的Web框架的的搭建过程step by step
- 蓝牙芯片----BK34341开发笔记------快速入门(2)
- .net core版 文件上传/ 支持批量上传,拖拽以及预览,bootstrap fileinput上传文件
- 蓝牙芯片----BK3431开发笔记------基本外部驱动应用(3)
- .net core 利用中间件处理常见的网站功能 包括 session、routers、重定向、重写和文件下载
- 蓝牙芯片----BK3431开发笔记------RW stack中添加自定义服务教程(4)