前言:
唯一约束索引自动过滤数据,隐藏重复数据,一度颠覆了本人对于SQL的基础认知,且看我一一道来。
事件说明:
本人写了个存过 专门记录ORACLE慢SQL,采用AWR的
SQL ordered by Gets
SQL逻辑,将所有的TOP-30 SQL 插入 JK_SLOW_SQL_YH表,然后将该表数据处理插入 JK_SLOW_MAIN表,对外展示。
首先,清空 JK_SLOW_MAIN表,插入数据:
可以看到插入了8757条数据,提交后单独查询,发现问题:
如上图所示,确认所有内容都一样,但是distinct都不能完全去重。
介绍下JK_SLOW_MAIN表结构:
约束索引:
老司机们已经知道问题了,重复数据,但是有唯一约束的情况下 怎么可能会有重复数据呢,我们尝试统计下去重后的数据量 :
数据量出现重大误差,第一个灵异事件出现了!我明明插入8757条数据,为什么会出现10619个数据呢,难道有什么奇葩空值?
让我们指定全表扫看下:
什么!居然又变了。。19315了。。这一瞬间,我顿时感觉怀疑人生。
收拾心情,让我们开始看执行计划,看看oracle到底做了什么:
这里为了省事,直接F5(在此栽了跟头):
一模一样的执行计划,结果还能不一样?看看rowid是不是一样,哪些数据是不是假的:
好吧,这里反复测试纠结了我大半个小时,也是看F5快速优化SQL错误习惯,虽然也知道F5的执行计划经常错,但是大部分SQL参考下就能快速优化了,,非高难度SQL不上SQLPLUS优化,初学者可别学我,~!
进入正题,直接查看SQLPLUS 真实执行计划:
可以看到,确实是走了索引的 ,那么问题来了,这个唯一索引为什么会过滤数据呢?因为我使用了hint 【ignore_row_on_dupkey_index】插入数据,不匹配该唯一约束 PK_JK_SLOW_MAIN的不应该进来才是,怎可能插入的时候连带生成了别的数据一起插入进去还不显示,这时候 我们需要查看下10046了。
生成该插入SQL的10046事件对应的trc文件,使用tkprof格式化后,查看SQL运行情况:
这里我们显示插入了8757条数据,实际想插入的数据(未过滤)总计有13293条,然而在运行时,受到唯一约束影响,在进行了hash group 以后 生成了 15668条数据 过滤后实际插入8757,但是不对啊 我都按照唯一约束字段做的group by ,怎么会重复呢?
定睛一看,group by的字段改了! 我的约束字段:
,两者不相同,所以导致隐藏重复数据也插入进去了,
(此时过去了1小时,数据发生新增)让我们改下逻辑:
可以看到 总体数据量完全保持一致了。
再次执行开始的SQL,发现异常重复的SQL已经不存在了:
问题虽然解决了. 那么 为什么会产生前面种种诡异现象呢?
让我们来一一分析:
<1> distinct为什么去重失败,有时候不重复有时候重复:
让我们随便找个重复的SQL_ID,用真实执行计划去分析:
写明部分字段,再次尝试:
很明显看出来,表里实际数据是有重复的,那么当SQL使用到唯一约束对应的索引时,基于SORT UNIQUE的特性,会自动将重复的数据去重。
那么什么时候会用到这个索引呢?
CBO估算COST足够低 且谓词/前置字段大部分包含索引列的时候。
所以这就是我之前发现部分数据 有的重复 有的不重复的问题。
<2> 显示插入与实际插入数据不一致数据如何进来的?
这里我们就要说下 hint 【ignore_row_on_dupkey_index】,这是ORACLE 11G推出的新特性,官方说明:
The IGNORE_ROW_ON_DUPKEY_INDEX hint applies only to single-table INSERT operations. It is not supported for UPDATE, DELETE, MERGE, or multitable insert operations. IGNORE_ROW_ON_DUPKEY_INDEX causes the statement to ignore a unique key violation for a specified set of columns or for a specified index. When a unique key violation is encountered, a row-level rollback occurs and execution resumes with the next input row. If you specify this hint when inserting data with DML error logging enabled, then the unique key violation is not logged and does not cause statement termination.
这里很明确告知,针对具有唯一性约束的键,在INSERT单表的语句中加入该hint可以让Oracle静默地(silently)忽略那些具有重复键值的插入行,所以这里显示插入了8757,实际插入2W+,但是那部分数据属于重复数据,因此不在插入结果中显示(也是个小坑,需要注意唯一索引跟插入SQL的匹配字段)。