一、场景
有个离奇的场景,Oracle数据库有两张千万级数据量的表A、B。A和B的对应关系为一对多,从A表中删除主键ID不在B表中的数据。
二、SQL优化
-
原删除语句
DELETE FROM A A WHERE A.ID NOT IN ( SELECT T.A_ID FROM B T WHERE T.FLAG = '1' );
-
调整两种删除语句,对比执行计划
-
第一种
由于B表中的存储外键,所以有重复的情况,所以这里对not in里面的值的数据量进行去重,减少后约有63000多外键。
DELETE FROM A A WHERE A.ID NOT IN ( SELECT DISTINCT T.A_ID FROM B T WHERE T.FLAG = '1' AND T.A_ID IS NOT NULL )
-
第二种
not in意味着每条数据都要进行6万多次的对比,这里使用not exists,每条数据进行一次子查询判断是否应该被删除。
DELETE FROM A A WHERE NOT EXISTS ( SELECT 1 FROM B T WHERE T.FLAG = '1' AND T.A_ID = A.ID )
但是很可惜,B表中的外键上并没有索引(且外力不允许在这个表上建索引),这样从执行计划上看上面两种语句的效果是一样的。第一种每条数据多次循环判断,第二种每条数据多次IO读取。在测试删除的过程中都会卡死。。。。
-
-
对sql语句进行了再优化
既然没有索引可用,那就用视图吧缓存一下,想要精确定位到要删除的数据,所以将sql改为这样了
DELETE FROM A T WHERE T.ID = ( SELECT A.ID FROM A A LEFT JOIN B B ON A.ID = B.A_ID AND B.FLAG = '1' WHERE B.A_ID IS NULL AND A.ID = T.ID )
更改后的sql,从执行计划上看已经有非常大的改观了。
可是实际跑起来就是无尽的等待。。。没法了给后面加上
AND ROWNUM < 1000000
后,基本上两分钟内会删除结束,赶紧commit。
三、换个思路
直接上SQL:
CREATE TABLE A_BAK AS
SELECT A.* FROM A A JOIN B B ON A.ID = B.A_ID AND B.FLAG = '1'
这么多数据要删除,这么难搞,还是重新建表吧。这样A、B两个表只需要全表扫描一次,拿到所有不需要删除的数据放到一张新表中。