体系学习让SQL语句性能提升千倍
未优化前,单车速度
drop table t purge;
create table t(x int);
create or replace procedure proc1
as
begin
for i in 1..100000
loop
execute immediate
'insert into t values ('||i||')';
commit;
end loop;
end;
/
/*这里要记得预先执行一遍,将过程创建起来!*/
SQL> conn scott/tiger@PDB1
Connected.
SQL>
SQL> drop table t purge;
Table dropped.
SQL> create table t(x int);
Table created.
SQL> conn system/system@PDB1;
Connected.
SQL> alter system flush shared_pool;
System altered.
SQL> conn scott/tiger@PDB1
Connected.
SQL> set timing on
SQL> exec proc1;
PL/SQL procedure successfully completed.
Elapsed: 00:01:08.95
SQL> select count(*) from t;
COUNT(*)
----------
100000
Elapsed: 00:00:00.05
SQL>
首次实验68.95秒完成,仅是单车速度。
共享池中缓存下来的SQL语句以及hash出来的唯一值,都可以在v$sql中对应的sql_text和sql_id字段中查询到,而解析的次数和执行的次数分别可以从PARSE_CALLS和EXECUTIONS字段中获取。
由于这个过程proc1执行的是insert into t 的系列插入,于是我们执行如下语句来查询proc1在数据库共享池中执行的情况,具体如脚本,原来是因为未用绑定变量:
SQL> conn system/system@PDB1;
Connected.
SQL>
SQL> set pagesize 200
SQL> set linesize 200
SQL> select
2 t.sql_text,t.sql_id,t.PARSE_CALLS,t.EXECUTIONS
3 from v$sql t
4 where sql_text like '%insert into t values%';
SQL_TEXT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID PARSE_CALLS EXECUTIONS
--------------------------------------- ----------- ----------
insert into t values (99995)
gqyu6ma5xw3py 1 1
insert into t values (99989)
2fn9gs19m43uc 1 1
insert into t values (99979)
23cu38akgwkv5 1 1
insert into t values (99994)
2vnmk62v3nmqa 1 1
insert into t values (99985)
dk8z68ns2nn0v 1 1
select t.sql_text,t.sql_id,t.PARSE_CALLS,t.EXECUTIONS from v$sql t where sql_text like '%insert into t values%'
frxh5v32hsrvn 2 2
insert into t values (99990)
441jb9gwj8vx1 1 1
insert into t values (99978)
2tq0v2xmqnwp6 1 1
insert into t values (99983)
f8tf9zkgx9153 1 1
insert into t values (99992)
cdg9816w1dbq9 1 1
insert into t values (99973)
8suj6myp2xhgf 1 1
insert into t values (99984)
brk4w33ad5mpr 1 1
insert into t values (99982)
fd7ubwctyduc9 1 1
insert into t values (100000)
g65kz1a5y1z3g 1 1
insert into t values (99980)
9z1r38t72jzb7 1 1
insert into t values (99987)
b05m5tng0a0p2 1 1
insert into t values (99975)
0tucc5wuwu5ag 1 1
insert into t values (99977)
aq8w8yyuxb0xx 1 1
insert into t values (99986)
bxncx5tr7m345 1 1
insert into t values (99999)
7t5qy2x3sr3jd 1 1
insert into t values (99998)
78pya7qqqg63x 1 1
insert into t values (99988)
0vjsduj05g684 1 1
insert into t values (99991)
6z00wthhvg87h 1 1
insert into t values (99974)
bda0639gcrcus 1 1
insert into t values (99997)
fcc6yrvnp7jqt 1 1
insert into t values (99996)
15jhq0cxsvkrh 1 1
insert into t values (99976)
bc9hd40jp3rrj 1 1
insert into t values (99981)
dtr9su4cnzsxh 1 1
insert into t values (99993)
8hbu4zqu2zxw5 1 1
29 rows selected.
Elapsed: 00:00:00.00
SQL>
发现共享池中有大量的类似的SQL语句,而sql_id各自不同,每条语句都只解析1次,执行1次,解析了10万次,怪不得速度如此之慢。
2.绑定变量,摩托速度
第2次改进,将proc1改造成有绑定变量的proc2
create or replace procedure proc2
as
begin
for i in 1..100000
loop
execute immediate
'insert into t values (:x)' using i;
commit;
end loop;
end;
/
/*这里要记得预先执行一遍,将过程创建起来!*/
接下来我们继续测试proc2过程(注意,重建表的目的是为了公平,测试都在无记录的空表上进行,并且共享池都清空.
第2次改进后7秒完成,单车变摩托车
SQL>
SQL> conn scott/tiger@PDB1
Connected.
SQL> drop table t purge;
Table dropped.
Elapsed: 00:00:00.27
SQL> create table t(x int);
Table created.
Elapsed: 00:00:00.03
SQL>
SQL> conn system/system@PDB1;
Connected.
SQL> alter system flush shared_pool;
System altered.
Elapsed: 00:00:00.12
SQL> conn scott/tiger@PDB1
Connected.
SQL> set pagesize 200
SQL> set linesize 200
SQL> set timing on
SQL> exec proc2;
PL/SQL procedure successfully completed.
Elapsed: 00:00:07.03
SQL> select count(*) from t;
COUNT(*)
----------
100000
Elapsed: 00:00:00.01
SQL>
解析与执行次数
虽然插入的语句值各不相同,但是都被绑定为:x,所以被hash成唯一一个hash值,名称为bbz6pdq577unj,很明显可以看出解析1次,执行10万次,这就是速度大幅度提升的原因了。
3.静态改写,汽车速度
execute immediate 是一种动态SQL写法,常用于表名和字段名是变量、入参的情况,由于表名都不知道,当然不能直接写SQL语句了,所以要靠动态SQL语句根据传入的表名参数来拼成一条SQL语句,由execute immediate调用执行。但这里显然不需要多此一举,因为insert into t values(i)完全可以满足需求,表名就是t。
第3此改进,将proc2改造成静态SQL的proc3
create or replace procedure proc3
as
begin
for i in 1..100000
loop
insert into t values (i);
commit;
end loop;
end;
/
/*这里要记得预先执行一遍,将过程创建起来!*/
第3次改进后3秒完成,摩托变汽车
[oracle@oracle-db-19c ~]$ sqlplus / as sysdba
SQL*Plus: Release 19.0.0.0.0 - Production on Tue Jan 3 13:19:12 2023
Version 19.3.0.0.0
Copyright (c) 1982, 2019, Oracle. All rights reserved.
Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0
SQL> set pagesize 200
SQL> set linesize 200
SQL> alter session set container=PDB1;
Session altered.
SQL> conn scott/tiger@PDB1;
Connected.
SQL>
SQL> drop table t purge;
Table dropped.
SQL> create table t(x int);
Table created.
SQL>
SQL> conn system/system@PDB1;
Connected.
SQL> alter system flush shared_pool;
System altered.
SQL>
SQL> conn scott/tiger@PDB1;
Connected.
SQL> set pagesize 200
SQL> set linesize 200
SQL> set timing on
SQL> exec proc3;
PL/SQL procedure successfully completed.
Elapsed: 00:00:03.83
SQL> select count(*) from t;
COUNT(*)
----------
100000
Elapsed: 00:00:00.03
SQL>
静态SQL解析与执行次数
select t.sql_text, t.sql_id, t.PARSE_CALLS, t.EXECUTIONS
from v$sql t
where upper(sql_text) like '%INSERT INTO T VALUES%';
果然如此,proc3也实现了绑定变量,而且动态SQL的特点是执行过程中再解析,而静态SQL的特点是编译的过程就解析好了。这点差别就是速度再度提升的原因。
4.批量提交,动车速度
commit触发LGWR将Redo Buffer写出到Redo Log中,并且将回滚段的活动事务标记为不活动,同时在回滚段中记录对应前镜像记录的所在位置,并标记为可以重写,切记commit可不是写数据的动作哦,写数据将数据从Data Buffer刷出磁盘是由CKPT决定的。
第4次改进,将proc3改造成提交在循环外的proc4
create or replace procedure proc4
as
begin
for i in 1..100000
loop
insert into t values (i);
end loop;
commit;
end;
/
/*这里要记得预先执行一遍,将过程创建起来!*/
第4次改进后1秒完成,汽车变动车
[oracle@oracle-db-19c ~]$ sqlplus / as sysdba
SQL*Plus: Release 19.0.0.0.0 - Production on Tue Jan 3 13:34:43 2023
Version 19.3.0.0.0
Copyright (c) 1982, 2019, Oracle. All rights reserved.
Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0
SQL> alter session set container=PDB1;
Session altered.
SQL> conn scott/tiger@PDB1;
Connected.
SQL> set pagesize 200
SQL> set linesize 200
SQL>
SQL> drop table t purge;
Table dropped.
SQL> create table t (x int);
Table created.
SQL>
SQL> conn system/system@PDB1;
Connected.
SQL> alter system flush shared_pool;
System altered.
SQL> conn scott/tiger@PDB1;
Connected.
SQL> set pagesize 200
SQL> set linesize 200
SQL> set timing on
SQL> exec proc4;
PL/SQL procedure successfully completed.
Elapsed: 00:00:01.57
SQL> select count(*) from t;
COUNT(*)
----------
100000
Elapsed: 00:00:00.00
SQL>
5.集合写法,飞机速度
将1到10万记录依次插入到t表中,完全满足刚才的需求,不过这种写法大家可能略感陌生,结果却是对的。
insert into t select rownum from dual connect by level<=100000;
第5次用集合写法后0.25秒完成,动车变飞机。
[oracle@oracle-db-19c ~]$ sqlplus / as sysdba
SQL*Plus: Release 19.0.0.0.0 - Production on Tue Jan 3 13:41:05 2023
Version 19.3.0.0.0
Copyright (c) 1982, 2019, Oracle. All rights reserved.
Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0
SQL> alter session set container=PDB1;
Session altered.
SQL> conn scott/tiger@PDB1;
Connected.
SQL> set pagesize 200
SQL> set linesize 200
SQL> drop table t purge;
Table dropped.
SQL> create table t (x int);
Table created.
SQL> conn system/system@PDB1;
Connected.
SQL> alter system flush shared_pool;
System altered.
SQL> conn scott/tiger@PDB1;
Connected.
SQL> set pagesize 200
SQL> set linesize 200
SQL> set timing on
SQL> insert into t select rownum from dual connect by level<=100000;
100000 rows created.
Elapsed: 00:00:00.25
SQL> commit;
Commit complete.
Elapsed: 00:00:00.00
SQL> select count(*) from t;
COUNT(*)
----------
100000
Elapsed: 00:00:00.01
SQL>
6.直接路径,火箭速度
实验准备,将集合写法的实验数据量放大100倍
7.并行设置,飞船速度
首先请大家自行从 Oracle 官方网站下载 CONCEPT 文档,并尽量在一周的时间内阅读完毕(可先不阅读其中指向的细节部分,那是自己选择性阅读的内容)