Oracle开发者性能课第8课(如何更快地进行插入更新和删除)实验
Posted dingdingfish
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Oracle开发者性能课第8课(如何更快地进行插入更新和删除)实验相关的知识,希望对你有一定的参考价值。
概述
本实验参考DevGym中的实验指南。
创建环境
创建表和过程,其中我加了注释:
-- 创建PL/SQL包,用于计时。
create or replace package timing_pkg as
start_time pls_integer;
time_taken pls_integer;
procedure set_start_time;
procedure calc_runtime (
operation varchar2
);
end;
/
create or replace package body timing_pkg as
procedure set_start_time as
begin
start_time := dbms_utility.get_time;
end;
procedure calc_runtime (
operation varchar2
) as
begin
time_taken :=
( dbms_utility.get_time - start_time );
dbms_output.put_line ( operation || ' ' || time_taken || ' hundredths of a second' );
end;
end;
/
-- 测试表bricks
create table bricks (
brick_id integer
not null
primary key,
colour varchar2(10),
shape varchar2(10),
weight integer
)
;
-- 下面这段代码不知何用。执不执行都无所谓。
begin
insert into bricks values ( 1, 'red', 'cylinder', 1 );
insert into bricks values ( 2, 'blue', 'cube', 1 );
insert into bricks values ( 3, 'green', 'cube', 1 );
delete bricks;
commit;
end;
/
truncate table bricks;
-- 此过程用于插入指定行数的记录到bricks表,插入前会清空表
create or replace procedure ins_rows ( num_rows int ) as
begin
execute immediate 'truncate table bricks';
dbms_random.seed ( 0 );
insert into bricks
with rws as (
select level x from dual
connect by level <= num_rows
)
select rownum,
case mod ( rownum, 3 )
when 0 then 'red'
when 1 then 'blue'
when 2 then 'green'
end,
case mod ( rownum, 2 )
when 0 then 'cube'
when 1 then 'cylinder'
end,
round ( dbms_random.value ( 2, 10 ) )
from rws;
end ins_rows;
/
查看数据,目前表为空:
SQL> select * from bricks;
no rows selected
获取DML的执行计划
DML语句其实也是包括SELECT的,无论插入、删除还是更新,都是使用DBMS_XPlan获取执行计划:
-- 插入单条,不会用到索引
insert /*+ gather_plan_statistics */ into bricks values ( 0, 'red', 'cylinder', 1 );
select * from table(dbms_xplan.display_cursor(format => 'iosTATS LAST'));
PLAN_TABLE_OUTPUT
________________________________________________________________________________________________
SQL_ID 81b27pds9qjrw, child number 0
-------------------------------------
insert /*+ gather_plan_statistics */ into bricks values ( 0, 'red',
'cylinder', 1 )
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | A-Rows | A-Time | Buffers | Reads |
---------------------------------------------------------------------------------------------
| 0 | INSERT STATEMENT | | 1 | 0 |00:00:00.01 | 41 | 8 |
| 1 | LOAD TABLE CONVENTIONAL | BRICKS | 1 | 0 |00:00:00.01 | 41 | 8 |
---------------------------------------------------------------------------------------------
Note
-----
- cpu costing is off (consider enabling it)
17 rows selected.
-- CTAS,批量插入,不会用到索引
insert /*+ gather_plan_statistics */ into bricks
select level, 'red', 'cylinder', 1
from dual
connect by level <= 100;
select * from table(dbms_xplan.display_cursor(format => 'IOSTATS LAST'));
PLAN_TABLE_OUTPUT
_____________________________________________________________________________________________________
SQL_ID fpmz0m5yrx4kj, child number 0
-------------------------------------
insert /*+ gather_plan_statistics */ into bricks select level,
'red', 'cylinder', 1 from dual connect by level <= 100
Plan hash value: 1236776825
--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------------------
| 0 | INSERT STATEMENT | | 1 | | 0 |00:00:00.01 | 5 |
| 1 | LOAD TABLE CONVENTIONAL | BRICKS | 1 | | 0 |00:00:00.01 | 5 |
| 2 | CONNECT BY WITHOUT FILTERING| | 1 | | 100 |00:00:00.01 | 0 |
| 3 | FAST DUAL | | 1 | 1 | 1 |00:00:00.01 | 0 |
--------------------------------------------------------------------------------------------------
16 rows selected.
-- 更新单条,用到了索引。因为表有主键
update /*+ gather_plan_statistics */ bricks
set shape = 'cube'
where brick_id = 1;
select * from table(dbms_xplan.display_cursor(format => 'IOSTATS LAST'));
PLAN_TABLE_OUTPUT
_______________________________________________________________________________________________
SQL_ID 0zbg60ms26www, child number 0
-------------------------------------
update /*+ gather_plan_statistics */ bricks set shape = 'cube'
where brick_id = 1
Plan hash value: 1792883719
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
--------------------------------------------------------------------------------------------
| 0 | UPDATE STATEMENT | | 1 | | 0 |00:00:00.01 | 2 |
| 1 | UPDATE | BRICKS | 1 | | 0 |00:00:00.01 | 2 |
|* 2 | INDEX UNIQUE SCAN| SYS_C008657 | 1 | 1 | 1 |00:00:00.01 | 1 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("BRICK_ID"=1)
20 rows selected.
-- 批量删除,用到了索引。因为表有主键
delete /*+ gather_plan_statistics */ bricks where brick_id <= 10;
select * from table(dbms_xplan.display_cursor(format => 'IOSTATS LAST'));
PLAN_TABLE_OUTPUT
______________________________________________________________________________________________
SQL_ID 2sdbmwz2j55y0, child number 0
-------------------------------------
delete /*+ gather_plan_statistics */ bricks where brick_id <= 10
Plan hash value: 1389320603
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------------
| 0 | DELETE STATEMENT | | 1 | | 0 |00:00:00.01 | 14 |
| 1 | DELETE | BRICKS | 1 | | 0 |00:00:00.01 | 14 |
|* 2 | INDEX RANGE SCAN| SYS_C008657 | 1 | 11 | 11 |00:00:00.01 | 1 |
-------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("BRICK_ID"<=10)
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
23 rows selected.
检查副作用
影响写的因素包括:
- 触发器
- 阻塞锁
触发器最好少用,但不是说不能用。否则发明它干嘛。
查看定义的触发器:
select * from user_triggers;
与 SELECTS 不同,其他会话中未提交的更改可以阻止写入完成。常见原因有:
- 插入的主键或唯一键发生冲突
- 另一个会话更新或删除相同的行
很难完全避免这个问题。例如,如果两个用户需要同时更新相同的行,则第二个用户必须等到第一个用户结束他们的事务。为尽量减少其影响,请确保尽快提交或回滚事务(也就是小事务)。如果这些问题仍然存在,您可能需要重新设计处理数据更改的方式。
单行插入与批量插入
以下为单行插入或逐行插入,是应该尽量避免的:
begin
delete bricks;
for i in 1 .. 10000 loop
insert into bricks
values (
i,
case mod ( i, 3 )
when 0 then 'red'
when 1 then 'blue'
when 2 then 'green'
end,
case mod ( i, 2 )
when 0 then 'cube'
when 1 then 'cylinder'
end,
round ( dbms_random.value ( 2, 10 ) )
);
end loop;
end;
/
尽管单个插入的执行速度都很快,但这些加起使整个过程变慢。如果 SQL 来自中间层应用程序,这尤其成问题,因为每一行往返数据库的时间很长。
如果在上面循环中加上提交(逐行提交),整个过程会更慢!
可以使用批量处理FOR ALL加快此过程。例如:
delete bricks;
select count(*) from bricks;
declare
type bricks_rec is record (
brick_id integer, colour varchar2(10),
shape varchar2(10), weight integer
);
type bricks_array is table of bricks_rec
index by pls_integer;
brick_values bricks_array;
begin
for i in 1 .. 10000 loop
brick_values ( i ) := bricks_rec (
brick_id => i + 20000,
colour => case mod ( i, 3 )
when 0 then 'red'
when 1 then 'blue'
when 2 then 'green'
end,
shape => case mod ( i, 2 )
when 0 then 'cube'
when 1 then 'cylinder'
end,
weight => round ( dbms_random.value ( 2, 10 ) )
);
end loop;
forall rws in 1 .. brick_values.count
insert into bricks
values brick_values ( rws );
end;
/
-- 下面的commit是我加的
commit;
select count(*) from bricks;
FORALL 看起来和 FOR 循环类似。但与循环不同的是,它只处理内部的 DML 语句一次(一次性插入一个数组)。
单行插入与批量插入性能比较
原文代码如下:
declare
num_rows pls_integer := 100000;
type bricks_rec is record (
brick_id integer, colour varchar2(10),
shape varchar2(10), weight integer
);
type bricks_array is table of bricks_rec
index by pls_integer;
brick_values bricks_array;
begin
delete bricks;
commit;
timing_pkg.set_start_time;
for i in 1 .. num_rows loop
insert into bricks
values (
i,
case mod ( i, 3 )
when 0 then 'red'
when 1 then 'blue'
when 2 then 'green'
end,
case mod ( i, 2 )
when 0 then 'cube'
when 1 then 'cylinder'
end,
round ( dbms_random.value ( 2, 10 ) )
);
end loop;
timing_pkg.calc_runtime ( 'Insert-loop' );
rollback;
timing_pkg.set_start_time;
for i in 1 .. num_rows loop
brick_values ( i ) := bricks_rec (
brick_id => i,
colour => case mod ( i, 3 )
when 0 then 'red'
when 1 then 'blue'
when 2 then 'green'
end,
shape => case mod ( i, 2 )
when 0 then 'cube'
when 1 then 'cylinder'
end,
weight => round ( dbms_random.value ( 2, 10 ) )
);
end loop;
forall rws in 1 .. brick_values.count
insert into bricks
values brick_values ( rws );
timing_pkg.calc_runtime ( 'Insert-forall' );
end;
/
要产生输出,需要设置:
set serveroutput on
比较结果如下,批量插入快了近10倍。其中hundredths of a second表示计时单位为1/100秒:
Insert-loop 231 hundredths of a second
Insert-forall 25 hundredths of a second
许多单行插入 vs. 一个多行插入
可以使用 INSERT 将行从一个表复制到另一个表。此代码使用游标将 10,000行从一个表加载到另一个表:
begin
delete bricks;
for rw in (
with rws as (
select level x from dual
connect by level <= 10000
)
select rownum id,
case mod ( rownum, 3 )
when 0 then 'red'
when 1 then 'blue'
when 2 then 'green'
end colour,
case mod ( rownum, 2 )
when 0 then 'cube'
when 1 then 'cylinder'
end shape,
round ( dbms_random.value ( 2, 10 ) ) weight
from rws
) loop
insert into bricks
values (
rw.id,
rw.colour,
rw.shape,
rw.weight
);
end loop;
end;
/
以下为更快的INSERT-AS-SELECT方法,一次性添加所有 10,000 行:
delete bricks;
insert into bricks
with rws as (
select level x from dual
connect by level <= 10000
)
select rownum,
case mod ( rownum, 3 )
when 0 then 'red'
when 1 then 'blue'
when 2 then 'green'
end,
case mod ( rownum, 2 )
when 0 then 'cube'
when 1 then 'cylinder'
end,
round ( dbms_random.value ( 2, 10 ) )
from rws;
当然,之前的FORALL INSERT方法仍然可用:
declare
type bricks_rec is record (
brick_id integer, colour varchar2(10),
shape varchar2(10), weight integer
);
type bricks_array is table of bricks_rec
index by pls_integer;
brick_values bricks_array;
num_rows pls_integer := 10000;
begin
delete bricks;
with rws as (
select level x from dual
connect by level <= num_rows
)
select rownum,
case mod ( rownum, 3 )
when 0 then 'red'
when 1 then 'blue'
when 2 then 'green'
end,
case mod ( rownum, 2 )
when 0 then以上是关于Oracle开发者性能课第8课(如何更快地进行插入更新和删除)实验的主要内容,如果未能解决你的问题,请参考以下文章