Oracle 9i 10g编程艺术 —— 读书笔记(二)

        4.1 进程全局区和用户全局区 
        系统全局区(System Global Area,SGA)这是一个很大的共享内存段,几乎所有Oracle进程都要访问这个区中的某一点。进程全局区(Process Global Area,PGA)这是一个进程或线程专用的内存,其他进程/线程不能访问。用户全局区(User Global Area,UGA)这个内存区与特定的会话相关联。它可能在SGA中分配,也可能在PGA中分配,这取决于是用共享服务器还是用专用服务器来连接数据库。如果使用共享服务器,UGA就在SGA中分配;如果使用专用服务器,UGA就会在PGA(即进程内存区)中。

        PGA不会在Oracle的SGA中分配,而总是由进程或线程在本地分配。
UGA就是你的会话的状态,你的会话总能访问这部分内存。
UGA的位置完全取决于你如何连接Oracle。如果通过一个共享服务器连接,UGA肯定存储在每个共享服务器进程都能访问的一个内存结构中,也就是SGA中。
PGA包含进程内存,还可能包含UGA,以及其他用于排序、位图合并以及散列。

       有两种办法来管理PGA中的这些非UGA内存:手动PGA内存管理和自动PGA内存管理

       系统全局区(SGA)分为不同的池java池:Java池是为数据库中运行的JVM分配的一段固定大小的内存。在Oracle10g中,Java池可以在数据库启动并运行时在线调整大小。大池:共享服务器连接使用大池作为会话内存,并行执行特性使用大池作为消息缓冲区,另外RMAN备份可能使用大池作为磁盘I/O缓冲区。在Oracle 10g 和 9i Release 2中,大池都可以在线调整大小。共享池:共享池包含共享游标(cursor)、存储过程、状态对象、字典缓存和诸如此类的大量其他数据。在Oracle 10g和9i中,共享池都可以在线调整大小。流池:这是Oracle流(Stream)专用的一个内存池,Oracle流是数据库中的一个数据共享工具。这个工具是Oracle 10g中新增的,可以在线调整大小。如果未配置流池,但是使用了流功能,Oracle会使用共享池中至多10%的空间作为流内存。"空"池:这个池其实没有名字。这是块缓冲区(缓存的数据库块)、重做日志缓冲区和"固定SGA"区专用的内存。

        5.连接与会话的不同,连接:是从客户到Oracle实例的一条物理路径,是进程。会话:是实例中存在的一个逻辑实体在一条连接上可以建立0个、一个或多个会话。disconnect是退出会话,exit是断开连接

查看会话
select * from v$session
查看连接
select * from v$process
查看会话所在的连接
select p.* from v$process p , v$session s
where p.ADDR = s.PADDR and s.userName = '登录名' and s.STATUS = 'ACTIVE'
 
        6.1 锁
1.应该延迟到适当的时刻才提交。不要太快提交,以避免对系统带来压力。这是因为,如果事务很长或很大,一般不会对系统有压力。相应的原则是:在必要时才提交,但是此前不要提交。事务的大小只应该根据业务逻辑来定。
2.只要需要,就应该尽可能长时间地保持对数据所加的锁。这些锁是你能利用的工具,而不是让你退避三舍的东西。锁不是稀有资源。恰恰相反,只要需要,你就应该长期地保持数据上的锁。锁可能并不稀少,而且它们可以防止其他会话修改信息。
3.在Oracle中,行级锁没有相关的开销,根本没有。不论你是有1个行锁,还是1 000 000个行锁,专用于锁定这个信息的“资源”数都是一样的。当然,与修改1行相比,修改1 000 000行要做的工作肯定多得多,但是对1 000 000行锁定所需的资源数与对1行锁定所需的资源数完全相同,这是一个固定的常量。
4.不要以为锁升级"对系统更好"(例如,使用表锁而不是行锁)。在Oracle中,锁升级(lock escalate)对系统没有任何好处,不会节省任何资源。也许有时会使用表锁,如批处理中,此时你很清楚会更新整个表,而且不希望其他会话锁定表中的行。但是使用表锁绝对不是为了避免分配行锁,想以此来方便系统。
5.可以同时得到并发性和一致性。每次你都能快速而准确地得到数据。数据读取器不会被数据写入器阻塞。数据写入器也不会被数据读取器阻塞。这是Oracle与大多数其他关系数据库之间的根本区别之一。

        6.2 悲观锁和乐观锁
        丢失更新的问题,一个会话获得某一行,并准备做更新,另一个会话也获得这一行,并也准备做更新。
结构前一次的更新将会丢失。锁定策略:悲观锁和乐观锁。悲观锁的解决方法是加行级锁,for update nowait 行被锁定后,不允许其他会话更新,但可以读乐观锁,即把所有锁定都延迟到即将执行更新之前才做。在应用中同时保留旧值和新值,即应用本身会存储行的所有“前”(before)映像。
如 test表中列名为content, 其中某一个行的值为'aaa' ;
启一个会话来修改 :update test set content = 'bbb' where content = 'aaa';
另一个会话 :update test set content = 'ccc' where content = 'aaa';
第一个会修改成功,第一个更新0行,但执行第二次的用户就必须重新操作一次。
实现乐观锁还有其他几个方法
1.用一列来记录版本号,一般用NUMBER或DATE/TIMESTAMP
如 create table test(t_id varchar2(20), content varchar2(50), last_mod timestamp with time zone default systimestamp);
timestamp with time zone 数据类型在Oracle中精度最高,通常可以精确到微秒(百万分之一秒)。
修改时 update test set content = 'gggg' where last_mod = to_timestamp_tz(:last_mod);
2.使用一个校验和或散列值,通过原来的数据计算得出的。
3.使用新增的Oracle 10g 特性ORA_ROWSCN。因为SCN只会推进,不会后退。
ORA_ROWSCN建立在内部oracle系统时钟(SCN)基础上,在oracle中,每次提交时,SCN都很推进。
得注意的是:除非是创建表时支持在行级维护ORA_ROWSCN,否则Oracle会在块级维护,
也就是在默认情况下,一个块上的多行会共享相同的ORA_ROWSCN值。若修改某一行,在这些行的SCN都会推进。

select  dbms_rowid.rowid_block_number(rowid) blockno, ora_rowscn   from  test;
-- -------------------------------------------------------------------------------
1      546      393855
2      546      393855
3      546      393855
4      546      393855
5      546      393855
6      546      393855
7      546      393855  

发现这些数据都在同一个块中,并且ORA_ROWSCN是在块级维护,这就会出现上面所说的问题,解决的方法只能启用ROWDEPENDENCIES再重建这张表。
create table test(deptno, dname, loc, data, constraint dept_pk primary key(deptno))
rowdependencies as select deptno, dname, loc , rpad('*', 3500,'*')
from scott.dept; 这样ORA_ROWSCN就是行级维护了,不过要重建表,不太适用。
SRC可以近似的转换为时钟时间(有+/-3秒的偏差)

悲观锁还是乐观锁 主要看应用的用户数量,若用户量只有几十或几百,悲观锁是首选,否则就选乐观锁,其中就以时间戳为首选。

尽管Oracle中有无限个行级锁,但是enqueue锁(这是一种队列锁)的个数是有限的,譬如在会话中插入大量行,而没有提交,可能就会发现创建了太多的enqueue队列锁,而耗尽了系统的队列资源,因为每行都会创建另一个enqueue锁,就这需要增大ENQUEUE_RESOURCES参数的值。

        6.2死锁
        Oracle认为应用死锁都是应用自己导致的错误,而且在大多数情况下,Oracle的这种看法都是正确的。
导致死锁的头号原因是外键未加索引:如果更新了父表的主键(一般不会发生),由于外键上没有索引,所以子表会被锁定。如果删除了父表的一行,由于外键上没有索引,整个子表也会被锁定。这些全表锁还是可能(而且确实会)导致很严重的锁定问题。

         索引
select * from dept, emp where emp.deptno = dept.deptno and dept.deptno = :X;

Oracle不会升级锁,但是它会执行锁转换(lock conversion)或锁提升(lock promotion),这些词通常会与锁升级混淆。
例如,如果用FOR UPDATE子句从表中选择一行,就会创建两个锁。一个锁放在所选的行上(这是一个排他锁;任何人都不能以独占模式锁定这一行)。另一个锁是ROW SHARE TABLE锁,放在表本身上。这个锁能防止其他会话在表上放置一个排他锁,举例来说,这样能相应地防止这些会话改变表的结构。另一个会话可以修改这个表中的任何其他行,而不会有冲突。假设表中有一个锁定的行,这样就可以成功执行尽可能多的命令。

在Oracle中,行级锁来说,1个锁的开销与1 000 000个锁是一样的,都没有开销。
修改时的工作肯定不同,但对1000000行锁定所需的资源数与对1行锁定所需的资源完全相同,这是一个固定的常量。

       6.3 锁类型
       分为三种锁:
       1.DML锁:指行级锁或表级锁
       TX(事务锁):
       有些数据库都有一个传统的锁管理器,锁定行时会先找到那一行的地址,在锁管理器中排队(锁管理器必须是串行化的,也是常见的内存结构),排到时锁定该列表,搜索列表,查看别人是否已经锁定了这一行,若没有则在列表创建一个新的条目,表明你已锁定了这一行。接着修改行内容,当提交时得再次在锁管理器中排队,排到时锁住列表,在这个列表中搜索到刚才新增的条目并释放,最后对列表解锁。而Oracle对数据行锁定时,行指向事务ID的一个副本,事务ID存储在包含数据的块中,释放锁时,事务ID却会保留下来,这个事务ID是事务所独有的,表示了回滚段号,槽和序列号。事务ID会留在包含数据行的块上,可以告诉其他会话你已锁定这个数据。
例如:
先对test表做修改但不commit
update test set loc = 'one' where deptno = 45;
然后执行

select  username, v$lock.sid, trunc(id1 / power ( 2 , 16 )) rbs, bitand(id1, to_number( ' ffff ' ' xxxx ' )) + 0  slot,id2 seq
, lmode, request 
from  v$lock, v$session  where  v$lock.TYPE  =   ' TX '   and  v$lock.sid  =  v$session.sid
and  v$session.username  =   USER ;
USERNAME                              SID        RBS       SLOT        SEQ      LMODE    REQUEST
-- ---------------------------- ---------- ---------- ---------- ---------- ---------- ----------
LSTSYS                                 145            4           13          328            6            0

对v$lock表中id1,id2做了些处理的得到刚才那个事务ID -- RBS, SLOT, SEQ(4,13,328)
v$lock的LMODE为6代表是一个排他锁,REQUEST为0无对它的请求,没有等待。
要一提的是Oracle不会在任何地方存储行级锁的列表,所以上面的update不论更新了几行在v$lock只有一条对应记录。
select xidusn, xidslot, xidsqn from v$transaction;
v$transaction对应每个活动事务都包含一个条目,上面的查询结果4,13,328 与上面的事务ID一致。

这时开启另一个会话,执行update test set loc = 'two' where deptno = 45;
在查询v$session和v$lock得到结果
USERNAME                              SID        RBS       SLOT        SEQ      LMODE    REQUEST
-- ---------------------------- ---------- ---------- ---------- ---------- ---------- ----------
LSTSYS                                 145            4           13          328            6            0
LSTSYS                                
151            4           13          328            0            6


SID为151的是我后开启的会话,它的REQUEST为6代表它正在对一个排他锁请求,而它请求行的事务ID正是前一个会话的事务ID。
既代表SID 145阻塞SID 151。突然想到前面在第三章记录的锁等待的情况,若这个时候第一个会话非正常退出,比如掉线,第一个会话的事务无法提交,第二个事务无限制等待下去,那么就可通过这个查询来得到应该被kill掉的SID,既145。

       2.DDL锁:如CREATE和ALTER语句等。DDL锁可以保护对象结构定义。

       3.内部锁和闩:
       Oracle解析一个查询并生成优化的查询计划时,它会把库缓存“临时闩”,将计划放在那里,以供其他会话使用。
闩(latch)是Oracle采用的一种轻量级的低级串行化设备,功能上类似于锁。不要被“轻量级”这个词搞糊涂或蒙骗了,你会看到,闩是数据库中导致竞争的一个常见原因。轻量级指的是闩的实现,而不是闩的作用。许多请求者可能会同时等待一个闩,你会看到一些进程等待的时间比其他进程要长一些。闩的分配相当随机,这要看运气的好坏。闩释放后,紧接着不论哪个会话请求闩都会得到它。等待闩的会话不会排队,只是一大堆会话在不断地重试。
队列锁(enqueue),这是一种更复杂的串行化设备,与闩的区别在于,队列锁允许请求者“排队”等待资源。对于闩请求,请求者会话会立即得到通知是否得到了闩。而对于队列锁,请求者会话会阻塞,直至真正得到锁。因此,队列锁没有闩快,但是它确实能提供闩所没有的一些功能。

锁的总结:
TX锁:修改数据的事务在执行期间会获得这种锁
TM锁和DDL锁:在你修改一个对象的内容(对应TM锁)或对象本身(对应DDL锁)时,这些锁可以确保对象的结构不被修改。
闩(latch):这是Oracle的内部锁,用来协调对其共享数据结构的访问。

        7.并发与多版本
隔离级别:                                      脏读                              不可重复读            幻象读
READ UNCOMMITTED                 允许                                   允许                  允许
READ COMMITTED                                                                  允许                  允许
REPEATABLE READ                                                                                          允许
SERIALIZABLE

Oracle中只支持READ COMMITTED和SERIALIZABLE。

脏读:指读取发生修改还未提交的数据
不可重复读:在T1时间读取某一行,在T2时间再读取这一会,它可能已被修改。
幻象读:在T1时间执行一个查询,在T2时间再执行这个查询,可能会得到新插入的数据。
READ UNCOMMITTED的隔离级别允许脏读,它的根本目标是提供一个基于标准的定义以支持非阻塞读,在oracle中默认地提供了非阻塞读,即读不会被写阻塞。所以Oracle中根本不需要脏读。

READ COMMITTED的隔离级别是指事务只能读取数据库中已提交的数据,与脏读恰恰相反。它允许不可重复读(在同一事务中重新读取同一行可能返回不同的答案)和幻象读(与事务早期相比,查询不光能看到已提交的行,还可看到新插入的行),这是Oracle的默认模式,其实不论READ UNCOMMITTED和READ COMMITTED,Oracle由于使用多版本和读一致,不会有差别。

REPEATABLE READ的隔离级别是不仅能给出一致的正确答案,还能避免丢失更新(非Oracle)。
Oracle中并未使用共享锁来实现可重复读,共享锁是讲需读取的行锁定,虽然能保证读一致性,但并发性太差。
Oracle从不使用共享锁,而是选择了多版本机制,同时保证了读一致性和高并发性。

SERIALIZABLE指的是最受限制的隔离级别,但是它也提供了最高程度的隔离性。获得的结果并非在查询开始的那个时间点,而是在事务开始的那一刻就固定了,Oracle会使用回滚段按事务开始时数据的原样来重建数据。

在java设置事务隔离级别:
Connection con = ...
con.setTransactionIsolation(int level);
也可以查看当前数据库级别int level = con.getTransactionIsolation();
jdbc api提供了5种数据库事务隔离操作:
static int TRANSACTION_NONE = 0;
static int TRANSACTION_READ_UNCOMMITTED = 1;
static int TRANSACTION_READ_COMMITTED = 2;
static int TRANSACTION_REPEATABLE_READ = 4;
static int TRANSACTION_SERIALIZABLE = 8;
若用hibernate可设置hibernate.connection.isolation

        7.3多版本带来的影响
        书中举了个例子:
先开启两个会话:session1  session2
在session1中
create table test(test_id varchar2(10) primary key, num number);
insert into test values('1', 1);

然后设置隔离级别
alter session set isolation_level=serializable;
观察执行的IO次数
set autotrace on statistics
select * from test;

Statistics
-- --------------------------------------------------------
           0   recursive calls
          
0   db block gets
          
7   consistent gets
          
0   physical reads
          
0   redo size
        
518   bytes sent via SQL * Net  to  client
        
385   bytes received via SQL * Net  from  client
          
2   SQL * Net roundtrips  to / from  client
          
0   sorts (memory)
          
0   sorts ( disk )
          
2   rows processed

-- consistent gets表示用了7个IO

这时在session2中执行

BEGIN  
for  i  in   1  ..  10000
loop 
   
update  test  set  num  =  i  +   1 ;
  
commit ;
  
end  loop;
end ;

执行完毕后。
再回到session1中
再次查询test
select * from test;
得到的结果

Statistics
-- --------------------------------------------------------
           0   recursive calls
          
0   db block gets
      
20003   consistent gets
          
0   physical reads
          
0   redo size
        
518   bytes sent via SQL * Net  to  client
        
385   bytes received via SQL * Net  from  client
          
2   SQL * Net roundtrips  to / from  client
          
0   sorts (memory)
          
0   sorts ( disk )
          
2   rows processed

发现IO增加到20003

这是因为Oracle回滚了对该数据库块的修改。在运行第二个查询时,Oracle获取查询和处理的所有块都必须针对事务开始的那个时刻。到达缓冲区时,发现另一个会话已经把这个块修改了10000次,查询无法看到这些修改,所以它开始查询undo信息,并撤销上一次做的修改,这个工作会反复进行,直至发现事务开始时的那个版本,这才是所需要的,也是在serializable隔离级别下的情况。这种情况在READ_COMMITTED也会出现。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值