Oracle PLSQLl的多线程编程架构 存储过程中使用多线程 定时任务 作业调度计划 JOB SCHEDULE

基于Oracle plsql的多线程编程架构 (附存储过程)

1年前 1413

作者介绍

冯守东,北京科讯华通科技发展有限公司高级项目经理。超12年Oracle开发及管理经验,多年运营商和政府企业级系统运维经验,曾获得东软最佳设计方案奖。熟悉Weblogic、TUXEDO、IBM WAS等相关中间件运维。熟悉MySQL、DB2、Informix等其他开源或商业数据,以及Openstack、Hadoop相关生态系统、网站架构设计等。

 引言  

1 文档编制目的

在日常编程范围内有很多大计算量的存储过程,在业务系统中使用Java实现多线程往往会有参与计算的任务不能均匀分配、不能完全发挥数据库服务器的高端性能,代码实现起来门槛较高,比较麻烦。因此,本文档将通过DIY方式介绍如何在Oracle数据库服务器实现存储过程的并行处理。

2 背景

  • 有一个很大的计算过程,参与计算的对象非常多。例如数据固化、应收核定等。

  • 要用PL/SQL逐行处理,当然这这样做会有大量的读取和DML操作。

  • 我们使用一台多CPU的数据库服务器,并且有大量磁盘空间。

  • 我们发现操作系统/SQL并不是非常容易扩展的,它只使用了1个CPU,并没有利用整个机器。

  • 由于我们使用固有的单线程程序来处理数据,ORACLE并行查询不可用!

  • “一个人的活多个人做”并行处理,将固有的任务/数据分解成N个不重叠的组,同时开始PL/SQL子程序N个拷贝。

3 范围

本文档适用于所有使用Oracle 10g的项目,系统吞吐量大,有一定执行时间限制的应用场合。

4 词汇表

词汇名称

词汇含义

备注

DBMS_SCHEDULER

Oracle任务调度器

只有Oracle 10g支持新特性

READ LOCK

Oracle行排他锁

Oracle 任何版本

Autonomous Transactions

Oracle自治事务

Oracle 任何版本

Dynamic SQL

Oracle 动态SQL

Oracle 任何版本

总体架构设计

  

设计原则和方法

原则:

  1. 提供一个脱离业务、通用性强的功能组件。

  2. 易于复用,代码修改少,可配置满足不同要求。

  3. 对控制重复执行有所考虑。

  4. 组件提供人性化的客户交互界面,解决进度情况展示的问题。

  5. 能有一个实现和效率之间的平衡。

方法:

此组件中的并行执行遵循了几乎相同的逻辑。通常可以将某个大“任务”划分为较小的部分,并且并发地执行各个部分。例如,如果需要计算一大批结果并把数据保存到数据库中,那么完全可以建立4 个或更多并行会话(P001~P004)来一起执行存储过程,任务的分派有一个任务分派器来做,分派器可以按照每个进程的负载情况均匀的分派任务。每个会话分别调用预定义的业务过程来执行分派器分派的任务。当需要提交处理结果的时候,可以在每个业务过程内进行保存。

此组件作为一种实现架构,可以使一些要求吞吐量大、执行效率高的的操作得到大幅改善,使其能够呈数量级提高。由于此架构对系统资源要求较高,通常情况下应该在非高峰期而且有足够资源的情况下之用。

2 系统整体架构

20170103074933450.png

3 系统级组件

DBMS_SCHEDULER是Oracle 10G中新增的一个包,与老版本的DBMS_JOB包相比,DBMS_SCHEDULER有很多新特性。Oracle 10g引入DBMS_SCHEDULER来替代先前的DBMS_JOB,在功能方面,它比DBMS_JOB提供了更强大的功能和更灵活的机制。

DBMS_JOB这个程序包存在的问题是它只能够处理 PL/SQL 代码段,即仅能处理匿名程序块和存储程序单元。它不能在数据库外部处理操作系统命令文件或可执行文件中的任何东西。 

为此,您将不得不求助于操作系统调度实用工具。另外DBMS_JOB所生成任务进程一旦生成就在数据库中一直有效,不能在任务结束后自动终止。

DBMS_SCHEDULE是直接在数据库内部的一个作业调度实用程序,强大到足够处理所有类型的作业,而不只是 PL/SQL 代码段。它可以在处理任务执行结束后自动终止。最好的一点是它是数据库自带的,无需任何额外的成本,这样我们在实现上直接使用即可。

4 流程图各部分说明

  • 前台用户:主要是进行程序调度,主要是任务是把我程序执行时机。

  • 任务列表:通过一个普通表实现,该表中主要包括了业务过程执行过程中所需要的入参,参数的形式为‘|’分隔的字符串。同时附加了异常及进度信息。该表是由程序开发人员根据具体的业务定义结构并生成入参数据。

  • 进程控制:根据系统参数或业务参数调用DBMS_SCHEDULER生成对应个数的服务进程。依据当前进程执行情况,控制任务的重复执行。当出现紧急情况时终止所有服务进程。

  • 任务读取:通过ORACLE的自制事务及锁特性,实现任务读取的一致性,即每次服务进程处理一行任务数据的时候,不被其它的服务进程重复读取。

  • 业务过程:该部分同样由程序开发人员根据具体业务类型开发存储过程。该过程根据传入的过程名称和参数个数动态调用业务存储过程。

  • 服务进程:ORACLE自动管理的进程,服务进程的数量取决于“进程控制”创建的进程数。服务进程主要工作1、通过“任务读取”获得“业务过程”的入参。2、调用“业务过程”生成业务数据。

  • 数据存储:业务过程处理之后生成的结果。

  • 进程监视:“前台用户”调用之后任务执行的监控,包括正在执行情况及最终执行结果。

 系统接口设计  

1 程序开发接口设计

此组件向开发人员公布3个系统接口,1个调试接口。

  • PKG_多线程服务.PRC_进程控制

入参:

PRM_PROGRAM   即要调用的业务过程名称。

PRM_PARMCOUNT 即对应业务过程的参数个数

  • PKG_多线程服务.PRC_进程停止

直接终止后台正在运行的多线程服务。

  • PKG_多线程服务.PRC_调试使用

入参:

PRM_PROGRAM   即要调用的业务过程名称。

PRM_PARMS      即对应业务过程的参数字符串。

因为是动态调用存储过程,所以SC01中的参数个数必须同业务过程个数严格对应,提供此方法主要是让开发人员调试使用。

  • 任务列表SC01

开发人员要压入的任务,即业务过程的参数。参数必须以‘|’分隔,并且保证在多线程服务运行的过程中,其他模块不能对SC01进行DML操作,为实现其他模块不能操作SC01,在任何DML操作之前必须执行一个语句如下:

SELECTCOUNT(*)

      INTO N_EXISTS

      FROM USER_SCHEDULER_JOBSS

     WHERE S.STATE = 'RUNNING'

AND S.JOB_ACTION = 'PKG_多线程服务.PRC_服务进程';

综上所述执行一个ORACLE多线程服务,开发人员只需要做一项工作即压入正确SC01参数,然后直接调用即可。

2 用户展示接口设计

前台查询SC02、SC03视图

 数据库结构设计  

1 数据表设计

描述:模块任务分派表Sc01

20170103074952198.png

2 视图设计

  • 描述:运行监视试图Sc02

CREATE OR REPLACE VIEW SC02 AS

SELECT S.JOB_CREATOR        现场创建用户,

      S.JOB_NAME            线程名称,

      S.JOB_ACTION          过程名称,

      S.last_start_date           启动时间,

      S.COMMENTS           备注,

      (SELECT COUNT(*)

         FROM SC01

        WHERE THREAD_NAME = lower(S.JOB_NAME)

          AND STATUS = '0') 未处理数,

      (SELECT COUNT(*)

         FROM SC01

        WHERE THREAD_NAME = lower(S.JOB_NAME)

          AND STATUS = '1') 运行成功数,

      (SELECT COUNT(*)

         FROM SC01

        WHERE THREAD_NAME = lower(S.JOB_NAME)

          AND STATUS = '-1') 运行失败数

 FROM USER_SCHEDULER_JOBS S

 ORDERBY S.JOB_NAME;

  

  • 描述:运行结果监视试图sc03

CREATE OR REPLACE VIEW SC03 AS

SELECT S.OWNER             用户名,

      S.JOB_NAME          线程名称,

      S.STATUS            线程状态,

      S.LOG_DATE          日志时间,

      S.ACTUAL_START_DATE 线程实际运行时间,

      S.RUN_DURATION      线程持续运行时间,

      S.SESSION_ID        会话ID,

      S.SLAVE_PID         进程ID,

      S.ADDITIONAL_INFO   线程备注信息,

      (SELECT COUNT(*) FROM Sc01 WHERE thread_name = lower(S.JOB_NAME) ANDstatus = '0') 未处理数,

      (SELECT COUNT(*) FROM Sc01 WHERE thread_name = lower(S.JOB_NAME) ANDstatus = '1') 运行成功数,

      (SELECT COUNT(*) FROM Sc01 WHERE thread_name = lower(S.JOB_NAME) ANDstatus = '-1')运行失败数

 FROM USER_SCHEDULER_JOB_RUN_DETAILS S

 ORDER BY S.JOB_NAME;

3 数据安全性

所需要系统的权限如下:

grant manage scheduler to AHSIMIS;

grant create any job to AHSIMIS;

 尚需解决的问题  

如果是RAC环境,程序尚不能跨实例运行。

原文发布时间为:2017-01-03

本文来自云栖社区合作伙伴DBAplus

架构 Oracle SQL 线程 数据库 多线程 编程 存储过程

作者

稀奇古怪

TA的文章

相关文章

https://m.aliyun.com/yunqi/articles/80140

存储过程中是否有多线程的概念 

[复制链接]

IT渔夫

论坛徽章:

23

2014年世界杯参赛球队: 哥斯达黎加 日期:2014-06-06 15:02:04沸羊羊 日期:2015-06-17 14:51:07天枰座 日期:2015-07-17 09:51:12金牛座 日期:2016-03-21 21:10:40摩羯座 日期:2016-03-29 15:07:30娜美 日期:2016-06-22 19:49:29奥运会纪念徽章:篮球 日期:2016-09-02 09:45:25奥运会纪念徽章:现代五项 日期:2016-09-23 16:25:05布鲁克 日期:2017-03-21 09:35:18山治 日期:2018-01-04 21:26:16

电梯直达跳转到指定楼层

1#

 发表于 2016-3-2 10:07 | 只看该作者 回帖奖励

本帖最后由 IT渔夫 于 2016-3-2 10:07 编辑

在目前所做的数据库中,需要接入不同的地区(地区有上百个)的数据,针对每个地区,都会需要创建很多自动JOB,这些JOB针对不同的执行频率,有的是5分钟执行一次,有的是10分钟执行一次,有的是1个小时执行一次,有的是半天执行一次。如果直接在JOB中创建的话,会需要在JOB中创建非常多JOB(如果按100个地区,JOB按执行频率分5分钟、10分钟、半小时、1小时、半天、一天分的话,将会要创建100*6=600个JOB),管理起来非常的繁琐,有没有更简单的办法创建和管理这些JOB。


ORACLE的存储过程中,是否有多线程处理的概念? 同时处理多个地区的数据,相互间不影响。这样的话,就只需要针对不同执行频率,一个执行频率,创建一个JOB。比如在10分钟执行频率的JOB中,调用某个计算存储过程,这个存储过程中,以多线程的方式同时启动对多个地区的计算,而不同地区的计算相互不影响(而不是按顺序对不同地区进行计算,执行完一个地区,再接着执行另外一个)。
 

使用道具 举报

回复
   

IT渔夫

论坛徽章:

23

2014年世界杯参赛球队: 哥斯达黎加 日期:2014-06-06 15:02:04沸羊羊 日期:2015-06-17 14:51:07天枰座 日期:2015-07-17 09:51:12金牛座 日期:2016-03-21 21:10:40摩羯座 日期:2016-03-29 15:07:30娜美 日期:2016-06-22 19:49:29奥运会纪念徽章:篮球 日期:2016-09-02 09:45:25奥运会纪念徽章:现代五项 日期:2016-09-23 16:25:05布鲁克 日期:2017-03-21 09:35:18山治 日期:2018-01-04 21:26:16

2#

  楼主| 发表于 2016-3-2 10:23 | 只看该作者

本帖最后由 IT渔夫 于 2016-3-2 11:50 编辑

用DBMS_JOB.SUBMIT 可以实现,但是会造成一个问题,每次的DBMS_JOB.SUBMIT,都是创建一个会话,如果循环100个地区,做DBMS_JOB.SUBMIT的话,就会产生100个会话。经过1分钟后,这些会话会被自动删除。因此需要把processes参数改得更大。
 
 

使用道具 举报

回复
   

隐剑埋名

认证徽章

论坛徽章:

10

射手座 日期:2015-11-23 14:22:242016猴年福章 日期:2016-02-18 09:31:30秀才 日期:2016-02-18 09:24:30双子座 日期:2016-01-27 10:10:49天枰座 日期:2016-01-13 16:00:59天枰座 日期:2016-01-13 15:48:12秀才 日期:2016-01-06 14:01:09天蝎座 日期:2015-11-25 16:11:14白羊座 日期:2015-11-24 10:35:21技术图书徽章 日期:2017-09-18 11:45:07

3#

 发表于 2016-3-2 11:35 | 只看该作者

我们做的是建一张JOB表,来管理,然后写一个存储过程批量建一遍就行了。
 
 

使用道具 举报

回复
   

test_100

认证徽章

论坛徽章:

16

ERP板块每日发贴之星 日期:2007-08-24 01:03:56秀才 日期:2017-03-02 10:30:14ITPUB14周年纪念章 日期:2015-10-26 17:23:44处女座 日期:2015-09-11 10:28:03狮子座 日期:2015-09-10 09:07:23喜羊羊 日期:2015-06-24 14:33:00慢羊羊 日期:2015-06-15 14:22:442015年新春福章 日期:2015-03-12 17:22:46优秀写手 日期:2014-11-20 06:00:13茶鸡蛋 日期:2013-06-29 22:37:49

4#

 发表于 2016-3-2 13:06 | 只看该作者

用dbms_parallel_execute试试~
 
 

使用道具 举报

回复
   

IT渔夫

论坛徽章:

23

2014年世界杯参赛球队: 哥斯达黎加 日期:2014-06-06 15:02:04沸羊羊 日期:2015-06-17 14:51:07天枰座 日期:2015-07-17 09:51:12金牛座 日期:2016-03-21 21:10:40摩羯座 日期:2016-03-29 15:07:30娜美 日期:2016-06-22 19:49:29奥运会纪念徽章:篮球 日期:2016-09-02 09:45:25奥运会纪念徽章:现代五项 日期:2016-09-23 16:25:05布鲁克 日期:2017-03-21 09:35:18山治 日期:2018-01-04 21:26:16

5#

  楼主| 发表于 2016-3-2 15:20 | 只看该作者

test_100 发表于 2016-3-2 13:06 
用dbms_parallel_execute试试~

这个应该不行。。。
 
 

使用道具 举报

回复
   

newkid

论坛徽章:

531

奥运会纪念徽章:垒球 日期:2008-09-15 01:28:12生肖徽章2007版:鸡 日期:2008-11-17 23:40:58生肖徽章2007版:马 日期:2008-11-18 05:09:48数据库板块每日发贴之星 日期:2008-11-29 01:01:02数据库板块每日发贴之星 日期:2008-12-05 01:01:03生肖徽章2007版:虎 日期:2008-12-10 07:47:462009新春纪念徽章 日期:2009-01-04 14:52:28数据库板块每日发贴之星 日期:2009-02-08 01:01:03生肖徽章2007版:蛇 日期:2009-03-09 22:18:532009日食纪念 日期:2009-07-22 09:30:00

6#

 发表于 2016-3-3 01:06 | 只看该作者

IT渔夫 发表于 2016-3-2 10:23 
用DBMS_JOB.SUBMIT 可以实现,但是会造成一个问题,每次的DBMS_JOB.SUBMIT,都是创建一个会话,如果循环100 ...

多线程必然要发起多个会话。
你这样动态创建一次性JOB的意义何在?还不如事先创建好。
 
 

使用道具 举报

回复
   

IT渔夫

论坛徽章:

23

2014年世界杯参赛球队: 哥斯达黎加 日期:2014-06-06 15:02:04沸羊羊 日期:2015-06-17 14:51:07天枰座 日期:2015-07-17 09:51:12金牛座 日期:2016-03-21 21:10:40摩羯座 日期:2016-03-29 15:07:30娜美 日期:2016-06-22 19:49:29奥运会纪念徽章:篮球 日期:2016-09-02 09:45:25奥运会纪念徽章:现代五项 日期:2016-09-23 16:25:05布鲁克 日期:2017-03-21 09:35:18山治 日期:2018-01-04 21:26:16

7#

  楼主| 发表于 2016-3-3 08:07 | 只看该作者

newkid 发表于 2016-3-3 01:06 
多线程必然要发起多个会话。
你这样动态创建一次性JOB的意义何在?还不如事先创建好。

这些JOB太多,要事先创建,管理起来很麻烦。
 
 

使用道具 举报

回复
   

newkid

论坛徽章:

531

奥运会纪念徽章:垒球 日期:2008-09-15 01:28:12生肖徽章2007版:鸡 日期:2008-11-17 23:40:58生肖徽章2007版:马 日期:2008-11-18 05:09:48数据库板块每日发贴之星 日期:2008-11-29 01:01:02数据库板块每日发贴之星 日期:2008-12-05 01:01:03生肖徽章2007版:虎 日期:2008-12-10 07:47:462009新春纪念徽章 日期:2009-01-04 14:52:28数据库板块每日发贴之星 日期:2009-02-08 01:01:03生肖徽章2007版:蛇 日期:2009-03-09 22:18:532009日食纪念 日期:2009-07-22 09:30:00

8#

 发表于 2016-3-3 23:52 | 只看该作者

IT渔夫 发表于 2016-3-3 08:07 
这些JOB太多,要事先创建,管理起来很麻烦。

创建完就被ORACLE管理了,你不用管。
创建的脚本你自己可以用SQL生成。
 

http://www.itpub.net/thread-2053863-1-1.html

[PL/SQL] 如何实现存储过程多线程执行 

[复制链接]

ciwei5525

论坛徽章:

0

电梯直达跳转到指定楼层

1#

 发表于 2017-11-27 10:09 | 只看该作者 回帖奖励

存储过程中会循环遍历一个标准表,通过提取标准表中的sql进行数据抽取并将符合条件的数据添加到另一个表中。标准表中有1000+条数据规则sql是期中的字段。
大致流程是
begin
  for a in select * from 标准表
    line:=a.sql;   --(sql内容是from XXX where XXX)
    line:='insert into table select 1,2,3,4,5 ' || line;
    EXECUTE IMMEDIATE (line);
end
目前我知道可以用/*+parallel(a,16)*/来进行DML并行,但是我想做这个存储过程的多线程该怎么做?
我百度了大多存储过程并发执行需要用job实现但是看的云里雾里,希望大佬们帮帮忙...
 

使用道具 举报

回复
   

test_100

认证徽章

论坛徽章:

16

ERP板块每日发贴之星 日期:2007-08-24 01:03:56秀才 日期:2017-03-02 10:30:14ITPUB14周年纪念章 日期:2015-10-26 17:23:44处女座 日期:2015-09-11 10:28:03狮子座 日期:2015-09-10 09:07:23喜羊羊 日期:2015-06-24 14:33:00慢羊羊 日期:2015-06-15 14:22:442015年新春福章 日期:2015-03-12 17:22:46优秀写手 日期:2014-11-20 06:00:13茶鸡蛋 日期:2013-06-29 22:37:49

2#

 发表于 2017-11-27 13:44 | 只看该作者

本帖最后由 test_100 于 2017-11-27 13:46 编辑
  1.     l_task      := 'XX_000004';
  2.     l_chunk_sql := ' select t.cst_id,t.cst_id from aa_xx_t t ';
  3.     l_sql       := 'Begin proce_name_pck.pro_mut(:start_id,:end_id); End;';
  4.     dbms_output.put_line(l_sql);
  5.     dbms_parallel_execute.create_task(task_name => l_task);
  6.     dbms_parallel_execute.create_chunks_by_sql(l_task, l_chunk_sql, False);
  7.   
  8.     dbms_parallel_execute.run_task(l_task,
  9.                                    l_sql,
  10.                                    dbms_sql.native,
  11.                                    parallel_level  => 16,
  12.                                    job_class       => Null);
  13.     dbms_parallel_execute.drop_task(l_task);
复制代码
 
 

使用道具 举报

回复
   

newkid

论坛徽章:

531

奥运会纪念徽章:垒球 日期:2008-09-15 01:28:12生肖徽章2007版:鸡 日期:2008-11-17 23:40:58生肖徽章2007版:马 日期:2008-11-18 05:09:48数据库板块每日发贴之星 日期:2008-11-29 01:01:02数据库板块每日发贴之星 日期:2008-12-05 01:01:03生肖徽章2007版:虎 日期:2008-12-10 07:47:462009新春纪念徽章 日期:2009-01-04 14:52:28数据库板块每日发贴之星 日期:2009-02-08 01:01:03生肖徽章2007版:蛇 日期:2009-03-09 22:18:532009日食纪念 日期:2009-07-22 09:30:00

3#

 发表于 2017-11-27 22:50 | 只看该作者

楼上那个包是专门给UPDATE用的,并不适用楼主的需求。
把每个子任务单独创建一个JOB,就相当于打开一个窗口去执行了一个INSERT,同时打开许多个窗口。有什么看不懂的?
 
 

使用道具 举报

回复
   

test_100

认证徽章

论坛徽章:

16

ERP板块每日发贴之星 日期:2007-08-24 01:03:56秀才 日期:2017-03-02 10:30:14ITPUB14周年纪念章 日期:2015-10-26 17:23:44处女座 日期:2015-09-11 10:28:03狮子座 日期:2015-09-10 09:07:23喜羊羊 日期:2015-06-24 14:33:00慢羊羊 日期:2015-06-15 14:22:442015年新春福章 日期:2015-03-12 17:22:46优秀写手 日期:2014-11-20 06:00:13茶鸡蛋 日期:2013-06-29 22:37:49

4#

 发表于 2017-11-28 17:12 | 只看该作者

newkid 发表于 2017-11-27 22:50
楼上那个包是专门给UPDATE用的,并不适用楼主的需求。
把每个子任务单独创建一个JOB,就相当于打开一个窗 ...

把要insert table的数据分多个线程insert的,这样不可以?
 
 

使用道具 举报

回复
   

newkid

论坛徽章:

531

奥运会纪念徽章:垒球 日期:2008-09-15 01:28:12生肖徽章2007版:鸡 日期:2008-11-17 23:40:58生肖徽章2007版:马 日期:2008-11-18 05:09:48数据库板块每日发贴之星 日期:2008-11-29 01:01:02数据库板块每日发贴之星 日期:2008-12-05 01:01:03生肖徽章2007版:虎 日期:2008-12-10 07:47:462009新春纪念徽章 日期:2009-01-04 14:52:28数据库板块每日发贴之星 日期:2009-02-08 01:01:03生肖徽章2007版:蛇 日期:2009-03-09 22:18:532009日食纪念 日期:2009-07-22 09:30:00

5#

 发表于 2017-11-28 22:29 | 只看该作者

test_100 发表于 2017-11-28 17:12
把要insert table的数据分多个线程insert的,这样不可以?

这里有篇文章提到了并行INSERT, 但是要求目标表是分区的:
https://blogs.oracle.com/warehou ... -partitioned-tables
 
 

使用道具 举报

回复
   

test_100

认证徽章

论坛徽章:

16

ERP板块每日发贴之星 日期:2007-08-24 01:03:56秀才 日期:2017-03-02 10:30:14ITPUB14周年纪念章 日期:2015-10-26 17:23:44处女座 日期:2015-09-11 10:28:03狮子座 日期:2015-09-10 09:07:23喜羊羊 日期:2015-06-24 14:33:00慢羊羊 日期:2015-06-15 14:22:442015年新春福章 日期:2015-03-12 17:22:46优秀写手 日期:2014-11-20 06:00:13茶鸡蛋 日期:2013-06-29 22:37:49

6#

 发表于 2017-11-29 09:39 | 只看该作者

newkid 发表于 2017-11-28 22:29
这里有篇文章提到了并行INSERT, 但是要求目标表是分区的:
https://blogs.oracle.com/warehousebuilder/ ...

谢谢~
 

http://www.itpub.net/thread-2094704-1-1.html

猜你喜欢

转载自blog.csdn.net/xuheng8600/article/details/84847007