事务

发表时间:2017-05-11 16:50:23 浏览量( 13 ) 留言数( 0 )

一、什么是事务

事务(TRANSACTION)是作为单个逻辑工作单元执行的一系列操作,这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行。事务是一个不可分割的工作逻辑单元 

1、事务的必要性

基本上讲解事务都会举这个例子讲解。银行转帐问题:

我们假定资金从帐户A转到帐户B,转账是一个单一的业务,但是却需要两部数据库操作才能完成:

(1)帐户A的资金减少

(2)然后帐户B的资金相应增加  

我们都知道,一般存款的约束条件都是大于0的,新建这个账户表如下:

create table user_account
(
   ua_id int primary key,
   ua_name varchar2(100),
   ua_money int check(ua_money >=0) 
)

然后插入两条数据

 insert into user_account(ua_id,ua_name,ua_money) values(1,'账号A',200);
insert into user_account(ua_id,ua_name,ua_money) values(2,'账号B',200);

现在从一个客户A转账100到另一个客户B,客户B的金额增加100,客户A的金额减少100。转账成功,这没有问题,因为客户A钱还没有出现少于0;

update  user_account set ua_money=ua_money+100 where ua_id=2;
update  user_account set ua_money=ua_money-100 where ua_id=1;

但是如果现在要转账1000呢,那么我们看看最后结果。客户A的钱没有少,因为它不符合约束,所以操作失败,但是账号B的钱却多了,如果这样的话,银行岂不亏大了。

update  user_account set ua_money=ua_money+1000 where ua_id=2;
update  user_account set ua_money=ua_money-1000 where ua_id=1;

结果如下:

attcontent/1bae491e-6f63-458e-bd99-96ff9f332613.png

帐号A还是100没有变化,帐号B却是1300。

所以一个业务逻辑可以是几个数据库的操作,这个几个操作必须作为一个整体,要么都成功执行,要么就都失败,如果一半成功,一半失败的话,那么整个业务执行就会错乱了。

2、事务的四个特性

事务必须具备以下属性:

  • 原子性(Atomicity):事务是一个完整的操作。事务的各步操作是不可分的(原子的);要么都执行,要么都不执行。

  • 一致性(Consistency):当事务完成时,数据必须处于一致状态。

  • 隔离性(Isolation):对数据进行修改的所有并发事务是彼此隔离的,这表明事务必须是独立的,它不应以任何方式依赖于或影响其他事务

  • 永久性(Durability):事务完成后,它对数据库的修改被永久保持,事务日志能够保持事务的永久性

3、事务和锁

那么数据库如何实现事务呢,这里就需要用到锁的机制了。当执行事务操作的时候(DML语句)时,Oracle会在被作用表上面加表锁.以防止其他用户改变表结构,同时会在被作用行上加行锁,以防止其他事务在应用行上执行DML操作。在Oracle数据库中,为了确保数据库数据的读一致性,不允许其他用户读取脏数据(未提交事务)。我们在操作事务时,数据库为自动加锁。

在执行一段sql语句时,如果确认没有错误,那么我们可以提交事务,这样所有的sql语句都会提交给数据库,如果有问题,那么我们可以回滚事务,这样所有的操作都会回到初始化时候的状态。

4、事务隔离级别

   大部分数据库事务操作都是并发执行的,这就可能遇到下面的几种问题:


   丢失更新:两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失,后果比较严重。一般是由于没加锁的原因造成的。

   脏读(Dirty reads):一个事务A读取到了另一个事务B还没有提交的数据,并在此基础上进行操作。如果B事务rollback,那么A事务所读取到的数据就是不正确的,会带来问题。

   不可重复读(Non-repeatable reads):在同一事务范围内读取两次相同的数据,所返回的结果不同。比如事务B第一次读数据后,事务A更新数据并commit,那么事务B第二次读取的数据就与第一次是不一样的。

   幻读(Phantom reads):一个事务A读取到了另一个事务B新提交的数据。比如,事务A对一个表中所有行的数据按照某规则进行修改(整表操作),同时,事务B向表中插入了一行原始数据,那么后面事务A再对表进行操作时,会发现表中居然还有一行数据没有被修改,就像发生了幻觉,飘飘欲仙一样。

  注意:不可重复读和幻读的区别是,不可重复读对应的表的操作是更改(UPDATE),而幻读对应的表的操作是插入(INSERT),两种的应对策略不一样。对于不可重复读,只需要采用行级锁防止该记录被更新即可,而对于幻读必须加个表级锁,防止在表中插入数据。有关锁的问题,下面会讨论。


    为了处理这几种问题,SQL定义了下面的4个等级的事务隔离级别:

    

    未提交读(READ UNCOMMITTED ):最低隔离级别,一个事务能读取到别的事务未提交的更新数据,很不安全,可能出现丢失更新、脏读、不可重复读、幻读;

    提交读(READ COMMITTED):一个事务能读取到别的事务提交的更新数据,不能看到未提交的更新数据,不会出现丢失更新、脏读,但可能出现不可重复读、幻读;

    可重复读(REPEATABLE READ):保证同一事务中先后执行的多次查询将返回同一结果,不受其他事务影响,不可能出现丢失更新、脏读、不可重复读,但可能出现幻读;

    序列化(SERIALIZABLE):最高隔离级别,不允许事务并发执行,而必须串行化执行,最安全,不可能出现更新、脏读、不可重复读、幻读,但是效率最低。

    隔离级别越高,数据库事务并发执行性能越差,能处理的操作越少。所以一般地,推荐使用REPEATABLE READ级别保证数据的读一致性对于幻读的问题,可以通过加锁来防止。

    MySQL支持这四种事务等级,默认事务隔离级别是REPEATABLE READ。Oracle数据库支持READ COMMITTED 和 SERIALIZABLE这两种事务隔离级别,所以Oracle数据库不支持脏读。Oracle数据库默认的事务隔离级别是READ COMMITTED。



(1)提交事务

使用commit语句可以提交事务。当执行了commit语句之后,会确认事务变化,结束事务,删除保存点,释放锁。这时候其他会话可以看到事务变化后的新数据。

但是有时候我们也不需要显示的commit,因为出现一下情况会自动提交事务: 

  • 执行DDL语句,如create,alter,drop table

  • 执行DCL语句, grant , revoke

  • 退出 sqlplus

(2)回退事务

首先我们先认识一下什么是保存点:保存点是事务回退点, 他用于取消部分事务,当结束事务的时候,会自动删除该事务所定义的所有保存点。

当执行rollback命令的时候,通过指定保存点可以取消部分事务。设置保存点命名格式是:

savepoint 保存点名称

取消部分事务

rollback  to  保存点

这样保存点之前的操作提交,之后的操作取消。

如果要取消全部事务那么可以直接写  

rollback; 

当使用rollback取消事务的时候,会取消所有事务变化,结束事务,删除所有保存点并释放锁。当出现系统灾难或应用程序地址例外的时候,会自动回退其事务变化.

二、示例操作

1、先打开sql plus测试。

(1)设置保存点。

  savepoint abc;

(2)删除用户。

  delete from user_account where ua_id=1;

 这时候你查询用户帐号A已经删除了。但是使用其他的客户端还是可以看到帐号A的,因为你还没有提交数据。

(3)回滚,如果你现在后悔,那么你可以回滚回去。刚才删除的数据有重新回来了。

  rollback to abc;

(4)再次删除数据。

  delete from user_account where ua_id=1;

(5)提交。这次我们不回滚了,而是提交数据。使用其他客户端也可以看到这条数据已经删除了。

  commit;

2、也可以使用PL/SQL写一个语句块。我们使用事务修改上面的转账操作。代码如下:

--PL/SQL 语句块
begin
   savepoint abc;
   update  user_account set ua_money=ua_money+100 where ua_id=2;
   update  user_account set ua_money=ua_money-100 where ua_id=1;
   --异常,回滚操作
   exception when others then rollback to abc;
end
  commit;

你可以测试一下,这个语句块要么转账成功,要么就会转账失败。