89652511 2016-12-20
平时工作的时候,经常会遇到这种事情,从一个大表A中,抽取字段a在一个相对较小B的表的数据,比如,从一个详单表中,抽取几万个用户号码的话单出来。这种时候,一般来说,
做关联查询:
create table A1 as select A.* from详单表 A,号码表 B wherea.号码 = b.号码
当然这个语句根据情况不一样有很多中写法,但是,不管是哪种写法,这个语句最大的问题是,如果数据量大,你非常纠结他到底能不能跑完,有时候一个事情,要求2个小时干完,现在跑了1个小时,你说到底要不要结束它另外想办法呢?
所以,这种时候,还是让事情可控一点比较好,首先,还是要考虑a字段的特征,如果a字段并集很大,A有上亿,a字段有几百万,这种时候,我们会给a字段建上索引,比如我要抽取1万个号码的详单,这样写其实就很好:
for x in (select号码 from Bwhere B.状态="未处理") loop
insert into A1 select * from A where a.号码=x.号码 ;
update B set B.状态=已处理 where B.号码=x.号码 ;
commit ;
end loop;
这样的好处,第一你可以看到执行了多少,进度可控,第二,你随时可以停了他继续。下次启动时,前面做完的他就不会继续做了。
但是,当a字段的并集,不是那么大,而是比较小的时候,比如只有十万的时候, 就不怎么舒服了,因为这种时候索引就没那么管用了。当然你还是可以按照上面的方式那样去做,只是会很慢很慢。所以,这个时候考虑完全做一次全表扫描,实际比索引字段查询多次还要快(为了贴近实际,我把这个字段叫做文件名跟上面的号码区分):
for x in (select rowid,文件名 from A)
select count(1) into cc from B where B.文件名=x.文件名 ;
if cc >= 1 then
insert into A1 select * from A where A.rowid=x.rowid;
commit ;
end if ;
end loop;
上面语句中,其实这么写是偷懒的做法,因为要插一条记录去新表A1中,理想的办法当然是在游标中读取所有字段,然后直接insert到A1中,用insert into A1 values这种,如果写成insert into A1 select * from A where A.rowid=x. rowid会增加IO,实际上,rowid定位一行数据是极快的方法(不管你表有多大,都是一样的效率),这种写法根本不会比insert into A1 values慢多少。
再次但是,这种场景下,其实是容易出问题,问题出在这个数据量很大,且记录不怎么连续的时候,当记录不怎么连续时,游标fentch到越后面,就越慢。一个亿的记录,没准在几百万的时候就卡住了。出现问题的原因,还是因为记录不够连续,导致寻址变慢,解决的办法,就是重建这个表(或者对这个表做表分析)。
再次再次但是,上亿的表,做表分析也好,还是重建,都不容易,可能重建个几个小时还是建不出来,而且浪费空间。所以,上面那个是想偷懒少些字段的逻辑,就变成了必然,我们考虑重建表的时候,只需要两个字段:
create table A1的映射 nolloggingas select rowid as rrowid ,文件名 from A ;
for x in (select rrowid,文件名 from A1的映射)
select count(1) into cc from B where B.文件名=x.文件名 ;
if cc >= 1 then
insert into A1 select * from A where A.rowid=x.rrowid;
commit ;
end if ;
end loop;
重建表的时候,只用两个字段,可以大大减少重建的时间(create table其实是获取的数据量越大越慢,还不是线性的,是几何增长的)。然后游标使用新的重建表,读取rowid字段(已经改为了rrowid),在根据获取到的rowid去实际表中获取数据。
要不怎么说,这个世界是懒人推动的呢?