zoukankan      html  css  js  c++  java
  • PL/SQL 训练04--事务

    --pl/sql通过SQL和ORACLE数据库紧密的整合在一起
    --在pl/sql中可以执行任何操作语句(DML语句),包括INSERT,UPDATE,DELETE,MERGE,也包括查询语句
    --可否执行DDL语句呢?
    --不可以直接执行,但可以通过动态SQL的方式执行,关于动态SQL,后面课程会专门拿一节课来讲

    --事务的ACID原则:原子性,一致性,隔离性,持久性

    --原子性:事务所涉及的改变是原子的:这些改变或者全部发生或者全部不发生

    --一致性:一个事务必须是一个正确的状态转换。事务中发生的行为作为一个整体不能违反状态的任何完整性约束

    --隔离:很多事务可以同时发生,不过从任何一个事务的角度看,其他的事务看起来都在它之前或之后发生的

    --持久性:一旦一个事务成功结束,状态的改变是永久的,可能经受住以后发生的任何故障

    --COMMIT或ROLLBACK,一个事务可以通过执行COMMIT保存,或者ROLLBACK回滚。
    --资源上的锁释放
    --事务和会话的关系:默认每个会话中只有一个事务。所有修改都属于当前事务的一部分。
    --自制事务特性:可以在会话的住事务中嵌套其它事务

    --建立日志表
    create table ma_user_log
    ( created_by     varchar2(100) default 'system' not null,
      created_date   date default sysdate not null,
      updated_by     varchar2(100) default 'system' not null,
      updated_date   date  default sysdate not null,
      id_ma_user_log varchar2(32) default sys_guid() not null,
      user_name      varchar2(100) not null,
      trace_mark     varchar2(1000) );
      
    
    create or replace procedure test_pragma(i_user         in varchar2,
                                            i_trace_remark in varchar2) is
    
      pragma autonomous_transaction; --定义自制事务
    begin
      insert into ma_user_log
        (user_name, trace_mark)
        select i_user, i_trace_remark from dual;
      --insert into ma_user_log(user_name, trace_mark)values(user_name,i_trace_remark);
      commit;
    
    end test_pragma;
    /
    declare
    
      cursor cur_users is
        select * from ma_users r where r.user_status = '1'; --当前用户对表有查询权限
    begin
    
      for v in cur_users loop
        update ma_users r --注意对此表进行操作需要有UPDATE的权限
           set r.user_point = 100
         where r.user_name = v.user_name;
        test_pragma(v.user_name, '数据修改,用户积分初始值100');
      end loop;
    
    end;
    /
    
    
    select * from ma_user_log;

    --dml语句的快速入门

    --insert语句:向表中插入一条或者多条记录了
    insert into ma_user_log(user_name, trace_mark)values('test','test test'); --插入一条数据
    insert into  ma_user_log(user_name, trace_mark)select 'test1', 'test1test1' from dual;--插入一条数据
    insert into  ma_user_log(user_name, trace_mark)
    select t.user_name,t.user_remark from ma_users t where t.user_status ='1';--插入多条数据
    insert into ma_user_log--必须写出所有的列且一一对应
      select 'system',
             sysdate,
             'system',
             sysdate,
             sys_guid(),
             t.user_name,
             t.user_remark
        from ma_users t
       where t.user_status = '1';--插入多条数据
    
    
    --UPDATE语句:更新一行或多行的一或多列
    update ma_users t 
    set t.user_point = 100,t.user_status ='0'
    where t.user_name ='乱世佳人';
    
    --DELETE语句:删除一个表的一行、多行、或者所有记录行
    
    delete from ma_user_log ;
    delete from ma_user_log where 1=1;
    create or replace procedure del_user_log(i_date in date,o_log_num out number)
    is 
    begin 
       delete from ma_user_log t where t.created_date < i_date ;
       o_log_num := sql%rowcount ;
    end ;
    /
    
    declare 
       v_num  number ;
    begin 
       
      del_user_log(sysdate,v_num);
      dbms_output.put_line('删除'||v_num||'个记录');
    end ;
    /
    
    --MERGE 语句:指定一个匹配条件,然后针对匹配和不匹配的记录分别采取不同的行为
    declare
    begin
      merge into ma_user_log mu
      using (select * from ma_users) t
      on (mu.user_name = t.user_name)
      when matched then
        update set mu.trace_mark = 'hello tt'
      when not matched then
        insert (mu.user_name, mu.trace_mark) values (t.user_name, 'hello'||t.user_name);
    
    end;
    /
    select * from ma_user_log;

    ---dml操作的游标属性
    --通过一些特殊的隐式游标属性访问最后一次运行的隐式游标的信息
    --sql%found:如果有一行或多行记录被成功修改(包括创建、修改、删除)返回TRUE
    --sql%notfound:如果DML语句没有修改任何行则返回TRUE
    --sql%rowcount:返回DML语句修改的记录行数
    --sql%isopen:对于隐式游标(及DML语句)总是返回FALSE,因为ORACLE数据库会自动打开和关闭这些游标

    create or replace procedure update_point(i_user in varchar2,
                                             o_bool out boolean,
                                             o_num  out number)
    
     is
    
    begin
    
      update ma_users t set t.user_point = 100 where t.user_name = i_user;
      o_bool := sql%found;
      o_num  := sql%rowcount;
    
    end;
    /
    
    declare
      v_bool boolean;
      v_num  number;
    begin
    
      update_point('乱世佳人', v_bool, v_num);
      if v_bool then
        dbms_output.put_line('true');
      else
        dbms_output.put_line('false');
      end if;
      dbms_output.put_line(v_num);
      update_point('乱世佳', v_bool, v_num);
      if v_bool then
        dbms_output.put_line('true');
      else
        dbms_output.put_line('false');
      end if;
      dbms_output.put_line(v_num);
    
    end;
    /

    --returning:从dml语句返回信息
    --可以从insert,update,delete,merge语句中添加一个RETURNING字句,返回信息到一个变量中
    --不需要单调去查询

    declare
      v_phone ma_users.user_name%type;
      v_email ma_users.user_email%type;
    begin
    
      for v in (select * from ma_users) loop
      
        update ma_users r
           set r.user_point = 1000
         where r.id_ma_users = v.id_ma_users
        returning r.user_phone, r.user_email into v_phone, v_email;
        --select r.user_phone, r.user_email into v_phone, v_email from ma_users r where r.id_ma_users = v.id_ma_users
        dbms_output.put_line(v.user_name || '-' || v_phone || '-' || v_email);
      end loop;
    
    end;
    /
    
    --如果UPDATE语句修改返回的记录函数多于一行,可以使用BULK COLLECT返回到一个集合中
    
    declare
      type point_t is table of number;
    
      v_point point_t;
    begin
    
      update ma_users t
         set t.user_point = 200*(sysdate - t.user_birth_date)
       where 1 = 1
      returning t.user_point bulk collect into v_point;
      for i in v_point.first..v_point.last loop 
          dbms_output.put_line(v_point(i));
      end loop ;
    end;
    /

    --异常处理
    --如果一个PL/SQL块出现异常时,oracle数据库不会回滚这个块中DML语句所做的修改
    --需要我们去决定采取什么行动

    create or replace procedure del_users(i_user in varchar2,o_count out number )
    is 
    
    begin 
    
       select count(1) into o_count from ma_users ;
       --o_count:= 3;
       delete from ma_users mu where mu.user_name = i_user;
       raise no_data_found ;
    end ;
    /
    declare
    
      v_count number :=-1;
    begin
    
      del_users('乱世佳人1', v_count);
      dbms_output.put_line('1=='||v_count);--不会被打印,直接进入异常
    exception
      when others then
        dbms_output.put_line('2=='||v_count);
        select count(1) into v_count from ma_users;
         dbms_output.put_line('3=='||v_count);
    end;
    /
    declare
      v_count number :=-1;
    begin
    
      del_users('乱世佳人1', v_count);
      dbms_output.put_line(v_count);
    
    end;
    /
    
    select count(1) from ma_users;

    --有几点需要注意
    --如果代码块中使用的是自治事务,在发生异常时需要执行回滚或者提交
    --可以通过SAVEPOINT来控制回滚的范围。可以回滚到某个特殊SAVEPOINT,
    --从而把会话所做出的改变部分保存下来
    --如果一个异常传播到最外层的代码块,多数PL/SQL执行环境比如SQL*PLUS,都会自动回滚
    --所有变化都会被撤销

    --基于记录的DML

    create or replace procedure save_user(i_user ma_users%rowtype) is
        
    begin
      insert into ma_users values i_user;
    exception
      when dup_val_on_index then
        update ma_users t
           set row = i_user
         where t.user_name = i_user.user_name;
    end;
    /
    
    declare
    
      v_user ma_users%rowtype;
    begin
      v_user.created_by      := 'system';
      v_user.created_date    := sysdate;
      v_user.updated_by      := 'system';
      v_user.updated_date    := sysdate;
      v_user.id_ma_users     := sys_guid();
      v_user.user_name       := '幸运小子2';
      v_user.user_password   := 'text123';
      v_user.user_sex        := '1';
      v_user.user_phone      := '223aa3333';
      v_user.real_name       := 'xinyuan';
      v_user.identity_no     := '22222';
      v_user.user_email      := 'test@163.com';
      v_user.user_address    := 'hhhhhhh';
      v_user.user_birth_date := date '1986-01-01';
      v_user.user_status     := '1';
      v_user.user_remark     := 'sss';
      v_user.user_point      := 1000;
      v_user.register_date   := sysdate;
    
      save_user(v_user);
    end;
    /
    select * from ma_users;
    
    declare
      type test_record is record(
        user_name  ma_users.user_name%type,
        user_point ma_users.user_point%type);
    
      v_record test_record;
    begin
    
      update ma_users t
         set t.user_point = t.user_point + 100
       where t.user_name = '幸运小子'
      returning t.user_name, t.user_point into v_record;
      dbms_output.put_line(v_record.user_name || '累计积分' || v_record.user_point);
    end;
    /

    --事务管理

    --commit:保存上一个commit或者rollback以来发生的所有变化,并且释放锁资源
    --commit会释放会话中使用的任何行锁和表锁,比如使用SELECT FOR UPDATE添加的
    --同时会把自上一个COMMIT或ROLLBACK语句以来创建的所有SAVEPOINT都清除
    COMMIT ;
    COMMIT WORK;
    COMMIT COMMENT 'THIS IS A COMMENT';

    --ROLLBACK 语句
    --撤销从上一个commit或者rollback以来发生的所有变化,并且释放锁资源
    rollback ;
    rollback work;
    rollback to savepoint_name;

    --savepoint:创建一个保存点,有了保存点后可以进行部分回滚操作
    savepoint savepoint_name;
    --rollback回滚到某个保存点,这个保存点之后的改变全部撤销并释放资源
    --不过在这点之前的改变以及锁仍然保留

    --savepoin没有所谓作用范围一说

    DECLARE
      test_savepoint_exp EXCEPTION;
      v_count number;
    BEGIN
    
      update ma_users t
         set t.user_point = t.user_point + 100
       where t.user_name = '幸运小子';
       
      SAVEPOINT TEST_SAVEPOINT;
       update ma_users t
         set t.user_point = t.user_point + 100
       where t.user_name = '幸运小子';
      
      SAVEPOINT TEST_SAVEPOINT;
       update ma_users t
         set t.user_point = t.user_point + 100
       where t.user_name = '幸运小子';
      raise test_savepoint_exp;
    exception
      when test_savepoint_exp then
        rollback to TEST_SAVEPOINT;
        select t.user_point
          into v_count
          from ma_users t
         where t.user_name = '幸运小子';
        dbms_output.put_line(v_count);
      
    END;
    /
    --set transaction
    --启动一个只读或者读写会话,构建一个隔离级别,或者为当前的事务分配一个专门的回滚段
    
    set transaction read only 
    --把当前事务定义成只读的,后续的查询看到的只是这个事务开始之前的已经提交的变化
    
    set transaction read write 
    --把当前事务定义成可读写的,并且这是缺省设置
    
    set transaction isolation level serializable | read commit ;
    --定义修改数据库的事务是如何处理的
    --如果是serializable,则dml语句已经被另一个尚未提交的事务修改了,这个语句就会失败。
    --这个命令要求数据库的初始化参数COMPATIBLE必须设置为7.3.0或者更高的值
    --如果是READ COMMIT,一个dml语句请求的行级已经被另一个事务持有,则这个语句要一直等待锁被释放
    --缺省行为
    
    set transaction use rollback segment rollback_segname;
    --为当前事务指定一个专门的回滚段,并把事务设置成可读写。不能和第一个命令一起使用
    
    declare
      v_count number;
    begin
      set transaction read only;
      select t.user_point
        into v_count
        from ma_users t
       where t.user_name = '幸运小子';
      dbms_output.put_line(v_count);
    
    end;
    /
     select t.user_point
        from ma_users t
       where t.user_name = '幸运小子';

    --lock talbe
    --用指定的模式锁定这个数据库表
    lock table table_reference_list in lock_mode mode [nowait];
    --lock mode
    row share --行共享 允许其他用户同时更新其他行,允许其他用户同时加共享锁,不允许有独占(排他性质)的锁
    row exclusive --行独占 行排他,允许其他用户同时更新其他行,只允许其他用户同时加行共享锁或者行排他锁
    share --共享锁 不允许其他用户同时更新任何行,只允许其他用户同时加共享锁或者行共享锁
    share row exclusive --共享行排他,允许其他用户同时更新其他行,只允许其他用户同时加行共享锁
    exclusive --排他,其他用户禁止更新任何行,禁止其他用户同时加任何锁
    --自治事务
    --定义自治事务
    --在声明单元加上以下语句
    pragma autonomous_transaction;

    --把一个块定义成自治事务,实际是把这个块中的DML语句和调用程序的事务环境完全的隔离开
    --这个块就成为一个由其它事务启动的独立事务,前一个事务叫做主事务

    --被定义程自治事务的块可以是
    --最顶层的匿名块
    --函数或者过程,或者在包里定义或者是一个独立的程序
    --对象类型的方法(函数或者方法)
    --数据库触发器


    --自治事务的规则和限制
    --如果自治事务的要访问的资源已经被主事务持有,程序就会发生死锁
    --不能只用一个PRAGMA声明一个包中所有的子程序全部标识程自治的。必须对于包体中每个程序声明单元
    --都明确指定自治事务

    --如果想从一个已经执行了至少一个INSERT、update,merge,delete语句的自治事务程序没有任何
    --错误的退出,必须明确的执行一个提交或者回滚

    --commit和rollback语句只是结束了活动的自治事务,但不会终止自治例程。在一个自治块中
    --可以使用多个commit或者ROLLBACK语句

    --在一个自治事务中,不能回滚到主事务创建的SAVEPOINT
    --自治事务提交后,对主事务可见
    create or replace PROCEDURE UPDATE_USER(I_USER  IN VARCHAR2,
                                            i_point in number) IS
      PRAGMA AUTONOMOUS_TRANSACTION;
    BEGIN
      UPDATE MA_USERS R
         SET R.USER_POINT = R.USER_POINT + i_point
       WHERE R.USER_NAME = I_USER;
        dbms_output.put_line('test1');
      COMMIT;
    END UPDATE_USER;
     
    DECLARE
    
    BEGIN
      update ma_users t
         set t.user_point = t.user_point + 100
       where t.user_name = '幸运小子';
      UPDATE_USER('幸运小子2', 1000);
      dbms_output.put_line('test2');
    END;
    /
    select * from ma_users
    declare
    
      v_user ma_users%rowtype;
    begin
      v_user.created_by      := 'system';
      v_user.created_date    := sysdate;
      v_user.updated_by      := 'system';
      v_user.updated_date    := sysdate;
      v_user.id_ma_users     := sys_guid();
      v_user.user_name       := '幸运小子1';
      v_user.user_password   := 'text123';
      v_user.user_sex        := '1';
      v_user.user_phone      := '233sss33';
      v_user.real_name       := 'xinyuan';
      v_user.identity_no     := '22222';
      v_user.user_email      := 'test@163.com';
      v_user.user_address    := 'hhhhhhh';
      v_user.user_birth_date := date '1986-01-01';
      v_user.user_status     := '1';
      v_user.user_remark     := 'sss';
      v_user.user_point      := 100;
      v_user.register_date   := sysdate;
      save_user(v_user);
      UPDATE_USER('幸运小子1', 1000);
      
    end;
    /
    select * from ma_users
    
    --自治事务的缺省行为是,只要在自治事务中执行了COMMIT或者ROLLBACK,这些改变立即对主事务可见
    
    
    --什么时候使用自治事务
    
    --自治事务的日志机制

    ------------------------------------------------------------------

    1. 第三课作业中第二题的异常记录方法,大家可以完善下方法,将之改成支持自治事务的方法。
    2. 这段程序中,ma_users是第一课作业建立的用户表,这段程序的目的是,一旦用户注册成功,在其默认积分基础上送1000积分。但程序里有BUG,请大家找出来,并且优化这个程序

    create or replace procedure save_user(i_user ma_users%rowtype) is   
    begin
      insert into ma_users values i_user;
    exception
      when dup_val_on_index then
        update ma_users t
           set row = i_user
         where t.user_name = i_user.user_name;
    end;
    /
    create or replace PROCEDURE UPDATE_USER(I_USER  IN VARCHAR2,
                                            i_point in number) IS
      PRAGMA AUTONOMOUS_TRANSACTION;
    BEGIN
      UPDATE MA_USERS R
         SET R.USER_POINT = R.USER_POINT + i_point
       WHERE R.USER_NAME = I_USER;
        dbms_output.put_line('test1');
      COMMIT;
    END UPDATE_USER;
    declare
      v_user ma_users%rowtype;
    begin
      v_user.created_by      := 'system';
      v_user.created_date    := sysdate;
      v_user.updated_by      := 'system';
      v_user.updated_date    := sysdate;
      v_user.id_ma_users     := sys_guid();
      v_user.user_name       := '幸运小子1';
      v_user.user_password   := 'text123';
      v_user.user_sex        := '1';
      v_user.user_phone      := '233sss33';
      v_user.real_name       := 'xinyuan';
      v_user.identity_no     := '22222';
      v_user.user_email      := 'test@163.com';
      v_user.user_address    := 'hhhhhhh';
      v_user.user_birth_date := date '1986-01-01';
      v_user.user_status     := '1';
      v_user.user_remark     := 'sss';
      v_user.user_point      := 100;
      v_user.register_date   := sysdate;
      save_user(v_user);
      UPDATE_USER('幸运小子1', 1000);
    end;
    /
    3.【可选做】 这节讲到了MERGE的语句,请大家改写这个语句,实现如果存在则更新,不存在则插入的逻辑,注意PLSQL程序要有异常处理
    
    --
    1 修改成自治事务
    PROCEDURE exception_logs_p (
          i_option_users        IN exception_logs.option_users%TYPE,
          i_method_name         IN exception_logs.method_name%TYPE,
          i_exception_line      IN exception_logs.exception_line%TYPE,
          i_exception_code      IN exception_logs.exception_code%TYPE,
          i_exception_message   IN exception_logs.exception_message%TYPE--i_exception_level    IN exception_logs.exception_level%TYPE
          )
       IS
          PRAGMA AUTONOMOUS_TRANSACTION;
          v_sysdate           DATE DEFAULT SYSDATE;
          v_exception_level   NUMBER DEFAULT 0;
     BEGIN     
      BEGIN
       SELECT   exception_level
          INTO v_exception_level
         FROM exception_level
          WHERE exception_code=i_exception_code;
          
       EXCEPTION 
       WHEN OTHERS THEN
        v_exception_level:=3;
        END ;
        
       BEGIN
          INSERT INTO exception_logs (option_users,
                                      method_name,
                                      exception_time,
                                      exception_line,
                                      exception_code,
                                      exception_message,
                                      exception_level)
               VALUES (i_option_users,
                       i_method_name,
                       v_sysdate,
                       i_exception_line,
                       i_exception_code,
                       i_exception_message,
                       v_exception_level);
    
          COMMIT;
       EXCEPTION
          WHEN OTHERS
          THEN
             ROLLBACK;
       END;
      END;
    END exception_logs_pkg;
    /

    2 程序bug
    1 由于UPDATE_USER过程使用了自治事务,这与主事务隔离开,主事务的insert没有提交,
    导致UPDATE_USER没有获取到数据,导致数据更新错误
    修改:1 可以在save_user上加上commit
    2 取消UPDATE_USER的自治事务

    3 
    
    declare
      v_user ma_users%rowtype;
      v_usero varchar2(32) default user;
      v_erroeline varchar2(100);--not a number
      v_sqlcode number;
      v_sqlerrm varchar2(100);
    begin
      v_user.created_by      := 'system';
      v_user.created_date    := sysdate;
      v_user.updated_by      := 'system';
      v_user.updated_date    := sysdate;
      v_user.id_ma_users     := sys_guid();
      v_user.user_name       := '乱世佳人12';
      v_user.user_password   := 'text123';
      v_user.user_sex        := '1';
      v_user.user_phone      := '233sss3311';
      v_user.real_name       := 'xinyuan';
      v_user.identity_no     := '22222';
      v_user.user_email      := 'test@163.com';
      v_user.user_address    := 'hhhhhhh';
      v_user.user_birth_date := date '1986-01-01';
      v_user.user_status     := '1';
      v_user.user_remark     := 'sss';
      v_user.user_point      := 100;
      v_user.register_date   := sysdate;
    begin  
    --register user
    merge into MA_USERS m1
    using( select count(id_ma_users) d from MA_USERS where user_name=v_user.user_name) m2
    on (m2.d<>0)
    --when matched then
     --update set m1.USER_POINT=m1.USER_POINT+1000,updated_date=sysdate 
     --where  user_name=v_user.user_name
    when not matched then
     insert   values v_user ;
     --update point
     merge into MA_USERS m1
    using( select m2.id_ma_users d from MA_USERS m2 where m2.id_ma_users=v_user.id_ma_users ) m2
    on (m2.d=m1.id_ma_users)
    when matched then
     update set m1.USER_POINT=m1.USER_POINT+1000,updated_date=sysdate ;
      dbms_output.put_line('v_user.id_ma_users='||v_user.id_ma_users);
     dbms_output.put_line('t1');
    exception
     when others then
     v_erroeline:=dbms_utility.format_error_backtrace;
        v_sqlcode:=sqlcode;
        v_sqlerrm:=substr(SQLERRM,1,100);
      exception_logs_pkg.exception_logs_p (v_usero,'testerror',v_erroeline,v_sqlcode, v_sqlerrm); 
          RAISE; 
    end;
    commit;
    end;
    /
  • 相关阅读:
    复用$.ajax方式传递参数错误处理
    JS传递函数并且调用
    JQuery $.axaj的基本格式
    AOP的具体实践-简化结果返回的处理
    将Python打包成可执行文件exe的心路历程
    通过Python实现一个文档的半自动录入工具
    我的第一次实习感悟
    docker nginx+php-fpm+mysql
    使用Harbor搭建docker私服
    python 验证码获取后处理降噪、灰度、保存
  • 原文地址:https://www.cnblogs.com/yhq1314/p/10613204.html
Copyright © 2011-2022 走看看