欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Oracle --事务,锁

程序员文章站 2022-03-09 19:43:38
...
-- 事务的应用案例
-- 创建一张账户表
DROP TABLE T_ACCOUNT;
create table t_account (
       -- 编号(主键)
       f_id int primary key,
       -- 户名
       f_name varchar2(30) not null,
       -- 金额
       f_money number default 0
);
-- 添加数据到表中
delete from t_account;
insert into t_account values(1, 'jack', 20000);
insert into t_account values(2, 'rose', 5000);

SELECT * FROM T_ACCOUNT;

-- 开始转账(jack转账给rose)
declare
   turnMoney number;
   -- 辅助变量,用来存放除的结果
   devision number;
begin
   --& 用于接收用户输入的数据,后面接提示文字
   turnMoney:='&请输入要转账的金额';
   --jack的账户金额减少turnMoney
   update t_account set f_money = f_money - turnMoney where f_name='jack';
   --在转账的过程中出现异常
   devision:= 1/0;
   update t_account set f_money = f_money + turnMoney where f_name='rose';
   commit; -- 在业务的所有的操作都完成后提交事务
   exception -- 异常处理
          when others then -- 捕获所有错误(对于更多异常的捕获会在后续的课程中讲解)
            dbms_output.put_line('出现错误:' || SQLERRM || ' 之前的操作回滚');
            --rollback; -- 回滚所有的操作     
end;

/*
  引例:
     刚才我们通过rollback能够回滚之前所有的操作,但有时候我们并不需要这样做.就像
     咱们现实生活中的工作一样,比如我们上午做了一些任务,而且我们可以确定上午的这些任务是没有问题的.
     如果我们在下午的任务出现问题了,肯定是不可能把上午的任务撤销的。事务当中也是同样的道理。这个时候我们
     就可以使用事务保存点来解决我们的问题
*/
-- 事务保存点案例
-- 给员工加工资
-- 创建一个测试表
create table t_emp as
select * from emp;

declare
  money number;
   -- 辅助变量,用来存放除的结果
   devision number;
begin
  money:= '&请输入涨工资的金额';
  -- 为SMITH,FORD,JAMES,MILLER,JONES涨工资
  update t_emp set sal = sal + money where ename = 'SMITH';
  update t_emp set sal = sal + money where ename = 'FORD';
  update t_emp set sal = sal + money where ename = 'JAMES';
  update t_emp set sal = sal + money where ename = 'MILLER';
  -- 设置保存点
  savepoint a;
  -- 出现错误
  devision:= 1/0;
  update t_emp set sal = sal + money where ename = 'JONES';
  commit; -- 提交所有的步骤
  exception
      when others then
          dbms_output.put_line('出现错误:'||SQLERRM);         
          rollback to a;
  end;

--设置事务(注意设置事务时当前的事务必须是已经提交的状态)
 --设置事务为只读
 CREATE TABLE T_EMP AS SELECT * FROM EMP
 set transaction read only; -- 设置当前事务处理集为只读
 update t_emp set sal = sal + 1000 where ename = 'JONES'; -- 事务设置为只读以后当前数据更新操作将失败
  -- 设置当前事务处理集为可读,可写
 set transaction read write;
 update t_emp set sal = sal + 1000 where ename = 'JONES';
 -- 注意:当遇到commit或rollback后当前事务集结束
 
 --设置事务自动提交(系统命令 在黑窗口执行)
 set autocommit on; -- 当执行数据的更新操作时,事务会自动提交
 --关闭事务的自动提交功能
 set autocommit off; -- 当执行数据的更新操作时,事务不会自动提交
 -- 查看autocommit的状态
 show autocommit;
-- 在以下情况会自动提交事务
   DDL(create,alter,drop,TRUNCATE 等)语句
   DCL(如:grant,revoke 等)
   会话异常结束或系统异常会导致自动回滚
   
-- 锁的案例
   /*
   当Oracle执行DML(insert(表有主键)|update|delete)语句时,
   系统自动在所要操作的表上申请表级类型的锁。
   当表级锁获得后,系统再自动申请行级类型的锁。
   */
   -- 接下来的案例建议在command window 中演示
   create table t_user(f_id int primary key,f_name varchar2(30));
   --数据准备
    insert into t_user values(1,'rose');
    insert into t_user values(2,'张三');
   --当Oracle执行DML(insert(表有主键)|update|delete)语句时会产生阻塞
   --在A窗口中执行
           update t_user set f_name='roseAndjack' where f_id = 1;
   --在B窗口中执行(在B窗口中会造成阻塞)
           update t_user set f_name='roseAndjack' where f_id = 1; 
 --排他锁的使用
 select * from 表名 [where] for update
  /*用户发出这条命令之后,oracle将会对返回集中的数据建立行级*,以防止其他用户的修改。
  如果此时其他用户对上面返回结果集的数据进行dml或ddl操作都会返回一个错误信息或发生阻塞。
  1:对返回结果集进行update或delete操作会发生阻塞。
  2:对该表进行ddl操作将会报:ORA-00054: 资源正忙, 但指定以 NOWAIT 方式获取资源, 或者超时失效.
  原因分析
  此时Oracle已经对返回的结果集上加了排它的行级锁,
  所有其他对这些数据进行的修改或删除操作都必须等待这个锁的释放,
  产生的外在现象就是其他的操作将发生阻塞,除非这个操作commit或rollback.
  同样这个查询的事务将会对该表加表级锁,
  不允许对该表的任何ddl操作,
  否则将会报出ora-00054错误::resource busy and acquire with nowait specified.
  */
   --在A窗口中执行
  select * from t_user for update;
   --在B窗口中执行
  alter table t_user drop column f_id; -- 这是会在B窗口中报错 ORA-00054
   
 -- 死锁案例
 --任务A,B,资源1,2。任务A独占了资源1,任务B独占了资源2。此时,任务A要资源2,向任务B提出请求并等待。
 --任务B要求资源1,并且也等待。AB两者均不释放所占有的资源,就造成了死锁。
 --用户订单表
 create table t_order(f_id int primary key,f_description varchar2(30),f_user_id int);
 insert into t_order values(1,'Java Vip课程', 1);
--1)在A窗口中执行
    update t_user set f_name ='RJ' where f_id = 1; 
--2)在B窗口中执行
   update t_order set f_description='test' where f_id = 1;  
--3)在A窗口中执行
   update t_order set f_description='test' where f_id = 1;
--4)在B窗口中执行
   update t_user set f_name ='RJ' where f_id = 1; 
-- 这个时候在A窗口会出现错误:
          --ORA-00060: 等待资源时检测到死锁
  /*如何解决死锁
     在Oracle中,存在某种轮询的机制
     一旦发生死锁,就将一个session抛出00060错误。
     最好的办法是从设计解决这个问题:
     设计时,要注意多用户并发处理时,不同用户间的相互影响,特别是相互之间对row(行),表的锁影响。
     提高对sql语句产生锁情况的了解,避免产生死锁的可能性。
   */
   
   --查询锁表的进程
   select object_name,machine,s.sid,s.serial#
   from v$locked_object l,dba_objects o ,v$session s
   where l.object_id = o.object_id and l.session_id=s.sid;
   
   --kill进程解锁
   alter system kill session 'sid,serial#';  --sid serial# 就是上个sql查询语句的字段sid serial#