三种批量删除PLSQL写法效率的比对

我们有一个重要的旧系统,最近夜维出现了一些问题,夜间执行5小时未完成,为了不影响业务,只能早上高峰期之前,DBA手工kill夜维进程。

这一个夜维程序采用了PLSQL写的存储过程,通过数据库job定时启动执行。存储过程我很少使用,借着这次机会,补习了下,这个存储过程中的逻辑比较简单,依次删除若干张业务表,每张表删除的逻辑相同,为了便于说明,模拟了下删除一张表的逻辑,示例如下,
TBL_CUSS表三个字段,第一个字段是NUMBER类型,第二个字段是VARCHAR2类型,第三个字段是DATE类型,

这个存储过程接受一个参数,表示删除几天前的数据,删除DELETE语句按照一个时间字段和这个参数比较得出符合条件的记录集,同时使用rownum限制每次执行DELETE-COMMIT事务的条数,循环执行,直至出现ORA-1403无记录的提示,退出此逻辑。这个存储过程最优的地方,是使用了批量提交,不是执行一次DELETE删除所有记录COMMIT,如果这么执行,可能会占用大量的UNDO回滚段,进而可能出现回滚段空间不足的报错,也可能出现ORA-1555的错误。毕竟UNDO中记录的是SQL语句的逆向,对于DELETE语句,逆向就是INSERT,即会存储删除的整条记录。

可能有朋友一眼就看出这个存储过程的逻辑有一些问题,比如对于这种批量删除,未使用游标,相当于每次要检索tbl_cuss表符合insert_time < trunc(SYSDATE)-:1条件的记录,可每次仅删除其中的rownum限制的条数,如果使用游标,检索只需要一次执行,不考虑是否有索引,执行语句次数的降低,可以带来性能的一定提升。

针对此问题,写了第二个存储过程,

接受删除天数的参数,使用了游标,执行一次SELECT,读取出的则是符合insert_time < trunc(SYSDATE)-:1条件的所有结果集记录的rowid信息,遍历游标的时候使用BULK批量的方式,设置了一次性执行的条数限制MAX_ROW_SIZE,并且删除语句是根据上面游标获取的rowid为条件进行的DELETE,如果各位了解rowid,可以知道他代表了这条记录的物理位置,通过换算可以得出这条记录存储于的文件、块和行上,即可以快速定位这条记录的物理位置,在RBO模式下,他的成本优先级是最优的,高于索引。

继续写了第三个存储过程,

这和第二个存储过程,基本一致,唯一不同就是第二个存储过程中使用了for循环,第三个存储过程则用forall循环。for循环会执行其中的每条SQL语句,forall则会将其中所有SQL批量发送SQL引擎执行。当然可能有其他的写法,比如使用游标,但不使用BULK,按照rowid删除,这种写法执行SQL语句的次数和结果集数据量一致,效率可能还不如原始procedure。

从原理上说,使用BULK比单条语句执行,减少PLSQL和SQL引擎之间的切换频率,也可以减少redo和undo的产生量。针对循环内执行的DELETE,适合于使用集合,放入forall。

这篇文章的实验说明了forall的一些适用场景,
http://blog.csdn.net/renfengjun/article/details/8334733#

delete和insert都可以从forall上面得到巨大的性能提升。但是对于update来说opcode没有相关操作,提升应该不会那么明显。

接下来我们会对这三个存储过程进行一些比对实验,通过一些数据,说明各自的适用场景,首先创建测试数据集,制造了1300万测试数据,

每天50万数据,一共26天,

第一个存储过程名称为clear_without_fetch。
第二个存储过程名称为clear_all_fetch。
第三个存储过程名称为clear_fetch。

实验有以下几类,(以下执行基本采用第二次执行的结果避免第一次刷缓存)
(1) 一次性删除1万条记录,insert_time不是索引,删除两天的数据(即100万),
clear_without_fetch用时01:16.31
clear_all_fetch用时00:40.50
clear_fetch用时00:21.73
clear_fetch胜出,clear_without_fetch最慢,说明TABLE ACCESS FULL下的SQL语句,一次性删除1万条记录,使用游标和BULK效率要高些,使用forall比for效率要高些。

(2) 一次性删除5万条记录,insert_time不是索引,删除两天的数据(即100万),
clear_without_fetch用时00:26.98
clear_all_fetch用时00:39.80
clear_fetch用时00:22.24
clear_fetch胜出,但一次性删除5万条记录,TABLE ACCESS FULL下的SQL语句由于执行次数减小为原来的5倍,效率上有提升,clear_all_fetch基本一致,是因为无论什么方式,其执行SQL语句的次数,和结果集一致,即100万次SQL。

(3) 一次性删除100万条记录,insert_time不是索引,删除两天的数据(即100万),
clear_without_fetch用时00:21.92
clear_all_fetch用时01:22.00
clear_fetch用时00:25.95
clear_without_fetch胜出,TABLE ACCESS FULL下的SQL语句执行一次,clear_fetch虽然仍是批量一次发送SQL,性能上的优势不很明显。

(4) 一次性删除1万条记录,insert_time是索引,删除两天的数据(即100万),
clear_without_fetch用时00:31.01
clear_all_fetch用时01:09.02
clear_fetch用时00:37.39
clear_without_fetch胜出,索引扫描执行效率提升,相比TABLE ACCESS FULL要明显一些。

(5) 一次性删除5万条记录,insert_time是索引,删除两天的数据(即100万),
clear_without_fetch用时00:35.35
clear_all_fetch用时01:03.26
clear_fetch用时00:37.40
clear_without_fetch胜出,clear_all_fetch和clear_fetch基本保持和1万一致。

(6) 一次性删除100万条记录,insert_time是索引,删除两天的数据(即100万),
clear_without_fetch用时00:23.33
clear_all_fetch用时01:27.80
clear_fetch用时00:33.68
clear_without_fetch胜出,相比1万和5万,效率提升一些,我理解主要是SQL执行次数从100次(1万)->20次(5万)->1次(100万)。

上面的实验中,数据接近的未必是绝对,和环境因素(例如机器配置、数据质量等)可能有关,因此大体方向上可以参考,不同次的实验可能会略有不同,但方向应该比较接近,毕竟原理摆着。

如下是上面六个实验,三个存储过程SQL,各自执行的10046的trace,从中可以看出一些端倪,

clear_without_fetch存储过程各场景trace,
(1) 一次性删除1万条记录,insert_time不是索引,删除两天的数据(即100万),

可以看出由于使用了绑定变量,解析一次,由于循环逻辑的问题,执行了100+1次。

(2) 一次性删除5万条记录,insert_time不是索引,删除两天的数据(即100万),

(3) 一次性删除100万条记录,insert_time不是索引,删除两天的数据(即100万),

从elapsed以及query可以比较(1)-(3)。

(4) 一次性删除1万条记录,insert_time是索引,删除两天的数据(即100万),

(5) 一次性删除5万条记录,insert_time是索引,删除两天的数据(即100万),

(6) 一次性删除100万条记录,insert_time是索引,删除两天的数据(即100万),

由于需要维护索引,相比TABLE ACCESS FULL,会有些消耗。

clear_all_fetch存储过程各场景trace,
(1) 一次性删除1万条记录,insert_time不是索引,删除两天的数据(即100万),

(2) 一次性删除5万条记录,insert_time不是索引,删除两天的数据(即100万),

(3) 一次性删除100万条记录,insert_time不是索引,删除两天的数据(即100万),


无论(1)、(2)、(3),DELETE语句均执行了100万次,唯一的区别就是SELECT语句和执行的次数。

(4) 一次性删除1万条记录,insert_time是索引,删除两天的数据(即100万),

(5) 一次性删除5万条记录,insert_time是索引,删除两天的数据(即100万),

(6) 一次性删除100万条记录,insert_time是索引,删除两天的数据(即100万),


有索引和无索引相比,有一些需要维护索引的消耗。

clear_fetch存储过程各场景trace,
(SELECT和clear_all_fetch存储过程相近,此处忽略)
(1) 一次性删除1万条记录,insert_time不是索引,删除两天的数据(即100万),

(2) 一次性删除5万条记录,insert_time不是索引,删除两天的数据(即100万),

(3) 一次性删除100万条记录,insert_time不是索引,删除两天的数据(即100万),

(4) 一次性删除1万条记录,insert_time是索引,删除两天的数据(即100万),

(5) 一次性删除5万条记录,insert_time是索引,删除两天的数据(即100万),

(6) 一次性删除100万条记录,insert_time是索引,删除两天的数据(即100万),

可以看见clear_fetch和clear_all_fetch唯一区别就是DELETE语句执行次数,clear_fetch中执行次数和循环次数一样,说明是批量发送的,单条DELETE相同,但执行次数的不同,影响了资源消耗和执行时间。

从实验中可以得出的结论,
(1) SQL使用TABLE ACCESS FULL的执行计划,若SQL执行次数较多时,则BULK+forall的方式,效率较高;若SQL执行次数较少时,很可能使用TABLE ACCESS FULL的执行计划的SQL,效率和BULK+forall接近,甚至有更优的可能。
(2) SQL使用INDEX RANGE SCAN的执行计划,效率会比BULK+forall略高,若SQL执行次数较少时,使用INDEX RANGE SCAN的执行计划的SQL,效率较高;SQL执行次数对于BULK+forall的方式基本一致。
(3) 无论是否用索引,BULK+forall的方式均优于BULK+for。可以使用索引,则用游标和不用游标,效率比较接近,从实验上看,不用游标反而可能略高一些,这和使用游标需要一些解析类的消耗可能有关,但游标可以带来便捷性,比如方便控制结果集,可以更灵活地编辑逻辑,既然效率比较接近,若时间均是可接受范围内,可以根据实际来考虑,选择什么方式。无论什么方式,大表数据的批量删除,这是首要原则。

时间: 2024-07-28 17:52:36

三种批量删除PLSQL写法效率的比对的相关文章

mysql三种批量增加的性能分析_Mysql

下面把代码写出来,希望大家批评指正. 首先domain对象.在这里使用的注解的方式,都是比较新的版本. User.java 复制代码 代码如下: package com.bao.sample.s3h4.domain; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Generatio

sql下三种批量插入数据的方法_MsSql

本文将介绍三种批量插入数据的方法.第一种方法是使用循环语句逐个将数据项插入到数据库中:第二种方法使用的是SqlBulkCopy,使您可以用其他源的数据有效批量加载 SQL Server 表:第三种使用的方法是sql server中的表值参数方法,表值参数是 SQL Server 2008 中的新参数类型.表值参数是使用用户定义的表类型来声明的.使用表值参数,可以不必创建临时表或许多参数,即可向 Transact-SQL 语句或例程(如存储过程或函数)发送多行数据.       代码示例:    

三种php删除文件代码

三种php删除文件代码  代码如下 复制代码 if($od=opendir($d))   //$d是目录名 {         while(($file=readdir($od))!==false)  //www.111cn.net 读取目录内文件         {         unlink($file);  //$file是文件名         } } 去了解下这三个php函数:opendir()  readdir()  unlink() 文件删除代码二 系统调用法    代码如下 复

sql下三种批量插入数据的方法

本文将介绍三种批量插入数据的方法.第一种方法是使用循环语句逐个将数据项插入到数据库中:第二种方法使用的是SqlBulkCopy,使您可以用其他源的数据有效批量加载 SQL Server 表:第三种使用的方法是sql server中的表值参数方法,表值参数是 SQL Server 2008 中的新参数类型.表值参数是使用用户定义的表类型来声明的.使用表值参数,可以不必创建临时表或许多参数,即可向 Transact-SQL 语句或例程(如存储过程或函数)发送多行数据. 代码示例: 此例子为控制台输出

一种批量删除数据的方法

这两天碰见一个比较紧急的生产问题,由于还在处理中,所以暂时不能给出整体描述,但其中涉及的一个问题就是删除一张大表中的过期历史数据,针对不同的类型的表可能有不同的解决方法,比如若是按照时间做的分区表,drop partition删除分区的操作可能是效率最快的.最简单的,若是一张普通表则需要有一些索引键值为删除条件,但需要注意的是最好做批量删除,且一次删除量不要太多,因为delete操作会将数据前镜像保存在UNDO回滚表空间,由于占用过多.事务过大.执行时间过长.UNDO空间过小等一系列问题存在,就

三种php删除指定目录下所有文件与目录

 代码如下 复制代码 function DeltreeDir($dir)  { $dir = realpath($dir);         if (!$dir || !@is_dir($dir))                 return 0;         $handle = @opendir($dir);         if ($dir[strlen($dir) - 1] != DIRECTORY_SEPARATOR)                 $dir .= DIRECTO

LinkedList和List在三种简单算法中效率比较

.Net 框架提供了两种List类型,一种是基于链表的LinkedList, 一种是基于数组的List.那么在实际应用中到底采用哪种List,如何取舍呢?本文对两种类型在队列,堆栈和简单插入三种简单算法中的效率进行了一个比较. 首先先让我们来看一下List的初始容量Capacity对List的性能是否有影响. 测试方法:分别设置初始容量为0,64,255,1024. List插入的最大长度为1000,循环1000次,得到如下结果,单位为ms,下同. 算法/初始容量 0 64 255 1024 队

linux中批量删除git分支的方法

使用 git 时候,经常会发现,不知不觉就创建了大量的分支.那么,麻烦事就来了,如此多废弃的分支,该怎么办呢? 总不能一个一个执行 git branch -D branchName 删除吧! 下面就给大家提供一种批量删除分支的方法: git branch |grep 'branchName' |xargs git branch -D 这是通过 shell 管道命令来实现的批量删除分支的功能 git branch 输出当前分支列表 grep 是对 git branch 的输出结果进行匹配,匹配值当

struts2批量删除示例

问题描述 需要一个struts2批量删除示例,在批量删除块不是很懂 解决方案 解决方案二:1.查询的时候把每条数据的ID绑定在CheckBox上:2.选择删除--利用JS对选中的CheckBox对象获取,然后把数据的ID从CheckBox的属性上获取:3.把ID利用","号分隔,然后提交至后台:4.后台获取到这个","号连接的字符串做出split(",")分隔处理,获取一组ID数组:5.循环根据ID编号删除数据.解决方案三:批量删除不是数据库端的