oracle事务和锁

在了解oracle锁之前,先来看下在多用户并发操作下数据库可能出现哪些问题?

1、丢失更新

简单的说当操作按照下面的操作进行时便会发生丢失更新的问题

a、会话session1中的一个事务获取一行数据,放入本地内存,并展现给最终用户User1

b、会话session2中的一个事务也获取该行数据,展现给用户User2

c、User1修改该行数据的某个字段x,并提交,此时session1中的事务已经执行

d、User2修改该行数据的某个字段y,并提交,session2中的事务也已经执行

按照上面的操作进行c步骤User1所做的更新被d步骤覆盖,也就是丢失了更新。

此类问题很容易的便会发生,必须由应用的开发人员自己去保证。那么开发人员需要做哪些事情呢?实际上就是使用某种锁策略,共有两种锁策略:乐观锁和悲观锁

悲观锁:

上面的丢失更新问题,在于查询和更新之间有其他的事务完成了更新操作,悲观锁的策略认为查询出来的数据总会被其他的事务修改,因此在查询的时候使用select for update来获得锁定,这样在更新之前保证不会有其他的事务无更新锁定的数据,悲观锁可能造成死锁并且需要和oracle之间维护一个长连接。

乐观锁:

乐观锁假定数据在查出来后不会被其他事务更改,因此在查询的时候不会锁定,直到修改的时候才会去检测冲突。乐观锁的实现方式通常是数据增加版本戳,具体来说就是给表增加一列,可以为number类型也可以为timestamp类型。每当更新数据的时候number+1或者更新timestamp为当前时间,更新的时候对版本戳进行比对,如果不同则说明该条数据已经被更新过,否则说明可以更新。

oracle的多版本控制

oracle多版本控制提供了语句级别的读一致性,同时可以实现非阻塞读。

oracle中的语句级一致性可以保证一个事务只能看到查询开始之前已经提交的事务更改的数据,未提交的事务和查询开始之后提交的数据不会被查询到。

做下面的一个实验来验证一下

数据准备如下:

----------create table-----------------
create table test (id varchar2(128),name varchar2(128));
-----------insert data-----------------------
insert into test values('1','a');
commit;
--------------delete data----------------------
delete test;
commit;
------------query and sleep 10 seconds---------------------------
set serveroutput on;
declare
  cursor cur is select * from test;
  begin
  for rec in cur
  loop
    dbms_output.put_line(rec.name);
    dbms_lock.sleep(10);--中间等待另外一个session启动并执行更新数据操作
   end loop;
   end;
/
------------------------------------------

创建test表,然后插入一条数据,开启两个plsql,在其中的一个plsql中数据查询plsql块如下:

oracle事务和锁_第1张图片

中间让该操作停顿10s中,然后在另外一个plsql中继续插入数据并提交,最后可以看到在第一个plsql中只查询到了一开始我们预置的一条数据,查询期间其他事务所做的更改没有被感知到。

但是在同一个事务中所做的更改会被本次事务可见,简单的一个实验如下:

插入一条数据,但是不提交,在本次事务中依然可以看到插入的本条数据。

oracle事务和锁_第2张图片

上面是事务的一致性问题

下面讲一下最常见的DML lock(数据锁)

DML语句在执行的一开始会自动的获取所操作表的表锁和行锁。下面以TX表示行锁,以TM表示表锁。先来解释一下这两个锁的含义

TX:行锁,也叫事务锁,是一种锁定表中某一行数据的锁。下面的语句会让一个事物获得每一个被修改行的行锁,instert,update,delete,merge,select ... for update。在事务提交和回滚前,锁一直存在。获得行锁的事务也会自动的获得对应修改行所在表的表锁,目的是防止DDL操作带来的冲突,行锁只有排他模式。

TM锁:当出现下面的几个语句时事务将自动获取TM锁,insert,update,delete,merge,select  ... for update。这些DML语句获取表锁为了保留访问表的权限同时阻止ddl操作带来的冲突。可以通过lock table 语句明确的获得一个表锁。

一个表锁可以以下面几种模式被获取:

。row share lock(RS)行共享,也叫subshare table lock(SS),意思是拥有此锁的事务已经锁住了表中的数据行并且试图去更新他们。RS锁是限制最少的表锁,拥有最高的并发度。

。row exclusive lock(RX)行排他,也叫subexclusive table lock(SX),意思是事务已经更新了数据正持有该锁,RX锁允许其他的事务在同一张表中执行query,insert,update,delete或者并发的锁其他行。因此RX锁允许多个事务在同一张表中同时获取RX(非同一行数据)或者RS锁。

。share table lock(S)共享锁,持有该锁的事务允许其他事务查询该表,但是仅在只有一个事务持有该锁的情况下才允许更新。多个事务可能同时持有该锁,因此持有某个表的共享锁的事务不能确保能够修改该表。

。share row exclusive table lock(SRX)共享行独占锁,也叫share-subexclusive table lock(SSX),一种比共享表锁更严格的锁。同一时刻只能有一个事务持有某张表的SRX锁。持有该锁的事务只允许其他事务查询,不能更新。

。exclusive table lock(X)独占表锁,表锁中最严格的一种锁,允许持有该锁的事务拥有排他写的权限。同一时刻只能有一个事务持有X锁。

也可以用下面的语句查询这两个锁的详细情况:

select * from v$lock_type where type in('TM','TX');

一个事务所持有的锁有以下几种:

oracle事务和锁_第3张图片

下面做一个小的实验看看select *  for update语句会获取哪种锁

1、首先执行查询命令 select * from test for update

2、执行下面的plsql

col owner for a12 
col object_name for a16 
select b.owner,b.object_name,l.session_id,l.locked_mode 
from v$locked_object l, dba_objects b 
where b.object_id=l.object_id 
/
查询当前锁的情况,结果如下:

oracle事务和锁_第4张图片

可以看到本次事务中我们锁定了表Test,锁的类型为3,对照上面的lock mode该锁为SX。

下面一组操作演示获取RS锁的过程

1、执行下面的语句

lock table test in row share mode;
2、执行查看锁的语句结果如下:


下面这种图是锁的矩阵向荣图

oracle事务和锁_第5张图片

查询时候的锁

查询可以是显示的比如select语句也可以是隐式的比如大多数的insert,merge,update,delete语句。唯一一个不包含查询子项的dml语句是带有values子句的insert语句。查询的时候是没有锁得,因为查询语句不会干扰到其他的语句。在oracle中这种不带for update的查询语句经常被描述成非阻塞查询。

nowait

nowait的作用是当发生锁冲突的时候立马返回错误信息而不是阻塞的等待。

oracle事务

事务的概念:事务可以看作是由对数据库的若干操作组成的一个单元,这些操作要么都完成,要么都取消,从而保证数据满足一致性的要求。

事务的ACID特性:

原子性( Atomicity):事务中的所有任务都必须执行或者都不执行,不存在部分事务

一致性( Consistency):事务将数据库从一个一致性状态带到另一个一致性状态

隔离性( Isolation):一个事务所带来的影响直到该事务提交之前对其他事务来说都是不可见的

持续性( Durability):经过提交的事务所进行的修改是永久性的

 

oracle中的事务可以由多个DML语句组成但只能有一个DDL语句,这是因为每个DDL语句都会产生依次隐式提交,所以在包含DDL的事务中,DDL语句最好不要放在中间,因为这会隐式的将一个“逻辑工作单元”划分成两个逻辑工作单元。

 

深入的理解ACID中的I,事务的隔离级别简单理解就是一个事务对数据库的修改与并行的另外一个事物的隔离级别

最简单的情况,两个并发的事务同时操作数据库表的相同行时,可能产生下面三个问题:

a、脏读

读取一个未提交的事务称为脏读。比如事务T1更新了一行记录,还未提交所做的修改,这个时候T2读取了更新后的数据,然后T1执行回滚操作,取消刚才的修改,所以T2所读取的行就无效,也就是脏数据。

b、不可重复读

当事务第二次执行同一个查询的时候,由于另一个事务提交了更新而得到了不同结果的时候就发生了不可重复读

比如事务T1读取一行记录,紧接着事务T2修改了T1刚刚读取的记录,然后T1再次查询,会发现与第一次读取的记录不同。

c、幻读:

如果事务中的一个查询第二次执行,但返回了满足条件过滤标准的记录,就认为是发生了幻读。

比如事务T1读取一条指定where条件的语句,返回结果集。此时事务T2插入一行新记录,恰好满足T1的where条件。然后T1使用相同的条件再次查询,结果集中可以看到T2插入的记录,这条新纪录就是幻读的结果。

 

在SQL92标准中,事务隔离级别分为四种,分别为:ReadUncommitted、Read Committed、Read Repeatable、Serializable

随着隔离级别的提升,事务之间的隔离水平也相应提高,使得数据一致性也就越高。

这几种隔离级别允许发生的情况如下:

: 可能出现    ×:不会出现

 

脏读

不可重复读

幻读

Read uncommitted

Read committed

×

Repeatable read

×

×

Serializable

×

×

×

 

上述四种隔离级别依次变高

Read uncommitted(未提交读):一个事务中的会话可以读取其他事务未提交的更新结果,如果这个事务最后已回滚结束,将会造成读取的数据是错误的。

Read committed(提交读):一个事务中的会话只能读取其他事务已经提交的更新结果,但是其他会话可以修该事务中已经读取的记录,因此当该事务再一次读取相同数据时,会出现错误的结果。

Read  repeatable(可重复读):在保证read committed的同时,也保证在一个事务中读取到的数据在整个事务期间不会被修改,也就是允许对某条数据的重复读取,但是不允许其他事务向表中添加记录(有可能出现幻读)。

Serializable(序列化):在保证read repeatable的同时,也保证该表的数据不会被其他的DML操作修改。限制最为严格,同时效率也最低。

 

Oracle中的隔离级别

Oracle中有Read committedSerializableread-only三种隔离级别,read-only事务只能看到事务开始时提交的修改,不允许执行INSERT、UPDATE、DELETE语句。同时oracle默认的隔离级别为Readcommited

 

Oracle中的读一致性:一个查询所获得的数据来自同一时间点。

1、语句级别读一致性

Oracle总是执行语句级别读一致性。这可以确保单个查询返回的所有数据来源于单个时间点(查询开始的时间点)。因而,一个查询看不到脏数据或者查询执行期间其他事务提交的任何改变。查询执行开始后,只有查询开始之前提交的数据可以被查询到。查询不能看到语句开始执行之后提交的任何数据。(举个例子,假如某个查询需要获取特定的数据总共有1W条满足,假设需要5分钟,在查询开始的第三分钟,有一个事务删除了某条满足条件的数据并且已经提交,对于oracle来说最终查到的数据仍然是1W条)

2、事务级别的读一致性

隔离级别为SERIALIZABLE和readonly的事务才支持事务级读一致性,事务中的所有查询语句(select 、update、delete)只读取事务开始之前提交的数据。

 

读数据一致性是相对于脏读来说的,要了解oracle数据读一致性的实现方式,需要先了解下面几个概念

3undo是什么

a.UNDO表空间用于存放UNDO数据。当执行DML操作时,Oracle会将这些操作的旧数据写入UNDO段。管理UNDO数据不仅可以使用回滚段,还可以使用UNDO表空间。

b.UNDO数据的作用:当用户执行DML操作修改数据时,UNDO数据被存放在UNDO段,而新数据则被存放到数据段中,如果事务操作存在问题,就需要回退事务,以取消事物变化。

例如:执行完UPDATE emp SETsal=1000 WHERE empno=7788后,发现应该修改雇员7963的工资,而不是7788.此时应该执行ROLLBACK语句。

c.读一致性

用户检索数据时,ORACLE总是使用户只能看到被提交过的数据,这是由Oracle自动提供的。当用户修改数据,但是没有提交时,另外一个用户使用select语句查找该值时,该值就是从undo表空间中取得的。

d.事务恢复

事务恢复是例程恢复的一部分,它是由OracleServer自动完成的。如果在数据库运行过程中出线历程失败,那么当启动OracleServer时,后台进程SMON会自动执行例程恢复。执行例程恢复时,Oracle会重做所有未应用的记录。然后打开数据库,回退未提交事务。

oracle的多版本用来实现数据的一致性读,简单来讲就是在undo空间存储之前的多个版本的数据,并记录SCN号,每次比较SCN号来获取想要的版本。这样即使正在查询数据也不会阻塞写操作。

你可能感兴趣的:(oracle)