博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Expert_PL_SQL_Practices-----Do Not Use
阅读量:5020 次
发布时间:2019-06-12

本文共 8869 字,大约阅读时间需要 29 分钟。

1、Row-by-Row Processing

1 DECLARE 2   CURSOR C1 IS 3     SELECT Prod_Id, Cust_Id, Time_Id, Amount_Sold 4       FROM Sales 5      WHERE Amount_Sold > 100; 6   C1_Rec            C1%ROWTYPE; 7   l_Cust_First_Name Customers.Cust_First_Name%TYPE; 8   l_Cust_Last_Name  Customers.Cust_Last_Name%TYPE; 9 BEGIN10   FOR C1_Rec IN C1 LOOP11     -- Query customer details12     SELECT Cust_First_Name, Cust_Last_Name13       INTO l_Cust_First_Name, l_Cust_Last_Name14       FROM Customers15      WHERE Cust_Id = C1_Rec.Cust_Id;16     --17     -- Insert in to target table18     --19     INSERT INTO Top_Sales_Customers20       (Prod_Id,21        Cust_Id,22        Time_Id,23        Cust_First_Name,24        Cust_Last_Name,25        Amount_Sold)26     VALUES27       (C1_Rec.Prod_Id,28        C1_Rec.Cust_Id,29        C1_Rec.Time_Id,30        l_Cust_First_Name,31        l_Cust_Last_Name,32        C1_Rec.Amount_Sold);33   END LOOP;34   COMMIT;35 END;

      花费:00:00:10.93 

      程序声明了游标变量C1,并且使用FOR LOOP游标显式的打开,每一行从游标中检索出来的数据,程序都将查询customer表查询first_name和last_name这两列,随后将向top_sales_customers插入数据。

存在的问题:

      即使LOOP中的语句,进行了很高级别的优化,程序执行的过程中,也可能花费大量的时间,假设查询customers花费01秒,insert语句花费0.1秒,那么每次LOOP循环花费0.2秒。如果游标检索出100000条数据,那么总共花费的时间为100000乘以0.2,20000秒大约是5.5小时。

    另一个问题是:SQL语句在PL/SQ的循环中,所以执行的过程中,将会造成PL/SQL和SQL引擎的相互交互。这种交互被称为上下文交互,上下文交互增加程序的执行时间,并造成了不必要的CUP负担,所以必须减少上下文的交互,通过减少或者消除这两种环境的交互。

   通常你必须避免row-by-row处理数据,如下所示,避免了使用PL/SQL引擎

---- Insert in to target table--INSERT INTO Top_Sales_Customers  (Prod_Id, Cust_Id, Time_Id, Cust_First_Name, Cust_Last_Name, Amount_Sold)  SELECT s.Prod_Id,         s.Cust_Id,         s.Time_Id,         c.Cust_First_Name,         c.Cust_Last_Name,         s.Amount_Sold    FROM Sales s, Customers c   WHERE s.Cust_Id = c.Cust_Id     AND s.Amount_Sold > 100;

花费:00:00:00.26

两者花费的时间比较明显,在开发的过程中不推荐一行一行的处理。

2、Nested Row-by-Row Processing

     可以在P/SQL中使用嵌套的游标,将从一级游标中查询出来的数据,放置到二级游标中进行输入的插入,查询出来后,在放置到三级游标中进行数据的过滤,然后在进行数据的更新,假如一级查询出20条数,二级查询出30条数,三级查询出来40条数据,那么总的执行次数就是20*30*40数据。

1 DECLARE 2   CURSOR C1 AS 3     SELECT N1 FROM T1; 4   CURSOR C2(p_N1) AS 5     SELECT N1, N2 FROM T2 WHERE N1 = p_N1; 6   CURSOR C3(p_N1, p_N2) AS 7     SELECT Text 8       FROM T3 9      WHERE N1 = p_N110        AND N2 = p_N2;11 BEGIN12   FOR C1_Rec IN C1 LOOP13     FOR C2_Rec IN C2(C1_Rec.N1) LOOP14       FOR C3_Rec IN C3(C2_Rec.N1, C2_Rec.N2) LOOP15         -- execute some sql here; 16         UPDATE … SET ..where n1=c3_rec.n1 AND n2=c3_rec.n2;17       EXCEPTION18     WHEN19     No_Data_Found THEN20        INSERT into… END;21     NULL;22   END LOOP;23 END LOOP;24 END LOOP; COMMIT; END;

除了性能之外,当在进行数据处理的过程中,假如发现了数据,进行更新,没有发现进行数据的插入,可以使用merge来减少SQL引擎和PL/SQL引擎的负载,merge可以将insert和update进行合并,如果存在数据将进行更新,否则进行插入数据。

可以进行重写上述的过程如下:使用merge进行优化

1 MERGE INTO Fact1 2 USING (SELECT DISTINCT C3.N1, C3.N2 3          FROM T1, T2, T3 4         WHERE T1.N1 = T2.N1 5           AND T2.N1 = T3.N1 6           AND T2.N2 = T3.N2) t 7 ON (Fact1.N1 = t.N1 AND Fact1.N2 = t.N2) 8 WHEN MATCHED THEN 9   UPDATE SET ..10 WHEN NOT MATCHED THEN11   INSERT ..;12 COMMIT;

3、Lookup Queries

Lookup Queries经常被用于变量的值构成和执行数据的校验,执行它可能造成性能上的问题

例如:

1 DECLARE 2   CURSOR C1 IS 3     SELECT Prod_Id, Cust_Id, Time_Id, Amount_Sold 4       FROM Sales 5      WHERE Amount_Sold > 100; 6   l_Cust_First_Name Customers.Cust_First_Name%TYPE; 7   l_Cust_Last_Name  Customers.Cust_Last_Name%TYPE; 8   l_Country_Id      Countries.Country_Id%TYPE; 9   l_Country_Name    Countries.Country_Name%TYPE;10 BEGIN11   FOR C1_Rec IN C1 LOOP12     -- Query customer details13     SELECT Cust_First_Name, Cust_Last_Name, Country_Id14       INTO l_Cust_First_Name, l_Cust_Last_Name, l_Country_Id15       FROM Customers16      WHERE Cust_Id = C1_Rec.Cust_Id;17     -- Query to get country_name18     SELECT Country_Name19       INTO l_Country_Name20       FROM Countries21      WHERE Country_Id = l_Country_Id;22     --23     -- Insert in to target table24     --25     INSERT INTO Top_Sales_Customers26       (Prod_Id,27        Cust_Id,28        Time_Id,29        Cust_First_Name,30        Cust_Last_Name,31        Amount_Sold,32        Country_Name)33     VALUES34       (C1_Rec.Prod_Id,35        C1_Rec.Cust_Id,36        C1_Rec.Time_Id,37        l_Cust_First_Name,38        l_Cust_Last_Name,39        C1_Rec.Amount_Sold,40        l_Country_Name);41   END LOOP;42   COMMIT;43 END;

每次都要进行国家名字的查询,这样子效率很不高,有两种优化方式:

1、使用join进行连接。

2、定义一个联合数据,用来缓存结果和在之后的查询中重复利用array,将(country_id, country_name) 在嵌套表中进行键值对的映射,在查询数据的时候,首先使用集合的EXISTS方法,判断,该国家ID对应的名字是否已经映射到了嵌套表中,否则进行查询,将结果放到嵌套表中。如下:

1 DECLARE 2   CURSOR C1 IS 3     SELECT Prod_Id, Cust_Id, Time_Id, Amount_Sold 4       FROM Sales 5      WHERE Amount_Sold > 100; 6   l_Country_Names   Country_Names_Type; 7   l_Country_Id      Countries.Country_Id%TYPE; 8   l_Country_Name    Countries.Country_Name%TYPE; 9   l_Cust_First_Name Customers.Cust_First_Name%TYPE;10   l_Cust_Last_Name  Customers.Cust_Last_Name%TYPE;11   TYPE Country_Names_Type IS TABLE OF VARCHAR2(40) INDEX BY PLS_INTEGER;12   l_Country_Names Country_Names_Type;--定义嵌套表13 BEGIN14   FOR C1_Rec IN C1 LOOP15     -- Query customer details16     SELECT Cust_First_Name, Cust_Last_Name, Country_Id17       INTO l_Cust_First_Name, l_Cust_Last_Name, l_Country_Id18       FROM Customers19      WHERE Cust_Id = C1_Rec.Cust_Id;20     -- Check array first before executing a SQL statement21     IF (l_Country_Names.Exists(l_Country_Id)) THEN22       l_Country_Name := l_Country_Names(l_Country_Id);23     ELSE 24       SELECT Country_Name25         INTO l_Country_Name26         FROM Countries27        WHERE Country_Id = l_Country_Id;28       -- Store in the array for further reuse29       l_Country_Names(l_Country_Id) := l_Country_Name;30     END IF;31     --判断嵌套表中是否有值32     -- Insert in to target table33     --34     INSERT INTO Top_Sales_Customers35       (Prod_Id,36        Cust_Id,37        Time_Id,38        Cust_First_Name,39        Cust_Last_Name,40        Amount_Sold,41        Country_Name)42     VALUES43       (C1_Rec.Prod_Id,44        C1_Rec.Cust_Id,45        C1_Rec.Time_Id,46        l_Cust_First_Name,47        l_Cust_Last_Name,48        C1_Rec.Amount_Sold,49        l_Country_Name);50   END LOOP;51   COMMIT;52 END;

基于嵌套表的技巧可以可以应用到其他的场景中,消除一些不必要的工作。

4、Excessive Access to DUAL

 过量的访问dual表,导致上下文的交互,会对性能产生伤害,在进行一些数字或者日期的运算的时候,不必要借助dual表来实现,因为在PL/SQL中可以直接访问所有的函数,并进行运算,要减少在程序中访问dual表。

5、Populating Master-Detail Rows

在开发的过程中,可以借用returning子句,来返回相应的信息,一般返回的主键ID,

1 INSERT INTO customers (cust_id, ...)2 VALUES (cust_id_seq.nextval,...)3 RETURNING cust_id into l_cust_id;4 ...5 INSERT INTO customer_transactions (cust_id, ...)6 VALUES (l_cust_id,...)7 ...

但是,也可以先定义一个变量,例如

v_id = sys_guid();insert into table1 values(v_id......);insert into table2 values(sys_guid(),v_id.....)

可以先声明一个变量,在插入数据之前,对ID进行赋值,在进行数据的插入操作。

6、Excessive Function Calls

防止不必要的函数调用,对函数进行优化,可以一次完成的事情,坚决不再两次完成。

7、Costly Function Calls

注意:一般不要在循环中调用函数,假如数据量会造成严重的性能问题。

(这里所说的循环-包括,对一个表中的数据进行查询,包含多行,但是每一行的都要进行函数的调用,所以也造成了循环查询的问题)

可以对函数进行优化,对函数建立索引:例如:

函数:

1 CREATE OR REPLACE FUNCTION calculate_epoch (d in date)2 RETURN NUMBER DETERMINISTIC IS3 l_epoch number;4 BEGIN5 l_epoch := (d - TO_DATE('01-JAN-1970 00:00:00', 'DD-MON-YYYY HH24:MI:SS'))6 * 24 *60 *60 ;7 RETURN l_epoch;8 END calculate_epoch;9 /

建立的索引:

1 CREATE INDEX compute_epoch_fbi ON sales2 (calculate_epoch(time_id))3 Parallel (degree 4);

通过给函数建立索引,可以提高过程的执行效率,大大减少运行时间。

Oracle 11G提供了一个新的解决方案,

在建立函数的时候,为函数设置缓存

1 DROP INDEX compute_epoch_fbi; 2 CREATE OR REPLACE FUNCTION calculate_epoch (d in date) 3 RETURN NUMBER DETERMINISTIC RESULT_CACHE IS 4 l_epoch number; 5 BEGIN 6 l_epoch := (d - TO_DATE('01-JAN-1970 00:00:00', 'DD-MON-YYYY HH24:MI:SS')) 7 * 24 *60 *60 ; 8 RETURN l_epoch; 9 END calculate_epoch;10 /

可以使用result_cache来解决问题,当使用该参数后,效率明显提高。

8、Excessive Commits

频繁的提交会造成会产生更多的redo log(重做日志),需要写日志进程频繁的刷新缓存数据到日志文件,这样会造成数据的不一致性。应该减少提交的次数,可以使用批量提交,大约1000-5000行提交一次数据。

9、Excessive Parsing

不要在PL/SQL的循环中使用LOOP循环,因为每一次执行都会进行解析,造成解析上的问题,但是,相反的可以使用绑定变量来提高性能,避免这个问题。

Summary

       SQL is a set language and PL/SQL is a procedural language(SQL是一个集合的语言,但是PL/SQL是一个过程化的语言)

在编写PL/SQL的过程中,应该遵守以下的规则:

• Solve query problems using SQL. Think in terms of sets! It’s easier to tune queries written in SQL than to tune, say, PL/SQL programs having nested loops to essentially execute queries using row-at-a-time processing.(解决查询问题,使用SQL,有时候,SQL语句一句话就可以完成,但是在PL/SQL中,需要使用嵌套循环)

• If you must code your program in PL/SQL, try offloading work to the SQL engine as much as possible. (如果需要在PL/SQL中进行编程,尽可能的减少访问SQL引擎,减少PL/SQL引擎和SQL引擎的交互)
• Use bulk processing facilities available in PL/SQL if you must use loop-based processing. Reduce unnecessary work in PL/SQL such as unnecessary execution of functions or excessive access to DUAL (可以使用批量操作,减少循环的处理,减少不必要的函数执行和DUAL表的访问)
• Use single-row, loop-based processing only as a last resort.(对于单行数据,在万不得已的时候,使用循环)

 --

转载于:https://www.cnblogs.com/caroline/archive/2012/05/27/2520617.html

你可能感兴趣的文章
Python3自动化测试生产测试报告
查看>>
玩转log4j
查看>>
window下查杀占用端口的进程
查看>>
2018年总结&2019年计划
查看>>
hdu 2795 Billboard(线段树+单点更新)
查看>>
本博客所有技术文章皆转自网络
查看>>
sass最佳实践
查看>>
移动端笔记
查看>>
Behaviac 腾讯开源行为树 简介(给策划)
查看>>
js:鼠标事件
查看>>
bzoj 2005: [Noi2010]能量采集
查看>>
2016级算法第一次练习赛-E.AlvinZH的儿时回忆——蛙声一片
查看>>
2016级算法第三次上机-G.Winter is coming
查看>>
SSAS使用MDX生成脱机的多维数据集CUB文件
查看>>
ACM_hdu1102最小生成树练习
查看>>
MyBatis源码分析(一)--SqlSessionFactory的生成
查看>>
android中ListView点击和里边按钮或ImageView点击不能同时生效问题解决
查看>>
CTF常用工具之汇总
查看>>
java的面向对象 (2013-09-30-163写的日志迁移
查看>>
HDU 2191 【多重背包】
查看>>