The single mostimportant feature of Oracle is one that first appeared in version 6: the changevector, a mechanism for describing changes to data blocks, the heart of redoand undo.
--对于Oracle来说,最具有革命性的新特性莫过于Oracle 6时出现的改变向量(changevector);改变向量:即描述数据块更改的信息, change vector 也是redo 和undo的核心。
This is thetechnology that keeps your data safe, minimizes conflict between readers andwriters, and allows for instance recovery, media recovery, all the standbytechnologies, flashback mechanisms, change data capture, and streams. So thisis the technology that we’re going to review first.
--change vector 技术也保证了数据安全,最小化数据读写之间的冲突,允许实例恢复和介质恢复…..
一.Basic Data Change -- 基本数据更改
One of thestrangest features of an Oracle database is that it records your data twice.One copy of the data exists in a set of data files which hold something that isnearly the latest, up-to-date version of your data (although the newest versionof some of the data will be in memory, waiting to be copied to disc); the othercopy of the data exists as a set of instructions—the redo log files—telling youhow to re-create the content of the data files from scratch.
--Oracle 数据库最有趣的特性就是会记录2次数据。一次是把最近的数据写入数据文件,这里是最近的数据,因为最新的数据只存在内存的buffer中,等待写入磁盘。另一次是数据写入redolog,这里的record 信息是一个操作描述的集合。
1.1 The Approach
Under the Oracleapproach to data change, when you issue an instruction to change an item ofdata, Oracle doesn’t just go to a data file (or the in-memory copy if the itemhappens to be buffered), find the item, and change it. Instead, Oracle worksthrough four critical steps to make the change happen. Stripped to the bareminimum of detail, these are
--在Oracle数据库里,当我们要改变一个记录时,Oracle 不会直接去修改datafile,而是按照以下的步骤来操作:
1. Create a description of how tochange the data item.
2. Create a description of how tore-create the original data item if needed.
3. Create a description of how tocreate the description of how to re-create the original data item.
4. Change the data item.
Thetongue-twisting nature of the third step gives you some idea of how convolutedthe mechanism is, but all will become clear. With the substitution of a fewtechnical labels in these steps, here’s another way of describing the actionsof changing a data block:
1. Create a redo change vectordescribing the change to the data block.
2. Create an undo record forinsertion into an undo block in the undo tablespace.
3. Create a redo change vectordescribing the change to the undo block.
4. Change the data block.
The exactsequence of steps and the various technicalities around the edges varydepending on the version of Oracle, the nature of the transaction, how muchwork has been done so far in the transaction, what the states of the
1.2 An Example
I’m going tostart with the simplest example of a data change, which you might expect to seeas you updated a single row in the middle of an OLTP transaction that hadalready updated a scattered set of rows. In fact, the order of the steps in thehistoric (and most general) case is not the order I’ve listed in the precedingsection.
The stepsactually go in the order 3, 1, 2, 4, and the two redo change vectors arecombined into a single redo change record and copied into the redo log (buffer)before the undo block and data block are modified (in that order). This means aslightly more accurate version of my list of actions would be:
一个update事务操作的具体流程如下:
1. Create a redo change vectordescribing how to insert an undo record into an undo block.
--创建undo record 对应的redochange vector。
2. Create a redo change vector forthe data block change.
--创建data block 对应的redochange vector。
3. Combine the redo change vectorsinto a redo record and write it to the log buffer.
--将undo record和 datablock 的redo change vector绑定成一个redo record并写入logbuffer中。
4. Insert the undo record into theundo block.
--将undo record 写入undoblock。
5. Change the data block.
--改变数据块内容。
Here’s a littlesample, taken from a system running Oracle 9.2.0.8 (the last version in whichit’s easy to create the most generic example of the mechanism). We’re going toexecute an update statement that updates five rows by jumping back and forthbetween two table blocks, dumping various bits of information into our processtrace file before and after the update. I need to make my update a little bitcomplicated because I want the example to be as simple as possible whileavoiding a few “special case” details.
Note The first change in a transactionincludes some special steps, and the first change a transaction makes to eachblock is slightly different from the most “typical” change.
1.3 Debriefing
So where have wegot to so far? When we change a data block, Oracle inserts an undo record intoan undo block to tell us how to reverse that change. But for every change thathappens to a block in the database, Oracle creates a redo change vectordescribing how to make that change, and it creates the vectors before it makesthe changes. Historically, it created the undo change vector before it createdthe “forward” change vector, hence, the following sequence of events (seeFigure 2-1) that I described earlier occurs:
1. Create the change vector forthe undo record.
2. Create the change vector forthe data block.
3. Combine the change vectors andwrite the redo record into the redo log (buffer).
4. Insert the undo record into theundo block.
5. Make the change to the datablock.
When you look atthe first two steps here, of course, there’s no reason to believe that I’ve gotthem in the right order. Nothing I’ve described or dumped shows that the actionsmust be happening in that order. But there is one little detail I can now showyou that I omitted from the dumps of the change vectors, partly because thingsare different from 10g onwards and partly because the description of theactivity is easier to comprehend if you first think about it in the wrongorder.
Note OracleDatabase 10g introduced an important change to the way that redo change vectorsare created and combined, but the underlying mechanisms are still very similar;moreover, the new mechanisms don’t apply to RAC, and even single instanceOracle falls back to the old mechanism if a transaction gets too large or youhave enabled supplemental logging or flashback database. We will be looking atthe new strategy later in this chapter. One thing that doesn’t change, though,is that redo is generated before changes are applied to data and undo blocks.
1.4 Summary of Observations
Before wecontinue, we can summarize our observations as follows: in the data files,every change we make to our own data is matched by Oracle with the creation ofan undo record (which is also a change to a data file); at the same time Oracleputs into the redo log a description of how to make our change and how to makeits own change.
You might notethat since data can be changed “in place,” we could make an “infinite” (i.e.,arbitrarily large) number of changes to our single row of data, but we clearlycan’t record an infinite number of undo records without growing the data filesof the undo tablespace, nor can we record an infinite number of changes in theredo log without constantly adding more redo log files. For the sake ofsimplicity, we’ll postpone the issue of infinite changes and simply pretend forthe moment that we can record as many undo and redo records as we need.
二.ACID
Although we’renot going to look at transactions in this chapter, it is, at this point, worthmentioning the ACID requirements of a transactional system and how Oracle’simplementation of undo and redo gives Oracle the capability of meeting thoserequirements. Table 2-1 lists the ACID requirements.
The following list goes into more detailabout each of the requirements in Table 2-1:
Atomicity(原子性): As we make a change, wecreate an undo record that describes how to reverse the change. This means thatwhen we are in the middle of a transaction, another user trying to view anydata we have modified can be instructed to use the undo records to see an olderversion of that data, thus making our work invisible until the moment we decideto publish (commit) it. We can ensure that the other user either sees nothingof what we’ve done or sees everything.
Consistency(一致性): This requirement isreally about constraints defining the legal states of the database; but wecould also argue that the presence of undo records means that other users canbe blocked from seeing the incremental application of our transaction andtherefore cannot see the database moving from one legal state to another by wayof a temporarily illegal state—what they see is either the old state or the newstate and nothing in between. (The internal code, of course, can see all theintermediate states—and take advantage of being able to see them—but theend-user code never sees inconsistent data.)
Isolation(隔离性): Yet again we can seethat the availability of undo records stops other users from seeing how we arechanging the data until the moment we decide that our transaction is completeand commit it. In fact, we do better than that: the availability of undo meansthat other users need not see the effects of our transactions for the entireduration of their transactions, even if we start and end our transactionbetween the start and end of their transaction. (This is not the defaultisolation level in Oracle, but it is an available isolation level; see the“Isolation Levels” sidebar.) Of course, we do run into confusing situationswhen two users try to change the same data at the same time; perfect isolationis not possible in a world where transactions have to take a finite amount oftime.
Durability(持久性): This is the requirementthat highlights the benefit of the redo log. How do you ensure that a completedtransaction will survive a system failure? The obvious strategy is to keepwriting any changes to disc, either as they happen or as the final step that“completes” the transaction. If you didn’t have the redo log, this could meanwriting a lot of random data blocks to disc as you change them. Imagineinserting ten rows into an order_lines table with three indexes; this couldrequire 31 randomly distributed disk writes to make changes to 1 table blockand 30 index blocks durable. But Oracle has the redo mechanism. Instead ofwriting an entire data block as you change it, you prepare a small descriptionof the change, and 31 small descriptions could end up as just one (relatively)small write to the end of the log file when you need to make sure that you’ve gota permanent record of the entire transaction.
2.1 ISOLATION LEVELS
Oracle 数据隔离级别(TransactionIsolation Levels) 说明
http://www.cndba.cn/dave/article/1389
Oracleoffers three isolation levels: read committed (the default), read only, andserializable.
--Oracle 提供了3种不同级别的隔离:read commited(默认级别),readonly和serializable。
As a briefsketch of the differences, consider the following scenario: table t1 holds onerow, and table t2 is identical to t1 in structure. We have two sessions that gothrough the following steps in order:
1. Session 1: select from t1;
2. Session 2: insert into t1select * from t1;
3. Session 2: commit;
4. Session 1: select from t1;
5. Session 1: insert into t2select * from t1;
If session 1 isoperating at isolation level read committed, it will select one row on thefirst select, select two rows on the second select, and insert two rows.
If session 1 isoperating at isolation level read only, it will select one row on the firstselect, select one row on the second select, and fail with Oracle error“ORA-01456: may not perform insert/delete/update operation inside a READ ONLYtransaction.” If session 1 is operating at isolation level serializable, itwill select one row on the first select, select one row on the second select,and insert one row.
Not only are themechanisms for undo and redo sufficient to implement the basic requirements ofACID, they also offer advantages in performance and recoverability.
The performancebenefit of redo has already been covered in the comments on durability; if youwant an example of the performance benefits of undo, think about isolation—howcan you run a report that takes minutes to complete if you have users who needto update data at the same time? In the absence of something like the undomechanism, you would have to choose between allowing wrong results and lockingout everyone who wants to change the data. This is a choice that you have tomake with some other database products. The undo mechanism allows for anextraordinary degree of concurrency because, per Oracle’s marketing sound bite,“readers don’t block writers, writers don’t block readers.”
As far asrecoverability is concerned (and we will examine recoverability in more detailin Chapter 6), if we record a complete list of changes we have made to thedatabase, then we could, in principle, start with a brand-new database andsimply reapply every single change description to reproduce an up-to-date copyof the original database. Practically, of course, we don’t (usually) start witha new database; instead we take regular backup copies of the data files so thatwe need only replay a small fraction of the total redo generated to bring thecopy database up to date.
三.Redo Simplicity – Redo 的简易性
The way wehandle redo is quite simple: we just keep generating a continuous stream ofredo records and pumping them as fast as we can into the redo log, initiallyinto an area of shared memory known as the redo log buffer. Eventually, ofcourse, Oracle has to deal with writing the buffer to disk and, for operationalreasons, actually writes the “continuous” stream to a small set of predefinedfiles—the online redo log files. The number of online redo log files islimited, so we have to reuse them constantly in a round-robin fashion.
--Oracle 处理redo 的方式很简单,就是持续生成redo records的stream,然后把这些redorecord近可能快的写入redo log中。 redo records最初是写入shared memory中的一个区域,叫redolog buffer,然后从buffer中写入disk,存放在onlineredo log files里,redo log files的大小是受限制的,所以我们需要循环的使用redo log files。
To protect the informationstored in the online redo log files over a longer time period, most systems areconfigured to make a copy, or possibly many copies, of each file as it becomesfull before allowing Oracle to reuse it: the copies are referred to as thearchived redo log files. As far as redo is concerned, though, it’s essentiallywrite it and forget it—once a redo record has gone into the redo log (buffer),we don’t (normally) expect the instance to reread it. At the basic level, this“write and forget” approach makes redo a very simple mechanism.
--为了保护onlineredo log files里的信息能保存一个较长的周期,大部分系统都会对redo log file 保留一个或者多个副本(归档)。而对于redo 来说,其本质上就是一个:write and forget的机制。一旦redo record 写入了redolog(buffer),一般我们就不会在去读这些信息,因为这个机制,就保证了redo 很简单。
Although wedon’t usually expect to do anything with the online redo log files except writethem and forget them, there is a special case where a session can read theonline redo log files when it discovers the in-memory version of a block to becorrupt and attempts to recover from the disk copy of the block. Of course,some features, such as Log Miner, Streams, and asynchronous Change DataCapture, have been created in recent years to take advantage of the redo logfiles, and some of the newer mechanisms for dealing with Standby databases havebecome real-time and are bound into the process that writes the online redo.
--虽然我们一般只对online redo files进行write 和 forget,但是还是有一些特殊情况我们需要读取online redo log files的信息,比如检测到内存坏块,并从disk 恢复坏块时。当然,Oracle一些特性也会用到Online redo logs,比如Log Miner,Streams和 asynchronous Change Data Capture等。
There is,however, one complication. There is a critical bottleneck in redo generation,the moment when a redo record has to be copied into the redo log buffer. Priorto 10g, Oracle would insert a redo record (typically consisting of just onepair of redo change vectors) into the redo log buffer for each change a sessionmade to user data. But a single session might make many changes in a very shortperiod of time, and there could be many sessions operating concurrently—andthere’s only one redo log buffer that everyone wants to access.
--但是在生成redo 时,将redo record 写入redolog buffer的过程会是一个非常严重的瓶颈。在Oracle 10g之前,Oracle 会将用户session中的每一个change 产生的redo record都会写入redolog buffer,这个redo record一般包含(data的undo change vector和redo change vector),单个session在很短的周期内可能产生多个change,并且可能有多个session 同时并发操作,但是只有一个redo logbuffer他们可以访问。
It’s relativelyeasy to create a mechanism to control access to a piece of shared memory, andOracle’s use of the redo allocation latch to protect the redo log buffer isfairly well known. A process that needs some space in the log buffer tries toacquire (get) the redo allocation latch, and once it has exclusive ownership ofthat latch, it can reserve some space in the buffer for the information itwants to write into the buffer. This avoids the threat of having multipleprocesses overwrite the same piece of memory in the log buffer, but if thereare lots of processes constantly competing for the redo allocation latch, thenthe level of competition could end up “invisibly” consuming lots of resources(typically CPU spent on latch spinning) or even lots of sleep time as sessionstake themselves off the run queue after failing to get the latch on the firstspin.
--有个很简单的方法来解决上面的问题,创建一个机制,来控制访问shared memory中的每一块,然后使用redo allocationlatch 来保护redo log buffer 被公平的使用。当一个进程需要使用log buffer中的space时,那么会申请redoallocation latch,一旦它已独占的方式获取了这个latch,它就可以把其对应的信息写入到redo log buffer中了,这样就可以避免多个线程重写log buffer中相同的块,如果有大量的进程来申请redo allocation latch,他们是拿不到这个latch,并且这个过程还会消耗大量的资源(主要是CPU 用来latch spin),或者是在第一次spin 失败后进入等待队列,休眠sleep time后继续进行spin操作,来申请redo allocation latch操作。
In olderversions of Oracle, when the databases were less busy and the volume of redogenerated was much lower, the “one change = one record = one allocation”strategy was good enough for most systems, but as systems became larger, therequirement for dealing with large numbers of concurrent allocations(particularly for OLTP systems) demanded a more scalable strategy. So a newmechanism combining private redo and in-memory undo appeared in 10g.
--在老版本的Oracle中,当数据库不忙时,产生redo record也会相对较少,这种情况下: one change = one record = one allocation 的策略对大部分系统都是够用的,当系统非常大时,就需要来处理大量的并发allocations 操作,所以在Oracle 10g的新策略里就出现了private redo 和in-memory undo。
In effect, aprocess can work its way through an entire transaction, generating all itschange vectors and storing them in a pair of private redo log buffers. When thetransaction completes, the process copies all the privately stored redo intothe public redo log buffer, at which point the traditional log bufferprocessing takes over. This means that a process acquires the public redoallocation latch only once per transaction, rather than once per change.
--事实上,一个进程可以在整个事务中工作,生成事务相关的所有的change vectors,在一对private redo logbuffers中存储这些vectors 。当这个事务结束时,进程就copy 所有privateredo log 里的信息到public redo log buffer中,剩下的操作就按照原始的方式处理。按照这种方式处理之后,一个进程仅需要在一个事务结束之后来申请public redo allocation latch,而不是每次改变都申请这个latch。
As a step towardimproved scalability, Oracle 9.2 introduced the option for multiple log bufferswith the log_parallelism parameter, but this option was kept fairly quiet andthe general suggestion was that you didn’t need to know about it unless you hadat least 16 CPUs. In 10g you get at least two public log buffers (redo threads)if you have more than one CPU.
--在Oracle 9.2中引入了一个选项,使用log_parallelism参数来控制多个log buffers,这个参数不会改变之前的策略,还是会公平的使用。同时在少于16个CPU的情况下可以忽略这个参数。在Oracle 10g,如果有多个CPU(>2),那么至少有2个public 的logbuffers(redo threads)。一个log buffer 对应一个redo thread。
There are anumber of details (and restrictions) that need to be mentioned, but before wego into any of the complexities, let’s just take a note of how this changessome of the instance activity reported in the dynamic performance views. I’vetaken the script in core_demo_02.sql, removed the dump commands, and replacedthem with calls to take snapshots of v$latch and v$sesstat (seecore_demo_02b.sql in the code library). I’ve also modified the SQL to update 50rows instead of 5 rows so that differences in workload stand out more clearly.The following results come from a 9i and a 10g system, respectively, runningthe same test. First the 9i results:
Latch Gets Im_Gets
----- ---- -------
redocopy 0 51
redoallocation 53 0
Name Value
---- -----
redoentries 51
redosize 12,668
Noteparticularly in the 9i output that we have hit the redo copy and redoallocation latches 51 times each (with a couple of extra gets on the allocationlatch from another process), and have created 51 redo entries. Compare thiswith the 10g results:
Latch Gets Im_Gets
----- ---- -------
redocopy 0 1
redoallocation 5 1
In memory undolatch 53 1
Name Value
---- -----
redoentries 1
redosize 12,048
In 10g, oursession has hit the redo copy latch just once, and there has been just a littlemore activity on the redo allocation latch. We can also see that we havegenerated a single redo entry with a size that is slightly smaller than thetotal redo size from the 9i test. These results appear after the commit; if wetook the same snapshot before the commit, we would see no redo entries (and azero redo size), the gets on the In memory undo latch would drop to 51, and thegets on the redo allocation latch would be 1, rather than 5.
So there’sclearly a notable reduction in the activity and the threat of contention at acritical location. On the downside, we can see that 10g has, however, hit thatnew latch called the In memory undo latch 53 times in the course of our test,which makes it look as if we may simply have moved a contention problem fromone place to another. We’ll take a note of that idea for later examination.
There arevarious places we can look in the database to understand what has happened. Wecan examine v$latch_children to understand why the change in latch activityisn’t a new threat. We can examine the redo log file to see what the one largeredo entry looks like. And we can find a couple of dynamic performance objects(x$kcrfstrand and x$ktifp) that will help us to gain an insight into the way inwhich various pieces of activity link together.
--我们可以查询相关的视图来帮助我们理解整个过程发生了什么。相关视图和字典有:v$latch_children,x$kcrfstrand ,x$ktifp。
The enhancedinfrastructure is based on two sets of memory structures. One set (calledx$kcrfstrand, the private redo) handles “forward” change vectors, and the otherset (called x$ktifp, the in-memory undo pool) handles the undo change vectors.The private redo structure also happens to hold information about thetraditional “public” redo log buffer(s), so don’t be worried if you see twodifferent patterns of information when you query it.
--增强结构(redo record)是根据两组memory structures来建立的。一个是set是x$kcrfstrand(privateredo),用来处理 forward change vectors,另一个set是x$ktifp(in-memoryundo pool),用来处理undo change vectors。
这里要注意的是,就是在public redo logbuffer也会保存旧机制存放进来的redo record。因为在一些特殊条件下,新机制不能使用就会切换到旧机制下,这样redo record就不会在commit之后批量的从private redo logbuffer写入public redo log buffer,而是每次修改都会写入一次public redolog buffer。
The number ofpools in x$ktifp (in-memory undo) is dependent on the size of the array thatholds transaction details (v$transaction), which is set by parametertransactions (but may be derived from parameter sessions or parameterprocesses). Essentially, the number of pools defaults to transactions / 10 andeach pool is covered by its own “In memory undo latch” latch.
--in-memory undo pools(x$ktifp)的数量由详细事务所持有数组的大小决定,其也可以受参数控制,不过一般根据sessions 或者processes 计算出来。一般默认的pools数量=transactions/10,每个pool 由自己的 in memory undo latch来控制。
For each entryin x$ktifp there is a corresponding private redo entry in x$kcrfstrand, and, asI mentioned earlier, there are then a few extra entries which are for thetraditional “public” redo threads. The number of public redo threads isdictated by the cpu_count parameter, and seems to be ceiling(1 + cpu_count /16). Each entry in x$kcrfstrand is covered by its own redo allocation latch,and each public redo thread is additionally covered by one redo copy latch perCPU (we’ll be examining the role of these latches in Chapter 6).
--在x$ktifp 中的每一个entry,在x$kcrfstrand中都有一个对应的privateredo entry,并且在x$kcrfstrand中还有一些额外的entry(traditional ‘public’ redothreads)。 Public redo threads的数量根据cpu_count计算,=(1+cpu_count/16). 每个x$kcrfstrand中的entry都受自己的redoallocation latch控制,而每一个public redo thread 则受每个CPU的redocopy latch控制。
If we go back toour original test, updating just five rows and two blocks in the table, Oraclewould still go through the action of visiting the rows and cached blocks in thesame order, but instead of packaging pairs of redo change vectors, writing theminto the redo log buffer, and modifying the blocks, it would operate asfollows:
--按照我们之前的例子来看我们上面讲的内容:
1. Start the transaction byacquiring a matching pair of the private memory structures , one from x$ktifpand one from x$kcrfstrand.
2. Flag each affected block as“has private redo” (but don’t change the block).
3. Write each undo change vectorinto the selected in-memory undo pool.
4. Write each redo change vectorinto the selected private redo thread.
5. End the transaction byconcatenating the two structures into a single redo change record.
6. Copy the redo change recordinto the redo log and apply the changes to the blocks.
If we look atthe memory structures (see core_imu_01.sql in the code depot) just before wecommit the transaction from the original test, we see the following:
INDX UNDO_SIZEUNDO_USAGE REDO_SIZE REDO_USAGE
----- ---------- ---------- --------------------
0 64000 4352 62976 3920
This show usthat the private memory areas for a session allow roughly 64KB for “forward”changes, and the same again for “undo” changes. For a 64-bit system this wouldbe closer to 128KB each. The update to five rows has used about 4KB from eachof the two areas.
If I then dumpthe redo log file after committing my change, this (stripped to a bare minimum)is the one redo record that I get:
REDO RECORD - Thread:1 RBA:0x0000d2.00000002.0010 LEN: 0x0594 VLD: 0x0d
SCN: 0x0000.040026ae SUBSCN: 104/06/2011 04:46:06
CHANGE #1 TYP:0 CLS: 1 AFN:5 DBA:0x0142298aOBJ:76887
SCN:0x0000.04002690 SEQ: 2OP:11.5
CHANGE #2 TYP:0 CLS:23 AFN:2 DBA:0x00800039OBJ:4294967295
SCN:0x0000.0400267e SEQ: 1OP:5.2
CHANGE #3 TYP:0 CLS: 1 AFN:5 DBA:0x0142298bOBJ:76887
SCN:0x0000.04002690 SEQ: 2OP:11.5
CHANGE #4 TYP:0 CLS: 1 AFN:5 DBA:0x0142298aOBJ:76887
SCN:0x0000.040026ae SEQ: 1OP:11.5
CHANGE #5 TYP:0 CLS: 1 AFN:5 DBA:0x0142298bOBJ:76887
SCN:0x0000.040026ae SEQ: 1OP:11.5
CHANGE #6 TYP:0 CLS: 1 AFN:5 DBA:0x0142298aOBJ:76887
SCN:0x0000.040026ae SEQ: 2OP:11.5
CHANGE #7 TYP:0 CLS:23 AFN:2 DBA:0x00800039OBJ:4294967295
SCN:0x0000.040026ae SEQ: 1OP:5.4
CHANGE #8 TYP:0 CLS:24 AFN:2 DBA:0x00804a9bOBJ:4294967295
SCN:0x0000.0400267d SEQ: 2OP:5.1
CHANGE #9 TYP:0 CLS:24 AFN:2 DBA:0x00804a9bOBJ:4294967295
SCN:0x0000.040026ae SEQ: 1OP:5.1
CHANGE #10 TYP:0 CLS:24 AFN:2DBA:0x00804a9b OBJ:4294967295
SCN:0x0000.040026ae SEQ: 2OP:5.1
CHANGE #11 TYP:0 CLS:24 AFN:2DBA:0x00804a9b OBJ:4294967295
SCN:0x0000.040026ae SEQ: 3OP:5.1
CHANGE #12 TYP:0 CLS:24 AFN:2DBA:0x00804a9b OBJ:4294967295
SCN:0x0000.040026ae SEQ: 4OP:5.1
You’ll noticethat the length of the undo record (LEN:) is 0x594 = 1428, which matched thevalue of the redo size statistic I saw when I ran this particular test. This issignificantly smaller than the sum of the 4352 and 3920 bytes reported as usedin the in-memory structures, so there are clearly lots of extra bytes involvedin tracking the private undo and redo—perhaps as starting overhead in thebuffers.
If you readthrough the headers of the 12 separate change vectors, taking note particularlyof the OP: code, you’ll see that we have five change vectors for code 11.5followed by five for code 5.1. These are the five forward change vectorsfollowed by the five undo block change vectors. Change vector #2 (code 5.2) isthe start of transaction, and change vector #7 (code 5.4) is the so-calledcommit record, the end of transaction. We’ll be looking at those change vectorsmore closely in Chapter 3, but it’s worth mentioning at this point that whilemost of the change vectors are applied to data blocks only when the transactioncommits, the change vector for the start of transaction is an important specialcase and is applied to the undo segment header block as the transaction starts.
So Oracle has amechanism for reducing the number of times a session demands space from, andcopies information into, the (public) redo log buffer, and that improves thelevel of concurrency we can achieve . . . up to a point. But you’re probablythinking that we have to pay for this benefit somewhere—and, of course, we do.
Earlier on wesaw that every change we made resulted in an access to the In memory undolatch. Does that mean we have just moved the threat of latch activity ratherthan actually relieving it? Yes and no. We now hit only one latch (In memoryundo latch) instead of two (redo allocation and redo copy), so we have at leasthalved the latch activity, but, more significantly, there are multiple childlatches for the In memory undo latches, one for each in-memory undo pool.Before the new mechanism appeared, most systems ran with just one redoallocation latch, so although we now hit an In memory undo latch just as manytimes as we used to hit the redo allocation latch, we are spreading the accessacross far more latches.
It’s also worthnoting that the new mechanism also has two types of redo allocation latch—onetype covers the private redo threads, one type covers the public redo threads,and each thread has its own latch. This helps to explain the extra gets on theredo allocation latch statistic that we saw earlier: our session uses a privateredo allocation latch to acquire a private redo thread, then on the commit ithas to acquire a public redo allocation latch, and then the log writer (as weshall see in Chapter 6) acquires the public redo allocation latches (and mytest system had two public redo threads) to write the log buffer to file.
--在Oracle 新机制中有两种类型的redo allocationlatch,一个是private redo threads,另一个是public redo threads,每个thread都受自己的latch控制。
Overall, then,the amount of latch activity decreases and the focus of latchactivity is spread a little more widely, which is a good thing. But in amultiuser system, there are always other points of view to consider—using theold mechanism, the amount of redo a session copied into the log buffer andapplied to the database blocks at any one instant was very small; using the newmechanism, the amount of redo to copy and apply could be relatively large,which means it takes more time to apply to the database blocks, potentiallyblocking other sessions from accessing those blocks as the changes are made.This may be one reason why the private redo threads are strictly limited insize.
--总体来说,新机制下latch活动减少。但对于一个多用户系统,总会有一些观点认为需要使用旧的机制,在旧机制下,每个操作产生的vector都会写入log buffer和应用到block上,这样每次身上的数据都很少,而使用新机制,redo 和apply的数据都会相对较大,也就意味着需要更多的时间来处理这些信息,也会潜在的block 其他session 访问我们正在修改的信息。这也是为什么privateredo thread 会严格的控制大小的原因。
Moreover, usingthe old mechanism, a second session reading a changed block would see thechanges immediately; with the new mechanism, a second session can see only thata block is subject to some private redo, so the second session is nowresponsible for tracking down the private redo and applying it to the block (ifnecessary), and then deciding what to do next with the block. (Think about theproblems of referential integrity if you can’t immediately see that anothersession has, for example, deleted a primary key that you need.) This leads tolonger code paths, and more complex code, but even if the resulting code forread consistency does use more CPU than it used to, there is always an argumentfor making several sessions use a little more CPU as a way of avoiding a singlepoint of contention.
--更深入一点的问题,使用旧的机制时,第二个session 会立即读取到block的变化,如果使用新机制,当block的信息到private redo,第二个session需要跟踪这些redo信息并apply这些block(如果需要),然后决定下一步做什么,这样就不能立即看到之前session的操作,这样也导致一个问题,就是代码路径过长,更负责,即我们需要使用更多的CPU 资源才可以一致性的读取我们的数据。
There is animportant principle of optimization that is often overlooked. Sometimes it isbetter for everyone to do a little more work if that means they are operatingin separate locations rather than constantly colliding on the same contentionpoint—competitionwastes resources.
--在性能优化时有一个非常重要的原则常被忽略:减少热快,因为竞争会浪费资源。
I don’t know howmany different events there are that could force a session to construct newversions of blocks from private redo and undo, but I do know that there areseveral events that result in a session abandoning the new strategy before thecommit.
--我不知道有多少events会强制session 用privateredo 和undo 来构建新的block,但是我知道有一些events会导致session在commit之前放弃新的策略。
An obviouscase where Oracle has to abandon the new mechanism is when eitherthe private redo thread or the in-memory undo pool becomes full. As we saw earlier,each private area is limited to roughly 64KB (or 128KB if you’re running a64-bit copy of Oracle). When an area is full, Oracle creates a single redorecord, copies it to the public redo thread, and then continues using thepublic redo thread in the old way.
--一个明显的案例,当private redo thread 或者in-memory undo pool 满了之后,Oracle就不得不放弃使用新的机制。正如我们前面示例中的,每个private area限制约64KB(64位系统是128KB),当privatearea满了之后,Oracle 就会直接创建单个的single redorecord,然后copy 这些到public redo thread,之后继续使用老方法来处理public redothread里的数据。
But there areother events that cause this switch prematurely. For example, your SQL mighttrigger a recursive statement. For a quick check on possible causes, and howmany times each has occurred, you could connect as SYS and run the followingSQL (sample taken from 10.2.0.3):
--但是也有一些enevts,其可以导致机制新旧机制之间过早的切换,比如我们的SQL可能会引发一个递归调用。为了快速的检查可能的原因,以及发生了多少次,就可以使用sys用户执行如下的语句:
select ktiffcat, ktiffflc from x$ktiff;
KTIFFCAT KTIFFFLC
--------------------------------------------------
Undo pool overflowflushes 0
Stack cvflushes 21
Multi-block undoflushes 0
Max. chgsflushes 9
NTPflushes 0
Contentionflushes 18
Redo pool overflowflushes 0
Logfile spaceflushes 0
Multiple persistent bufferflushes 0
Bind timeflushes 0
Rollbackflushes 6
Commitflushes 13628
Recursive txnflushes 2
Redo only CRflushes 0
Ditributed txnflushes 0
Set txn use rbsflushes 0
Bitmap state changeflushes 26
Presumed commitviolation 0
18 rows selected.
Unfortunately,although there are various statistics relating to IMU in the v$sysstat dynamicperformance view (e.g., IMU flushes), they don’t seem to correlate terriblywell with the figures from the x$ structure—although, if you ignore a couple ofthe numbers, you can get quite close to thinking you’ve found the matchingbits.
Oracle Redo 机制的小结:
假设我们修改一个记录,那么操作步骤如下:
1. 创建对应block的redo changevector
2. 创建对应block的undo recordvector并写入undotablespace。
3. 将undo record和 data block 的redo change vector绑定成一个redo record并写入log buffer中。
4. 修改block的内容。
在Oracle 10g之前使用的是旧的机制:
Session在事务中产生的每一个改变都会生成一个record,然后写入redo log buffer。单个session在很短的周期内可能产生多个change,并且可能有多个session 同时并发操作,但是只有一个redo log buffer他们可以访问。因此在生成redo 时,将redo record 写入redo log buffer的过程会是一个非常严重的瓶颈。
当数据库不忙时,产生redo record也会相对较少,这种情况下: one change = onerecord = one allocation 的策略对大部分系统都是够用的,当系统非常大时,就需要来处理大量的并发allocations 操作,所以在Oracle 10g的新策略里就出现了private redo 和in-memory undo。
在oracle 10g之后使用的是新的机制:
在Oracle 10g中使用了Latch的机制,通过redo allocation latch 来保护redo log buffer 被公平的使用。当一个进程需要使用log buffer中的space时,那么会申请redo allocation latch,一旦它已独占的方式获取了这个latch,它就可以把其对应的信息写入到redo log buffer中了,这样就可以避免多个线程重写log buffer中相同的块,如果有大量的进程来申请redo allocationlatch,他们是拿不到这个latch,并且这个过程还会消耗大量的资源(主要是CPU 用来latch spin),或者是在第一次spin 失败后进入等待队列,休眠sleep time后继续进行spin操作,来申请redo allocation latch操作。
Latch机制保证了logbuffer被公平的使用,在高并发的情况下还是会有进程申请不到资源,所以又产生了private redo log buffer的概念。
一个进程可以在整个事务中工作,生成事务相关的所有的change vectors,在一对private redo log buffers中存储这些vectors 。 当这个事务结束时,进程就copy 所有private redo log 里的信息到public redo log buffer中,剩下的操作就按照原始的方式处理。 按照这种方式处理之后,一个进程仅需要在一个事务结束之后来申请public redo allocation latch,而不是每次改变都申请这个latch。
在Oracle 新机制中有两种类型的redo allocation latch,一个是private redo threads,另一个是public redo threads,每个thread都受自己的latch控制。
总体来说,新机制下latch活动减少。但对于一个多用户系统,总会有一些观点认为需要使用旧的机制,在旧机制下,每个操作产生的vector都会写入log buffer和应用到block上,这样每次身上的数据都很少,而使用新机制,redo 和apply的数据都会相对较大,也就意味着需要更多的时间来处理这些信息,也会潜在的block 其他session 访问我们正在修改的信息。 这也是为什么private redo thread 会严格的控制大小的原因。
更深入一点的问题,使用旧的机制时,第二个session 会立即读取到block的变化,如果使用新机制,当block的信息到private redo,第二个session需要跟踪这些redo 信息并apply这些block(如果需要),然后决定下一步做什么,这样就不能立即看到之前session的操作,这样也导致一个问题,就是代码路径过长,更负责,即我们需要使用更多的CPU 资源才可以一致性的读取我们的数据。
一个明显的案例,当private redo thread 或者in-memory undo pool 满了之后,Oracle 就不得不放弃使用新的机制。正如我们前面示例中的,每个privatearea限制约64KB(64位系统是128KB),当privatearea满了之后,Oracle 就会直接创建单个的single redo record,然后copy 这些到public redo thread,之后继续使用老方法来处理public redo thread里的数据。
四.Undo Complexity
Undo is morecomplicated than redo. Most significantly, any process may, in principle, needto access any undo record at any time to “hide” an item of data that it is notyet supposed to see. To meet this requirement efficiently, Oracle keeps theundo records inside the database in a special tablespace known, unsurprisingly,as the undo tablespace; then the code has to maintain various pointers to theundo records so that a process knows where to find the undo records it needs.The advantage of keeping undo information inside the database in “ordinary”data files is that the blocks are subject to exactly the same buffering,writing, and recovery algorithms as every block in the database—the basic codeto manage undo blocks is the same as the code to handle every other type ofblock.
--Oracle 中的Undo 信息要比Redo 复杂。理论上,任何进程在任何时候都可能会访问任何的undo record,来隐藏那些还不可见的数据(查询UNDO获取前镜像),为了满足这种要求,Oracle 把undo records保存在一个单独的表空间:undo tablespace里,然后根据需要使用代码来维护undo tablespace里的信息。
把undo records放在表空间里的好处是block的信息可以精确的定位在同样的buffering上,可以把block作为数据库的block来进行写和恢复操作。而且用来管理undo blocks的代码和处理其他类型block的代码是一样的。
There are threereasons why a process needs to read an undo record, and therefore three ways inwhich chains of pointers run through the undo tablespace. We will examine allthree in detail in Chapter 3, but I will make some initial comments about thecommonest two uses now.
--进程需要访问undo record的原因有三个,因此在undo tablespace里也就有三种不同的pointers chains。这个在后面章节里会说明,这里我们看最常见的2个原因。
Linked lists ofundo records are used to deal with read consistency, rolling back changes, andderiving commit SCNs that have been “lost” due to delayed block cleanout. Thethird topic will be postponed until Chapter 3.
--undo records的Linked lists用来处理read consistency,rollingback changes等。
4.1 Read Consistency
The first, andmost commonly invoked, use of undo is read consistency, and I have alreadycommented briefly on read consistency. The existence of undo allows a sessionto see an older version of the data when it’s not yet supposed to see a newerversion.
--第一个原因,也是最常见的,使用undo 来实现read consistency。如果已经修改的记录还没有提交,那么我们就可以通过undo信息来查看block的前镜像,从而来实现read consistency。
The requirementfor read consistency means that a block must contain a pointer to the undorecords that describe how to hide changes to the block. But there could be anarbitrarily large number of changes that need to be concealed, and insufficientspace for that many pointers in a single block. So Oracle allows a limitednumber of pointers in each block (one for each concurrent transaction affectingthe block), which are stored in the ITL entries. When a process creates an undorecord, it (usually) overwrites one of the existing pointers, saving theprevious value as part of the undo record.
--Read consistency 也就要求block 必须包含一个指针,指向对应的undo records,该undo records描述了如何来隐藏修改的block。但是block上可能有大量的change需要进行隐藏(即对非修改的session,只能看到前镜像),并且单个block上也没有足够的空间来保存这些指针,因此Oracle 在单个block只允许有限数量的pointers,这些pointers信息保存在ITL entry中,当一个进程创建一个undo record,它通常会重写ITL中一个已经存在pointer,然后保存之前的值来作为undo record的一部分。
OraceITL(Interested Transaction List) 说明
http://www.cndba.cn/Dave/article/1397
Take anotherlook at the undo record I showed you earlier, after updating three rows in asingle block:
*-----------------------------
* Rec #0xf slt:0x1a objn: 45810(0x0000b2f2) objd:45810 tblspc: 12(0x0000000c)
* Layer: 11(Row) opc: 1 rci 0x0e Undotype: Regular undo Last buffer split: No
Temp Object: No
Tablespace Undo: No
rdba: 0x00000000*-----------------------------
KDO undo record:
KTB Redo
op: 0x02 ver: 0x01
op: C uba: 0x0080009a.09d4.0d
KDO Op code: URP row dependencies Disabled xtype: XA bdba: 0x02c0018a hdba: 0x02c00189
itli: 2 ispac:0 maxfr: 4863
tabn: 0 slot: 4(0x4) flag: 0x2c lock: 0ckix: 16
ncol: 4 nnew: 1 size: -4 col 2:[ 6] 78 78 78 78 78 78
The table blockholding the fifth row I had updated was pointing to this undo record, and wecan see from the second line of the dump that it is record 0xf in the undoblock. Seven lines up from the bottom of the dump you see that this record hasop: C, which tells us that it is the continuation of an earlier update by thesame transaction. This lets Oracle know that the rest of the line uba:0x0080009a.09d4.0d is part of the information that has to be used to re-createthe older version of the block: as the xxxxxx (78s) are copied back to column 2of row 4, the value 0x0080009a.09d4.0d has to be copied back to ITL entry 2.
Of course, onceOracle has taken these steps to reconstruct an older version of the block, itwill discover that it hasn’t yet gone far enough, but the pointer in ITL 2 isnow telling it where to find the next undo record to apply. In this way aprocess can gradually work its way backward through time; the pointer in eachITL entry tells Oracle where to find an undo record to apply, and each undorecord includes the information to take the ITL entry backward in time as wellas taking the data backward in time.
4.2 Rollback
The second,major use of undo is in rolling back changes, either with an explicit rollback(or rollback to savepoint) or because a step in a transaction has failed andOracle has issued an implicit, statement-level rollback.
--第二个原因,undo 主要用来进行rollback change。我们可以通过执行rollback命令显示的进行rolling操作,也可以在事务失败时让Oracle 隐式的执行rollback操作。
Read consistencyis about a single block, and finding a linked list of all the undo records forthat block. Rolling back is about the history of a transaction, so we need alinked list that runs through all the undo records for a transaction in thecorrect (which, in this case, means reverse) order.
--Read consistency是关于单个block,操作时也是查找block对应的所有undorecords的linked list。
Rolling Back 是关于事务的历史操作,所以我们需要一个linked list,然后按照正确的顺序来处理事务的所有的undorecords。
Here is a simpleexample demonstrating why we need to link the undo records “backward.” Imaginewe update a row twice, changing a single column value from A to B and then fromB to C, giving us two undo records. If we want to reverse the change, we haveto change the C back to B before we can apply an undo record that says “changea B to an A”; in other words, we have to apply the second undo record before weapply the first undo record.
--这有一个简单的例子来帮助我们理解为什么我们需要link 这些undo records。假如我们修改了一个记录2次,从A改成B,在从B改成C,这时候我们进行rollback,那么我们在进行操作时,必须先执行C到B的undorecords,然后执行B到A的undo records。
Looking again atthe sample undo record, we can see signs of the linked list. Line 3 of the dumpincludes the entry rci 0x0e. This tells Oracle that the undo record createdimmediately before this undo record was number 14 (0x0e) in the same undoblock. It’s possible,of course, that the previous undo record will be in a different undo block, butthat should be the case only if the current undo record is the first undorecord of the undo block, in which case the rci entry would be zero and therdba: entry four lines below it would give the block address of the previousundo record. If you have to go back a block, then the last record of the blockwill usually be the required record, although technically what you need is therecord pointed at by the irb: entry. However, the only case in which the irb:entry might not point to the last record is if you have done a rollback tosavepoint.
There’s animportant difference between read consistency and rolling back, of course. Forread consistency we make a copy of the data block in memory and apply the undorecords to that block, and it’s a copy of the block that we can discard veryrapidly once we’ve finished with it; when rolling back we acquire the currentblock and apply the undo record to that. This has three important effects:
--Read consistency和Rolling back有很大的区别,对于Read consistency,我们仅copy data block到内存里,然后应用undo record到datablock上,对data block的copy 在我们使用结束之后就可以迅速的丢弃。但当我们进行rollingback时,我们需要需要获取current block,然后应用undo record,最主要的三点不同:
1. The data block is the currentblock, so it is the version of the block that must eventually be written todisc.
--data block是currentblock,所以该block最终还是要写回disk。
2. Because it is the currentblock, we will be generating redo as we change it (even though we are “changingit back to the way it used to be”).
--因为是current block,所以我们需要对我们的rolling back 生成redo records。
3. Because Oracle hascrash-recovery mechanisms that clean up accidents as efficiently as possible,we need to ensure that the undo record is marked as “undo applied” as we useit, and doing that generates even more redo.
--因为Oracle 有crash-recovery的机制来clean up故障,所以我们必须确保我们使用过的undo record被标记为undo applied,然后生成额为的redorecords。
If the undorecord was one that had already been used for rolling back, line 4 of the dumpwould have looked like this:
Undotype: Regular undo User UndoApplied Last buffer split: No
In the raw blockdump, the User Undo Applied flag is just 1 byte rather than a 17-characterstring.
Rolling backinvolves a lot of work, and a rollback can take roughly the same amount of timeas the original transaction, possibly generating a similar amount of redo. Butyou have to remember that rolling back is an activity that changes data blocks,so you have to reacquire, modify, and write those blocks, and write the redothat describes how you’ve changed those blocks. Moreover, if the transactionwas a large, long-running transaction, you may find that some of the blocksyou’ve changed have been written to disc and flushed from the cache—so they’llhave to be read from disc before you can roll them back!
--Rolling back牵涉到很多的工作,因此rollback 可能会和原始操作使用相同的时间,生成和原始操作差不多的redo records。我们还必须记住的是rolling back是一次change data block操作,所以我们必须再次获取,修改然后写回block到磁盘,同事还需要生成相关的redo 和undo records。
如果一个事务非常大,并且运行了很长时间,我们可能发现一些修改的block 已经写入饿了disc,所以在这种情况下我们在rollback之前还必须先从磁盘将block读取到内存。
Some systems useOracle tables to hold “temporary” or “scratchpad”information. One of the strategies used with such tables is to insert datawithout committing it so that read consistency makes it private to the session,and then roll back to make the data “go away.” There are many flaws in thisstrategy, the potentially high cost of rolling back being just one of them. Theability to eliminate the cost of rollback is one of the things that makesglobal temporary tables useful.
--在一些系统上,使用Oracle 表来保存临时的信息。其中一个策略就是使用表来insert 数据,但是不提交,从而利用read consistency来作为私人的数据,最后在rollback 这些修改。这是一个有缺陷的策略,因为在rolling back的时候可能会消耗很多的资源,在这种情况下我们可以使用全局的临时表来代替。
There are other overheadsintroduced by rolling back, of course. When a session creates undo records, itacquires, pins, and fills one undo block at a time; when it is rolling back itgets one record from an undo block at a time, releasing and reacquiring theblock for each record. This means that you generate more buffer visits on undoblocks to roll back than you generated when initially executing thetransaction. Moreover, every time Oracle acquires an undo record, it checksthat the tablespace it should be applied to is still online (if it isn’t,Oracle will transfer the undo record into a save undo segment in the systemtablespace); this shows up as get on the dictionary cache (specifically thedc_tablespaces cache).
--rolling back还有一些其他的内容,当session 创建undo records时,每次操作都需要获取,pins和fill one undo block。当进行rolling back时,会从undoblock里一次获取一个record,然后releasing,在从block里搜索下一个record。也就意味着在进行rollback时生成的buffer 会大于我们执行事务时生成的buffer。
并且Oracle 每次获取一个undorecord时,需要检查表空间是否一直是online状态,如果不是online,oracle 需要将undorecord 写入SYSTEM 表空间的undo segment中。
小结:
In some waysredo is a very simple concept: every change to a block in a data file isdescribed by a redo change vector, and these change vectors are written to theredo log buffer (almost) immediately, and are ultimately written into the redolog file.
--从某种意义上讲,redo 是非常简单的概念,block上的每个change都会生成一个redochange vector,这些redo change vector会立即写入到redo log buffer,最后这些redochange vector会写入online redo log file。
As we makechanges to data (which includes index entries and structural metadata), we alsocreate undo records in the undo tablespace that describe how to reverse thosechanges. Since the undo tablespace is just another set of data files, we createredo change vectors to describe the undo records we store there.
--当我们改变一个data时,我们也会在undo tablespace里生成对应的undo record,因为undotablespace是另一个data files,所以我们需要再次创建一个redo change vectors来记录我们的undo recored存在在什么地方。
In earlierversions of Oracle, change vectors were usually combined in pairs—onedescribing the forward change, one describing the undo record—to create asingle redo record that was written (initially) into the redo log buffer.
--在Oracle 早期的版本中,change vectors通常是一对,即一个用来描述forward change(redo),另一个用来描述undo record。然后把这一对vectors创建成一个redorecord,然后写入redo log buffer。
In laterversions of Oracle, the step of moving change vectors into the redo log bufferwas seen as an important bottleneck in OLTP systems, and a new mechanism wascreated to allow a session to accumulate all the changes for a transaction “inprivate” before creating one large redo record in the redo buffer.
--在Oracle 后面的版本中,在OLTP系统中将redovectors移动到redo log buffer中成了一个重要的瓶颈,于是出现了一个新的机制,允许我们将一个事务的所有操作打包到一起,存放到一个private redo log buffer中,待事务完成之后,在生成一个大的redo record,写入public redo log buffer中,这样可以减少对public redo log buffer的latch争用。
The newmechanism is strictly limited in the amount of work a session will do before itflushes its change vectors to the redo log buffer and switches to the oldermechanism, and there are various events that will make this switch happenprematurely.
--新机制在将change vectors写入到redo log buffer 之前会严格限制session中总的操作量,写入redo log buffer之后的操作按照老的机制进行,当然也有一些事件会导致新旧机制之间的切换,这个上面有示例说明。
While redooperates as a simple “write it and forget it” stream, undo may be frequentlyreread in the ongoing activity of the database, and undo records have to belinked together in different ways to allow for efficient access. Readconsistency requires chains of undo records for a given block; rolling backrequires a chain of undo records for a given transaction.
说明:根据<OracleCore Essential Internals for DBAs and Developers> 进行翻译整理。
-------------------------------------------------------------------------------------------------------
版权所有,文章允许转载,但必须以链接方式注明源地址,否则追究法律责任!
QQ:492913789
Email:ahdba@qq.com
Blog: http://www.cndba.cn/dave
Weibo: http://weibo.com/tianlesoftware
Twitter: http://twitter.com/tianlesoftware
Facebook: http://www.facebook.com/tianlesoftware
Linkedin: http://cn.linkedin.com/in/tianlesoftware
-------加群需要在备注说明Oracle表空间和数据文件的关系,否则拒绝申请----
DBA1 群:62697716(满); DBA2 群:62697977(满) DBA3 群:62697850(满)
DBA 超级群:63306533(满); DBA4 群:83829929 DBA5群: 142216823
DBA6 群:158654907 DBA7 群:172855474 DBA总群:104207940