qbqqq 发表于 2011-12-23 01:32

一个高人写的oracle资料

转自:<a href="http://http://blogold.chinaunix.net/u/16952/showart.php?id=430561" target="_blank" target="_blank">http://blogold.chinaunix.net/u/16952/showart.php?id=430561</a><br><br>

<p style="text-indent: 24pt;">我们从一个用户请求开始讲<span lang="EN-US">,ORACLE</span>的完整的工作机制是怎样的<span lang="EN-US">,</span>首先一个用户进程发出一个连接请求<span lang="EN-US">,</span>如果使用的是主机命名或者是本地服务命中的 主机名使用的是机器名<span lang="EN-US">(</span>非<span lang="EN-US">IP</span>地址<span lang="EN-US">)</span>,那么这个请求都会通过<span lang="EN-US">DNS</span>服务器或<span lang="EN-US">HOST</span>文件的服务名解析然后传送到<span lang="EN-US">ORACLE</span>监听进程<span lang="EN-US">,</span>监听进程接收到用户 请求后会采取两种方式来处理这个用户请求<span lang="EN-US">,</span>下面我们分专用服务器和共享服务器分别采用这两种方式时的情况来讲:</p><p style="text-indent:24.0pt;mso-char-indent-count:2.0"><br></p>

<p style="text-indent: 24pt;">专用服务器模式下:一种方式是监听进程接收到用户进程请求后,产生一个新的专用服务器进程,并且将对用户进程的所有控制信息传给此服务器进程,也就
是说新建的服务器进程继承了监听进程的信息,然后服务器进程给用户进程发一个<span lang="EN-US">RESEND</span>包,通知用户进程可以开始给它发信息了,用户进程给这个新建的服
务器进程发一个<span lang="EN-US">CONNECT</span>包,服务器进程再以<span lang="EN-US">ACCEPT</span>包回应用户进程,致此,用户进程正式与服务器进程确定连接。我们把这种连接叫做<span lang="EN-US">HAND- OFF</span>连接,也叫转换连接。另一种方式是监听进程接收到用户进程的请求后产生一个新的专用服务器进程,这个服务器进程选用一个<span lang="EN-US">TCP/IP</span>端口来控制与用 户进程的交互,然后将此信息回传给监听进程,监听进程再将此信息传给用户进程,用户进程使用这个端口给服务器进程发送一个<span lang="EN-US">CONNECT</span>包,服务器进程再 给用户进程发送一个<span lang="EN-US">ACCEPT</span>包,致此,用户进程可以正式向服务器进程发送信息了。这种方式我们叫做重定向连接。<span lang="EN-US">HAND-OFF</span>连接需要系统平台具有 进程继承的能力,为了使<span lang="EN-US">WINDOWS
NT/2000</span>支持<span lang="EN-US">HAND-OFF</span>必须在<span lang="EN-US">HKEY_LOCAL_MACHINE&gt;SOFTWARE&gt;ORACLE&gt;HOMEX</span>中设置<span lang="EN-US"> USE_SHARED_SOCKET</span>。</p><p style="text-indent:24.0pt;mso-char-indent-count:2.0"><br></p>

<p style="text-indent: 24pt;">共享服务器模式下:只有重定向连接的方式,工作方式是监听进程接收到用户进程的请求后产生一个新的调度进程,这个调度进程选用一个<span lang="EN-US">TCP/IP</span>端口 来控制与用户进程的交互,然后将此信息回传给监听进程,监听进程再将此信息传给用户进程,用户进程使用这个端口给调度进程发送一个<span lang="EN-US">CONNECT</span>包,调度 进程再给用户进程发送一个<span lang="EN-US">ACCEPT</span>包,致此,用户进程可以正式向调度进程发送信息了。可以通过设置<span lang="EN-US">MAX_DISPIATCHERS</span>这个参数来确定调 度进程的最大数目,如果调度进程的个数已经达到了最大,或者已有的调度进程不是满负荷,监听进程将不再创建新的调度进程,而是让其中一个调度进程选用一个<span lang="EN-US"> TCP/IP</span>端口来与此用户进程交互。调度进程每接收一个用户进程请求都会在监听进程处作一个登记,以便监听进程能够均衡每个调度进程的负荷,所有的用户
进程请求将分别在有限的调度进程中排队,所有调度进程再顺序的把各自队列中的部分用户进程请求放入同一个请求队列,等候多个<span lang="EN-US">ORACLE</span>的共享服务器进程
进行处理(可以通过<span lang="EN-US">SHARED_SERVERS</span>参数设置共享服务器进程的个数),也就是说所有的调度进程共享同一个请求队列,共享服务器模式下一个实例
只有一个请求队列,共享服务器进程处理完用户进程的请求后将根据用户进程请求取自不同的调度进程将返回结果放入不同的响应队列,也就是说有多少调度进程就 有多少响应队列,然后各个调度进程从各自的响应队列中将结果取出再返回给用户进程。</p><p style="text-indent:24.0pt;mso-char-indent-count:2.0"><br></p>

<p style="text-indent: 24pt;">以上我们讲完了用户与<span lang="EN-US">ORACLE</span>的连接方式,下面我们要讲<span lang="EN-US">ORACLE</span>服务器进程如可处理用户进程的请求,当一个用户进程发出了一条<span lang="EN-US">SQL</span>语 名:<span lang="EN-US">UPDATE TABBLEA SET SALARY=SALARY*2</span>;首先,服务器进程把这条语句的字符转换成<span lang="EN-US">ASCII</span>等效数字码,接着这个<span lang="EN-US">ASCII</span>码被传递给一个<span lang="EN-US">HASH</span>函数,并返回 一个<span lang="EN-US">HASH</span>值,服务器进程将到<span lang="EN-US">SHARED POOL </span>的共享<span lang="EN-US">PL/SQL</span>区去查找是否存在同样的<span lang="EN-US">HASH</span>值,如果存在,服务器进程将使用这条语句已高速缓存在<span lang="EN-US">SHARED POOL</span>中的已分析过的版本来执行,如果不存在,服务器进程将对该语句进行语法分析,首先检查该语句的语法的正确性,接着对语句中涉及的表、索引、视图等
对象进行解析,并对照数据字典检查这些对象的名称以及相关结构,并根据<span lang="EN-US">ORACLE</span>选用的优化模式以及数据字典中是否存在相应对象的统计数据和是否使用了
存储大纲来生成一个执行计划或从存储大纲中选用一个执行计划,然后再用数据字典核对此用户对相应对象的执行权限,最后生成一个编译代码。<span lang="EN-US">ORACLE</span>将这 条语名的本身实际文本、<span lang="EN-US">HASH</span>值、编译代码、与此语名相关联的任何统计数据和该语句的执行计划缓存在<span lang="EN-US">SHARED POOL</span>的共享<span lang="EN-US">PL/SQL</span>区。服务器进程通过<span lang="EN-US">SHARED POOL </span>锁存器来申请可以向哪些共享<span lang="EN-US">PL/SQL</span>区中缓存这此内容,也就是说被<span lang="EN-US">SHARED POOL</span>锁存器锁定的<span lang="EN-US">PL/SQL</span>区中的块不可被覆盖,因为这些块可能被其它进程所使用。在<span lang="EN-US">SQL</span>分析阶段将用到<span lang="EN-US">LIBRARY CACHE</span>,从数据字典中核对表、视图等结构的时候,需要将数据字典从磁盘读入<span lang="EN-US">LIBRARY CACHE</span>,因此,在读入之前也要使用<span lang="EN-US">LIBRARY CACHE</span>锁存器来申请用于缓存数据字典。</p><p style="text-indent:24.0pt;mso-char-indent-count:2.0"><br></p>

<p style="text-indent: 24pt;">生成编译代码之后,接着下一步服务器进程要准备开始更新数据,服务器进程将到<span lang="EN-US">DB BUFFER</span>中查找是否有相关对象的缓存数据,下面分两个可能进行解释:</p><p style="text-indent:24.0pt;mso-char-indent-count:2.0"><br></p>

<p style="text-indent: 24pt;">如果没有,服务器进程将在表头部请求一些行锁,如果成功加锁,服务器进程将从数据文件中读这些行所在的数据块放入<span lang="EN-US">DB BUFFER</span>中空闲的区域或者覆盖已被挤出<span lang="EN-US">LRU</span>列表的非脏数据块缓冲区,并且排列在<span lang="EN-US">LRU</span>列表的头部,如果这些非脏数据缓冲区写完也不能满足新数据的请 求时,会立即触发<span lang="EN-US">DBWN</span>进程将脏数据列表中指向的缓冲块写入数据文件,并且清洗掉这些缓冲区,来腾出空间缓冲新读入的数据,也就是在放入<span lang="EN-US">DB BUFFER</span>之前也是要先申请<span lang="EN-US">DB BUFFER</span>中的锁存器,成功锁定后,再写入<span lang="EN-US">DB BUFFER</span>,然后服务器程将该语句影响的被读入<span lang="EN-US">DB BUFFER</span>块中的这些行的<span lang="EN-US">ROWID</span>及将要更新的原值和新值及<span lang="EN-US">SCN</span>等信息逐条的写入<span lang="EN-US">REDO LOG BUFFER</span>,在写入<span lang="EN-US">REDO LOG BUFFER</span>之前也是先请求<span lang="EN-US">REDO LOG BUFFER</span>块的锁存器,成功锁定之后才开始写入,当写入达到<span lang="EN-US">REDO
LOG BUFFER</span>大小的三分之一或写入量达到<span lang="EN-US">1M</span>或超过三秒后或发生检查点时或者<span lang="EN-US">DBWN</span>之前发生,<span lang="EN-US">LGWR</span>将把<span lang="EN-US">REDO LOG BUFFER</span>中的数据写入磁盘上的重做日志文件,已被写入重做日志文件的<span lang="EN-US">REDO LOG BUFFER</span>中的块上的锁存器被释放,并可被后来写入的信息所覆盖,<span lang="EN-US">REDO
LOG BUFFER</span>以循环的方式工作。当一个重做日志文件写满后,<span lang="EN-US">LGWR</span>将切换到下一个重做日志文件,如果是归档模式,归档进程还将前一个写满的重做日志进程
写入归档日志文件,重作日志文件也是循环工作方式。写完所有的<span lang="EN-US">REDO LOG BUFFER</span>之后,服务器进程开始改写这个<span lang="EN-US">DB BUFFER</span>块头部的事务列表并写入<span lang="EN-US">SCN</span>,然后<span lang="EN-US">COPY</span>包含这个块的头部事务列表及<span lang="EN-US">SCN</span>信息的数据副本放入回滚段中,我们将回滚段中的副本称为数据块
的<span lang="EN-US">“</span>前映像<span lang="EN-US">”</span>。(回滚段可以存储在专门的回滚表空间中,这个表空间由一个或多个物理文件组成,并专用于回滚表空间,回滚段也可在其它表空间中的数据文件中
开辟。)然后改写这个<span lang="EN-US">DB BUFFER</span>块的数据,并在其头部写入对应的回滚段地址,如果对一行数据多次<span lang="EN-US">UPDATE</span>而不<span lang="EN-US">COMMIT</span>则在回滚段中将会有多个<span lang="EN-US">“</span>前映像<span lang="EN-US">”</span>,除第一个<span lang="EN-US"> “</span>前映像<span lang="EN-US">”</span>含有<span lang="EN-US">SCN</span>信息外,其它的每个<span lang="EN-US">“</span>前映像<span lang="EN-US">”</span>的头部还含有<span lang="EN-US">SCN</span>信息和<span lang="EN-US">“</span>前前映像<span lang="EN-US">”</span>的回滚段地址。一次<span lang="EN-US">UPDATE</span>操作只对应一个<span lang="EN-US">SCN</span>。然后服务 器进程在脏数据列表中建立一条指向此缓冲块的指针。接着服务器进程会从数据文件读入第二个块重复以上读入,记日志,建立回滚段,修改,放入脏列表的动作,
当脏数据列表达到一定长度时,<span lang="EN-US">DBWN</span>进程将脏数据列表中指向的缓冲块全部写入数据文件,也就是释放加在这些<span lang="EN-US">DB BUFER </span>块上的锁存器。其实<span lang="EN-US">ORACLE</span>可以一次从数据文件中读入几个块放入<span lang="EN-US">DB BUFFER</span>,可以通过参数<span lang="EN-US">DB_FILE_MULTIBLOCK_READ_COUNT</span>来设置一次读入的块的个数。</p><p style="text-indent:24.0pt;mso-char-indent-count:2.0"><br></p>

<p style="text-indent: 24pt;">如果要查找的数据已缓存,则根据用户的<span lang="EN-US">SQL</span>操作类型决定如何操作,如果是<span lang="EN-US">SELECT </span>则查看<span lang="EN-US">DB BUFFER</span>块的头部是否有事务,如果有,将从回滚段读取,如果没有则比较<span lang="EN-US">SELECT
</span>的<span lang="EN-US">SCN</span>与<span lang="EN-US">DB BUFFER</span>块头部的<span lang="EN-US">SCN</span>如果比自己大,仍然从回滚段读取,如果比自己小则认这是一个非脏缓存,可以直接从这个<span lang="EN-US">DB
BUFFER</span>块中读取。如果是<span lang="EN-US">UPDATE</span>则即使在<span lang="EN-US">DB
BUFFER</span>中找到一个没有事务,而且<span lang="EN-US">SCN</span>比自己小的非脏缓存数据块,服务器进程仍然要到表的头部对这条记录申请加锁,加锁成功则进行后续动作,如果不
成功,则要等待前面的进程解锁后才能进行动作。</p><p style="text-indent:24.0pt;mso-char-indent-count:2.0"><br></p>

<p style="text-indent: 24pt;">只有当<span lang="EN-US">SQL</span>语句影响的所有行所在的最后一个块被读入<span lang="EN-US">DB BUFFER</span>并且重做信息被写入<span lang="EN-US">REDO LOG BUFFER</span>(仅是指重做日志缓冲,而非重做日志文件)之后,用户才可以发出<span lang="EN-US">COMMIT</span>,<span lang="EN-US">COMMIT</span>触发<span lang="EN-US">LGRW</span>,但并不强制立即<span lang="EN-US">DBWN</span>来释放所有 相应的<span lang="EN-US">DB BUFFER</span>块上的锁,也就是说有可能出现已<span lang="EN-US">COMMIT</span>,但在随后的一段时间内<span lang="EN-US">DBWN</span>还在写这条语句涉及的数据块的情形,表头部的行锁,并不是在<span lang="EN-US"> COMMIT</span>一发出就马上释放,实际上要等到相应的<span lang="EN-US">DBWN</span>进程结束才会释放。一个用户请求锁定另一个用户已<span lang="EN-US">COMMIT</span>的资源不成功的机会是存在的,从<span lang="EN-US"> COMMIT</span>到<span lang="EN-US">DBWN</span>进程结束之间的时间很短,如果恰巧在这个时间断电,由于<span lang="EN-US">COMMIT</span>已触发<span lang="EN-US">LGWR</span>进程,所以这些未来得及写入数据文件的改变会在 实例重启后由<span lang="EN-US">SMON</span>进程根据重做日志文件来前滚。如果未<span lang="EN-US">COMMIT</span>就断电,由于<span lang="EN-US">DBWN</span>之前触发<span lang="EN-US">LGWR</span>,所有<span lang="EN-US">DBWN</span>在数据文件上的修改都会被先一 步记入重做日志文件,实例重启后,<span lang="EN-US">SMON</span>进程再根据重做日志文件来回滚。</p><p style="text-indent:24.0pt;mso-char-indent-count:2.0"><br></p>

<p style="text-indent: 24pt;">如果用户<span lang="EN-US">ROOLBACK</span>,则服务器进程会根据数据文件块和<span lang="EN-US">DB BUFFER</span>中块的头部的事务列表和<span lang="EN-US">SCN</span>以及回滚段地址找到回滚段中相应的修改前的副本,并且用这些原值来还原当前数据文件中已修改但未提交的改变。如
果有多个<span lang="EN-US">“</span>前映像<span lang="EN-US">”</span>,服务器进程会在一个<span lang="EN-US">“</span>前映像<span lang="EN-US">”</span>的头部找到<span lang="EN-US">“</span>前前映像<span lang="EN-US">”</span>的回滚段地址,一直找到同一事务下的最早的一个<span lang="EN-US">“</span>前映像<span lang="EN-US">”</span>为止。一旦发出了<span lang="EN-US"> COMMIT</span>,用户就不能<span lang="EN-US">ROOLBACK</span>,这使得<span lang="EN-US">COMMIT</span>后<span lang="EN-US">DBWN</span>进程还没有全部完成的后续动作得到了保障。</p><p style="text-indent:24.0pt;mso-char-indent-count:2.0"><br></p>

<p style="text-indent: 24pt;">下面我们要提到检查点的作用,当一个全部检查点发生的时候,首先让<span lang="EN-US">LGWR</span>进程将<span lang="EN-US">REDO LOG BUFFER</span>中的所有缓冲(包含未提交的重做信息)写入重做日志文件,然后让<span lang="EN-US">DBWN</span>进程将<span lang="EN-US">DB BUFFER</span>中所有已提交的缓冲写入数据文件(不强制写未提交的)。然后更新控制文件和数据文件头部的<span lang="EN-US">SCN</span>,表明当前数据库是一致的,如果在发生检点之 前断电,并且当时有一个未提交的改变正在进行,实例重启之后,<span lang="EN-US">SMON</span>进程将从上一个检查点开始核对这个检查点之后记录在重做日志文件中已提交的和未提交 改变,因为<span lang="EN-US">DBWN</span>之前会触发<span lang="EN-US">LGWR</span>,所以<span lang="EN-US">DBWN</span>对数据文件的修改一定会被先记录在重做日志文件中。因此,断电前被<span lang="EN-US">DBWN</span>写进数据文件的改变将通过 重做日志文件中的记录进行还原,叫做回滚,如果断电时有一个已提交,但<span lang="EN-US">DBWN</span>动作还没有完全完成的改变存在,因为已经提交,提交会触发<span lang="EN-US">LGWR</span>进程,所
以不管<span lang="EN-US">DBWN</span>动作是否已完成,该语句将要影响的行及其产生的结果一定已经记录在重做日志文件中了,则实例重启后,<span lang="EN-US">SMON</span>进程根据重做日志文件进行前 滚。由此可见,实例失败后用于恢复的时间由两个检查点之间的间隔大小来决定,我们可以通个四个参数设置检查点执行的频
率,<span lang="EN-US">LOG_CHECKPOINT_IMTERVAL</span>决定了两个检查点之间写入重做日志文件的系统物理块的大 小,<span lang="EN-US">LOG_CHECKPOINT_TIMEOUT</span>决定了两个检查点之间的时间长度,<span lang="EN-US">FAST_START_IO_TARGET</span>决定了用于恢复时需要处理
的块的大小,<span lang="EN-US">FAST_START_MTTR_TARGET</span>直接决定了用于恢复的时间的长短。<span lang="EN-US">SMON</span>进程执行的前滚和回滚与用户的回滚是不同 的,<span lang="EN-US">SMON</span>是根据重做日志文件进行前滚或回滚,而用户的回滚一定是根据回滚段的内容进行回滚的。在这里我们要说一下回滚段存储的数据,假如是<span lang="EN-US"> delete</span>操作,则回滚段将会记录整个行的数据,假如是<span lang="EN-US">update,</span>则回滚段只记录被修改了的字段的变化前的数据(前映像),也就是没有被修改的字段
是不会被记录的,假如是<span lang="EN-US">insert</span>,则回滚段只记录插入记录的<span lang="EN-US">rowid</span>。这样假如事务提交,那回滚段中简单标记该事务已经提交;假如是回退,则如果操
作是是<span lang="EN-US">delete,</span>回退的时候把回滚段中数据重新写回数据块,操作如果是<span lang="EN-US">update</span>,则把变化前数据修改回去,操作如果是<span lang="EN-US">insert</span>,则根据记录的<span lang="EN-US"> rowid </span>把该记录删除。</p><p style="text-indent:24.0pt;mso-char-indent-count:2.0"><br></p>

<p style="text-indent: 24pt;">下面我们要讲<span lang="EN-US">DBWN</span>如何来写数据文件,在写数据文件前首先要找到可写的空闲数据块,<span lang="EN-US">ORACLE</span>中空闲数据块可以通过<span lang="EN-US">FREELIST</span>或<span lang="EN-US"> BITMAP</span>来维护,它们位于一个段的头部用来标识当前段中哪些数据块可以进行<span lang="EN-US">INSERT</span>。在本地管理表空间中<span lang="EN-US">ORACLE</span>自动管理分配给段的区的大 小,区的分配信息存储在组成表空间的数据文件的头部,而数据字典管理的表空间用户可以在创建时决定区的大小,并且区的分配信息是存储在数据字典中的,只在
本地管理的表空间中才能选用段自动管理,采用自动段空间管理的本地管理表空间中的段中的空闲数据块的信息就存放在段的头部并且使用位图来管理,采用手动管 理的本地管理表空间中的段和数据字典管理的表空间中的段中的空闲数据块的管理都使用位于段头部的空闲列表来管理,空闲列表的工作方式:首先一个空的数据块
被加入空闲列表,当其中空闲空间小于<span lang="EN-US">PCTFREE</span>设置的值之后,这个块从空闲列表删除,当这个块中的内容降至<span lang="EN-US">PCTUSED</span>设置的值之下后,这个数据块 被再次加入空闲列表,位于空闲列表中的数据块都是可以向其中<span lang="EN-US">INSERT</span>的块,当一个块移出了空闲列表,但只要其中还有保留空间就可以进行<span lang="EN-US">UPDATE</span>,
当对其中一行<span lang="EN-US">UPDATE</span>一个大数据时,如果当前块不能完全放下整个行,只会把整个行迁移到一个新的数据块,并在原块位置留下一个指向新块的指针,这叫行
迁移。如果一个数据块可以<span lang="EN-US">INSERT</span>,当插入一个当前块装不下的行时,这个行会溢出到两个或两个几上的块中,这叫行链接。如果用户的动作是<span lang="EN-US">INSERT </span>则服务器进程会先锁定<span lang="EN-US">FREELIST</span>,然后找到空闲块的地址,再释放<span lang="EN-US">FREELIST</span>,当多个服务器进程同时想要锁定<span lang="EN-US">FREELIST</span>时即发生<span lang="EN-US"> FREELIST</span>的争用,可以在非采用自动段空间管理的表空间中创建表时指定<span lang="EN-US">FREELIST</span>的个数,默认为<span lang="EN-US">1</span>,如果是在采用自动段空间管理的表空间中创 建表,即使指定了<span lang="EN-US">FREELIST</span>也会被忽略,因为此时将使用<span lang="EN-US">BITMAP</span>而不是<span lang="EN-US">FREELIST</span>来管理段中的空闲空间。如果用户动作是<span lang="EN-US">UPDATE</span>服务 器进程将不会使用到<span lang="EN-US">FREELIST</span>和<span lang="EN-US">BITMAP</span>,因为不要去寻找一个空闲块,而使用锁的队列。</p><p style="text-indent:24.0pt;mso-char-indent-count:2.0"><br></p>

<p style="text-indent: 24pt;">下面来讲一下<span lang="EN-US">ORACLE</span>锁的机制,<span lang="EN-US">ORACLE</span>分锁存器和锁两种。锁存器是用来保护对内存结构的访问,比如对<span lang="EN-US">DB BUFFER</span>中块的锁存器申请,只有在<span lang="EN-US">DBWN</span>完成后,这些<span lang="EN-US">DB BUFFER</span>块被解锁。然后用于其它的申请。锁存器不可以在进程间共享,锁存器的申请要么成功要么失败,没有锁存器申请队列。主要的锁存器有<span lang="EN-US">SHARED POOL</span>锁存器,<span lang="EN-US">LIBRARY CACHE</span>锁存器,<span lang="EN-US">CACHE BUFFERS LRU CHAIN</span>锁存器,<span lang="EN-US">CACHE BUFFERS
CHAINS </span>锁存器,<span lang="EN-US">REDO ALLOCATION </span>锁存器,<span lang="EN-US">REDO
COPY </span>锁存器。<span lang="EN-US">ORACLE</span>的锁是用来保护数据访问的,锁的限制比锁存器要更宽松,比如,多个用户在修改同一表的不同行时,可以共享一个表上的一个锁,锁的申请
可以按照被申请的顺序来排队等候,然后依次应用,这种排队机制叫做队列(<span lang="EN-US">ENPUEUE</span>),如果两个服务器进程试图对同一表的同一行进行加锁,则都进入锁
的申请队列,先进的加锁成功,后面的进程要等待,直到前一个进程解锁才可以加锁,这叫做锁的争用,而且一旦加锁成功,这个锁将一直保持到用户发出<span lang="EN-US"> COMMIT</span>或<span lang="EN-US">ROOLBACK</span>命令为止。如果两个用户锁定各自的一行并请求对方锁定的行的时候将发生无限期等待即死锁,死锁的发生都是由于锁的争用而不
是锁存器的争用引起的,<span lang="EN-US">ORACLE</span>在遇到死锁时,自动释放其中一个用户的锁并回滚此用户的改变。正常情况下发生锁的争用时,数据的最终保存结果由<span lang="EN-US">SCN </span>来决定哪个进程的更改被最终保存。两个用户的服务器进程在申请同一表的多个行的锁的时候是可以交错进入锁的申请队列的。只有其中发生争用才会进行等待。创
建表时指定的<span lang="EN-US">MAXTRANS</span>参数决了,表中的一个数据块最多可以被几个事务同时锁定。</p><p style="text-indent:24.0pt;mso-char-indent-count:2.0"><br></p>

<p style="text-indent:24.0pt;mso-char-indent-count:2.0">下面是几个关于回滚段和死锁的事例:</p>

<p style="text-indent:24.0pt;mso-char-indent-count:2.0">有表:<span lang="EN-US">Test
(id number(10)) </span>有记录<span lang="EN-US">1000000</span>条</p>

<p style="text-indent:24.0pt;mso-char-indent-count:2.0">一,大<span lang="EN-US">SELECT</span>,小<span lang="EN-US">UPDATE<br>
A</span>会话<span lang="EN-US">----Select * from test;----</span>设<span lang="EN-US">scn=101----</span>执行时间<span lang="EN-US">09:10:11<br>
B</span>会话<span lang="EN-US">-----Update test set id=9999999 where id=1000000----</span>设<span lang="EN-US">scn=102-----</span>执行时间<span lang="EN-US">09:10:12 </span></p>

<p style="text-indent:24.0pt;mso-char-indent-count:2.0">我们会发现<span lang="EN-US">B</span>会话会在<span lang="EN-US">A</span>会话前完成,<span lang="EN-US">A</span>会话中显示的<span lang="EN-US">ID=100000</span>是从回滚段中读取的,因为<span lang="EN-US">A</span>会话在读到<span lang="EN-US">ID=1000000</span>所在的<span lang="EN-US">BLOCK
</span>时发现<span lang="EN-US">BLOCK</span>上有事务信息,因此要从回滚段中读,如果<span lang="EN-US">UPDATE</span>在<span lang="EN-US">SELECT</span>读到此<span lang="EN-US">BLOCK</span>之前已经<span lang="EN-US">COMMIT</span>,则<span lang="EN-US">SELECT </span>读到此<span lang="EN-US">BLOCK</span>时发现其<span lang="EN-US">BLOCK</span>上没有事务信息,但是会发现其<span lang="EN-US">BLICK</span>的<span lang="EN-US">SCN</span>比<span lang="EN-US">SELECT</span>自己的<span lang="EN-US">SCN</span>大,因此也会从回滚段中读取。因此是否从 回滚段读一是看是否有事务信息二是比较<span lang="EN-US">SCN</span>大小。如果<span lang="EN-US">B</span>会话在<span lang="EN-US">A</span>会话结束前连续多次对同一条记录<span lang="EN-US">UPDATE</span>并<span lang="EN-US">COMMIT<br>
</span>,那么在回滚段中将 记录多个<span lang="EN-US">“</span>前映像<span lang="EN-US">”</span>,而每个<span lang="EN-US">“</span>前映像<span lang="EN-US">”</span>中不但包括了原<span lang="EN-US">BLOCK</span>的数据和<span lang="EN-US">SCN</span>也记录了<span lang="EN-US">“</span>前前映像<span lang="EN-US">”</span>的回滚段地址,因此<span lang="EN-US">A</span>会话在查询到被<span lang="EN-US">UPDATE</span>过的<span lang="EN-US">
BLOCK</span>时,会根据<span lang="EN-US">BLOCK</span>记录的回滚段的地址,找到回滚段中的<span lang="EN-US">“</span>前映像<span lang="EN-US">”</span>,发现这个<span lang="EN-US">“</span>前映像<span lang="EN-US">”</span>的<span lang="EN-US">SCN</span>也比自己的大,因此将根据这个<span lang="EN-US">“</span>前映像<span lang="EN-US">”</span>中记 录的<span lang="EN-US">“</span>前前映像<span lang="EN-US">”</span>的回滚段地址,在回滚段中找到<span lang="EN-US">“</span>前前映像<span lang="EN-US">”</span>,再与这个<span lang="EN-US">“</span>前前映像<span lang="EN-US">”</span>比较<span lang="EN-US">SCN</span>,如果比自己小就读取,如果还比自己大,则重复以上步骤,直 到找到比自己<span lang="EN-US">SCN</span>小的<span lang="EN-US">“</span>前<span lang="EN-US">…</span>前映像<span lang="EN-US">”</span>为止,如果找不到,就会报<span lang="EN-US">ORA-01555</span>快照太旧这个错误。</p>

<p style="text-indent:24.0pt;mso-char-indent-count:2.0">二、大<span lang="EN-US">UPDATE</span>,小<span lang="EN-US">SELECT</span></p>

<p style="text-indent:24.0pt;mso-char-indent-count:2.0"><span lang="EN-US">A</span>会话<span lang="EN-US">----Update test set id=1;----</span>设<span lang="EN-US">scn=101----</span>执行时间<span lang="EN-US">09:10:11<br>
B</span>会话<span lang="EN-US">-----select * from test where id=1000000----</span>设<span lang="EN-US">scn=102-----</span>执行时间<span lang="EN-US">09:10:12 </span></p>

<p style="text-indent:24.0pt;mso-char-indent-count:2.0">我们会发现<span lang="EN-US">B</span>会话会在<span lang="EN-US">A</span>会话前完成,<span lang="EN-US">B</span>会话中显示的<span lang="EN-US">ID=1000000</span>是从<span lang="EN-US">BLOCK</span>中直接读取的,因为<span lang="EN-US">B</span>会话在读到<span lang="EN-US">ID=1000000</span>所在的<span lang="EN-US"> BLOCK</span>时,<span lang="EN-US">A</span>会话还没有来得及对其锁定,因此<span lang="EN-US">B</span>会话既不会发现<span lang="EN-US">BLOCK</span>上有事务信息,也不会发现<span lang="EN-US">BLOCK</span>上的<span lang="EN-US">SCN</span>比<span lang="EN-US">SELECT</span>的大,因此会从<span lang="EN-US"> BLOCK</span>中直接读取,如果<span lang="EN-US">SELECT</span>在<span lang="EN-US">UPDATE</span>锁定此<span lang="EN-US">BLOCK</span>后才发出,<span lang="EN-US">B</span>会话读到此<span lang="EN-US">BLOCK</span>时发现其<span lang="EN-US">BLOCK</span>上有事务信息,因此会从回滚 段中读取。</p>

<p style="text-indent:24.0pt;mso-char-indent-count:2.0">三、大<span lang="EN-US">UPDATE</span>,小<span lang="EN-US">UPDATE</span></p>

<p style="text-indent:24.0pt;mso-char-indent-count:2.0"><span lang="EN-US">A</span>会话<span lang="EN-US">----Update test set id=1;----</span>设<span lang="EN-US">scn=101----</span>执行时间<span lang="EN-US">09:10:11<br>
B</span>会话<span lang="EN-US">1-----Update test set id=999999 where id=1000000----</span>设<span lang="EN-US">scn=102-----</span>执行时间<span lang="EN-US">09:10:12 <br>
B</span>会话<span lang="EN-US">2----- select * from test where id=2----</span>设<span lang="EN-US">scn=103-----</span>执行时间<span lang="EN-US">09:10:14 <br>
B</span>会话<span lang="EN-US">3----- update test set id=3 where id=2----</span>设<span lang="EN-US">scn=104-----</span>执行时间<span lang="EN-US">09:10:15 </span></p>

<p style="text-indent:24.0pt;mso-char-indent-count:2.0">我们会发现<span lang="EN-US">B</span>会话<span lang="EN-US">1</span>会完成,<span lang="EN-US">A</span>会话将一直等待,因为<span lang="EN-US">B</span>会话<span lang="EN-US">1</span>会先于<span lang="EN-US">A</span>会话锁定<span lang="EN-US">ID=1000000</span>所在的<span lang="EN-US">BLOCK</span>,并改写头部的事务信息,<span lang="EN-US">A</span>会话 在试图锁定此<span lang="EN-US">BLOCK</span>时,发现其上有事务信息,将会一直等待<span lang="EN-US">B</span>会话<span lang="EN-US">1</span>事务结束后再行锁定,<span lang="EN-US"> B</span>会话<span lang="EN-US">2</span>查询到的<span lang="EN-US">ID=2</span>是从回滚段中读取的而不是从<span lang="EN-US">BLOCK</span>中直接读出来的。因为<span lang="EN-US">A</span>会话已将<span lang="EN-US">ID=2</span>的<span lang="EN-US">BLOCK</span>锁定,并写入了回滚段,从<span lang="EN-US">B</span>会话<span lang="EN-US">3</span>可 以证明这一点,<span lang="EN-US">B</span>会话<span lang="EN-US">3</span>发出后,<span lang="EN-US">B</span>会话<span lang="EN-US">3</span>会收到死锁的信息,死锁的原因是<span lang="EN-US">A</span>会话在等待<span lang="EN-US">B</span>会话对<span lang="EN-US">ID=1000000</span>所在的<span lang="EN-US">BLOCK</span>解锁,现在<span lang="EN-US">B</span>会话又在 等待<span lang="EN-US">A</span>会话对<span lang="EN-US">ID=2</span>所在的<span lang="EN-US">BLOCK</span>解锁,因此形成死锁,因此证明<span lang="EN-US">ID=2</span>所在的<span lang="EN-US">BLOCK</span>已被<span lang="EN-US">A</span>会话锁定,然后<span lang="EN-US">A</span>会话也会收到死锁的信息。</p>
页: [1]
查看完整版本: 一个高人写的oracle资料