2011年6月18日

          [轉(zhuǎn)]怎樣看懂Oracle的執(zhí)行計(jì)劃

          盡量用鳥語描述了,翻譯成中文反而容易誤解。


          一、什么是執(zhí)行計(jì)劃

          An explain plan is a representation of the access path that is taken when a query is executed within Oracle.


          二、如何訪問數(shù)據(jù)

          At the physical level Oracle reads blocks of data. The smallest amount of data read is a single Oracle block, the largest is constrained by operating system limits (and multiblock i/o). Logically Oracle finds the data to read by using the following methods:
          Full Table Scan (FTS)    --全表掃描
          Index Lookup (unique & non-unique)    --索引掃描(唯一和非唯一)
          Rowid    --物理行id


          三、執(zhí)行計(jì)劃層次關(guān)系

          When looking at a plan, the rightmost (ie most inndented) uppermost operation is the first thing that is executed. --采用最右最上最先執(zhí)行的原則看層次關(guān)系,在同一級(jí)如果某個(gè)動(dòng)作沒有子ID就最先執(zhí)行

          1.看一個(gè)簡(jiǎn)單的例子

          Query Plan
          -----------------------------------------
          SELECT STATEMENT [CHOOSE] Cost=1234
          **TABLE ACCESS FULL LARGE [:Q65001] [ANALYZED] --[:Q65001]表示是并行方式,[ANALYZED]表示該對(duì)象已經(jīng)分析過了

          優(yōu)化模式是CHOOSE的情況下,看Cost參數(shù)是否有值來決定采用CBO還是RBO:
          SELECT STATEMENT [CHOOSE] Cost=1234
           --Cost有值,采用CBO
          SELECT STATEMENT [CHOOSE] Cost= --Cost為空,采用RBO

          2.層次的父子關(guān)系,看比較復(fù)雜的例子:

          PARENT1

          **FIRST CHILD
          ****FIRST GRANDCHILD
          **SECOND CHILD

          Here the same principles apply, the FIRST GRANDCHILD is the initial operation then the FIRST CHILD followed by the SECOND CHILD and finally the PARENT collates the output. 


          四、例子解說

          Execution Plan
          ----------------------------------------------------------
          0 
          **SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=8 Bytes=248)
          1 0 
          **HASH JOIN (Cost=3 Card=8 Bytes=248)
          2 1 
          ****TABLE ACCESS (FULL) OF 'DEPT' (Cost=1 Card=3 Bytes=36)
          3 1 
          ****TABLE ACCESS (FULL) OF 'EMP' (Cost=1 Card=16 Bytes=304) 

          左側(cè)的兩排數(shù)據(jù),前面的是序列號(hào)ID,后面的是對(duì)應(yīng)的PID(父ID)。

          A shortened summary of this is:
          Execution starts with ID=0: SELECT STATEMENT but this is dependand on it's child objects
          So it executes its first child step: ID=1 PID=0 HASH JOIN but this is dependand on it's child objects
          So it executes its first child step: ID=2 PID=1 TABLE ACCESS (FULL) OF 'DEPT'
          Then the second child step: ID=3 PID=2 TABLE ACCESS (FULL) OF 'EMP'
          Rows are returned to the parent step(s) until finished 


          五、表訪問方式

          1.Full Table Scan (FTS) 全表掃描

          In a FTS operation, the whole table is read up to the high water mark (HWM). The HWM marks the last block in the table that has ever had data written to it. If you have deleted all the rows then you will still read up to the HWM. Truncate resets the HWM back to the start of the table. FTS uses multiblock i/o to read the blocks from disk.   --全表掃描模式下會(huì)讀數(shù)據(jù)到表的高水位線(HWM即表示表曾經(jīng)擴(kuò)展的最后一個(gè)數(shù)據(jù)塊),讀取速度依賴于Oracle初始化參數(shù)db_block_multiblock_read_count

          Query Plan
          ------------------------------------
          SELECT STATEMENT [CHOOSE] Cost=1
          **INDEX UNIQUE SCAN EMP_I1   --如果索引里就找到了所要的數(shù)據(jù),就不會(huì)再去訪問表了

          2.Index Lookup 索引掃描

          There are 5 methods of index lookup:

          index unique scan   --索引唯一掃描
          Method for looking up a single key value via a unique index. always returns a single value, You must supply AT LEAST the leading column of the index to access data via the index. 

          eg:
          SQL> explain plan for select empno,ename from emp where empno=10;

          index range scan   --索引局部掃描
          Index range scan is a method for accessing a range values of a particular column. AT LEAST the leading column of the index must be supplied to access data via the index. Can be used for range operations (e.g. > < <> >= <= between) .
          eg:
          SQL> explain plan for select mgr from emp where mgr = 5;

          index full scan   --索引全局掃描
          Full index scans are only available in the CBO as otherwise we are unable to determine whether a full scan would be a good idea or not. We choose an index Full Scan when we have statistics that indicate that it is going to be more efficient than a Full table scan and a sort. For example we may do a Full index scan when we do an unbounded scan of an index and want the data to be ordered in the index order. 
          eg: 

          SQL> explain plan for 
          select empno,ename from big_emp order by empno,ename;

          index fast full scan   --索引快速全局掃描,不帶order by情況下常發(fā)生
          Scans all the block in the index, Rows are not returned in sorted order, Introduced in 7.3 and requires V733_PLANS_ENABLED=TRUE and CBO, may be hinted using INDEX_FFS hint, uses multiblock i/o, can be executed in parallel, can be used to access second column of concatenated indexes. This is because we are selecting all of the index. 
          eg: 
          SQL> explain plan for 
          select empno,ename from big_emp;

          index skip scan   --索引跳躍掃描,where條件列是非索引的前導(dǎo)列情況下常發(fā)生
          Index skip scan finds rows even if the column is not the leading column of a concatenated index. It skips the first column(s) during the search.
          eg:

          SQL> 
          create index i_emp on emp(empno, ename);
          SQL> select /*+ index_ss(emp i_emp)*/ job from emp where ename='SMITH';

          3.Rowid 物理ID掃描

          This is the quickest access method available.Oracle retrieves the specified block and extracts the rows it is interested in. --Rowid掃描是最快的訪問數(shù)據(jù)方式


          六、表連接方式

          有三種連接方式:

          1.Sort Merge Join (SMJ)    --由于sort是非常耗資源的,所以這種連接方式要避免

          Rows are produced by Row Source 1 and are then sorted Rows from Row Source 2 are then produced and sorted by the same sort key as Row Source 1. Row Source 1 and 2 are NOT accessed concurrently. 

          SQL> explain plan for
          select /*+ ordered */ e.deptno,d.deptno
          from emp e,dept d
          where e.deptno = d.deptno
          order by e.deptno,d.deptno;

          Query Plan
          -------------------------------------
          SELECT STATEMENT [CHOOSE] Cost=17
          **MERGE JOIN
          ****SORT JOIN
          ******TABLE ACCESS FULL EMP [ANALYZED]
          ****SORT JOIN
          ******TABLE ACCESS FULL DEPT [ANALYZED]

          Sorting is an expensive operation, especially with large tables. Because of this, SMJ is often not a particularly efficient join method.

          2.Nested Loops (NL)    --比較高效的一種連接方式

          Fetches the first batch of rows from row source 1, Then we probe row source 2 once for each row returned from row source 1.
          For nested loops to be efficient it is important that the first row source returns as few rows as possible as this directly controls the number of probes of the second row source. Also it helps if the access method for row source 2 is efficient as this operation is being repeated once for every row returned by row source 1.

          SQL> explain plan for
          select a.dname,b.sql
          from dept a,emp b
          where a.deptno = b.deptno;

          Query Plan
          -------------------------
          SELECT STATEMENT [CHOOSE] Cost=5
          **NESTED LOOPS
          ****TABLE ACCESS FULL DEPT [ANALYZED]
          ****TABLE ACCESS FULL EMP [ANALYZED]

          3.Hash Join    --最為高效的一種連接方式

          New join type introduced in 7.3, More efficient in theory than NL & SMJ, Only accessible via the CBO. Smallest row source is chosen and used to build a hash table and a bitmap The second row source is hashed and checked against the hash table looking for joins. The bitmap is used as a quick lookup to check if rows are in the hash table and are especially useful when the hash table is too large to fit in memory.

          SQL> explain plan for
          select /*+ use_hash(emp) */ empno
          from emp,dept
          where emp.deptno = dept.deptno;

          Query Plan
          ----------------------------
          SELECT STATEMENT [CHOOSE] Cost=3
          **HASH JOIN
          ****TABLE ACCESS FULL DEPT
          ****TABLE ACCESS FULL EMP

          Hash joins are enabled by the parameter HASH_JOIN_ENABLED=TRUE in the init.ora or session. TRUE is the default in 7.3.

          3.Cartesian Product    --卡迪爾積,不算真正的連接方式,sql肯定寫的有問題

          A Cartesian Product is done where they are no join conditions between 2 row sources and there is no alternative method of accessing the data. Not really a join as such as there is no join! Typically this is caused by a coding mistake where a join has been left out.
          It can be useful in some circumstances - Star joins uses cartesian products.Notice that there is no join between the 2 tables:

          SQL> explain plan for
          select emp.deptno,dept,deptno
          from emp,dept

          Query Plan
          ------------------------------
          SLECT STATEMENT [CHOOSE] Cost=5
          **MERGE JOIN CARTESIAN
          ****TABLE ACCESS FULL DEPT
          ****SORT JOIN
          ******TABLE ACCESS FULL EMP

          The CARTESIAN keyword indicate that we are doing a cartesian product. 

          七、運(yùn)算符

          1.sort    --排序,很消耗資源

          There are a number of different operations that promote sorts:
          order by clauses
          group by
          sort merge join

          2.filter    --過濾,如not in、min函數(shù)等容易產(chǎn)生

          Has a number of different meanings, used to indicate partition elimination, may also indicate an actual filter step where one row source is filtering, another, functions such as min may introduce filter steps into query plans.

          3.view    --視圖,大都由內(nèi)聯(lián)視圖產(chǎn)生

          When a view cannot be merged into the main query you will often see a projection view operation. This indicates that the 'view' will be selected from directly as opposed to being broken down into joins on the base tables. A number of constructs make a view non mergeable. Inline views are also non mergeable.
          eg: 
          SQL> explain plan for
          select ename,tot
          from emp,(select empno,sum(empno) tot from big_emp group by empno) tmp
          where emp.empno = tmp.empno;

          Query Plan
          ------------------------
          SELECT STATEMENT [CHOOSE]
          **HASH JOIN
          **TABLE ACCESS FULL EMP [ANALYZED]
          **VIEW
          ****SORT GROUP BY
          ******INDEX FULL SCAN BE_IX 

          4.partition view     --分區(qū)視圖

          Partition views are a legacy technology that were superceded by the partitioning option. This section of the article is provided as reference for such legacy systems.

          posted @ 2011-06-18 16:33 ... 閱讀(177) | 評(píng)論 (0)編輯 收藏

          [轉(zhuǎn)]Oracle執(zhí)行計(jì)劃的相關(guān)概念

          本文介紹了ORACLE執(zhí)行計(jì)劃的一些基本概念,供學(xué)習(xí)應(yīng)用。

          一.相關(guān)的概念

          Rowid的概念:rowid是一個(gè)偽列,既然是偽列,那么這個(gè)列就不是用戶定義,而是系統(tǒng)自己給加上的。 對(duì)每個(gè)表都有一個(gè)rowid的偽列,但是表中并不物理存儲(chǔ)ROWID列的值。不過你可以像使用其它列那樣使用它,但是不能刪除改列,也不能對(duì)該列的值進(jìn)行 修改、插入。一旦一行數(shù)據(jù)插入數(shù)據(jù)庫,則rowid在該行的生命周期內(nèi)是唯一的,即即使該行產(chǎn)生行遷移,行的rowid也不會(huì)改變。

          Recursive SQL概念:有時(shí)為了執(zhí)行用戶發(fā)出的一個(gè)sql語句,Oracle必須執(zhí)行一些額外的語句,我們將這些額外的語句稱之為'recursive calls'或'recursive SQL statements'。如當(dāng)一個(gè)DDL語句發(fā)出后,ORACLE總是隱含的發(fā)出一些recursive SQL語句,來修改數(shù)據(jù)字典信息,以便用戶可以成功的執(zhí)行該DDL語句。當(dāng)需要的數(shù)據(jù)字典信息沒有在共享內(nèi)存中時(shí),經(jīng)常會(huì)發(fā)生Recursive calls,這些Recursive calls會(huì)將數(shù)據(jù)字典信息從硬盤讀入內(nèi)存中。用戶不比關(guān)心這些recursive SQL語句的執(zhí)行情況,在需要的時(shí)候,ORACLE會(huì)自動(dòng)的在內(nèi)部執(zhí)行這些語句。當(dāng)然DML語句與SELECT都可能引起recursive SQL。簡(jiǎn)單的說,我們可以將觸發(fā)器視為recursive SQL。

          Row Source(行源):用在查詢中,由上一操作返回的符合條件的行的集合,即可以是表的全部行數(shù)據(jù)的集合;也可以是表的部分行數(shù)據(jù)的集合;也可以為對(duì)上2個(gè)row source進(jìn)行連接操作(如join連接)后得到的行數(shù)據(jù)集合。

          Predicate(謂詞):一個(gè)查詢中的WHERE限制條件

          Driving Table(驅(qū)動(dòng)表):該表又稱為外層表(OUTER TABLE)。這個(gè)概念用于嵌套與HASH連接中。如果該row source返回較多的行數(shù)據(jù),則對(duì)所有的后續(xù)操作有負(fù)面影響。注意此處雖然翻譯為驅(qū)動(dòng)表,但實(shí)際上翻譯為驅(qū)動(dòng)行源(driving row source)更為確切。一般說來,是應(yīng)用查詢的限制條件后,返回較少行源的表作為驅(qū)動(dòng)表,所以如果一個(gè)大表在WHERE條件有有限制條件(如等值限 制),則該大表作為驅(qū)動(dòng)表也是合適的,所以并不是只有較小的表可以作為驅(qū)動(dòng)表,正確說法應(yīng)該為應(yīng)用查詢的限制條件后,返回較少行源的表作為驅(qū)動(dòng)表。在執(zhí)行 計(jì)劃中,應(yīng)該為靠上的那個(gè)row source,后面會(huì)給出具體說明。在我們后面的描述中,一般將該表稱為連接操作的row source 1。

          Probed Table(被探查表):該表又稱為內(nèi)層表(INNER TABLE)。在我們從驅(qū)動(dòng)表中得到具體一行的數(shù)據(jù)后,在該表中尋找符合連接條件的行。所以該表應(yīng)當(dāng)為大表(實(shí)際上應(yīng)該為返回較大row source的表)且相應(yīng)的列上應(yīng)該有索引。在我們后面的描述中,一般將該表稱為連接操作的row source 2。

          組合索引(concatenated index):由多個(gè)列構(gòu)成的索引,如create index idx_emp on emp(col1, col2, col3, ……),則我們稱idx_emp索引為組合索引。在組合索引中有一個(gè)重要的概念:引導(dǎo)列(leading column),在上面的例子中,col1列為引導(dǎo)列。當(dāng)我們進(jìn)行查詢時(shí)可以使用”where col1 = ? ”,也可以使用”where col1 = ? and col2 = ?”,這樣的限制條件都會(huì)使用索引,但是”where col2 = ? ”查詢就不會(huì)使用該索引。所以限制條件中包含先導(dǎo)列時(shí),該限制條件才會(huì)使用該組合索引。

          可選擇性(selectivity):比較一下列中唯一鍵的數(shù)量和表中的行數(shù),就可以判斷該列的可選擇性。 如果該列的”唯一鍵的數(shù)量/表中的行數(shù)”的比值越接近1,則該列的可選擇性越高,該列就越適合創(chuàng)建索引,同樣索引的可選擇性也越高。在可選擇性高的列上進(jìn) 行查詢時(shí),返回的數(shù)據(jù)就較少,比較適合使用索引查詢。

          二.oracle訪問數(shù)據(jù)的存取方法

          1) 全表掃描(Full Table Scans, FTS)

          為實(shí)現(xiàn)全表掃描,Oracle讀取表中所有的行,并檢查每一行是否滿足語句的WHERE限制條件一個(gè)多塊讀 操作可以使一次I/O能讀取多塊數(shù)據(jù)塊(db_block_multiblock_read_count參數(shù)設(shè)定),而不是只讀取一個(gè)數(shù)據(jù)塊,這極大的減 少了I/O總次數(shù),提高了系統(tǒng)的吞吐量,所以利用多塊讀的方法可以十分高效地實(shí)現(xiàn)全表掃描,而且只有在全表掃描的情況下才能使用多塊讀操作。在這種訪問模 式下,每個(gè)數(shù)據(jù)塊只被讀一次。

          使用FTS的前提條件:在較大的表上不建議使用全表掃描,除非取出數(shù)據(jù)的比較多,超過總量的5% -- 10%,或你想使用并行查詢功能時(shí)。

          使用全表掃描的例子:

          ~~~~~~~~~~~~~~~~~~~~~~~~ SQL> explain plan for select * from dual;

          Query Plan

          -----------------------------------------

          SELECT STATEMENT[CHOOSE] Cost=

          TABLE ACCESS FULL DUAL

          2) 通過ROWID的表存?。═able Access by ROWID或rowid lookup)

          行的ROWID指出了該行所在的數(shù)據(jù)文件、數(shù)據(jù)塊以及行在該塊中的位置,所以通過ROWID來存取數(shù)據(jù)可以快速定位到目標(biāo)數(shù)據(jù)上,是Oracle存取單行數(shù)據(jù)的最快方法。

          這種存取方法不會(huì)用到多塊讀操作,一次I/O只能讀取一個(gè)數(shù)據(jù)塊。我們會(huì)經(jīng)常在執(zhí)行計(jì)劃中看到該存取方法,如通過索引查詢數(shù)據(jù)。

          使用ROWID存取的方法: SQL> explain plan for select * from dept where rowid = 'AAAAyGAADAAAAATAAF';

          Query Plan

          ------------------------------------

          SELECT STATEMENT [CHOOSE] Cost=1

          TABLE ACCESS BY ROWID DEPT [ANALYZED]


          3)索引掃描(Index Scan或index lookup)

          我們先通過index查找到數(shù)據(jù)對(duì)應(yīng)的rowid值(對(duì)于非唯一索引可能返回多個(gè)rowid值),然后根據(jù)rowid直接從表中得到具體的數(shù)據(jù),這 種查找方式稱為索引掃描或索引查找(index lookup)。一個(gè)rowid唯一的表示一行數(shù)據(jù),該行對(duì)應(yīng)的數(shù)據(jù)塊是通過一次i/o得到的,在此情況下該次i/o只會(huì)讀取一個(gè)數(shù)據(jù)庫塊。

          在索引中,除了存儲(chǔ)每個(gè)索引的值外,索引還存儲(chǔ)具有此值的行對(duì)應(yīng)的ROWID值。索引掃描可以由2步組成:(1) 掃描索引得到對(duì)應(yīng)的rowid值。 (2) 通過找到的rowid從表中讀出具體的數(shù)據(jù)。每步都是單獨(dú)的一次I/O,但是對(duì)于索引,由于經(jīng)常使用,絕大多數(shù)都已經(jīng)CACHE到內(nèi)存中,所以第1步的 I/O經(jīng)常是邏輯I/O,即數(shù)據(jù)可以從內(nèi)存中得到。但是對(duì)于第2步來說,如果表比較大,則其數(shù)據(jù)不可能全在內(nèi)存中,所以其I/O很有可能是物理I/O,這 是一個(gè)機(jī)械操作,相對(duì)邏輯I/O來說,是極其費(fèi)時(shí)間的。所以如果多大表進(jìn)行索引掃描,取出的數(shù)據(jù)如果大于總量的5% -- 10%,使用索引掃描會(huì)效率下降很多。如下列所示:
          SQL> explain plan for select empno, ename from emp where empno=10;

          Query Plan

          ------------------------------------

          SELECT STATEMENT [CHOOSE] Cost=1

          TABLE ACCESS BY ROWID EMP [ANALYZED]

          INDEX UNIQUE SCAN EMP_I1


          但是如果查詢的數(shù)據(jù)能全在索引中找到,就可以避免進(jìn)行第2步操作,避免了不必要的I/O,此時(shí)即使通過索引掃描取出的數(shù)據(jù)比較多,效率還是很高的

          SQL> explain plan for select empno from emp where empno=10;-- 只查詢empno列值

          Query Plan

          ------------------------------------

          SELECT STATEMENT [CHOOSE] Cost=1

          INDEX UNIQUE SCAN EMP_I1

          進(jìn)一步講,如果sql語句中對(duì)索引列進(jìn)行排序,因?yàn)樗饕呀?jīng)預(yù)先排序好了,所以在執(zhí)行計(jì)劃中不需要再對(duì)索引列進(jìn)行排序
          SQL> explain plan for select empno, ename from emp

          where empno > 7876 order by empno;

          Query Plan

          --------------------------------------------------------------------------------

          SELECT STATEMENT[CHOOSE] Cost=1

          TABLE ACCESS BY ROWID EMP [ANALYZED]

          INDEX RANGE SCAN EMP_I1 [ANALYZED]


          從這個(gè)例子中可以看到:因?yàn)樗饕且呀?jīng)排序了的,所以將按照索引的順序查詢出符合條件的行,因此避免了進(jìn)一步排序操作。

          根據(jù)索引的類型與where限制條件的不同,有4種類型的索引掃描:

          索引唯一掃描(index unique scan)

          索引范圍掃描(index range scan)

          索引全掃描(index full scan)

          索引快速掃描(index fast full scan)

          (1) 索引唯一掃描(index unique scan)

          通過唯一索引查找一個(gè)數(shù)值經(jīng)常返回單個(gè)ROWID。如果存在UNIQUE 或PRIMARY KEY 約束(它保證了語句只存取單行)的話,Oracle經(jīng)常實(shí)現(xiàn)唯一性掃描。

          使用唯一性約束的例子:

          SQL> explain plan for

          select empno,ename from emp where empno=10;

          Query Plan

          ------------------------------------

          SELECT STATEMENT [CHOOSE] Cost=1

          TABLE ACCESS BY ROWID EMP [ANALYZED]

          INDEX UNIQUE SCAN EMP_I1

          (2) 索引范圍掃描(index range scan)

          使用一個(gè)索引存取多行數(shù)據(jù),在唯一索引上使用索引范圍掃描的典型情況下是在謂詞(where限制條件)中使用了范圍操作符(如>、<、<>、>=、<=、between)

          使用索引范圍掃描的例子:


          SQL> explain plan for select empno,ename from emp

          where empno > 7876 order by empno;

          Query Plan

          --------------------------------------------------------------------------------

          SELECT STATEMENT[CHOOSE] Cost=1

          TABLE ACCESS BY ROWID EMP [ANALYZED]

          INDEX RANGE SCAN EMP_I1 [ANALYZED]

          在非唯一索引上,謂詞col = 5可能返回多行數(shù)據(jù),所以在非唯一索引上都使用索引范圍掃描。

          使用index rang scan的3種情況:

          (a) 在唯一索引列上使用了range操作符(> < <> >= <= between)

          (b) 在組合索引上,只使用部分列進(jìn)行查詢,導(dǎo)致查詢出多行

          (c) 對(duì)非唯一索引列上進(jìn)行的任何查詢。

          (3) 索引全掃描(index full scan)

          與全表掃描對(duì)應(yīng),也有相應(yīng)的全索引掃描。而且此時(shí)查詢出的數(shù)據(jù)都必須從索引中可以直接得到。

          全索引掃描的例子:

          An Index full scan will not perform single block i/o's and so it may prove to be inefficient.

          e.g.

          Index BE_IX is a concatenated index on big_emp (empno, ename)


          SQL> explain plan for select empno, ename from big_emp order by empno,ename;

          Query Plan

          --------------------------------------------------------------------------------

          SELECT STATEMENT[CHOOSE] Cost=26

          INDEX FULL SCAN BE_IX [ANALYZED]


          (4) 索引快速掃描(index fast full scan)

          掃描索引中的所有的數(shù)據(jù)塊,與 index full scan很類似,但是一個(gè)顯著的區(qū)別就是它不對(duì)查詢出的數(shù)據(jù)進(jìn)行排序,即數(shù)據(jù)不是以排序順序被返回。在這種存取方法中,可以使用多塊讀功能,也可以使用并行讀入,以便獲得最大吞吐量與縮短執(zhí)行時(shí)間。

          索引快速掃描的例子:

          BE_IX索引是一個(gè)多列索引: big_emp (empno,ename)

          SQL> explain plan for select empno,ename from big_emp;

          Query Plan

          ------------------------------------------

          SELECT STATEMENT[CHOOSE] Cost=1

          INDEX FAST FULL SCAN BE_IX [ANALYZED]


          只選擇多列索引的第2列:

          SQL> explain plan for select ename from big_emp;

          Query Plan

          ------------------------------------------

          SELECT STATEMENT[CHOOSE] Cost=1

          INDEX FAST FULL SCAN BE_IX [ANALYZED]

          三.表之間的連接

          Join是一種試圖將兩個(gè)表結(jié)合在一起的謂詞,一次只能連接2個(gè)表,表連接也可以被稱為表關(guān)聯(lián)。在后面的敘 述中,我們將會(huì)使用”row source”來代替”表”,因?yàn)槭褂胷ow source更嚴(yán)謹(jǐn)一些,并且將參與連接的2個(gè)row source分別稱為row source1和row source 2。Join過程的各個(gè)步驟經(jīng)常是串行操作,即使相關(guān)的row source可以被并行訪問,即可以并行的讀取做join連接的兩個(gè)row source的數(shù)據(jù),但是在將表中符合限制條件的數(shù)據(jù)讀入到內(nèi)存形成row source后,join的其它步驟一般是串行的。有多種方法可以將2個(gè)表連接起來,當(dāng)然每種方法都有自己的優(yōu)缺點(diǎn),每種連接類型只有在特定的條件下才會(huì) 發(fā)揮出其最大優(yōu)勢(shì)。

          row source(表)之間的連接順序?qū)τ诓樵兊男视蟹浅4蟮挠绊?。通過首先存取特定的表,即將該表作為驅(qū)動(dòng)表,這樣可以先應(yīng)用某些限制條件,從而得到一個(gè) 較小的row source,使連接的效率較高,這也就是我們常說的要先執(zhí)行限制條件的原因。一般是在將表讀入內(nèi)存時(shí),應(yīng)用where子句中對(duì)該表的限制條件。

          根據(jù)2個(gè)row source的連接條件的中操作符的不同,可以將連接分為等值連接(如WHERE A.COL3 = B.COL4)、非等值連接(WHERE A.COL3 > B.COL4)、外連接(WHERE A.COL3 = B.COL4(+))。上面的各個(gè)連接的連接原理都基本一樣,所以為了簡(jiǎn)單期間,下面以等值連接為例進(jìn)行介紹。

          在后面的介紹中,都已:

          SELECT A.COL1, B.COL2

          FROM A, B

          WHERE A.COL3 = B.COL4;

          為例進(jìn)行說明,假設(shè)A表為Row Soruce1,則其對(duì)應(yīng)的連接操作關(guān)聯(lián)列為COL 3;B表為Row Soruce2,則其對(duì)應(yīng)的連接操作關(guān)聯(lián)列為COL 4;

          連接類型:

          目前為止,無論連接操作符如何,典型的連接類型共有3種:

          排序 - - 合并連接(Sort Merge Join (SMJ) )

          嵌套循環(huán)(Nested Loops (NL) )

          哈希連接(Hash Join)

          排序 - - 合并連接(Sort Merge Join, SMJ)

          內(nèi)部連接過程:

          1) 首先生成row source1需要的數(shù)據(jù),然后對(duì)這些數(shù)據(jù)按照連接操作關(guān)聯(lián)列(如A.col3)進(jìn)行排序。

          2) 隨后生成row source2需要的數(shù)據(jù),然后對(duì)這些數(shù)據(jù)按照與sort source1對(duì)應(yīng)的連接操作關(guān)聯(lián)列(如B.col4)進(jìn)行排序。

          3) 最后兩邊已排序的行被放在一起執(zhí)行合并操作,即將2個(gè)row source按照連接條件連接起來

          下面是連接步驟的圖形表示:

          MERGE

          /\

          SORTSORT

          ||

          Row Source 1Row Source 2

          如果row source已經(jīng)在連接關(guān)聯(lián)列上被排序,則該連接操作就不需要再進(jìn)行sort操作,這樣可以大大提高這種連接操作的連接速度,因?yàn)榕判蚴莻€(gè)極其費(fèi)資源的操 作,特別是對(duì)于較大的表。預(yù)先排序的row source包括已經(jīng)被索引的列(如a.col3或b.col4上有索引)或row source已經(jīng)在前面的步驟中被排序了。盡管合并兩個(gè)row source的過程是串行的,但是可以并行訪問這兩個(gè)row source(如并行讀入數(shù)據(jù),并行排序).

          SMJ連接的例子:
          SQL> explain plan for

          select /*+ ordered */ e.deptno, d.deptno

          from emp e, dept d

          where e.deptno = d.deptno

          order by e.deptno, d.deptno;


          Query Plan

          -------------------------------------

          SELECT STATEMENT [CHOOSE] Cost=17

          MERGE JOIN

          SORT JOIN

          TABLE ACCESS FULL EMP [ANALYZED]

          SORT JOIN

          TABLE ACCESS FULL DEPT [ANALYZED]


          排序是一個(gè)費(fèi)時(shí)、費(fèi)資源的操作,特別對(duì)于大表。基于這個(gè)原因,SMJ經(jīng)常不是一個(gè)特別有效的連接方法,但是如果2個(gè)row source都已經(jīng)預(yù)先排序,則這種連接方法的效率也是蠻高的。

          嵌套循環(huán)(Nested Loops, NL)

          這個(gè)連接方法有驅(qū)動(dòng)表(外部表)的概念。其實(shí),該連接過程就是一個(gè)2層嵌套循環(huán),所以外層循環(huán)的次數(shù)越少越好,這也就是我們?yōu)槭裁磳⑿”砘蚍祷剌^小 row source的表作為驅(qū)動(dòng)表(用于外層循環(huán))的理論依據(jù)。但是這個(gè)理論只是一般指導(dǎo)原則,因?yàn)樽裱@個(gè)理論并不能總保證使語句產(chǎn)生的I/O次數(shù)最少。有時(shí) 不遵守這個(gè)理論依據(jù),反而會(huì)獲得更好的效率。如果使用這種方法,決定使用哪個(gè)表作為驅(qū)動(dòng)表很重要。有時(shí)如果驅(qū)動(dòng)表選擇不正確,將會(huì)導(dǎo)致語句的性能很差、很 差。

          內(nèi)部連接過程:

          Row source1的Row 1 ---------------- Probe ->Row source 2

          Row source1的Row 2 ---------------- Probe ->Row source 2

          Row source1的Row 3 ---------------- Probe ->Row source 2

          …….

          Row source1的Row n ---------------- Probe ->Row source 2

          從內(nèi)部連接過程來看,需要用row source1中的每一行,去匹配row source2中的所有行,所以此時(shí)保持row source1盡可能的小與高效的訪問row source2(一般通過索引實(shí)現(xiàn))是影響這個(gè)連接效率的關(guān)鍵問題。這只是理論指導(dǎo)原則,目的是使整個(gè)連接操作產(chǎn)生最少的物理I/O次數(shù),而且如果遵守這 個(gè)原則,一般也會(huì)使總的物理I/O數(shù)最少。但是如果不遵從這個(gè)指導(dǎo)原則,反而能用更少的物理I/O實(shí)現(xiàn)連接操作,那盡管違反指導(dǎo)原則吧!因?yàn)樽钌俚奈锢?I/O次數(shù)才是我們應(yīng)該遵從的真正的指導(dǎo)原則,在后面的具體案例分析中就給出這樣的例子。

          在上面的連接過程中,我們稱Row source1為驅(qū)動(dòng)表或外部表。Row Source2被稱為被探查表或內(nèi)部表。

          在NESTED LOOPS連接中,Oracle讀取row source1中的每一行,然后在row sourc2中檢查是否有匹配的行,所有被匹配的行都被放到結(jié)果集中,然后處理row source1中的下一行。這個(gè)過程一直繼續(xù),直到row source1中的所有行都被處理。這是從連接操作中可以得到第一個(gè)匹配行的最快的方法之一,這種類型的連接可以用在需要快速響應(yīng)的語句中,以響應(yīng)速度為 主要目標(biāo)。

          如果driving row source(外部表)比較小,并且在inner row source(內(nèi)部表)上有唯一索引,或有高選擇性非唯一索引時(shí),使用這種方法可以得到較好的效率。NESTED LOOPS有其它連接方法沒有的的一個(gè)優(yōu)點(diǎn)是:可以先返回已經(jīng)連接的行,而不必等待所有的連接操作處理完才返回?cái)?shù)據(jù),這可以實(shí)現(xiàn)快速的響應(yīng)時(shí)間。

          如果不使用并行操作,最好的驅(qū)動(dòng)表是那些應(yīng)用了where 限制條件后,可以返回較少行數(shù)據(jù)的的表,所以大表也可能稱為驅(qū)動(dòng)表,關(guān)鍵看限制條件。對(duì)于并行查詢,我們經(jīng)常選擇大表作為驅(qū)動(dòng)表,因?yàn)榇蟊砜梢猿浞掷貌?行功能。當(dāng)然,有時(shí)對(duì)查詢使用并行操作并不一定會(huì)比查詢不使用并行操作效率高,因?yàn)樽詈罂赡苊總€(gè)表只有很少的行符合限制條件,而且還要看你的硬件配置是否 可以支持并行(如是否有多個(gè)CPU,多個(gè)硬盤控制器),所以要具體問題具體對(duì)待。

          NL連接的例子:

          SQL> explain plan for

          select a.dname,b.sql

          from dept a,emp b

          where a.deptno = b.deptno;

          Query Plan

          -------------------------

          SELECT STATEMENT [CHOOSE] Cost=5

          NESTED LOOPS

          TABLE ACCESS FULL DEPT [ANALYZED]

          TABLE ACCESS FULL EMP [ANALYZED]


          哈希連接(Hash Join, HJ)

          這種連接是在oracle 7.3以后引入的,從理論上來說比NL與SMJ更高效,而且只用在CBO優(yōu)化器中。

          較小的row source被用來構(gòu)建hash table與bitmap,第2個(gè)row source被用來被hansed,并與第一個(gè)row source生成的hash table進(jìn)行匹配,以便進(jìn)行進(jìn)一步的連接。Bitmap被用來作為一種比較快的查找方法,來檢查在hash table中是否有匹配的行。特別的,當(dāng)hash table比較大而不能全部容納在內(nèi)存中時(shí),這種查找方法更為有用。這種連接方法也有NL連接中所謂的驅(qū)動(dòng)表的概念,被構(gòu)建為hash table與bitmap的表為驅(qū)動(dòng)表,當(dāng)被構(gòu)建的hash table與bitmap能被容納在內(nèi)存中時(shí),這種連接方式的效率極高。

          HASH連接的例子:

          SQL> explain plan for

          select /*+ use_hash(emp) */ empno

          from emp, dept

          where emp.deptno = dept.deptno;

          Query Plan

          ----------------------------

          SELECT STATEMENT[CHOOSE] Cost=3

          HASH JOIN

          TABLE ACCESS FULL DEPT

          TABLE ACCESS FULL EMP


          要使哈希連接有效,需要設(shè)置HASH_JOIN_ENABLED=TRUE,缺省情況下該參數(shù)為TRUE,另外,不要忘了還要設(shè)置 hash_area_size參數(shù),以使哈希連接高效運(yùn)行,因?yàn)楣_B接會(huì)在該參數(shù)指定大小的內(nèi)存中運(yùn)行,過小的參數(shù)會(huì)使哈希連接的性能比其他連接方式還 要低。

          總結(jié)一下,在哪種情況下用哪種連接方法比較好:

          排序 - - 合并連接(Sort Merge Join, SMJ):

          a) 對(duì)于非等值連接,這種連接方式的效率是比較高的。

          b) 如果在關(guān)聯(lián)的列上都有索引,效果更好。

          c) 對(duì)于將2個(gè)較大的row source做連接,該連接方法比NL連接要好一些。

          d) 但是如果sort merge返回的row source過大,則又會(huì)導(dǎo)致使用過多的rowid在表中查詢數(shù)據(jù)時(shí),數(shù)據(jù)庫性能下降,因?yàn)檫^多的I/O。

          嵌套循環(huán)(Nested Loops, NL):

          a) 如果driving row source(外部表)比較小,并且在inner row source(內(nèi)部表)上有唯一索引,或有高選擇性非唯一索引時(shí),使用這種方法可以得到較好的效率。

          b) NESTED LOOPS有其它連接方法沒有的的一個(gè)優(yōu)點(diǎn)是:可以先返回已經(jīng)連接的行,而不必等待所有的連接操作處理完才返回?cái)?shù)據(jù),這可以實(shí)現(xiàn)快速的響應(yīng)時(shí)間。

          哈希連接(Hash Join, HJ):

          a) 這種方法是在oracle7后來引入的,使用了比較先進(jìn)的連接理論,一般來說,其效率應(yīng)該好于其它2種連接,但是這種連接只能用在CBO優(yōu)化器中,而且需要設(shè)置合適的hash_area_size參數(shù),才能取得較好的性能。

          b) 在2個(gè)較大的row source之間連接時(shí)會(huì)取得相對(duì)較好的效率,在一個(gè)row source較小時(shí)則能取得更好的效率。

          c) 只能用于等值連接中

          笛卡兒乘積(Cartesian Product)

          當(dāng)兩個(gè)row source做連接,但是它們之間沒有關(guān)聯(lián)條件時(shí),就會(huì)在兩個(gè)row source中做笛卡兒乘積,這通常由編寫代碼疏漏造成(即程序員忘了寫關(guān)聯(lián)條件)。笛卡爾乘積是一個(gè)表的每一行依次與另一個(gè)表中的所有行匹配。在特殊情 況下我們可以使用笛卡兒乘積,如在星形連接中,除此之外,我們要盡量使用笛卡兒乘積,否則,自己想結(jié)果是什么吧!

          注意在下面的語句中,在2個(gè)表之間沒有連接。

          SQL> explain plan for

          select emp.deptno,dept,deptno

          from emp,dept

          Query Plan

          ------------------------

          SLECT STATEMENT [CHOOSE] Cost=5

          MERGE JOIN CARTESIAN

          TABLE ACCESS FULL DEPT

          SORT JOIN

          TABLE ACCESS FULL EMP


          CARTESIAN關(guān)鍵字指出了在2個(gè)表之間做笛卡爾乘積。假如表emp有n行,dept表有m行,笛卡爾乘積的結(jié)果就是得到n * m行結(jié)果。

          posted @ 2011-06-18 16:16 ... 閱讀(148) | 評(píng)論 (0)編輯 收藏

          [轉(zhuǎn)]你是否懂得Oracle UNION ALL

          Oracle有很多值得學(xué)習(xí)的地方,這里我們主要介紹Oracle UNION ALL,包括介紹UNION等方面。通常情況下,用UNION替換WHERE子句中的OR將會(huì)起到較好的效果。對(duì)索引列使用OR將造成全表掃描。注意,以上規(guī)則只針對(duì)多個(gè)索引列有效。假如有column沒有被索引,查詢效率可能會(huì)因?yàn)槟鷽]有選擇OR而降低。在下面的例子中,LOC_ID REGION上都建有索引。

          高效:

          1.  SELECT LOC_ID 。 LOC_DESC REGION FROM LOCATION WHERE LOC_ID = 10 UNION SELECT LOC_ID ,
          LOC_DESC 
          ,REGION FROM LOCATION WHERE REGION = “MELBOURNE” 

          低效:

          1.  SELECT LOC_ID ,LOC_DESC REGION FROM LOCATION WHERE LOC_ID = 10 OR REGION = “MELBOURNE” 

          IN來替換OR

          這是一條簡(jiǎn)單易記的規(guī)則,但是實(shí)際的執(zhí)行效果還須檢驗(yàn),在Oracle8i下,兩者的執(zhí)行路徑似乎是相同的:

          低效:

          1.  SELECT…. FROM LOCATION WHERE LOC_ID = 10 OR LOC_ID = 20 OR LOC_ID = 30 

          高效:

          1.  SELECT… FROM LOCATION WHERE LOC_IN IN (10,20,30); 

          避免在索引列上使用IS NULLIS NOT NULL

          避免在索引中使用任何能夠?yàn)榭盏牧校?span lang="EN-US">Oracle將無法使用該索引。對(duì)于單列索引,假如列包含空值,索引中將不存在此記錄。對(duì)于復(fù)合索引,假如每個(gè)列都為空,索引中同樣不存在此記錄。假如至少有一個(gè)列不為空,則記錄存在于索引中。舉例:假如唯一性索引建立在表的A列和B列上,并且表中存在一條記錄的 AB值為(123,null), Oracle將不接受下一條具備相同AB值(123,null)的記錄(插入)。然而假如任何的索引列都為空,Oracle將認(rèn)為整個(gè)鍵值為空而空不等于空。因此您能夠插入1000 條具備相同鍵值的記錄,當(dāng)然他們都是空! 因?yàn)榭罩挡淮嬖谟谒饕兄?span lang="EN-US">,所以WHERE子句中對(duì)索引列進(jìn)行空值比較將使ORACLE停用該索引。

          總是使用索引的第一個(gè)列:

          假如索引是建立在多個(gè)列上,只有在他的第一個(gè)列(leading column)where子句引用時(shí),優(yōu)化器才會(huì)選擇使用該索引。這也是一條簡(jiǎn)單而重要的規(guī)則,當(dāng)僅引用索引的第二個(gè)列時(shí),優(yōu)化器使用了全表掃描而忽略了索引。

          Oracle UNION ALL替換UNION ( 假如有可能的話)

          當(dāng)SQL語句需要UNION兩個(gè)查詢結(jié)果集合時(shí),這兩個(gè)結(jié)果集合會(huì)以Oracle UNION ALL的方式被合并,然后在輸出最終結(jié)果前進(jìn)行排序。假如用 Oracle UNION ALL替代UNION,這樣排序就不是必要了。效率就會(huì)因此得到提高。需要注意的是,Oracle UNION ALL將重復(fù)輸出兩個(gè)結(jié)果集合中相同記錄。因此各位還是要從業(yè)務(wù)需求分析使用Oracle UNION ALL的可行性。 UNION 將對(duì)結(jié)果集合排序,這個(gè)操作會(huì)使用到SORT_AREA_SIZE這塊內(nèi)存。對(duì)于這塊內(nèi)存的優(yōu)化也是相當(dāng)重要的。

          posted @ 2011-06-18 16:01 ... 閱讀(496) | 評(píng)論 (0)編輯 收藏

          [轉(zhuǎn)]hibernate抓取策略

          原文:http://jiangxuwen7515.blog.163.com/blog/static/817523502010730481252/?fromdm&fromSearch&isFromSearchEngine=yes

          一、
          hibernate抓取策略(單端代理的批量抓取fetch=select(默認(rèn))/join

          測(cè)試用例:

          Student student = (Student)session.get(Student.class, 1);
          System.out.println(student.getName());
          System.out.println(student.getClasses().getName());

          1)保持默認(rèn),同fetch="select",如:
          <many-to-one name="classes" column="classesid"
          fetch="select"/>

          fetch="select",另外發(fā)送一條select語句抓取當(dāng)前對(duì)象關(guān)聯(lián)實(shí)體或集合

          執(zhí)行結(jié)果:2條語句

          Hibernate: select student0_.id as id1_0_, student0_.name as name1_0_, student0_.class_id as class3_1_0_ from student_join student0_ where student0_.id=?
          學(xué)生1
          Hibernate: select classes0_.id as id0_0_, classes0_.name as name0_0_
          from classes_join classes0_ where classes0_.id=?
          高一(1)

          ======================================

          2)設(shè)置fetch="join",如:
          <many-to-one name="classes" column="classesid" fetch="join"/>

          fetch="join",hibernate會(huì)通過select語句使用外連接來加載其關(guān)聯(lián)實(shí)體或集合

          此時(shí)lazy會(huì)失效

          執(zhí)行結(jié)果:一條join語句

          Hibernate: select student0_.id as id1_1_, student0_.name as name1_1_, student0_.class_id as class3_1_1_, classes1_.id as id0_0_, classes1_.name as name0_0_ from student_join student0_ left outer join classes_join classes1_ on student0_.class_id=classes1_.id where student0_.id=?
          學(xué)生1
          高一(1)

          ======================================================

          二、hibernate抓取策略(集合代理的批量抓取,fetch=select(默認(rèn))/join/subselect

          測(cè)試用例:

          Classes c = (Classes) session.load(Classes.class, new Integer(1));
              System.out.println("Class.name=" + c.getName());
              Set stuSet = c.getStudents();
              System.out.println(stuSet.size());
              if(stuSet != null && !stuSet.isEmpty()){
               for(Iterator it = stuSet.iterator(); it.hasNext();){
                Student s = (Student) it.next();
                System.out.println("student.name=" + s.getName());
               }
              }

          1)保持默認(rèn),同fetch="select",如:
          <set name="students" inverse="true"
          fetch="select">

          fetch="select",另外發(fā)送一條select語句抓取當(dāng)前對(duì)象關(guān)聯(lián)實(shí)體或集合

          測(cè)試結(jié)果:2條獨(dú)立的查詢語句

          Hibernate: select classes0_.id as id0_0_, classes0_.name as name0_0_ from classes_join classes0_ where classes0_.id=?
          Class.name=高一(1)
          Hibernate: select students0_.class_id as class3_1_, students0_.id as id1_, students0_.id as id1_0_, students0_.name as name1_0_, students0_.class_id as class3_1_0_
          from student_join students0_ where students0_.class_id=?
          9
          student.name=
          學(xué)生7
          student.name=
          學(xué)生3
          student.name=
          學(xué)生1
          student.name=
          學(xué)生8
          student.name=
          學(xué)生2
          student.name=
          學(xué)生4
          student.name=
          學(xué)生5
          student.name=
          學(xué)生9
          student.name=
          學(xué)生6

          (2)設(shè)置fetch="join",如:
          <set name="students" inverse="true"
          fetch="join">

          fetch="join",hibernate會(huì)通過select語句使用外連接來加載其關(guān)聯(lián)實(shí)體或集合

          此時(shí)lazy會(huì)失效

          測(cè)試結(jié)果:1條獨(dú)立的join查詢語句

          Hibernate: select classes0_.id as id0_1_, classes0_.name as name0_1_, students1_.class_id as class3_3_, students1_.id as id3_, students1_.id as id1_0_, students1_.name as name1_0_, students1_.class_id as class3_1_0_ from classes_join classes0_ left outer join student_join students1_ on classes0_.id=students1_.class_id where classes0_.id=?
          Class.name=
          高一(1)
          9
          student.name=
          學(xué)生6
          student.name=
          學(xué)生4
          student.name=
          學(xué)生9
          student.name=
          學(xué)生7
          student.name=
          學(xué)生2
          student.name=
          學(xué)生3
          student.name=
          學(xué)生8
          student.name=
          學(xué)生1
          student.name=
          學(xué)生5

          (3)設(shè)置fetch="subselect",如:用在查詢語句
          <set name="students" inverse="true"
          fetch="subselect">

          fetch="subselect",另外發(fā)送一條select語句抓取在前面查詢到的所有實(shí)體對(duì)象的關(guān)聯(lián)集合

          測(cè)試用例:

          List classList = session.createQuery("from Classes where id in (1,2,3)").list();
              for(Iterator iter = classList.iterator(); iter.hasNext();){
               Classes c = (Classes)iter.next();
               System.out.println("Class.name=" + c.getName());
               Set stuSet = c.getStudents();
               System.out.println(stuSet.size());
               if(stuSet != null && !stuSet.isEmpty()){
                for(Iterator it = stuSet.iterator(); it.hasNext();){
                 Student s = (Student) it.next();
                 System.out.println("student.name=" + s.getName());
                }
               }
              }   

          當(dāng)不設(shè)fetch="subselect" ,即:<set name="students" inverse="true">,結(jié)果如下:

          執(zhí)行了3條查詢語句

          Hibernate: select classes0_.id as id0_, classes0_.name as name0_ from classes_join classes0_ where classes0_.id in (1 , 2 , 3)
          Class.name=
          高一(1)
          Hibernate:
          select students0_.class_id as class3_1_, students0_.id as id1_, students0_.id as id1_0_, students0_.name as name1_0_, students0_.class_id as class3_1_0_ from student_join students0_ where students0_.class_id=?
          9
          student.name=
          學(xué)生8
          student.name=
          學(xué)生5
          student.name=
          學(xué)生3
          student.name=
          學(xué)生9
          student.name=
          學(xué)生7
          student.name=
          學(xué)生1
          student.name=
          學(xué)生4
          student.name=
          學(xué)生6
          student.name=
          學(xué)生2
          Class.name=
          高一(2)
          Hibernate:
          select students0_.class_id as class3_1_, students0_.id as id1_, students0_.id as id1_0_, students0_.name as name1_0_, students0_.class_id as class3_1_0_ from student_join students0_ where students0_.class_id=?
          4
          student.name=
          學(xué)生3
          student.name=
          學(xué)生4
          student.name=
          學(xué)生1
          student.name=
          學(xué)生2
          Class.name=
          高一(3)
          Hibernate:
          select students0_.class_id as class3_1_, students0_.id as id1_, students0_.id as id1_0_, students0_.name as name1_0_, students0_.class_id as class3_1_0_ from student_join students0_ where students0_.class_id=?
          0

          當(dāng)不設(shè)fetch="subselect" ,即:<set name="students" inverse="true" fetch="subselect">,結(jié)果如下:

          執(zhí)行了1條查詢語句(嵌套子查詢)

          Hibernate: select classes0_.id as id0_, classes0_.name as name0_ from classes_join classes0_ where classes0_.id in (1 , 2 , 3)
          Class.name=
          高一(1)
          Hibernate:
          select students0_.class_id as class3_1_, students0_.id as id1_, students0_.id as id1_0_, students0_.name as name1_0_, students0_.class_id as class3_1_0_ from student_join students0_ where students0_.class_id in (select classes0_.id from classes_join classes0_ where classes0_.id in (1 , 2 , 3))
          9
          student.name=
          學(xué)生8
          student.name=
          學(xué)生4
          student.name=
          學(xué)生5
          student.name=
          學(xué)生9
          student.name=
          學(xué)生6
          student.name=
          學(xué)生2
          student.name=
          學(xué)生3
          student.name=
          學(xué)生1
          student.name=
          學(xué)生7
          Class.name=
          高一(2)
          4
          student.name=
          學(xué)生3
          student.name=
          學(xué)生4
          student.name=
          學(xué)生2
          student.name=
          學(xué)生1
          Class.name=
          高一(3)
          0

          posted @ 2011-06-18 15:33 ... 閱讀(147) | 評(píng)論 (0)編輯 收藏

          [轉(zhuǎn)]Hibernate性能優(yōu)化要點(diǎn)

          Robbin總結(jié)的Hibernate性能優(yōu)化要點(diǎn):

          1.盡量使用many-to-one,避免使用單項(xiàng)one-to-many
          2.靈活使用單向one-to-many
          3.不用一對(duì)一,使用多對(duì)一代替一對(duì)一
          4.配置對(duì)象緩存,不使用集合緩存
          5.一對(duì)多使用Bag 多對(duì)一使用Set
          6.繼承使用顯示多態(tài)  HQL:from object  polymorphism="exlicit" 避免查處所有對(duì)象
          7.消除大表,使用二級(jí)緩存
              對(duì)于上面這些,Robbin進(jìn)行了詳細(xì)的講解。
          one-to-many
               使用inverse=false(default),對(duì)象的關(guān)聯(lián)關(guān)系是由parent對(duì)象來維護(hù)的
               而inverse=true的情況下,一般用戶雙向多對(duì)多關(guān)聯(lián),由子對(duì)象維護(hù)關(guān)聯(lián)關(guān)系,增加子對(duì)象的時(shí)候需要顯示:child.setParent(child)
               為了提高性能,應(yīng)該盡量使用雙向one-to-many inverse=true,在MVC結(jié)構(gòu)中的DAO接口中應(yīng)該直接用Session持久化對(duì)象,避免通過關(guān)聯(lián)關(guān)系(這句話有點(diǎn)不理解),而在單項(xiàng)關(guān)系中正確使用二級(jí)緩存,則可以大幅提高以查詢?yōu)橹鞯膽?yīng)用。
               多對(duì)一性能問題比較少,但是要避免經(jīng)典N+1問題。
               通過主鍵進(jìn)行關(guān)聯(lián),相當(dāng)于大表拆分小表。(這個(gè)是區(qū)分面向?qū)ο笤O(shè)計(jì)和面向過程設(shè)計(jì)的一個(gè)關(guān)鍵點(diǎn))
          list、bag、set的正確運(yùn)用
               one-to-many:
               A、使用list 需要維護(hù)Index Column字段,不能被用于雙向關(guān)聯(lián),而且必須使用inverse=false,需要謹(jǐn)慎使用在某些稀有場(chǎng)合(基本上是不予考慮使用)
               B、bag/set在one-to-many中語義基本相同,推薦使用bag
               many-to-one:
               A、bag和set不同,bag允許重復(fù)插入,建議使用set
          在龐大的集合分頁中應(yīng)該使用session.createFilter
              session.createFilter(parent.getChildren(),""),setFirstResult(0),setMaxResult(10))
          避免N+1 參考(http://www.iteye.com/post/266972)
              在多對(duì)一的情況下,查詢child對(duì)象,當(dāng)在頁面上顯示每個(gè)子類的父類對(duì)象的時(shí)候會(huì)導(dǎo)致N+1次查詢,需要采用下面的方法避免:many-to-one fetch="join|select"(該方法可能有問題)
          inverse=true 無法維護(hù)集合緩存(還不是很理解集合緩存和對(duì)象緩存)
          OLTP類型的web應(yīng)用,可以群集水平擴(kuò)展,不可避免的出現(xiàn)數(shù)據(jù)庫瓶頸
              框架能降低訪問數(shù)據(jù)庫的壓力,采用緩存是衡量一個(gè)框架是否優(yōu)秀的重要標(biāo)準(zhǔn),從緩存方面看Hibernate
              A、對(duì)象緩存,細(xì)顆粒度,是針對(duì)表的級(jí)別,透明化訪問,因?yàn)橛胁桓淖兇a的好處,所以是ORM提高性能的法寶
              B、Hibernate是目前ORM框架中緩存性能最好的框架
              C、查詢緩存
          最后Robbin還針對(duì)大家經(jīng)常出現(xiàn)的Hibernate vs iBatis的討論進(jìn)行了一個(gè)總結(jié):
             對(duì)于OLTP應(yīng)用,使用ORM框架 而OLEB應(yīng)用(不確定是什么應(yīng)用)最好采用JDBC或者其他方法處理
             Hibernate傾向于細(xì)顆粒度設(shè)計(jì),面向?qū)ο螅瑢⒋蟊聿鸱譃槎鄠€(gè)小表,消除冗余字段,通過二級(jí)緩存提升性能。
             iBatis傾向于粗顆粒度設(shè)計(jì),面向關(guān)系,盡量把表合并,通過Column冗余,消除關(guān)聯(lián)關(guān)系,但是iBatis沒有有效的緩存手段。

             可以說Robbin的性能總結(jié)對(duì)于使用Hibernate的開發(fā)人員有著很重要的點(diǎn)撥作用。非常感謝他無私奉獻(xiàn)自己的經(jīng)驗(yàn)。

          posted @ 2011-06-18 12:27 ... 閱讀(665) | 評(píng)論 (0)編輯 收藏

          [轉(zhuǎn)]加速你的Hibernate引擎(下)

          原文: http://www.infoq.com/cn/articles/hibernate_tuning-ii

          4.6 HQL調(diào)優(yōu)

          4.6.1 索引調(diào)優(yōu)

          HQL看起來和SQL很相似。從HQL的WHERE子句中通??梢圆碌较鄳?yīng)的SQL WHERE子句。WHERE子句中的字段決定了數(shù)據(jù)庫將選擇的索引。

          大多數(shù)Hibernate開發(fā)者所常犯的一個(gè)錯(cuò)誤是無論何時(shí),當(dāng)需要新WHERE子句的時(shí)候都會(huì)創(chuàng)建一個(gè)新的索引。因?yàn)樗饕龝?huì)帶來額外的數(shù)據(jù)更新開銷,所以應(yīng)該爭(zhēng)取創(chuàng)建少量索引來覆蓋盡可能多的查詢。
          4.1節(jié)讓你使用一個(gè)集合來處理所有可能的數(shù)據(jù)搜索條件。如果這不太實(shí)際,那么你可以使用后端剖析工具來創(chuàng)建一個(gè)針對(duì)應(yīng)用程序涉及的所有SQL的集合?;谀切┧阉鳁l件的分類,你最終會(huì)得到一個(gè)小的索引集。與此同時(shí),還可以嘗試向WHERE子句中添加額外的謂語來匹配其他WHERE子句。

          范例7

          有兩個(gè)UI搜索器和一個(gè)后端守護(hù)進(jìn)程搜索器來搜索名為iso_deals的表。第一個(gè)UI搜索器在unexpectedFlag、dealStatus、tradeDate和isold屬性上有謂語。

          第二個(gè)UI搜索器基于用戶鍵入的過濾器,其中包括的內(nèi)容除tradeDate和isold以外還有其他屬性。開始時(shí)所有這些過濾器屬性都是可選的。
          后端搜索器基于isold、participantCode和transactionType屬性。
          經(jīng)過進(jìn)一步業(yè)務(wù)分析,發(fā)現(xiàn)第二個(gè)UI搜索器實(shí)際是基于一些隱式的unexpectedFlag和dealStatus值來選擇數(shù)據(jù)的。我們還讓tradeDate成為過濾器的必要屬性(為了使用數(shù)據(jù)庫索引,每個(gè)搜索過濾器都應(yīng)該有必要屬性)。

          鑒于這一點(diǎn),我們依次使用unexpectedFlag、dealStatus、tradeDate和isold構(gòu)造了一個(gè)復(fù)合索引。兩個(gè)UI搜索器都能共用它。(順序很重要,如果你的謂語以不同的順序指定這些屬性或在它們前羅列了其他屬性,數(shù)據(jù)庫就不會(huì)選擇該復(fù)合索引。)

          后端搜索器和UI搜索器區(qū)別太大,因此我們不得不為它構(gòu)造另一個(gè)復(fù)合索引,依次使用isold、participantCode和transactionType。

          4.6.2綁定參數(shù) vs.字符串拼接

          既可以使用綁定參數(shù)構(gòu)造HQL的WHERE子句,也可以使用字符串拼接的方法,該決定對(duì)性能會(huì)有一定影響。使用綁定參數(shù)的原因是讓數(shù)據(jù)庫一次解析SQL,對(duì)后續(xù)的重復(fù)請(qǐng)求復(fù)用生成好的執(zhí)行計(jì)劃,這樣做節(jié)省了CPU時(shí)間和內(nèi)存。然而,為達(dá)到最優(yōu)的數(shù)據(jù)訪問效率,不同的綁定值可能需要不同的SQL執(zhí)行計(jì)劃。

          例如,一小段數(shù)據(jù)范圍可能只返回?cái)?shù)據(jù)總量的5%,而一大段數(shù)據(jù)范圍可能返回?cái)?shù)據(jù)總量的90%。前者使用索引更好,而后者則最好使用全表掃描。

          建議OLTP使用綁定參數(shù),數(shù)據(jù)倉庫使用字符串拼接,因?yàn)镺LTP通常在一個(gè)事務(wù)中重復(fù)插入和更新數(shù)據(jù),只取少量數(shù)據(jù);數(shù)據(jù)倉庫通常只有少量SQL查詢,有一個(gè)確定的執(zhí)行計(jì)劃比節(jié)省CPU時(shí)間和內(nèi)存更為重要。

          要是你知道你的OLTP搜索對(duì)不同綁定值應(yīng)該使用相同執(zhí)行計(jì)劃又該怎么辦呢?

          Oracle 9i及以后版本在第一次調(diào)用綁定參數(shù)并生成執(zhí)行計(jì)劃時(shí)能探出參數(shù)值。后續(xù)調(diào)用不會(huì)再探測(cè),而是重用之前的執(zhí)行計(jì)劃。

          4.6.3聚合及排序

          你可以在數(shù)據(jù)庫中進(jìn)行聚合和“order by”,也可以在應(yīng)用程序的服務(wù)層中事先加載所有數(shù)據(jù)然后做聚合和“order by”操作。推薦使用前者,因?yàn)閿?shù)據(jù)庫在這方面通常會(huì)比你的應(yīng)用程序做得好。此外,這樣做還能節(jié)省網(wǎng)絡(luò)帶寬,這也是一種擁有跨數(shù)據(jù)庫移植性的做法。

          當(dāng)你的應(yīng)用程序?qū)?shù)據(jù)聚合和排序有HQL不支持的特定業(yè)務(wù)規(guī)則時(shí)除外。

          4.6.4覆蓋抓取策略

          詳見4.7.1節(jié)。

          4.6.5本地查詢

          本地查詢調(diào)優(yōu)其實(shí)并不直接與HQL有關(guān)。但HQL的確可以讓你直接向底層數(shù)據(jù)庫傳遞本地查詢。我們并不建議這么做,因?yàn)楸镜夭樵冊(cè)跀?shù)據(jù)庫間不可移植。

          4.7抓取策略調(diào)優(yōu)

          抓取策略決定了在應(yīng)用程序需要訪問關(guān)聯(lián)對(duì)象時(shí),Hibernate以何種方式以及何時(shí)獲取關(guān)聯(lián)對(duì)象。HRD中的第20章“改善性能”對(duì)該主題作了很好的闡述,我們?cè)诖藢㈥P(guān)注它的使用方法。

          4.7.1覆蓋抓取策略

          不同的用戶可能會(huì)有不同的數(shù)據(jù)抓取要求。Hibernate允許在兩個(gè)地方定義數(shù)據(jù)抓取策略,一處是在映射元數(shù)據(jù)中,另一處是在HQL或Criteria中覆蓋它。

          常見的做法是基于主要的抓取用例在映射元數(shù)據(jù)中定義默認(rèn)抓取策略,針對(duì)少數(shù)用例在HQL和Criteria中覆蓋抓取策略。

          假設(shè)pojoA和pojoB是父子關(guān)系實(shí)例。如果根據(jù)業(yè)務(wù)規(guī)則,只是偶爾需要從實(shí)體兩端加載數(shù)據(jù),那你可以聲明一個(gè)延遲加載集合或代理抓?。╬roxy fetching)。當(dāng)你需要從實(shí)體兩端獲取數(shù)據(jù)時(shí),可以用立即抓取(eager fetching)覆蓋默認(rèn)策略,例如使用HQL或Criteria配置連接抓取(join fetching)。

          另一方面,如果業(yè)務(wù)規(guī)則在大多數(shù)時(shí)候需要從實(shí)體兩端加載數(shù)據(jù),那么你可以聲明立即抓取并在Criteria中設(shè)置延遲加載集合或代理抓取來覆蓋它(HQL目前還不支持這樣的覆蓋)。

          4.7.2 N+1模式或是反模式?

          select抓取會(huì)導(dǎo)致N+1問題。如果你知道自己總是需要從關(guān)聯(lián)中加載數(shù)據(jù),那么就該始終使用連接抓取。在下面兩個(gè)場(chǎng)景中,你可能會(huì)把N+1視為一種模式而非反模式。

          第一種場(chǎng)景,你不知道用戶是否會(huì)訪問關(guān)聯(lián)對(duì)象。如果他/她沒有訪問,那么你贏了;否則你仍然需要額外的N次select SQL語句。這是一種令人左右為難的局面。

          第二種場(chǎng)景,pojoA和很多其他POJO有one-to-many關(guān)聯(lián),例如pojoB和pojoC。使用立即的內(nèi)連接或外連接抓取會(huì)在結(jié)果集中將pojoA重復(fù)很多次。當(dāng)pojoA中有很多非空屬性時(shí),你不得不將大量數(shù)據(jù)加載到持久層中。這種加載需要很多時(shí)間,既有網(wǎng)絡(luò)帶寬的原因,如果Hibernate的會(huì)話是有狀態(tài)的,其中也會(huì)有會(huì)話緩存的原因(內(nèi)存消耗和GC暫停)。

          如果你有一個(gè)很長(zhǎng)的one-to-many關(guān)聯(lián)鏈,例如從pojoA到pojoB到pojoC以此類推,情況也是類似的。

          你也許會(huì)去使用HQL中的DISTINCT關(guān)鍵字或Cirteria中的distinct功能或是Java的Set接口來消除重復(fù)數(shù)據(jù)。但所有這些都是在Hibernate(在持久層)中實(shí)現(xiàn)的,而非數(shù)據(jù)庫中。

          如果基于你的網(wǎng)絡(luò)和內(nèi)存配置的測(cè)試表明N+1性能更好,那么你可以使用批量抓取、subselect抓取或二級(jí)緩存來做進(jìn)一步調(diào)優(yōu)。

          范例8

          以下是一個(gè)使用批量抓取的HBM文件片段:

          <class name="pojoA" table="pojoA">
          …
          <set name="pojoBs" fetch="select" batch-size="10">
          
          <key column="pojoa_id"/>
          …
          </set>
          </class> 
          

          以下是多端pojoB生成的SQL:

          selectfrom pojoB where pojoa_id in(?,?,?,?,?, ?,?,?,?,?);

          問號(hào)數(shù)量與batch-size值相等。因此N次額外的關(guān)于pojoB的select SQL語句被減少到了N/10次。

          如果將fetch="select"替換成fetch="subselect",pojoB生成的SQL語句就是這樣的:

          selectfrom pojoB where pojoa_id in(select id from pojoA where …); 

          盡管N次額外的select減少到1次,但這只在重復(fù)運(yùn)行pojoA的查詢開銷很低時(shí)才有好處。

          如果pojoA中的pojoB集合很穩(wěn)定,或pojoB有pojoA的many-to-one關(guān)聯(lián),而且pojoA是只讀引用數(shù)據(jù),那么你可以使用二級(jí)緩存來緩存pojoA以消除N+1問題(4.8.1節(jié)中有一個(gè)例子)。

          4.7.3延遲屬性抓取

          除非有一張擁有很多你不需要的字段的遺留表,否則不應(yīng)該使用這種抓取策略,因?yàn)樗难舆t屬性分組會(huì)帶來額外的SQL。

          在業(yè)務(wù)分析和設(shè)計(jì)過程中,你應(yīng)該將不同數(shù)據(jù)獲取或修改分組放到不同的領(lǐng)域?qū)ο髮?shí)體中,而不是使用這種抓取策略。

          如果不能重新設(shè)計(jì)遺留表,可以使用HQL或Criteria提供的投影功能來獲取數(shù)據(jù)。

          4.8 二級(jí)緩存調(diào)優(yōu)

          HRD第20.2節(jié) “二級(jí)緩存”中的描述對(duì)大多數(shù)開發(fā)者來說過于簡(jiǎn)單,無法做出選擇。3.3版及以后版本不再推薦使用基于“CacheProvider”的緩存,而用基于“RegionFactory”的緩存,這也讓人更糊涂了。但是就算是最新的3.5參考文檔也沒有提及如何使用新緩存方法。

          出于下述考慮,我們將繼續(xù)關(guān)注于老方法:

          • 所有流行的Hibernate二級(jí)緩存提供商中只有JBoss Cache 2、Infinispan 4Ehcache 2支持新方法。OSCache、SwarmCache、CoherenceGigaspaces XAP-Data Grid只支持老方法。
          • 兩種方法共用相同的<cache>配置。例如,它們?nèi)耘f使用相同的usage屬性值“transactional|read-write|nonstrict-read-write|read-only”。
          • 多個(gè)cache-region適配器仍然內(nèi)置老方法的支持,理解它能幫助你快速理解新方法。

          4.8.1 基于CacheProvider的緩存機(jī)制

          理解該機(jī)制是做出合理選擇的關(guān)鍵。關(guān)鍵的類/接口是CacheConcurrencyStrategy和它針對(duì)4中不同緩存使用的實(shí)現(xiàn)類,還有EntityUpdate/Delete/InsertAction。

          針對(duì)并發(fā)緩存訪問,有三種實(shí)現(xiàn)模式:

          • 針對(duì)“read-only”的只讀模式。

            無論是鎖還是事務(wù)都沒影響,因?yàn)榫彺孀詳?shù)據(jù)從數(shù)據(jù)庫加載后就不會(huì)改變。

          • 針對(duì)“read-write”和“nonstrict-read-write”的非事務(wù)感知(non-transaction-aware)讀寫模式。

            對(duì)緩存的更新發(fā)生在數(shù)據(jù)庫事務(wù)完成后。緩存需要支持鎖。

          • 針對(duì)“transactional”的事務(wù)感知讀寫。

            對(duì)緩存和數(shù)據(jù)庫的更新被包裝在同一個(gè)JTA事務(wù)中,這樣緩存與數(shù)據(jù)庫總是保持同步的。數(shù)據(jù)庫和緩存都必須支持JTA。盡管緩存事務(wù)內(nèi)部依賴于緩存鎖,但Hibernate不會(huì)顯式調(diào)用任何的緩存鎖函數(shù)。

          以數(shù)據(jù)庫更新為例。EntityUpdateAction對(duì)于事務(wù)感知讀寫、“read-write”的非事務(wù)感知讀寫,還有“nonstrict-read-write”的非事務(wù)感知讀寫相應(yīng)有如下調(diào)用序列:

          • 在一個(gè)JTA事務(wù)中更新數(shù)據(jù)庫;在同一個(gè)事務(wù)中更新緩存。
          • 軟鎖緩存;在一個(gè)事務(wù)中更新數(shù)據(jù)庫;在上一個(gè)事務(wù)成功完成后更新緩存;否則釋放軟鎖。

            軟鎖只是一種特定的緩存值失效表述方式,在它獲得新數(shù)據(jù)庫值前阻止其他事務(wù)讀寫緩存。那些事務(wù)會(huì)轉(zhuǎn)而直接讀取數(shù)據(jù)庫。

            緩存必須支持鎖;事務(wù)支持則不是必須的。如果緩存是一個(gè)集群,“更新緩存”的調(diào)用會(huì)將新值推送給所有副本,這通常被稱為“推(push)”更新策略。

          • 在一個(gè)事務(wù)中更新數(shù)據(jù)庫;在上一個(gè)事務(wù)完成前就清除緩存;為了安全起見,無論事務(wù)成功與否,在事務(wù)完成后再次清除緩存。

            既不需要支持緩存鎖,也不需要支持事務(wù)。如果是緩存集群,“清除緩存”調(diào)用會(huì)讓所有副本都失效,這通常被稱為“拉(pull)”更新策略。

          對(duì)于實(shí)體的刪除或插入動(dòng)作,或者集合變更,調(diào)用序列都是相似的。

          實(shí)際上,最后兩個(gè)異步調(diào)用序列仍能保證數(shù)據(jù)庫和緩存的一致性(基本就是“read committed”的隔離了級(jí)別),這要?dú)w功于第二個(gè)序列中的軟鎖和“更新數(shù)據(jù)庫”后的“更新緩存”,還有最后一個(gè)調(diào)用序列中的悲觀“清除緩存”。

          基于上述分析,我們的建議是: 

          • 如果數(shù)據(jù)是只讀的,例如引用數(shù)據(jù),那么總是使用“read-only”策略,因?yàn)樗亲詈?jiǎn)單、最高效的策略,也是集群安全的策略。
          • 除非你真的想將緩存更新和數(shù)據(jù)庫更新放在一個(gè)JTA事務(wù)里,否則不要使用“transactional”策略,因?yàn)镴TA需要漫長(zhǎng)的兩階段提交處理,這導(dǎo)致它基本是性能最差的策略。

            依筆者看來,二級(jí)緩存并非一級(jí)數(shù)據(jù)源,因此使用JTA也未必合理。實(shí)際上最后兩個(gè)調(diào)用序列在大多數(shù)場(chǎng)景下是個(gè)不錯(cuò)的替代方案,這要?dú)w功于它們的數(shù)據(jù)一致性保障。

          • 如果你的數(shù)據(jù)讀很多或者很少有并發(fā)緩存訪問和更新,那么可以使用“nonstrict-read-write”策略。感謝它的輕量級(jí)“拉”更新策略,它通常是性能第二好的策略。
          • 如果你的數(shù)據(jù)是又讀又寫的,那么使用“read-write”策略。這通常是性能倒數(shù)第二的策略,因?yàn)樗笥芯彺骀i,緩存集群中使用重量級(jí)的“推”更新策略。

          范例9

          以下是一個(gè)ISO收費(fèi)類型的HBM文件片段:

          <class name="IsoChargeType">
             <property name="isoId" column="ISO_ID" not-null="true"/>
          
             <many-to-one name="estimateMethod" fetch="join" lazy="false"/>
          
             <many-to-one  name="allocationMethod" fetch="join" lazy="false"/>
          
             <many-to-one name="chargeTypeCategory" fetch="join" lazy="false"/>
          
          </class> 

          一些用戶只需要ISO收費(fèi)類型本身;一些用戶既需要ISO收費(fèi)類型,還需要它的三個(gè)關(guān)聯(lián)對(duì)象。簡(jiǎn)單起見,開發(fā)者會(huì)立即加載所有三個(gè)關(guān)聯(lián)對(duì)象。如果項(xiàng)目中沒人負(fù)責(zé)Hibernate調(diào)優(yōu),這是很常見的。

          4.7.1節(jié)中講過了最好的方法。因?yàn)樗械年P(guān)聯(lián)對(duì)象都是只讀引用數(shù)據(jù),另一種方法是使用延遲抓取,打開這些對(duì)象的二級(jí)緩存以避免N+1問題。實(shí)際上前一種方法也能從引用數(shù)據(jù)緩存中獲益。

          因?yàn)榇蠖鄶?shù)項(xiàng)目都有很多被其他數(shù)據(jù)引用的只讀引用數(shù)據(jù),上述兩種方法都能改善全局系統(tǒng)性能。

          4.8.2 RegionFactory

          下表是新老兩種方法中對(duì)應(yīng)的主要類/接口:

          新方法

          老方法

          RegionFactory

          CacheProvider

          Region

          Cache

          EntityRegionAccessStrategy

          CacheConcurrencyStrategy

          CollectionRegionAccessStrategy

          CacheConcurrencyStrategy

          第一個(gè)改進(jìn)是RegionFactory構(gòu)建了特定的Region,例如EntityRegion和TransactionRegion,而不是使用一個(gè)通用的訪問Region。第二個(gè)改進(jìn)是對(duì)于特定緩存的“usage”屬性值,Region要求構(gòu)建自己的訪問策略,而不是所有Region都一直使用CacheConcurrencyStrategy的4種實(shí)現(xiàn)。

          要使用新方法,應(yīng)該設(shè)置factory_class而非provider_class配置屬性。以Ehcache 2.0為例:

          <property name="hibernate.cache.region.factory_class">
                  net.sf.ehcache.hibernate.EhCacheRegionFactory  
          </property>

          其他相關(guān)的Hibernate緩存配置都和老方法一樣。

          新方法也能向后兼容遺留方法。如果還是只配了CacheProvider,新方法中將使用下列自說明(self-explanatory)適配器和橋隱式地調(diào)用老的接口/類:

          RegionFactoryCacheProviderBridge、EntityRegionAdapter、CollectionRegionAdapter、QueryResultsRegionAdapter、EntityAccessStrategyAdapter和CollectionAccessStrategyAdapter

          4.8.3 查詢緩存

          二級(jí)緩存也能緩存查詢結(jié)果。如果查詢開銷很大而且要重復(fù)運(yùn)行,這也會(huì)很有幫助。

          4.9批量處理調(diào)優(yōu)

          大多數(shù)Hibernate的功能都很適合那些每個(gè)事務(wù)都通常只處理少量數(shù)據(jù)的OLTP系統(tǒng)。但是,如果你有一個(gè)數(shù)據(jù)倉庫或者事務(wù)需要處理大量數(shù)據(jù),那么就另當(dāng)別論了。

          4.9.1使用有狀態(tài)會(huì)話的非DML風(fēng)格批處理

          如果你已經(jīng)在使用常規(guī)會(huì)話了,那這是最自然的方法。你需要做三件事:

          • 配置下列3個(gè)屬性以開啟批處理特性:
              hibernate.jdbc.batch_size 30
              hibernate.jdbc.batch_versioned_data true
              hibernate.cache.use_second_level_cache false

            batch_size設(shè)置為正值會(huì)開啟JDBC2的批量更新,Hibernate的建議值是5到30?;谖覀兊臏y(cè)試,極低值和極高值性能都很差。只要取值在合理范圍內(nèi),區(qū)別就只有幾秒而已。如果網(wǎng)絡(luò)夠快,這個(gè)結(jié)果是一定的。

            第二個(gè)配置設(shè)為true,這要求JDBC驅(qū)動(dòng)在executeBatch()方法中返回正確的行數(shù)。對(duì)于Oracle用戶而言,批量更新時(shí)不能將其設(shè)為true。請(qǐng)閱讀Oracle的《JDBC Developer’s Guide and Reference》中的“標(biāo)準(zhǔn)批處理的Oracle實(shí)現(xiàn)中的更新計(jì)數(shù)”(Update Counts in the Oracle Implementation of Standard Batching)以獲得更多詳細(xì)信息。因?yàn)樗鼘?duì)批量插入來說還是安全的,所以你可以為批量插入創(chuàng)建單獨(dú)的專用數(shù)據(jù)源。最后一個(gè)配置項(xiàng)是可選的,因?yàn)槟憧梢栽跁?huì)話中顯式關(guān)閉二級(jí)緩存。

          • 像如下范例中那樣定期刷新(flush)并清除一級(jí)會(huì)話緩存:
             Session session = sessionFactory.openSession();
             Transaction tx = session.beginTransaction();
            

             for ( int i=0; i<100000; i++ ) {
                 Customer customer = new Customer(.....);
                 //if your hibernate.cache.use_second_level_cache is true, call the following:
                 session.setCacheMode(CacheMode.IGNORE);
                 session.save(customer);
                 if (i % 50 == 0) { //50, same as the JDBC batch size
                 //flush a batch of inserts and release memory:
                 session.flush();
                 session.clear();
                 }
             }
             tx.commit();
             session.close();

            批處理通常不需要數(shù)據(jù)緩存,否則你會(huì)將內(nèi)存耗盡并大量增加GC開銷。如果內(nèi)存有限,那這種情況會(huì)很明顯。

          • 總是將批量插入嵌套在事務(wù)中。

          每次事務(wù)修改的對(duì)象數(shù)量越少就意味著會(huì)有更多數(shù)據(jù)庫提交,正如4.5節(jié)所述每次提交都會(huì)帶來磁盤相關(guān)的開銷。

          另一方面,每次事務(wù)修改的對(duì)象數(shù)量越多就意味著鎖定變更時(shí)間越長(zhǎng),同時(shí)數(shù)據(jù)庫需要更大的redo log。

          4.9.2使用無狀態(tài)會(huì)話的非DML風(fēng)格批處理

          無狀態(tài)會(huì)話執(zhí)行起來比上一種方法更好,因?yàn)樗皇荍DBC的簡(jiǎn)單包裝,而且可以繞開很多常規(guī)會(huì)話要求的操作。例如,它不需要會(huì)話緩存,也不和任何二級(jí)緩存或查詢緩存有交互。
          然而它的用法并不簡(jiǎn)單。尤其是它的操作并不會(huì)級(jí)聯(lián)到所關(guān)聯(lián)的實(shí)例上;你必須自己來處理它們。

          4.9.3 DML風(fēng)格

          使用DML風(fēng)格的插入、更新或刪除,你直接在數(shù)據(jù)庫中操作數(shù)據(jù),這和前兩種方法在Hibernate中操作數(shù)據(jù)的情況有所不同。

          因?yàn)橐粋€(gè)DML風(fēng)格的更新或刪除相當(dāng)于前兩種方法中的多個(gè)單獨(dú)的更新或刪除,所以如果更新或刪除中的WHERE子句暗示了恰當(dāng)?shù)臄?shù)據(jù)庫索引,那么使用DML風(fēng)格的操作能節(jié)省網(wǎng)絡(luò)開銷,執(zhí)行得更好。

          強(qiáng)烈建議結(jié)合使用DML風(fēng)格操作和無狀態(tài)會(huì)話。如果使用有狀態(tài)會(huì)話,不要忘記在執(zhí)行DML前清除緩存,否則Hibernate將會(huì)更新或清除相關(guān)緩存(見下面的范例10)。

          4.9.4批量加載

          如果你的HQL或Criteria會(huì)返回很多數(shù)據(jù),那么要注意兩件事:

          • 用下列配置開啟批量抓取特性:
            hibernate.jdbc.fetch_size 10

            fetch_size設(shè)置為正值將開啟JDBC批量抓取特性。相對(duì)快速網(wǎng)絡(luò),在慢速網(wǎng)絡(luò)中這一點(diǎn)更為重要。Oracle建議的經(jīng)驗(yàn)值是10。你應(yīng)該基于自己的環(huán)境進(jìn)行測(cè)試。

          • 在使用上述任一方法時(shí)都要關(guān)閉緩存,因?yàn)榕考虞d一般是一次性任務(wù)。受限于內(nèi)存容量,向緩存中加載大量數(shù)據(jù)通常也意味著它們很快會(huì)被清除出去,這會(huì)增加GC開銷。

          范例10

          我們有一個(gè)后臺(tái)任務(wù),分段加載大量的IsoDeal數(shù)據(jù)用于后續(xù)處理。我們還會(huì)在分段數(shù)據(jù)交給下游系統(tǒng)處理前將其更新為處理中狀態(tài)。最大的一段有50萬行數(shù)據(jù)。以下是原始代碼中截取出來的一段:

          Query query = session.createQuery("FROM IsoDeal d WHERE chunk-clause");
          query.setLockMode("d", LockMode.UPGRADE); //for Inprocess status update
          List<IsoDeal> isoDeals = query.list();
          for (IsoDeal isoDeal : isoDeals) { //update status to Inprocess
             isoDeal.setStatus("Inprocess");
          }
          return isoDeals; 

          包含上述代碼的方法加上了Spring 2.5聲明式事務(wù)的注解。加載并更新50萬行數(shù)據(jù)大約花了10分鐘。我們識(shí)別出了以下這些問題:

          • 由于會(huì)話緩存和二級(jí)緩存的原因,系統(tǒng)會(huì)頻繁地內(nèi)存溢出。
          • 就算沒有內(nèi)存溢出,當(dāng)內(nèi)存消耗很高時(shí)GC的開銷也會(huì)很大。
          • 我們還未設(shè)置fetch_size。
          • 就算我們?cè)O(shè)置了batch_size,for循環(huán)也創(chuàng)建了太多update SQL語句。

          不幸的是Spring 2.5不支持Hibernate無狀態(tài)會(huì)話,所以我們只能關(guān)閉二級(jí)緩存;設(shè)置fetch_size;用DML風(fēng)格的更新來代替for循環(huán),以此改善性能。

          但是,執(zhí)行時(shí)間還是要6分鐘。將Hibernate的日志級(jí)別調(diào)成trace后,我們發(fā)現(xiàn)是更新會(huì)話緩存造成了延時(shí)。通過在DML更新前清除會(huì)話緩存,我們將時(shí)間縮短到了4分鐘,全部都是將數(shù)據(jù)加載到會(huì)話緩存中花費(fèi)的時(shí)間。

          4.10 SQL生成調(diào)優(yōu)

          本節(jié)將向你展示如何減少SQL生成的數(shù)量。

          4.10.1 N+1抓取問題

          “select抓取”策略會(huì)導(dǎo)致N+1問題。如果“連接抓取”策略適合你的話,你應(yīng)該始終使用該策略避免N+1問題。

          但是,如果“連接抓取”策略執(zhí)行效果不理想,就像4.7.2節(jié)中那樣,你可以使用“subselect抓取”、“批量抓取”或“延遲集合抓取”來減少所需的額外SQL語句數(shù)。

          4.10.2 Insert+Update問題

          范例11

          我們的ElectricityDeal與DealCharge有單向one-to-many關(guān)聯(lián),如下列HBM文件片段所示:

          <class name="ElectricityDeal"
                 select-before-update="true" dynamic-update="true"
          
                 dynamic-insert="true">
              <id name="key" column="ID">
          
                  <generator class="sequence">
                      <param name="sequence">SEQ_ELECTRICITY_DEALS</param>
          
                  </generator>
              </id>
              …
              <set
          name="dealCharges" cascade="all-delete-orphan">         <key column="DEAL_KEY" not-null="false" update="true"              on-delete="noaction"/>         <one-to-many class="DealCharge"/>     </set> </class>

          在“key”元素中,“not-null”和“update”對(duì)應(yīng)的默認(rèn)值是false和true,上述代碼為了明確這些取值,將它們寫了出來。

          如果你想創(chuàng)建一個(gè)ElectricityDeal和十個(gè)DealCharge,會(huì)生成如下SQL語句:

          • 1句ElectricityDeal的插入語句;
          • 10句DealCharge的插入語句,其中不包括外鍵“DEAL_KEY”;
          • 10句DealCharge字段“DEAL_KEY”的更新語句。

          為了消除那額外的10句更新語句,可以在那10句DealCharge插入語句中包含“DEAL_KEY”,你需要將“not-null”和“update”分別修改為true和false。

          另一種做法是使用雙向或many-to-one關(guān)聯(lián),讓DealCharge來管理關(guān)聯(lián)。

          4.10.3 更新前執(zhí)行select

          在范例11中,我們?yōu)镋lectricityDeal加上了select-before-update,這會(huì)對(duì)瞬時(shí)(transient)對(duì)象或分離(detached)對(duì)象產(chǎn)生額外的select語句,但卻能避免不必要的數(shù)據(jù)庫更新。

          你應(yīng)該做出一些權(quán)衡,如果對(duì)象沒多少屬性,不需要防止不必要的數(shù)據(jù)庫更新,那么就不要使用該特性,因?yàn)槟隳切┯邢薜臄?shù)據(jù)既沒有太多網(wǎng)絡(luò)傳輸開銷,也不會(huì)帶來太多數(shù)據(jù)庫更新開銷。

          如果對(duì)象的屬性較多,例如是一張大的遺留表,那你應(yīng)該開啟該特性,和“dynamic-update”結(jié)合使用以避免太多數(shù)據(jù)庫更新開銷。

          4.10.4 級(jí)聯(lián)刪除

          在范例11中,如果你想刪除1個(gè)ElectricityDeal和它的100個(gè)DealCharge,Hibernate會(huì)對(duì)DealCharge做100次刪除。

          如果將“on-delete”修改為“cascade”,Hibernate不會(huì)執(zhí)行DealCharge的刪除動(dòng)作;而是讓數(shù)據(jù)庫根據(jù)ON CASCADE DELETE約束自動(dòng)刪除那100個(gè)DealCharge。不過,需要讓DBA開啟ON CASCADE DELETE約束,大多數(shù)DBA不愿意這么做,因?yàn)樗麄兿氡苊飧笇?duì)象的意外刪除級(jí)聯(lián)到它的依賴對(duì)象上。此外,還要注意,該特性會(huì)繞過Hibernate對(duì)版本數(shù)據(jù)(versioned data)的常用樂觀鎖策略。

          4.10.5 增強(qiáng)的序列標(biāo)識(shí)符生成器

          范例11中使用Oracle的序列作為標(biāo)識(shí)符生成器。假設(shè)我們保存100個(gè)ElectricityDeal,Hibernate會(huì)將下面的SQL語句執(zhí)行100次來獲取下一個(gè)可用的標(biāo)識(shí)符:

          select SEQ_ELECTRICITY_DEALS.NEXTVAL from dual; 

          如果網(wǎng)絡(luò)不是很快,那這無疑會(huì)降低效率。3.2.3及后續(xù)版本中增加了一個(gè)增強(qiáng)的生成器“SequenceStyleGenerator”,它帶了兩個(gè)優(yōu)化器:hilo和pooled。盡管HRD的第5章“基礎(chǔ)O/R映射” 講到了這兩個(gè)優(yōu)化器,不過內(nèi)容有限。兩個(gè)優(yōu)化器都使用了HiLo算法,該算法生成的標(biāo)識(shí)符等于Hi值加上Lo值,其中Hi值代表組號(hào),Lo值順序且重復(fù)地從1迭代到最大組大小,組號(hào)在Lo值“轉(zhuǎn)回到”1時(shí)加1。

          假設(shè)組大小是5(可以用max_lo或increment_size參數(shù)來表示),下面是個(gè)例子:

          • hilo優(yōu)化器

            組號(hào)取自數(shù)據(jù)庫序列的下一個(gè)可用值,Hi值由Hibernate定義,是組號(hào)乘以increment_size參數(shù)值。

          • pooled優(yōu)化器

            Hi值直接取自數(shù)據(jù)庫序列的下一個(gè)可用值。數(shù)據(jù)庫序列的增量應(yīng)該設(shè)置為increment_size參數(shù)值。

          直到內(nèi)存組中的值耗盡后,兩個(gè)優(yōu)化器才會(huì)去訪問數(shù)據(jù)庫,上面的例子每5個(gè)標(biāo)識(shí)值符訪問一次數(shù)據(jù)庫。使用hilo優(yōu)化器時(shí),你的序列不能再被其他應(yīng)用程序使用,除非它們使用與Hibernate相同的邏輯。使用pooled優(yōu)化器,在其他應(yīng)用程序使用同一序列時(shí)則相當(dāng)安全。

          兩個(gè)優(yōu)化器都有一個(gè)問題,如果Hibernate崩潰,當(dāng)前組內(nèi)的一些標(biāo)識(shí)符值就會(huì)丟失,然而大多數(shù)應(yīng)用程序都不要求擁有連續(xù)的標(biāo)識(shí)符值(如果你的數(shù)據(jù)庫,比方說Oracle,緩存了序列值,當(dāng)它崩潰時(shí)你也會(huì)丟失標(biāo)識(shí)符值)。

          如果在范例11中使用pooled優(yōu)化器,新的id配置如下:

          <id name="key" column="ID">
              <generator class="org.hibernate.id.enhance.SequenceStyleGenerator">
          <param name="sequence_name">SEQ_ELECTRICITY_DEALS</param> <param name="initial_value">0</param> <param name="increment_size">100</param> <param name="optimizer ">pooled</param> </generator> </id>

          5 總結(jié)

          本文涵蓋了大多數(shù)你在Hibernate應(yīng)用程序調(diào)優(yōu)時(shí)會(huì)覺得很有用的調(diào)優(yōu)技巧,其中的大多數(shù)時(shí)間都在討論那些行之有效卻缺乏文檔的調(diào)優(yōu)主題,例如繼承映射、二級(jí)緩存和增強(qiáng)的序列標(biāo)識(shí)符生成器。

          它還提到了一些Hibernate調(diào)優(yōu)所必需的數(shù)據(jù)庫知識(shí)。一些范例中包含了你可能遇到的問題的實(shí)際解決方案。

          除此之外,值得一提的是Hibernate也可以和In-Memory Data Grid(IMDG)一起使用,例如Oracle的Coherance或GigaSpaces IMDG,這能讓你的應(yīng)用程序達(dá)到毫秒級(jí)別。

          6 資源

          [1] Latest Hibernate Reference Documentation on jboss.com

          [2] Oracle 9i Performance Tuning Guide and Reference

          [3] Performance Engineering on Wikipedia

          [4] Program Optimization on Wikipedia

          [5] Pareto Principle (the 80/20 rule) on Wikipedia

          [6] Premature Optimization on acm.org

          [7] Java Performance Tuning by Jack Shirazi

          [8] The Law of Leaky Abstractions by Joel Spolsky

          [9] Hibernate’s StatisticsService Mbean configuration with Spring

          [10] JProbe by Quest Software

          [11] Java VisualVM

          [12] Column-oriented DBMS on Wikipedia

          [13] Apache DBCP BasicDataSource

          [14] JDBC Connection Pool by Oracle

          [15] Connection Failover by Oracle

          [16] Last Resource Commit Optimization (LRCO)

          [17] GigaSpaces for Hibernate ORM Users



          posted @ 2011-06-18 12:03 ... 閱讀(207) | 評(píng)論 (0)編輯 收藏

          [轉(zhuǎn)]加速你的Hibernate引擎(上)

          http://www.infoq.com/cn/articles/hibernate_tuning

          1.引言

          Hibernate是最流行的對(duì)象關(guān)系映射(ORM)引擎之一,它提供了數(shù)據(jù)持久化和查詢服務(wù)。


          2.Hibernate性能調(diào)優(yōu)

          調(diào)優(yōu)是一個(gè)迭代的、持續(xù)進(jìn)行的過程,涉及軟件開發(fā)生命周期(SDLC)的所有階段。在一個(gè)典型的使用Hibernate進(jìn)行持久化的Java EE應(yīng)用程序中,調(diào)優(yōu)會(huì)涉及以下幾個(gè)方面:

          • 業(yè)務(wù)規(guī)則調(diào)優(yōu)
          • 設(shè)計(jì)調(diào)優(yōu)
          • Hibernate調(diào)優(yōu)
          • Java GC調(diào)優(yōu)
          • 應(yīng)用程序容器調(diào)優(yōu)
          • 底層系統(tǒng)調(diào)優(yōu),包括數(shù)據(jù)庫和OS。

          沒有一套精心設(shè)計(jì)的方案就去進(jìn)行以上調(diào)優(yōu)是非常耗時(shí)的,而且很可能收效甚微。好的調(diào)優(yōu)方法的重要部分是為調(diào)優(yōu)內(nèi)容劃分優(yōu)先級(jí)??梢杂肞areto定律(又稱“80/20法則”)來解釋這一點(diǎn),即通常80%的應(yīng)用程序性能改善源自頭20%的性能問題[5]。

          相比基于磁盤和網(wǎng)絡(luò)的訪問,基于內(nèi)存和CPU的訪問能提供更低的延遲和更高的吞吐量。這種基于IO的Hibernate調(diào)優(yōu)與底層系統(tǒng)IO部分的調(diào)優(yōu)應(yīng)該優(yōu)先于基于CPU和內(nèi)存的底層系統(tǒng)GC、CPU和內(nèi)存部分的調(diào)優(yōu)。

          范例1

          我們調(diào)優(yōu)了一個(gè)選擇電流的HQL查詢,把它從30秒降到了1秒以內(nèi)。如果我們?cè)诶厥辗矫嫦鹿Ψ?,可能收效甚?#8212;—也許只有幾毫秒或者最多幾秒,相比HQL的改進(jìn),GC方面的改善可以忽略不計(jì)。

          好的調(diào)優(yōu)方法的另一個(gè)重要部分是決定何時(shí)優(yōu)化[4]。

          積極優(yōu)化的提倡者主張開始時(shí)就進(jìn)行調(diào)優(yōu),例如在業(yè)務(wù)規(guī)則和設(shè)計(jì)階段,在整個(gè)SDLC都持續(xù)進(jìn)行優(yōu)化,因?yàn)樗麄冋J(rèn)為后期改變業(yè)務(wù)規(guī)則和重新設(shè)計(jì)代價(jià)太大。

          另一派人提倡在SDLC末期進(jìn)行調(diào)優(yōu),因?yàn)樗麄儽г骨捌谡{(diào)優(yōu)經(jīng)常會(huì)讓設(shè)計(jì)和編碼變得復(fù)雜。他們經(jīng)常引用Donald Knuth的名言“過早優(yōu)化是萬惡之源 [6]。

          為了平衡調(diào)優(yōu)和編碼需要一些權(quán)衡。根據(jù)筆者的經(jīng)驗(yàn),適當(dāng)?shù)那捌谡{(diào)優(yōu)能帶來更明智的設(shè)計(jì)和細(xì)致的編碼。很多項(xiàng)目就失敗在應(yīng)用程序調(diào)優(yōu)上,因?yàn)樯厦嫣岬降?#8220;過早優(yōu)化”階段在被引用時(shí)脫離了上下文,而且相應(yīng)的調(diào)優(yōu)不是被推遲得太晚就是投入資源過少。

          但是,要做很多前期調(diào)優(yōu)也不太可能,因?yàn)闆]有經(jīng)過剖析,你并不能確定應(yīng)用程序的瓶頸究竟在何處,應(yīng)用程序一般都是這樣演化的。

          對(duì)我們的多線程企業(yè)級(jí)應(yīng)用程序的剖析也表現(xiàn)出大多數(shù)應(yīng)用程序平均只有20-50%的CPU使用率。剩余的CPU開銷只是在等待數(shù)據(jù)庫和網(wǎng)絡(luò)相關(guān)的IO。

          基于上述分析,我們得出這樣一個(gè)結(jié)論,結(jié)合業(yè)務(wù)規(guī)則和設(shè)計(jì)的Hibernate調(diào)優(yōu)在Pareto定律中20%的那個(gè)部分,相應(yīng)的它們的優(yōu)先級(jí)更高。

          一種比較實(shí)際的做法是:

          1. 識(shí)別出主要瓶頸,可以預(yù)見其中多數(shù)是Hibernate、業(yè)務(wù)規(guī)則和設(shè)計(jì)方面的(其數(shù)量視你的調(diào)優(yōu)目標(biāo)而定;但三到五個(gè)是不錯(cuò)的開端)。
          2. 修改應(yīng)用程序以便消除這些瓶頸。
          3. 測(cè)試應(yīng)用程序,然后重復(fù)步驟1,直到達(dá)到你的調(diào)優(yōu)目標(biāo)為止。

          你能在Jack Shirazi的《Java Performance Tuning》 [7]一書中找到更多關(guān)于性能調(diào)優(yōu)階段的常見建議。

          下面的章節(jié)中,我們會(huì)按照調(diào)優(yōu)的大致順序(列在前面的通常影響最大)去解釋一些特定的調(diào)優(yōu)技術(shù)。

          3. 監(jiān)控和剖析

          沒有對(duì)Hibernate應(yīng)用程序的有效監(jiān)控和剖析,你無法得知性能瓶頸以及何處需要調(diào)優(yōu)。

          3.1.1 監(jiān)控SQL生成

          盡管使用Hibernate的主要目的是將你從直接使用SQL的痛苦中解救出來,為了對(duì)應(yīng)用程序進(jìn)行調(diào)優(yōu),你必須知道Hibernate生成了哪些SQL。JoeSplosky在他的《The Law of Leaky Abstractions》一文中詳細(xì)描述了這個(gè)問題。

          你可以在log4j中將org.hibernate.SQL包的日志級(jí)別設(shè)為DEBUG,這樣便能看到生成的所有SQL。你還可以將其他包的日志級(jí)別設(shè)為DEBUG,甚至TRACE來定位一些性能問題。

          3.1.2 查看Hibernate統(tǒng)計(jì)

          如果開啟hibernate.generate.statistics,Hibernate會(huì)導(dǎo)出實(shí)體、集合、會(huì)話、二級(jí)緩存、查詢和會(huì)話工廠的統(tǒng)計(jì)信息,這對(duì)通過SessionFactory.getStatistics()進(jìn)行的調(diào)優(yōu)很有幫助。為了簡(jiǎn)單起見,Hibernate還可以使用MBean“org.hibernate.jmx.StatisticsService”通過JMX來導(dǎo)出統(tǒng)計(jì)信息。你可以在這個(gè)網(wǎng)站找到配置范例 。

          3.1.3 剖析

          一個(gè)好的剖析工具不僅有利于Hibernate調(diào)優(yōu),還能為應(yīng)用程序的其他部分帶來好處。然而,大多數(shù)商業(yè)工具(例如JProbe [10])都很昂貴。幸運(yùn)的是Sun/Oracle的JDK1.6自帶了一個(gè)名為“Java VisualVM” [11]的調(diào)試接口。雖然比起那些商業(yè)競(jìng)爭(zhēng)對(duì)手,它還相當(dāng)基礎(chǔ),但它提供了很多調(diào)試和調(diào)優(yōu)信息。

          4. 調(diào)優(yōu)技術(shù)

          4.1 業(yè)務(wù)規(guī)則與設(shè)計(jì)調(diào)優(yōu)

          盡管業(yè)務(wù)規(guī)則和設(shè)計(jì)調(diào)優(yōu)并不屬于Hibernate調(diào)優(yōu)的范疇,但此處的決定對(duì)后面Hibernate的調(diào)優(yōu)有很大影響。因此我們特意指出一些與Hibernate調(diào)優(yōu)有關(guān)的點(diǎn)。

          在業(yè)務(wù)需求收集與調(diào)優(yōu)過程中,你需要知道:

          • 數(shù)據(jù)獲取特性包括引用數(shù)據(jù)(reference data)、只讀數(shù)據(jù)、讀分組(read group)、讀取大小、搜索條件以及數(shù)據(jù)分組和聚合。
          • 數(shù)據(jù)修改特性包括數(shù)據(jù)變更、變更組、變更大小、無效修改補(bǔ)償、數(shù)據(jù)庫(所有變更都在一個(gè)數(shù)據(jù)庫中或在多個(gè)數(shù)據(jù)庫中)、變更頻率和并發(fā)性,以及變更響應(yīng)和吞吐量要求。
          • 數(shù)據(jù)關(guān)系,例如關(guān)聯(lián)(association)、泛化(generalization)、實(shí)現(xiàn)(realization)和依賴(dependency)。

          基于業(yè)務(wù)需求,你會(huì)得到一個(gè)最優(yōu)設(shè)計(jì),其中決定了應(yīng)用程序類型(是OLTP還是數(shù)據(jù)倉庫,亦或者與其中某一種比較接近)和分層結(jié)構(gòu)(將持久層和服務(wù)層分離還是合并),創(chuàng)建領(lǐng)域?qū)ο螅ㄍǔJ荘OJO),決定數(shù)據(jù)聚合的地方(在數(shù)據(jù)庫中進(jìn)行聚合能利用強(qiáng)大的數(shù)據(jù)庫功能,節(jié)省網(wǎng)絡(luò)帶寬;但是除了像COUNT、SUM、AVG、MIN和MAX這樣的標(biāo)準(zhǔn)聚合,其他的聚合通常不具有移植性。在應(yīng)用服務(wù)器上進(jìn)行聚合允許你應(yīng)用更復(fù)雜的業(yè)務(wù)邏輯;但你需要先在應(yīng)用程序中載入詳細(xì)的數(shù)據(jù))。

          范例2

          分析員需要查看一個(gè)取自大數(shù)據(jù)表的電流ISO(Independent System Operator)聚合列表。最開始他們想要顯示大多數(shù)字段,盡管數(shù)據(jù)庫能在1分鐘內(nèi)做出響應(yīng),應(yīng)用程序也要花30分鐘將1百萬行數(shù)據(jù)加載到前端UI。經(jīng)過重新分析,分析員保留了14個(gè)字段。因?yàn)槿サ袅撕芏嗫蛇x的高聚合度字段,從剩下的字段中進(jìn)行聚合分組返回的數(shù)據(jù)要少很多,而且大多數(shù)情況下的數(shù)據(jù)加載時(shí)間也縮小到了可接受的范圍內(nèi)。

          范例3

          過24個(gè)“非標(biāo)準(zhǔn)”(shaped,表示每小時(shí)都可以有自己的電量和價(jià)格;如果所有24小時(shí)的電量和價(jià)格相同,我們稱之為“標(biāo)準(zhǔn)”)小時(shí)會(huì)修改小時(shí)電流交易,其中包括2個(gè)屬性:每小時(shí)電量和價(jià)格。起初我們使用Hibernate的select-before-update特性,就是更新24行數(shù)據(jù)需要24次選擇。因?yàn)槲覀冎恍枰?個(gè)屬性,而且如果不修改電量或價(jià)格的話也沒有業(yè)務(wù)規(guī)則禁止無效修改,我們就關(guān)閉了select-before-update特性,避免了24次選擇。

          4.2繼承映射調(diào)優(yōu)

          盡管繼承映射是領(lǐng)域?qū)ο蟮囊徊糠?,出于它的重要性我們將它單?dú)出來。HRD [1]中的第9章“繼承映射”已經(jīng)說得很清楚了,所以我們將關(guān)注SQL生成和針對(duì)每個(gè)策略的調(diào)優(yōu)建議。

          以下是HRD中范例的類圖:


          4.2.1 每個(gè)類層次一張表

          只需要一張表,一條多態(tài)查詢生成的SQL大概是這樣的:

          select id, payment_type, amount, currency, rtn, credit_card_type from payment

          針對(duì)具體子類(例如CashPayment)的查詢生成的SQL是這樣的:

          select id, amount, currency from payment where payment_type=’CASH’ 

          這樣做的優(yōu)點(diǎn)包括只有一張表、查詢簡(jiǎn)單以及容易與其他表進(jìn)行關(guān)聯(lián)。第二個(gè)查詢中不需要包含其他子類中的屬性。所有這些特性讓該策略的性能調(diào)優(yōu)要比其他策略容易得多。這種方法通常比較適合數(shù)據(jù)倉庫系統(tǒng),因?yàn)樗袛?shù)據(jù)都在一張表里,不需要做表連接。

          主要的缺點(diǎn)整個(gè)類層次中的所有屬性都擠在一張大表里,如果有很多子類特有的屬性,數(shù)據(jù)庫中就會(huì)有太多字段的取值為null,這為當(dāng)前基于行的數(shù)據(jù)庫(使用基于列的DBMS的數(shù)據(jù)倉庫處理這個(gè)會(huì)更好些)的SQL調(diào)優(yōu)增加了難度。除非進(jìn)行分區(qū),否則唯一的數(shù)據(jù)表會(huì)成為熱點(diǎn),OLTP系統(tǒng)通常在這方面都不太好。

          4.2.2每個(gè)子類一張表

          需要4張表,多態(tài)查詢生成的SQL如下:

          select id, payment_type, amount, currency, rtn, credit_card type,
                  case when c.payment_id is not null then 1
                       when ck.payment_id is not null then 2
                       when cc.payment_id is not null then 3
                       when p.id is not null then 0 end as clazz
          
          from payment p left join cash_payment c on p.id=c.payment_id left join
             cheque_payment ck on p.id=ck.payment_id left join 
          
             credit_payment cc on p.id=cc.payment_id; 

          針對(duì)具體子類(例如CashPayment)的查詢生成的SQL是這樣的:

          select id, payment_type, amount, currency
          from payment p left join cash_payment c on p.id=c.payment_id; 

          優(yōu)點(diǎn)包括數(shù)據(jù)表比較緊湊(沒有不需要的可空字段),數(shù)據(jù)跨三個(gè)子類的表進(jìn)行分區(qū),容易使用超類的表與其他表進(jìn)行關(guān)聯(lián)。緊湊的數(shù)據(jù)表可以針對(duì)基于行的數(shù)據(jù)庫做存儲(chǔ)塊優(yōu)化,讓SQL執(zhí)行得更好。數(shù)據(jù)分區(qū)增加了數(shù)據(jù)修改的并發(fā)性(除了超類,沒有熱點(diǎn)),OLTP系統(tǒng)通常會(huì)更好些。

          同樣的,第二個(gè)查詢不需要包含其他子類的屬性。

          缺點(diǎn)是在所有策略中它使用的表和表連接最多,SQL語句稍顯復(fù)雜(看看Hibernate動(dòng)態(tài)鑒別器的長(zhǎng)CASE子句)。相比單張表,數(shù)據(jù)庫要花更多時(shí)間調(diào)優(yōu)數(shù)據(jù)表連接,數(shù)據(jù)倉庫在使用該策略時(shí)通常不太理想。

          因?yàn)椴荒芸绯惡妥宇惖淖侄蝸斫?fù)合索引,如果需要按這些列進(jìn)行查詢,性能會(huì)受影響。任何子類數(shù)據(jù)的修改都涉及兩張表:超類的表和子類的表。

          4.2.3每個(gè)具體類一張表

          涉及三張或更多的表,多態(tài)查詢生成的SQL是這樣的:

          select p.id, p.amount, p.currency, p.rtn, p. credit_card_type, p.clazz
          from (select id, amount, currency, null as rtn,null as credit_card type,
                       1 as clazz from cash_payment union all
          
                select id, amount, null as currency, rtn,null as credit_card type,
                       2 as clazz from cheque_payment union all
          
                select id, amount, null as currency, null as rtn,credit_card type,
                       3 as clazz from credit_payment) p;  

          針對(duì)具體子類(例如CashPayment)的查詢生成的SQL是這樣的:

          select id, payment_type, amount, currency from cash_payment; 

          優(yōu)點(diǎn)和上面的“每個(gè)子類一張表”策略相似。因?yàn)槌愅ǔJ浅橄蟮?,所以具體的三張表是必須的[開頭處說的3張或更多的表是必須的],任何子類的數(shù)據(jù)修改只涉及一張表,運(yùn)行起來更快。

          缺點(diǎn)是SQL(from子句和union all子查詢)太復(fù)雜。但是大多數(shù)數(shù)據(jù)庫對(duì)此類SQL的調(diào)優(yōu)都很好。

          如果一個(gè)類想和Payment超類關(guān)聯(lián),數(shù)據(jù)庫無法使用引用完整性(referential integrity)來實(shí)現(xiàn)它;必須使用觸發(fā)器來實(shí)現(xiàn)它。這對(duì)數(shù)據(jù)庫性能有些影響。

          4.2.4使用隱式多態(tài)實(shí)現(xiàn)每個(gè)具體類一張表

          只需要三張表。對(duì)于Payment的多態(tài)查詢生成三條獨(dú)立的SQL語句,每個(gè)對(duì)應(yīng)一個(gè)子類。Hibernate引擎通過Java反射找出Payment的所有三個(gè)子類。

          具體子類的查詢只生成該子類的SQL。這些SQL語句都很簡(jiǎn)單,這里就不再闡述了。

          它的優(yōu)點(diǎn)和上節(jié)類似:緊湊數(shù)據(jù)表、跨三個(gè)具體子類的數(shù)據(jù)分區(qū)以及對(duì)子類任意數(shù)據(jù)的修改都只涉及一張表。

          缺點(diǎn)是用三條獨(dú)立的SQL語句代替了一條聯(lián)合SQL,這會(huì)帶來更多網(wǎng)絡(luò)IO。Java反射也需要時(shí)間。假設(shè)如果你有一大堆領(lǐng)域?qū)ο?,你從最上層的Object類進(jìn)行隱式選擇查詢,那該需要多長(zhǎng)時(shí)間啊!

          根據(jù)你的映射策略制定合理的選擇查詢并非易事;這需要你仔細(xì)調(diào)優(yōu)業(yè)務(wù)需求,基于特定的數(shù)據(jù)場(chǎng)景制定合理的設(shè)計(jì)決策。

          以下是一些建議:

          • 設(shè)計(jì)細(xì)粒度的類層次和粗粒度的數(shù)據(jù)表。細(xì)粒度的數(shù)據(jù)表意味著更多數(shù)據(jù)表連接,相應(yīng)的查詢也會(huì)更復(fù)雜。
          • 如非必要,不要使用多態(tài)查詢。正如上文所示,對(duì)具體類的查詢只選擇需要的數(shù)據(jù),沒有不必要的表連接和聯(lián)合。
          • “每個(gè)類層次一張表”對(duì)有高并發(fā)、簡(jiǎn)單查詢并且沒有共享列的OLTP系統(tǒng)來說是個(gè)不錯(cuò)的選擇。如果你想用數(shù)據(jù)庫的引用完整性來做關(guān)聯(lián),那它也是個(gè)合適的選擇。
          • “每個(gè)具體類一張表”對(duì)有高并發(fā)、復(fù)雜查詢并且沒有共享列的OLTP系統(tǒng)來說是個(gè)不錯(cuò)的選擇。當(dāng)然你不得不犧牲超類與其他類之間的關(guān)聯(lián)。
          • 采用混合策略,例如“每個(gè)類層次一張表”中嵌入“每個(gè)子類一張表”,這樣可以利用不同策略的優(yōu)勢(shì)。隨著你項(xiàng)目的進(jìn)化,如果你要反復(fù)重新映射,那你可能也會(huì)采用該策略。
          • “使用隱式多態(tài)實(shí)現(xiàn)每個(gè)具體類一張表”這種做法并不推薦,因?yàn)槠渑渲眠^于繁縟、使用“any”元素的復(fù)雜關(guān)聯(lián)語法和隱式查詢的潛在危險(xiǎn)性。

          范例4

          下面是一個(gè)交易描述應(yīng)用程序的部分領(lǐng)域類圖:

          開始時(shí),項(xiàng)目只有GasDeal和少數(shù)用戶,它使用“每個(gè)類層次一張表”。

          OilDeal和ElectricityDeal是后期產(chǎn)生更多業(yè)務(wù)需求后加入的。沒有改變映射策略。但是ElectricityDeal有太多自己的屬性,因此有很多電相關(guān)的可空字段加入了Deal表。因?yàn)橛脩袅恳苍谠鲩L(zhǎng),數(shù)據(jù)修改變得越來越慢。

          重新設(shè)計(jì)時(shí)我們使用了兩張單獨(dú)的表,分別針對(duì)氣/油和電相關(guān)的屬性。新的映射混合了“每個(gè)類層次一張表”和“每個(gè)子類一張表”。我們還重新設(shè)計(jì)了查詢,以便允許針對(duì)具體交易子類進(jìn)行選擇,消除不必要的列和表連接。

          4.3 領(lǐng)域?qū)ο笳{(diào)優(yōu)

          基于4.1節(jié)中對(duì)業(yè)務(wù)規(guī)則和設(shè)計(jì)的調(diào)優(yōu),你得到了一個(gè)用POJO來表示的領(lǐng)域?qū)ο蟮念悎D。我們建議:

          4.3.1 POJO調(diào)優(yōu)

          • 從讀寫數(shù)據(jù)中將類似引用這樣的只讀數(shù)據(jù)和以讀為主的數(shù)據(jù)分離出來。
            只讀數(shù)據(jù)的二級(jí)緩存是最有效的,其次是以讀為主的數(shù)據(jù)的非嚴(yán)格讀寫。將只讀POJO標(biāo)識(shí)為不可更改的(immutable)也是一個(gè)調(diào)優(yōu)點(diǎn)。如果一個(gè)服務(wù)層方法只處理只讀數(shù)據(jù),可以將它的事務(wù)標(biāo)為只讀,這是優(yōu)化Hibernate和底層JDBC驅(qū)動(dòng)的一個(gè)方法。
          • 細(xì)粒度的POJO和粗粒度的數(shù)據(jù)表。
            基于數(shù)據(jù)的修改并發(fā)量和頻率等內(nèi)容來分解大的POJO。盡管你可以定義一個(gè)粒度非常細(xì)的對(duì)象模型,但粒度過細(xì)的表會(huì)導(dǎo)致大量表連接,這對(duì)數(shù)據(jù)倉庫來說是不能接受的。
          • 優(yōu)先使用非final的類。
            Hibernate只會(huì)針對(duì)非final的類使用CGLIB代理來實(shí)現(xiàn)延時(shí)關(guān)聯(lián)獲取。如果被關(guān)聯(lián)的類是final的,Hibernate會(huì)一次加載所有內(nèi)容,這對(duì)性能會(huì)有影響。
          • 使用業(yè)務(wù)鍵為分離(detached)實(shí)例實(shí)現(xiàn)equals()和hashCode()方法。
            在多層系統(tǒng)中,經(jīng)??梢栽诜蛛x對(duì)象上使用樂觀鎖來提升系統(tǒng)并發(fā)性,達(dá)到更高的性能。
          • 定義一個(gè)版本或時(shí)間戳屬性。
            樂觀鎖需要這個(gè)字段來實(shí)現(xiàn)長(zhǎng)對(duì)話(應(yīng)用程序事務(wù))[譯注:session譯為會(huì)話,conversion譯為對(duì)話,以示區(qū)別]。
          • 優(yōu)先使用組合POJO。
            你的前端UI經(jīng)常需要來自多個(gè)不同POJO的數(shù)據(jù)。你應(yīng)該向UI傳遞一個(gè)組合POJO而不是獨(dú)立的POJO以獲得更好的網(wǎng)絡(luò)性能。
            有兩種方式在服務(wù)層構(gòu)建組合POJO。一種是在開始時(shí)加3.2載所有需要的獨(dú)立POJO,隨后抽取需要的屬性放入組合POJO;另一種是使用HQL投影,直接從數(shù)據(jù)庫中選擇需要的屬性。
            如果其他地方也要查找這些獨(dú)立POJO,可以把它們放進(jìn)二級(jí)緩存以便共享,這時(shí)第一種方式更好;其他情況下第二種方式更好。

          4.3.2 POJO之間關(guān)聯(lián)的調(diào)優(yōu)

          • 如果可以用one-to-one、one-to-many或many-to-one的關(guān)聯(lián),就不要使用many-to-many。
          • many-to-many關(guān)聯(lián)需要額外的映射表。
            盡管你的Java代碼只需要處理兩端的POJO,但查詢時(shí),數(shù)據(jù)庫需要額外地關(guān)聯(lián)映射表,修改時(shí)需要額外的刪除和插入。
          • 單向關(guān)聯(lián)優(yōu)先于雙向關(guān)聯(lián)。
            由于many-to-many的特性,在雙向關(guān)聯(lián)的一端加載對(duì)象會(huì)觸發(fā)另一端的加載,這會(huì)進(jìn)一步觸發(fā)原始端加載更多的數(shù)據(jù),等等。
            one-to-many和many-to-one的雙向關(guān)聯(lián)也是類似的,當(dāng)你從多端(子實(shí)體)定位到一端(父實(shí)體)。
            這樣的來回加載很耗時(shí),而且可能也不是你所期望的。
          • 不要為了關(guān)聯(lián)而定義關(guān)聯(lián);只在你需要一起加載它們時(shí)才這么做,這應(yīng)該由你的業(yè)務(wù)規(guī)則和設(shè)計(jì)來決定(見范例5)。
            另外,你要么不定義任何關(guān)聯(lián),要么在子POJO中定義一個(gè)值類型的屬性來表示父POJO的ID(另一個(gè)方向也是類似的)。
          • 集合調(diào)優(yōu)
            如果集合排序邏輯能由底層數(shù)據(jù)庫實(shí)現(xiàn),就使用“order-by”屬性來代替“sort”,因?yàn)橥ǔ?shù)據(jù)庫在這方面做得比你好。
            集合可以是值類型的(元素或組合元素),也可以是實(shí)體引用類型的(one-to-many或many-to-many關(guān)聯(lián))。對(duì)引用類型集合的調(diào)優(yōu)主要是調(diào)優(yōu)獲取策略。對(duì)于值類型集合的調(diào)優(yōu),HRD [1]中的20.5節(jié)“理解集合性能”已經(jīng)做了很好的闡述。
          • 獲取策略調(diào)優(yōu)。請(qǐng)見4.7節(jié)的范例5。

          范例5

          我們有一個(gè)名為ElectricityDeals的核心POJO用于描述電的交易。從業(yè)務(wù)角度來看,它有很多many-to-one關(guān)聯(lián),例如和Portfolio、Strategy和Trader等的關(guān)聯(lián)。因?yàn)橐脭?shù)據(jù)十分穩(wěn)定,它們被緩存在前端,能基于其ID屬性快速定位到它們。

          為了有好的加載性能,ElectricityDeal只映射元數(shù)據(jù),即那些引用POJO的值類型ID屬性,因?yàn)樵谛枰獣r(shí),可以在前端通過portfolioKey從緩存中快速查找Portfolio:

          <property name="portfolioKey" column="PORTFOLIO_ID" type="integer"/> 

          這種隱式關(guān)聯(lián)避免了數(shù)據(jù)庫表連接和額外的字段選擇,降低了數(shù)據(jù)傳輸?shù)拇笮 ?/p>

          4.4 連接池調(diào)優(yōu)

          由于創(chuàng)建物理數(shù)據(jù)庫連接非常耗時(shí),你應(yīng)該始終使用連接池,而且應(yīng)該始終使用生產(chǎn)級(jí)連接池而非Hibernate內(nèi)置的基本連接池算法。

          通常會(huì)為Hibernate提供一個(gè)有連接池功能的數(shù)據(jù)源。Apache DBCP的BasicDataSource[13]是一個(gè)流行的開源生產(chǎn)級(jí)數(shù)據(jù)源。大多數(shù)數(shù)據(jù)庫廠商也實(shí)現(xiàn)了自己的兼容JDBC 3.0的連接池。舉例來說,你也可以使用Oracle ReaApplication Cluster [15]提供的JDBC連接池[14]以獲得連接的負(fù)載均衡和失敗轉(zhuǎn)移。

          不用多說,你在網(wǎng)上能找到很多關(guān)于連接池調(diào)優(yōu)的技術(shù),因此我們只討論那些大多數(shù)連接池所共有的通用調(diào)優(yōu)參數(shù):

          • 最小池大小:連接池中可保持的最小連接數(shù)。
          • 最大池大?。哼B接池中可以分配的最大連接數(shù)。
            如果應(yīng)用程序有高并發(fā),而最大池大小又太小,連接池就會(huì)經(jīng)常等待。相反,如果最小池大小太大,又會(huì)分配不需要的連接。
          • 最大空閑時(shí)間:連接池中的連接被物理關(guān)閉前能保持空閑的最大時(shí)間。
          • 最大等待時(shí)間:連接池等待連接返回的最大時(shí)間。該參數(shù)可以預(yù)防失控事務(wù)(runaway transaction)。
          • 驗(yàn)證查詢:在將連接返回給調(diào)用方前用于驗(yàn)證連接的SQL查詢。這是因?yàn)橐恍?shù)據(jù)庫被配置為會(huì)殺掉長(zhǎng)時(shí)間空閑的連接,網(wǎng)絡(luò)或數(shù)據(jù)庫相關(guān)的異常也可能會(huì)殺死連接。為了減少此類開銷,連接池在空閑時(shí)會(huì)運(yùn)行該驗(yàn)證。

          4.5事務(wù)和并發(fā)的調(diào)優(yōu)

          短數(shù)據(jù)庫事務(wù)對(duì)任何高性能、高可擴(kuò)展性的應(yīng)用程序來說都是必不可少的。你使用表示對(duì)話請(qǐng)求的會(huì)話來處理單個(gè)工作單元,以此來處理事務(wù)。

          考慮到工作單元的范圍和事務(wù)邊界的劃分,有3中模式:

          • 每次操作一個(gè)會(huì)話。每次數(shù)據(jù)庫調(diào)用需要一個(gè)新會(huì)話和事務(wù)。因?yàn)檎鎸?shí)的業(yè)務(wù)事務(wù)通常包含多個(gè)此類操作和大量小事務(wù),這一般會(huì)引起更多數(shù)據(jù)庫活動(dòng)(主要是數(shù)據(jù)庫每次提交需要將變更刷新到磁盤上),影響應(yīng)用程序性能。這是一種反模式,不該使用它。
          • 使用分離對(duì)象,每次請(qǐng)求一個(gè)會(huì)話。每次客戶端請(qǐng)求有一個(gè)新會(huì)話和一個(gè)事務(wù),使用Hibernate的“當(dāng)前會(huì)話”特性將兩者關(guān)聯(lián)起來。
            在一個(gè)多層系統(tǒng)中,用戶通常會(huì)發(fā)起長(zhǎng)對(duì)話(或應(yīng)用程序事務(wù))。大多數(shù)時(shí)間我們使用Hibernate的自動(dòng)版本和分離對(duì)象來實(shí)現(xiàn)樂觀并發(fā)控制和高性能。
          • 帶擴(kuò)展(或長(zhǎng))會(huì)話的每次對(duì)話一會(huì)話。在一個(gè)也許會(huì)跨多個(gè)事務(wù)的長(zhǎng)對(duì)話中保持會(huì)話開啟。盡管這能把你從重新關(guān)聯(lián)中解脫出來,但會(huì)話可能會(huì)內(nèi)存溢出,在高并發(fā)系統(tǒng)中可能會(huì)有舊數(shù)據(jù)。

          你還應(yīng)該注意以下幾點(diǎn)。 

          • 如果不需要JTA就用本地事務(wù),因?yàn)镴TA需要更多資源,比本地事務(wù)更慢。就算你有多個(gè)數(shù)據(jù)源,除非有跨多個(gè)數(shù)據(jù)庫的事務(wù),否則也不需要JTA。在最后的一個(gè)場(chǎng)景下,可以考慮在每個(gè)數(shù)據(jù)源中使用本地事務(wù),使用一種類似“Last Resource Commit Optimization”[16]的技術(shù)(見下面的范例6)。
          • 如果不涉及數(shù)據(jù)變更,將事務(wù)標(biāo)記為只讀的,就像4.3.1節(jié)提到的那樣。
          • 總是設(shè)置默認(rèn)事務(wù)超時(shí)。保證在沒有響應(yīng)返回給用戶時(shí),沒有行為不當(dāng)?shù)氖聞?wù)會(huì)完全占有資源。這對(duì)本地事務(wù)也同樣有效。
          • 如果Hibernate不是獨(dú)占數(shù)據(jù)庫用戶,樂觀鎖會(huì)失效,除非創(chuàng)建數(shù)據(jù)庫觸發(fā)器為其他應(yīng)用程序?qū)ο嗤瑪?shù)據(jù)的變更增加版本字段值。

          范例6

          我們的應(yīng)用程序有多個(gè)在大多數(shù)情況下只和數(shù)據(jù)庫“A”打交道的服務(wù)層方法;它們偶爾也會(huì)從數(shù)據(jù)庫“B”中獲取只讀數(shù)據(jù)。因?yàn)閿?shù)據(jù)庫“B”只提供只讀數(shù)據(jù),我們對(duì)這些方法在這兩個(gè)數(shù)據(jù)庫上仍然使用本地事務(wù)。

          服務(wù)層上有一個(gè)方法設(shè)計(jì)在兩個(gè)數(shù)據(jù)庫上執(zhí)行數(shù)據(jù)變更。以下是偽代碼:

          //Make sure a local transaction on database A exists
          @Transactional (readOnly=false, propagation=Propagation.REQUIRED)
          public void saveIsoBids() {
            //it participates in the above annotated local transaction
            insertBidsInDatabaseA();
          
            //it runs in its own local transaction on database B 
            insertBidRequestsInDatabaseB(); //must be the last operation 

          因?yàn)?strong>insertBidRequestsInDatabaseB()是saveIsoBids ()中的最后一個(gè)方法,所以只有下面的場(chǎng)景會(huì)造成數(shù)據(jù)不一致:

          在saveIsoBids()執(zhí)行返回時(shí),數(shù)據(jù)庫“A”的本地事務(wù)提交失敗。

          但是,就算saveIsoBids()使用JTA,在兩階段提交(2PC)的第二個(gè)提交階段失敗的時(shí)候,你還是會(huì)碰到數(shù)據(jù)不一致。因此如果你能處理好上述的數(shù)據(jù)不一致性,而且不想為了一個(gè)或少數(shù)幾個(gè)方法引入JTA的復(fù)雜性,你應(yīng)該使用本地事務(wù)。

          (未完待續(xù))

          posted @ 2011-06-18 11:59 ... 閱讀(179) | 評(píng)論 (0)編輯 收藏

          [轉(zhuǎn)]Spring3.05簡(jiǎn)單集成MyBatis3.03

               摘要: 原文: http://blog.csdn.net/sustbeckham/archive/2010/12/17/6082677.aspxmybatis一直沒有發(fā)布release版本。所以spring也坐著看。但是spring還是必須用啊。 1. Pojo & mapper配置   view plaincopy to clipboardprint? package&...  閱讀全文

          posted @ 2011-06-18 11:42 ... 閱讀(455) | 評(píng)論 (0)編輯 收藏

          2011年3月16日

          獲取textarea選中文本

           

          1code_area.text.substr(code_area.selectionBeginIndex, code_area.selectionEndIndex - code_area.selectionBeginIndex);

          posted @ 2011-03-16 21:34 ... 閱讀(277) | 評(píng)論 (0)編輯 收藏

          僅列出標(biāo)題  
          <2025年6月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          導(dǎo)航

          統(tǒng)計(jì)

          常用鏈接

          留言簿

          隨筆分類

          隨筆檔案

          收藏夾

          搜索

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 巴林右旗| 项城市| 东阳市| 海门市| 四会市| 怀安县| 张家川| 淳化县| 郸城县| 东莞市| 云林县| 汉川市| 平邑县| 广德县| 青田县| 临朐县| 龙游县| 交城县| 前郭尔| 青海省| 独山县| 中牟县| 诏安县| 亚东县| 利辛县| 雅江县| 安庆市| 班戈县| 扬州市| 资源县| 科尔| 宜兴市| 城口县| 土默特右旗| 阳谷县| 夹江县| 晴隆县| 镇巴县| 隆德县| 平山县| 甘德县|