隨筆-17  評論-0  文章-4  trackbacks-0
            2008年12月2日
          運行看結果,慢慢了解
          import java.lang.ref.ReferenceQueue;
          import java.lang.ref.SoftReference;
          import java.lang.ref.WeakReference;


          public class Testone {
          public static void main(String args[]){
          A a=new A();
          //a.test();
          //SoftReference sr = new SoftReference(a);
          ReferenceQueue<A> rq = new ReferenceQueue<A>();
          WeakReference<A> wr = new WeakReference<A>(a, rq);
          a = null;
          System.out.println(wr.get());
          System.out.println(rq.poll());
          System.gc();
          System.runFinalization();
          System.out.println(wr.get());
          System.out.println(rq.poll());
          if (wr != null) {
          a = (A)wr.get();
          System.out.println("asdasdas");
          a.test();
          }
          else{
          a = new A();
          System.out.println("123123");
          a.test();
          a = null;
          wr = new WeakReference<A>(a);
          }

          }
          }
          class A{
          void test(){
          System.out.println("A.test()");
          }
          }
          posted @ 2008-12-06 21:14 竹子 閱讀(457) | 評論 (0)編輯 收藏

          垃圾回收與強引用,軟引用,弱引用,幻引用的關系(一)

          Java 2 平臺引入了 java.lang.ref 包,其中包括的類可以讓您引用對象,而不將它們留在內存中。這些類還提供了與垃圾收集器(garbage collector)之間有限的交互。Peter Haggar 在本文中分析了 SoftReference、WeakReference 和 PhantomReference 類的功能和行為,并就這些類的使用給出了一些編程風格上的建議。
          當在 Java 2 平臺中首次引入 java.lang.ref 包(其中包含 SoftReference、WeakReference 和 PhantomReference 類)時,它的實用性顯然被過分夸大了。它包含的類可能是有用的,但這些類具有的某些局限性會使它們顯得不是很有吸引力,而且其應用程序也將特別局限于解決一類特定的問題。

          垃圾收集概述
          引用類的主要功能就是能夠引用仍可以被垃圾收集器回收的對象。在引入引用類之前,我們只能使用強引用(strong reference)。舉例來說,下面一行代碼顯示的就是強引用 obj:


          Object obj = new Object();




          obj 這個引用將引用堆中存儲的一個對象。只要 obj 引用還存在,垃圾收集器就永遠不會釋放用來容納該對象的存儲空間。

          當 obj 超出范圍或被顯式地指定為 null 時,垃圾收集器就認為沒有對這個對象的其它引用,也就可以收集它了。然而您還需要注意一個重要的細節:僅憑對象可以被收集并不意味著垃圾收集器的一次指定運行就能夠回收它。由于各種垃圾收集算法有所不同,某些算法會更頻繁地分析生存期較短的對象,而不是較老、生存期較長的對象。因此,一個可供收集的對象可能永遠也不會被回收。如果程序在垃圾收集器釋放對象之前結束,這種情況就可能會出現。因此,概括地說,您永遠無法保證可供收集的對象總是會被垃圾收集器收集。

          這些信息對于您分析引用類是很重要的。由于垃圾收集有著特定的性質,所以引用類實際上可能沒有您原來想像的那么有用,盡管如此,它們對于特定問題來說還是很有用的類。軟引用(soft reference)、弱引用(weak reference)和虛引用(phantom reference)對象提供了三種不同的方式來在不妨礙收集的情況下引用堆對象。每種引用對象都有不同的行為,而且它們與垃圾收集器之間的交互也有所不同。此外,這幾個新的引用類都表現出比典型的強引用“更弱”的引用形式。而且,內存中的一個對象可以被多個引用(可以是強引用、軟引用、弱引用或虛引用)引用。在進一步往下討論之前,讓我們來看看一些術語:

          強可及對象(strongly reachable):可以通過強引用訪問的對象。


          軟可及對象(softly reachable):不是強可及對象,并且能夠通過軟引用訪問的對象。


          弱可及對象(weakly reachable):不是強可及對象也不是軟可及對象,并且能夠通過弱引用訪問的對象。


          虛可及對象(phantomly reachable):不是強可及對象、軟可及對象,也不是弱可及對象,已經結束的,可以通過虛引用訪問的對象。


          清除:將引用對象的 referent 域設置為 null,并將引用類在堆中引用的對象聲明為可結束的。
          SoftReference 類
          SoftReference 類的一個典型用途就是用于內存敏感的高速緩存。SoftReference 的原理是:在保持對對象的引用時保證在 JVM 報告內存不足情況之前將清除所有的軟引用。關鍵之處在于,垃圾收集器在運行時可能會(也可能不會)釋放軟可及對象。對象是否被釋放取決于垃圾收集器的算法以及垃圾收集器運行時可用的內存數量。 

          WeakReference 類
          WeakReference 類的一個典型用途就是規范化映射(canonicalized mapping)。另外,對于那些生存期相對較長而且重新創建的開銷也不高的對象來說,弱引用也比較有用。關鍵之處在于,垃圾收集器運行時如果碰到了弱可及對象,將釋放 WeakReference 引用的對象。然而,請注意,垃圾收集器可能要運行多次才能找到并釋放弱可及對象。

          PhantomReference 類
          PhantomReference 類只能用于跟蹤對被引用對象即將進行的收集。同樣,它還能用于執行 pre-mortem 清除操作。PhantomReference 必須與 ReferenceQueue 類一起使用。需要 ReferenceQueue 是因為它能夠充當通知機制。當垃圾收集器確定了某個對象是虛可及對象時,PhantomReference 對象就被放在它的 ReferenceQueue 上。將 PhantomReference 對象放在 ReferenceQueue 上也就是一個通知,表明 PhantomReference 對象引用的對象已經結束,可供收集了。這使您能夠剛好在對象占用的內存被回收之前采取行動。  

          垃圾收集器和引用交互
          垃圾收集器每次運行時都可以隨意地釋放不再是強可及的對象占用的內存。如果垃圾收集器發現了軟可及對象,就會出現下列情況:

          SoftReference 對象的 referent 域被設置為 null,從而使該對象不再引用 heap 對象。


          SoftReference 引用過的 heap 對象被聲明為 finalizable。


          當 heap 對象的 finalize() 方法被運行而且該對象占用的內存被釋放,SoftReference 對象就被添加到它的 ReferenceQueue(如果后者存在的話)。
          如果垃圾收集器發現了弱可及對象,就會出現下列情況:

          WeakReference 對象的 referent 域被設置為 null,從而使該對象不再引用 heap 對象。


          WeakReference 引用過的 heap 對象被聲明為 finalizable。


          當 heap 對象的 finalize() 方法被運行而且該對象占用的內存被釋放時,WeakReference 對象就被添加到它的 ReferenceQueue(如果后者存在的話)。
          如果垃圾收集器發現了虛可及對象,就會出現下列情況:

          PhantomReference 引用過的 heap 對象被聲明為 finalizable。


          與軟引用和弱引用有所不同,PhantomReference 在堆對象被釋放之前就被添加到它的 ReferenceQueue。(請記住,所有的 PhantomReference 對象都必須用經過關聯的 ReferenceQueue 來創建。)這使您能夠在堆對象被回收之前采取行動。
          請考慮清單 1 中的代碼。

          清單 1. 使用 WeakReference 及 ReferenceQueue 的示例代碼
          //Create a strong reference to an object
          MyObject obj = new MyObject(); //1

          //Create a reference queue
          ReferenceQueue rq = new ReferenceQueue(); //2

          //Create a weakReference to obj and associate our reference queue
          WeakReference wr = new WeakReference(obj, rq); //3



          行 //1 創建 MyObject 對象,而行 //2 則創建 ReferenceQueue 對象。行 //3 創建引用其引用對象 MyObject 的 WeakReference 對象,還創建它的 ReferenceQueue。請注意,每個對象引用(obj、rq 及 wr)都是強引用。要利用這些引用類,您必須取消對 MyObject 對象的強引用,方法是將 obj 設置為 null。前面說過,如果不這樣做,對象 MyObject 永遠都不會被回收,引用類的任何優點都會被削弱。

          每個引用類都有一個 get() 方法,而 ReferenceQueue 類有一個 poll() 方法。get() 方法返回對被引用對象的引用。在 PhantomReference 上調用 get() 總是會返回 null。這是因為 PhantomReference 只用于跟蹤收集。poll() 方法返回已被添加到隊列中的引用對象,如果隊列中沒有任何對象,它就返回 null。因此,執行清單 1 之后再調用 get() 和 poll() 的結果可能是:


          wr.get(); //returns reference to MyObject
          rq.poll(); //returns null




          現在我們假定垃圾收集器開始運行。由于 MyObject 對象沒有被釋放,所以 get() 和 poll() 方法將返回同樣的值;obj 仍然保持對該對象進行強引用。實際上,對象布局還是沒有改變,和圖 1 所示的差不多。然而,請考慮下面的代碼:


          obj = null;
          System.gc(); //run the collector







          現在,調用 get() 和 poll() 將產生與前面不同的結果:


          wr.get(); //returns null
          rq.poll(); //returns a reference to the WeakReference object




          這種情況表明,MyObject 對象(對它的引用原來是由 WeakReference 對象進行的)不再可用。這意味著垃圾收集器釋放了 MyObject 占用的內存,從而使 WeakReference 對象可以被放在它的 ReferenceQueue 上。這樣,您就可以知道當 WeakReference 或 SoftReference 類的 get() 方法返回 null 時,就有一個對象被聲明為 finalizable,而且可能(不過不一定)被收集。只有當 heap 對象完全結束而且其內存被回收后,WeakReference 或 SoftReference 才會被放到與其關聯的 ReferenceQueue 上。清單 2 顯示了一個完整的可運行程序,它展示了這些原理中的一部分。這段代碼本身就頗具說明性,它含有很多注釋和打印語句,可以幫助您理解。

          清單 2. 展示引用類原理的完整程序
          import java.lang.ref.*;
          class MyObject
          {
          protected void finalize() throws Throwable
          {
          System.out.println("In finalize method for this object: " +
          this);
          }
          }

          class ReferenceUsage
          {
          public static void main(String args[])
          {
          hold();
          release();
          }

          public static void hold()
          {
          System.out.println("Example of incorrectly holding a strong " +
          "reference");
          //Create an object
          MyObject obj = new MyObject();
          System.out.println("object is " + obj);

          //Create a reference queue
          ReferenceQueue rq = new ReferenceQueue();

          //Create a weakReference to obj and associate our reference queue
          WeakReference wr = new WeakReference(obj, rq);

          System.out.println("The weak reference is " + wr);

          //Check to see if it´s on the ref queue yet
          System.out.println("Polling the reference queue returns " +
          rq.poll());
          System.out.println("Getting the referent from the " +
          "weak reference returns " + wr.get());

          System.out.println("Calling GC");
          System.gc();
          System.out.println("Polling the reference queue returns " +
          rq.poll());
          System.out.println("Getting the referent from the " +
          "weak reference returns " + wr.get());
          }

          public static void release()
          {
          System.out.println("");
          System.out.println("Example of correctly releasing a strong " +
          "reference");
          //Create an object
          MyObject obj = new MyObject();
          System.out.println("object is " + obj);

          //Create a reference queue
          ReferenceQueue rq = new ReferenceQueue();

          //Create a weakReference to obj and associate our reference queue
          WeakReference wr = new WeakReference(obj, rq);

          System.out.println("The weak reference is " + wr);

          //Check to see if it´s on the ref queue yet
          System.out.println("Polling the reference queue returns " +
          rq.poll());
          System.out.println("Getting the referent from the " +
          "weak reference returns " + wr.get());

          System.out.println("Set the obj reference to null and call GC");
          obj = null;
          System.gc();
          System.out.println("Polling the reference queue returns " +
          rq.poll());
          System.out.println("Getting the referent from the " +
          "weak reference returns " + wr.get());
          }
          }




          用途和風格
          這些類背后的原理就是避免在應用程序執行期間將對象留在內存中。相反,您以軟引用、弱引用或虛引用的方式引用對象,這樣垃圾收集器就能夠隨意地釋放對象。當您希望盡可能減小應用程序在其生命周期中使用的堆內存大小時,這種用途就很有好處。您必須記住,要使用這些類,您就不能保留對對象的強引用。如果您這么做了,那就會浪費這些類所提供的任何好處。

          另外,您必須使用正確的編程風格以檢查收集器在使用對象之前是否已經回收了它,如果已經回收了,您首先必須重新創建該對象。這個過程可以用不同的編程風格來完成。選擇錯誤的風格會導致出問題。請考慮清單 3 中從 WeakReference 檢索被引用對象的代碼風格:

          清單 3. 檢索被引用對象的風格
          obj = wr.get();
          if (obj == null)
          {
          wr = new WeakReference(recreateIt()); //1
          obj = wr.get(); //2
          }
          //code that works with obj




          研究了這段代碼之后,請看看清單 4 中從 WeakReference 檢索被引用對象的另一種代碼風格:

          清單 4. 檢索被引用對象的另一種風格
          obj = wr.get();
          if (obj == null)
          {
          obj = recreateIt(); //1
          wr = new WeakReference(obj); //2
          }
          //code that works with obj




          請比較這兩種風格,看看您能否確定哪種風格一定可行,哪一種不一定可行。清單 3 中體現出的風格不一定在所有情況下都可行,但清單 4 的風格就可以。清單 3 中的風格不夠好的原因在于,if 塊的主體結束之后 obj 不一定是非空值。請考慮一下,如果垃圾收集器在清單 3 的行 //1 之后但在行 //2 執行之前運行會怎樣。recreateIt() 方法將重新創建該對象,但它會被 WeakReference 引用,而不是強引用。因此,如果收集器在行 //2 在重新創建的對象上施加一個強引用之前運行,對象就會丟失,wr.get() 則返回 null。

          清單 4 不會出現這種問題,因為行 //1 重新創建了對象并為其指定了一個強引用。因此,如果垃圾收集器在該行之后(但在行 //2 之前)運行,該對象就不會被回收。然后,行 //2 將創建對 obj 的 WeakReference。在使用這個 if 塊之后的 obj 之后,您應該將 obj 設置為 null,從而讓垃圾收集器能夠回收這個對象以充分利用弱引用。清單 5 顯示了一個完整的程序,它將展示剛才我們描述的風格之間的差異。(要運行該程序,其運行目錄中必須有一個“temp.fil”文件。

          清單 5. 展示正確的和不正確的編程風格的完整程序。
          import java.io.*;
          import java.lang.ref.*;

          class ReferenceIdiom
          {
          public static void main(String args[]) throws FileNotFoundException
          {
          broken();
          correct();
          }

          public static FileReader recreateIt() throws FileNotFoundException
          {
          return new FileReader("temp.fil");
          }

          public static void broken() throws FileNotFoundException
          {
          System.out.println("Executing method broken");
          FileReader obj = recreateIt();
          WeakReference wr = new WeakReference(obj);

          System.out.println("wr refers to object " + wr.get());

          System.out.println("Now, clear the reference and run GC");
          //Clear the strong reference, then run GC to collect obj.
          obj = null;
          System.gc();

          System.out.println("wr refers to object " + wr.get());

          //Now see if obj was collected and recreate it if it was.
          obj = (FileReader)wr.get();
          if (obj == null)
          {
          System.out.println("Now, recreate the object and wrap it
          in a WeakReference");
          wr = new WeakReference(recreateIt());
          System.gc(); //FileReader object is NOT pinned...there is no
          //strong reference to it. Therefore, the next
          //line can return null.
          obj = (FileReader)wr.get();
          }
          System.out.println("wr refers to object " + wr.get());
          }

          public static void correct() throws FileNotFoundException
          {
          System.out.println("");
          System.out.println("Executing method correct");
          FileReader obj = recreateIt();
          WeakReference wr = new WeakReference(obj);

          System.out.println("wr refers to object " + wr.get());

          System.out.println("Now, clear the reference and run GC");
          //Clear the strong reference, then run GC to collect obj
          obj = null;
          System.gc();

          System.out.println("wr refers to object " + wr.get());

          //Now see if obj was collected and recreate it if it was.
          obj = (FileReader)wr.get();
          if (obj == null)
          {
          System.out.println("Now, recreate the object and wrap it
          in a WeakReference");
          obj = recreateIt();
          System.gc(); //FileReader is pinned, this will not affect
          //anything.
          wr = new WeakReference(obj);
          }
          System.out.println("wr refers to object " + wr.get());
          }
          }




          總結
          如果使用得當,引用類還是很有用的。然而,由于它們所依賴的垃圾收集器行為有時候無法預知,所以其實用性就會受到影響。能否有效地使用它們還取決于是否應用了正確的編程風格;關鍵在于您要理解這些類是如何實現的以及如何對它們進行編程。
          =================================================================================

          Java 對象的狀態有:

              * 已創建(created)
              * 強可達(strong reachable)
              * 不可見(invisible)
              * 不可達(unreachable)
              * 已收集(collected)
              * 終化(finalized)
              * 已回收(deallocated) 

          Java對象生命周期的狀態轉換: {image:img=objectstatus.jpg|width=400} 引用對象
          三種新的引用類型:

              * 軟引用(soft reference)
              * 弱引用(weak reference)
              * 幻引用(phantom reference) 

          強可達(Strong Reachable)
          定義: ~An object is strong reachable if it can be reached by some thread without traversing any reference objects. A newly-created object is strong reachable by the thread that created it.~
          處于強可達狀態的對象, 在任何情況下都不會被回收掉. 軟可達(Softly Reachable)
          定義:~An object is softly reachable if it is not strongly reachable but can be reached by traversing a soft reference.~
          含義是:當對象不處于強可達狀態, 并且可以通過軟引用進行訪問時, 即處于軟可達狀態.
          當程序申請內存的時候, 垃圾收集器會判斷是否開始回收處于軟可達狀態的對象, 如果決定回收某個對象, 那么垃圾收集器會清除所有指向該對象的軟引用, 如果任何處于其它軟可達狀態的對象可以通過強引用訪問該對象, 那么指向這些對象的軟引用也會被清除掉. 垃圾收集器在決定哪些軟可達狀態的對象被收集時, 采用"最久未被使用"原則, 或稱"最不常使用"原則. 垃圾收集器也保證在OutOfMemeryError產生以前, 所有的軟引用都被清除.

              * 產生和使用一個軟引用 

          // createSoftReference sr = new SoftReference(new SomeObject());// getSomeObject o = (SomeObject) sf.get();// create in a reference queue;ReferenceQueue queue = new ReferenceQueue();SoftReference sr = new SoftReference(new SomeObject(), queue);

          弱可達(Weakly Reachable)
          定義:~An Object is weakly reachable if it is neither strongly nor softly reachable but can be reached by traversing a weak reference.~
          垃圾收集器會一次清除所有弱引用. 幻可達(Phantomly Reachable)
          定義:~An object is phantomly reachable if it is neither strongly, softly, nor weakly reachable, it has been finalized, and some phantom reference refers to it.~
          幻引用不能直接創建. 必須通過向引用隊列等級的途徑來創建:

          ReferenceQueue queue = new ReferenceQueue();PhantomReference pr = new PhantomReference (new SomeObject(), queue);

          你不可能從幻引用再次得到對象, pr.get()永遠返回null. 另外, 必須調用Reference.clear()手工清除幻引用. All About ReferenceObjects No InterWiki reference defined in properties for Wiki called '[http'!)]
          Reference Objects No InterWiki reference defined in properties for Wiki called '[http'!)]
          Reference Objects and Garbage Collection No InterWiki reference defined in properties for Wiki called '[http'!)]
          \[Jike Thread\?Soft, Weak, and Phantom References|http://www-124.ibm.com/pipermail/jikesrvm-core/2003-May/000365.html]


          Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1492810
          posted @ 2008-12-06 21:13 竹子 閱讀(488) | 評論 (0)編輯 收藏

          T-SQL 編碼標準

          發布日期: 4/15/2005 | 更新日期: 4/15/2005

          Brian Walker

          可能讓人覺得很奇怪,但好像的確沒有什么正式的”T-SQL 編碼標準。早在 1999 年末的時候,我驚喜地發現 John Hindmarsh 提出的 SQL Server 7.0 標準,我在 2000 2 月的社論中對他的某些建議進行了總結。(2000 2 月以及本月的下載中都包括了 John 原來的標準。)后來,Ron Talmage 撰寫了一系列專欄文章,提出了他對各種最佳方法的建議,當然,SQL Server 小組也已正式發布了 SQL Server 最佳方法分析器 (SQLBPA)。現在,一位具有超過 25 年經驗的數據庫管理員和應用程序開發員 Brian Walker 又提出了他的建議和提示。

          *

          進行 T-SQL 編程時常常會忽略編碼標準,但這些標準卻是開發小組順利開展工作的關鍵工具。這里介紹的編碼標準是我多年的開發成果。它們當然還沒有得到普遍接受,而且不可否認,有些標準帶有主觀色彩。我的目的實際上更多的是為了提高大家的意識,而不是吹捧自己是 T-SQL 樣式方面的仲裁者:最重要的是要建立某些合理的編碼標準并遵循這些標準。您在這篇文章中會發現有關 T-SQL 編程的一系列不同的編碼標準、技巧和提示。它們并未以任何特定的優先級或重要性順序列出。

          讓我們從格式開始。表面上,T-SQL 代碼的格式似乎并不重要,但一致的格式可以使您的同事(不論是同一小組的成員還是更大范圍的 T-SQL 開發團隊的成員)更輕松地瀏覽和理解您的代碼。T-SQL 語句有一個結構,遵循一目了然的結構使您可以更輕松地查找和確認語句的不同部分。統一的格式還使您可以更輕松地在復雜 T-SQL 語句中增刪代碼段,使調試工作變得更容易。下面是 SELECT 語句的格式示例:

                 SELECT C.Name
          , E.NameLast
          , E.NameFirst
          , E.Number
          , ISNULL(I.Description,'NA') AS Description
          FROM tblCompany AS C
          JOIN tblEmployee AS E
          ON C.CompanyID = E.CompanyID
          LEFT JOIN tblCoverage AS V
          ON E.EmployeeID = V.EmployeeID
          LEFT JOIN tblInsurance AS I
          ON V.InsuranceID = I.InsuranceID
          WHERE C.Name LIKE @Name
          AND V.CreateDate > CONVERT(smalldatetime,
          '01/01/2000')
          ORDER BY C.Name
          , E.NameLast
          , E.NameFirst
          , E.Number
          , ISNULL(I.Description,'NA')
          SELECT @Retain = @@ERROR, @Rows = @@ROWCOUNT
          IF @Status = 0 SET @Status = @Retain
          

          ?一個嵌套代碼塊中的語句使用四個空格的縮進。(上述代碼中的多行 SELECT 語句是一個 SQL 語句。)在同一語句中開始新行時,使 SQL 關鍵字右對齊。將代碼編輯器配置為使用空格,而不是使用制表符。這樣,不管使用何種程序查看代碼,格式都是一致的。

          ?大寫所有的 T-SQL 關鍵字,包括 T-SQL 函數。變量名稱及光標名稱使用混和大小寫。數據類型使用小寫。

          ?表名別名要簡短,但意義要盡量明確。通常,使用大寫的表名作為別名,使用 AS 關鍵字指定表或字段的別名。

          ?當一個 T-SQL 語句中涉及到多個表時,始終使用表名別名來限定字段名。這使其他人閱讀起來更清楚,避免了含義模糊的引用。

          ?當相關數字出現在連續的代碼行中時(例如一系列 SUBSTRING 函數調用),將它們排成列。這樣容易瀏覽數字列表。

          ?使用一個(而不是兩個)空行分隔 T-SQL 代碼的邏輯塊,只要需要就可以使用。

          ?聲明 T-SQL 局部變量(例如 @lngTableID)時,使用適當的數據類型聲明和一致的大寫。

          ?始終指定字符數據類型的長度,并確保允許用戶可能需要的最大字符數,因為超出最大長度的字符會丟失。

          ?始終指定十進制數據類型的精度和范圍,否則,將默認為未指定精度和整數范圍。

          ?使用錯誤處理程序,但要記住行首 (BOL) 中的錯誤檢查示例不會象介紹的那樣起作用。用來檢查 @@ERROR 系統函數的 T-SQL 語句 (IF) 實際上在進程中清除了 @@ERROR 值,無法再捕獲除零之外的任何值。(即使示例起作用,它們也只能捕獲最后發生的一個錯誤,而不是您更想捕獲的第一個錯誤。)必須使用 SET 或 SELECT 立即捕獲錯誤代碼,如前面示例所示。如果狀態變量仍然為零,應轉換到狀態變量。

          ?避免使用“未聲明的”功能,例如系統表中未聲明的列、T-SQL 語句中未聲明的功能或者未聲明的系統存儲過程或擴展的存儲過程。

          ?不要依賴任何隱式的數據類型轉換。例如,不能為數字變量賦予字符值,而假定 T-SQL 會進行必要的轉換。相反,在為變量賦值或比較值之前,應使用適當的 CONVERT 函數使數據類型相匹配。另一個示例:雖然 T-SQL 會在進行比較之前對字符表達式進行隱式且自動的 RTRIM,但不能依賴此行為,因為兼容性級別設置非字符表達式會使情況復雜化。

          ?不要將空的變量值直接與比較運算符(符號)比較。如果變量可能為空,應使用 IS NULL 或 IS NOT NULL 進行比較,或者使用 ISNULL 函數。

          ?不要使用 STR 函數進行舍入,此函數只能用于整數。如果需要十進制值的字符串形式,應先使用 CONVERT 函數(轉至不同的范圍)或 ROUND 函數,然后將其轉換為字符串。也可以使用 CEILING 和 FLOOR 函數。

          ?使用數學公式時要小心,因為 T-SQL 可能會將表達式強制理解為一個不需要的數據類型。如果需要十進制結果,應在整數常量后加點和零 (.0)。

          ?決不要依賴 SELECT 語句會按任何特定順序返回行,除非在 ORDER BY 子句中指定了順序。

          ?通常,應將 ORDER BY 子句與 SELECT 語句一起使用。可預知的順序(即使不是最方便的)比不可預知的順序強,尤其是在開發或調試過程中。(部署到生產環境中之前,可能需要刪除 ORDER BY 子句。)在返回行的順序無關緊要的情況下,可以忽略 ORDER BY 的開銷。

          ?不要在 T-SQL 代碼中使用雙引號。應為字符常量使用單引號。如果沒有必要限定對象名稱,可以使用(非 ANSI SQL 標準)括號將名稱括起來。

          ?在 SQL Server 2000 中,盡量使用表變量來代替臨時表。如果表變量包含大量數據,請注意索引非常有限(只有主鍵索引)。

          ?先在例程中創建臨時表,最后再顯式刪除臨時表。將 DDL 與 DML 語句混合使用有助于處理額外的重新編譯活動。

          ?要認識到臨時表并不是不可使用,適當地使用它們可以使某些例程更有效,例如,當需要重復引用大型表或常用表中的某個數據集時。但是,對于一次性事件,最好使用導出表。

          ?使用表值 UDF 時要小心,因為在變量(而不是常量)中傳遞某個參數時,如果在 WHERE 子句中使用該參數,會導致表掃描。還要避免在一個查詢中多次使用相同的表值 UDF。但是,表值 UDF 確實具有某些非常方便的動態編譯功能。[相關資料:參閱 Tom Moreau 2003 11 月份生成序列號專欄中的使用 UDF 填充表變量。-編者按]

          ?幾乎所有的存儲過程都應在開始時設置 SET NOCOUNT ON,而在結束時設置 SET NOCOUNT OFF。[SET NOCOUNT ON 使 SQL Server 無需在執行存儲過程的每個語句后向客戶端發送 DONE_IN_PROC 消息。- 編者按] 此標準同樣適用于觸發器。

          ?只要在例程中使用多個數據庫修改語句,包括在一個循環中多次執行一個語句,就應考慮聲明顯式事務。

          ?使用基于光標的方法或臨時表方法之前,應先尋找基于集的解決方案來解決問題。基于集的方法通常更有效。

          ?與臨時表一樣,光標并不是不可使用。對小型數據集使用 FAST_FORWARD 光標通常要優于其他逐行處理方法,尤其是在必須引用幾個表才能獲得所需的數據時。在結果集中包括“合計”的例程通常要比使用光標執行的速度快。如果開發時間允許,基于光標的方法和基于集的方法都可以嘗試一下,看哪一種方法的效果更好。

          ?使用包含序號(從 1 到 N)的表很方便。

          ?理解 CROSS JOIN 的工作原理并加以利用。例如,您可以在工作數據表和序號表之間有效地使用 CROSS JOIN,結果集中將包含每個工作數據與序號組合的記錄。

          ?我的結束語是:T-SQL 代碼往往很簡潔,因此如果某個代碼塊看起來很難處理或重復內容較多,那么可能存在一種更簡單,更好的方法。

          結論

          如果您對我的建議有任何看法,歡迎隨時向我發送電子郵件進行討論,也可以就其他問題提出您的建議。我希望您將此作為談話的開場白。

          其他信息:摘自 Karen 2000 2 月份的社論

          在標準開發的前沿陣地上,有一股以 SQL Server 數據庫管理員 John Hindmarsh 為首的獨立的新生力量。MCT、MCSE 和 MCDBA 都是最值得您花時間去研究的。John 的貢獻是撰寫了一份詳細的白皮書,概述了他對各種 SQL Server 相關標準提出的建議。我所知道的其他唯一提出類似建議的文章是 Andrew Zanevsky 的《Transact-SQL Programming》(ISBN 1-56592-401-0) 中的“Format and Style”一章。Andrew、SQL Server Professional 的投稿人 Tom Moreau 和 Paul Munkenbeck 以及 John 的朋友兼同事 Stephen James 都為 John 的白皮書做出過貢獻。下面是 John 為編寫存儲過程提供的建議示例:

          使用 SQL-92 標準連接句法。

          為了提高性能,應優先使用連接,然后使用子查詢或嵌套查詢。

          確保變量和參數的類型和大小與表數據列相匹配。

          確保使用所有變量和參數,或者全部刪除。

          盡可能將臨時對象放置在本地。

          只使用在存儲過程中創建的臨時表。

          檢查輸入參數的有效性。

          優先使用 SELECT...INTO,然后使用 INSERT...SELECT,以避免大量死鎖。

          維護工作需要的邏輯單元;在可以縮短的情況下,不要創建大量或長時間運行的進程。

          不要在任何代碼中使用 SELECT *。

          在過程中使用縮進、塊、制表符和空格(參閱示例腳本)。

          T-SQL 語句要大寫。

          在過程中添加大量注釋,確保可以識別進程。在有助于澄清處理步驟的地方使用行注釋。

          包括事務管理,除非要從 MTS 進程中調用過程。(為 MTS 進程編寫獨立的過程。)

          監視 @@TRANCOUNT 以確定事務的責任級別。

          避免使用 GOTO,錯誤處理程序中除外。

          避免使用嵌套過程。

          避免隱式解析對象名稱,確保所有對象都歸 dbo 所有。

          下載 412BRIAN.ZIP

          鏈接至www.microsoft.com/downloads/details.aspx?displayla%20ng=en&familyid=B352EB1F-D3CA-44EE-893E-9E07339C1F22&displaylang=en

          有關 SQL Server Professional 和 Pinnacle Publishing 的詳細信息,請訪問其 Web 站點 http://www.pinpub.com/

          注意:這不是 Microsoft Corporation 的 Web 站點。Microsoft 對該 Web 站點上的內容不承擔任何責任。

          本文轉載自 2004 年 12 月份的 SQL Server Professional。除非另行說明,否則版權所有 2004 Pinnacle Publishing, Inc.。保留所有權利。SQL Server Professional 是 Pinnacle Publishing 獨立發行的刊物。未經 Pinnacle Publishing, Inc. 事先同意,不得以任何方式使用或復制本文的任何部分(評論文章中的簡短引用除外)。如需與 Pinnacle Publishing, Inc. 聯系,請撥打 1-800-788-1900。

          © 2005 Microsoft Corporation 版權所有。保留所有權利。使用規定。

          原文出處:http://www.microsoft.com/china/msdn/library/data/sqlserver/sp04l9.mspx?mfr=true

          posted @ 2008-12-02 10:50 竹子 閱讀(356) | 評論 (0)編輯 收藏
            1      選擇最有效率的表名順序(只在基于規則的優化器中有效)

          ORACLE 的解析器按照從右到左的順序處理FROM子句中的表名,FROM子句中寫在最后的表(基礎表 driving table)將被最先處理,在FROM子句中包含多個表的情況下,你必須選擇記錄條數最少的表作為基礎表。如果有3個以上的表連接查詢, 那就需要選擇交叉表(intersection table)作為基礎表, 交叉表是指那個被其他表所引用的表.

          2      WHERE子句中的連接順序.:

          ORACLE采用自下而上的順序解析WHERE子句,根據這個原理,表之間的連接必須寫在其他WHERE條件之前, 那些可以過濾掉最大數量記錄的條件必須寫在WHERE子句的末尾.

          3      SELECT子句中避免使用 * ‘:

          ORACLE在解析的過程中, 會將'*' 依次轉換成所有的列名, 這個工作是通過查詢數據字典完成的, 這意味著將耗費更多的時間

          4      減少訪問數據庫的次數:

          ORACLE在內部執行了許多工作: 解析SQL語句, 估算索引的利用率, 綁定變量 , 讀數據塊等;

          5      SQL*Plus , SQL*FormsPro*C中重新設置ARRAYSIZE參數, 可以增加每次數據庫訪問的檢索數據量 ,建議值為200

          6      使用DECODE函數來減少處理時間:

          使用DECODE函數可以避免重復掃描相同記錄或重復連接相同的表.

          7      整合簡單,無關聯的數據庫訪問:

          如果你有幾個簡單的數據庫查詢語句,你可以把它們整合到一個查詢中(即使它們之間沒有關系)

          8      刪除重復記錄:

          最高效的刪除重復記錄方法 ( 因為使用了ROWID)例子:

          DELETE FROM EMP E WHERE E.ROWID > (SELECT MIN(X.ROWID)

          FROM EMP X WHERE X.EMP_NO = E.EMP_NO);

          9      TRUNCATE替代DELETE

          當刪除表中的記錄時,在通常情況下, 回滾段(rollback segments ) 用來存放可以被恢復的信息. 如果你沒有COMMIT事務,ORACLE會將數據恢復到刪除之前的狀態(準確地說是恢復到執行刪除命令之前的狀況) 而當運用TRUNCATE, 回滾段不再存放任何可被恢復的信息.當命令運行后,數據不能被恢復.因此很少的資源被調用,執行時間也會很短. (譯者按: TRUNCATE只在刪除全表適用,TRUNCATEDDL不是DML)

          10 盡量多使用COMMIT

          只要有可能,在程序中盡量多使用COMMIT, 這樣程序的性能得到提高,需求也會因為COMMIT所釋放的資源而減少:

          COMMIT所釋放的資源:

          a. 回滾段上用于恢復數據的信息.

          b. 被程序語句獲得的鎖

          c. redo log buffer 中的空間

          d. ORACLE為管理上述3種資源中的內部花費

          11 Where子句替換HAVING子句:

          避免使用HAVING子句, HAVING 只會在檢索出所有記錄之后才對結果集進行過濾. 這個處理需要排序,總計等操作. 如果能通過WHERE子句限制記錄的數目,那就能減少這方面的開銷. (oracle)onwherehaving這三個都可以加條件的子句中,on是最先執行,where次之,having最后,因為on是先把不符合條件的記錄過濾后才進行統計,它就可以減少中間運算要處理的數據,按理說應該速度是最快的,where也應該比having快點的,因為它過濾數據后才進行sum,在兩個表聯接時才用on的,所以在一個表的時候,就剩下wherehaving比較了。在這單表查詢統計的情況下,如果要過濾的條件沒有涉及到要計算字段,那它們的結果是一樣的,只是where可以使用rushmore技術,而having就不能,在速度上后者要慢如果要涉及到計算的字段,就表示在沒計算之前,這個字段的值是不確定的,根據上篇寫的工作流程,where的作用時間是在計算之前就完成的,而having就是在計算后才起作用的,所以在這種情況下,兩者的結果會不同。在多表聯接查詢時,onwhere更早起作用。系統首先根據各個表之間的聯接條件,把多個表合成一個臨時表后,再由where進行過濾,然后再計算,計算完后再由having進行過濾。由此可見,要想過濾條件起到正確的作用,首先要明白這個條件應該在什么時候起作用,然后再決定放在那里

          12 減少對表的查詢:

          在含有子查詢的SQL語句中,要特別注意減少對表的查詢.例子:

               SELECT TAB_NAME FROM TABLES WHERE (TAB_NAME,DB_VER) = ( SELECT

          TAB_NAME,DB_VER FROM TAB_COLUMNS WHERE VERSION = 604)

          13 通過內部函數提高SQL效率.

          復雜的SQL往往犧牲了執行效率. 能夠掌握上面的運用函數解決問題的方法在實際工作中是非常有意義的

          14 使用表的別名(Alias)

          當在SQL語句中連接多個表時, 請使用表的別名并把別名前綴于每個Column.這樣一來,就可以減少解析的時間并減少那些由Column歧義引起的語法錯誤.

          15 EXISTS替代IN、用NOT EXISTS替代NOT IN

          在許多基于基礎表的查詢中,為了滿足一個條件,往往需要對另一個表進行聯接.在這種情況下, 使用EXISTS(NOT EXISTS)通常將提高查詢的效率. 在子查詢中,NOT IN子句將執行一個內部的排序和合并. 無論在哪種情況下,NOT IN都是最低效的 (因為它對子查詢中的表執行了一個全表遍歷). 為了避免使用NOT IN ,我們可以把它改寫成外連接(Outer Joins)NOT EXISTS.

          例子:

          (高效)SELECT * FROM EMP (基礎表) WHERE EMPNO > 0 AND EXISTS (SELECT X' FROM DEPT WHERE DEPT.DEPTNO = EMP.DEPTNO AND LOC = MELB')

          (低效)SELECT * FROM EMP (基礎表) WHERE EMPNO > 0 AND DEPTNO IN(SELECT DEPTNO FROM DEPT WHERE LOC = MELB')

          16 識別'低效執行'SQL語句:

          雖然目前各種關于SQL優化的圖形化工具層出不窮,但是寫出自己的SQL工具來解決問題始終是一個最好的方法:

          SELECT EXECUTIONS , DISK_READS, BUFFER_GETS,

          ROUND((BUFFER_GETS-DISK_READS)/BUFFER_GETS,2) Hit_radio,

          ROUND(DISK_READS/EXECUTIONS,2) Reads_per_run,

          SQL_TEXT

          FROM V$SQLAREA

          WHERE EXECUTIONS>0

          AND BUFFER_GETS > 0

          AND (BUFFER_GETS-DISK_READS)/BUFFER_GETS < 0.8

          ORDER BY 4 DESC;

          17 用索引提高效率:

          索引是表的一個概念部分,用來提高檢索數據的效率,ORACLE使用了一個復雜的自平衡B-tree結構. 通常,通過索引查詢數據比全表掃描要快. ORACLE找出執行查詢和Update語句的最佳路徑時, ORACLE優化器將使用索引. 同樣在聯結多個表時使用索引也可以提高效率. 另一個使用索引的好處是,它提供了主鍵(primary key)的唯一性驗證.。那些LONGLONG RAW數據類型, 你可以索引幾乎所有的列. 通常, 在大型表中使用索引特別有效. 當然,你也會發現, 在掃描小表時,使用索引同樣能提高效率. 雖然使用索引能得到查詢效率的提高,但是我們也必須注意到它的代價. 索引需要空間來存儲,也需要定期維護, 每當有記錄在表中增減或索引列被修改時, 索引本身也會被修改. 這意味著每條記錄的INSERT , DELETE , UPDATE將為此多付出4 , 5 次的磁盤I/O . 因為索引需要額外的存儲空間和處理,那些不必要的索引反而會使查詢反應時間變慢.。定期的重構索引是有必要的.

          ALTER INDEX <INDEXNAME> REBUILD <TABLESPACENAME>

          18 EXISTS替換DISTINCT

          當提交一個包含一對多表信息(比如部門表和雇員表)的查詢時,避免在SELECT子句中使用DISTINCT. 一般可以考慮用EXIST替換, EXISTS 使查詢更為迅速,因為RDBMS核心模塊將在子查詢的條件一旦滿足后,立刻返回結果. 例子:

                 (低效):

          SELECT DISTINCT DEPT_NO,DEPT_NAME FROM DEPT D , EMP E

          WHERE D.DEPT_NO = E.DEPT_NO

          (高效):

          SELECT DEPT_NO,DEPT_NAME FROM DEPT D WHERE EXISTS ( SELECT ‘X'

          FROM EMP E WHERE E.DEPT_NO = D.DEPT_NO);

          19 sql語句用大寫的;因為oracle總是先解析sql語句,把小寫的字母轉換成大寫的再執行

          20 java代碼中盡量少用連接符“+”連接字符串!

          21 避免在索引列上使用NOT 通常, 

          我們要避免在索引列上使用NOT, NOT會產生在和在索引列上使用函數相同的影響. ORACLE”遇到”NOT,他就會停止使用索引轉而執行全表掃描.

          22 避免在索引列上使用計算.

          WHERE子句中,如果索引列是函數的一部分.優化器將不使用索引而使用全表掃描.

          舉例:

          低效:

          SELECT … FROM DEPT WHERE SAL * 12 > 25000;

          高效:

          SELECT … FROM DEPT WHERE SAL > 25000/12;

          23 >=替代>

          高效:

          SELECT * FROM EMP WHERE DEPTNO >=4

          低效:

          SELECT * FROM EMP WHERE DEPTNO >3

          兩者的區別在于, 前者DBMS將直接跳到第一個DEPT等于4的記錄而后者將首先定位到DEPTNO=3的記錄并且向前掃描到第一個DEPT大于3的記錄.

          24 UNION替換OR (適用于索引列)

          通常情況下, UNION替換WHERE子句中的OR將會起到較好的效果. 對索引列使用OR將造成全表掃描. 注意, 以上規則只針對多個索引列有效. 如果有column沒有被索引, 查詢效率可能會因為你沒有選擇OR而降低. 在下面的例子中, LOC_ID REGION上都建有索引.

          高效:

          SELECT LOC_ID , LOC_DESC , REGION

          FROM LOCATION

          WHERE LOC_ID = 10

          UNION

          SELECT LOC_ID , LOC_DESC , REGION

          FROM LOCATION

          WHERE REGION = “MELBOURNE”

          低效:

          SELECT LOC_ID , LOC_DESC , REGION

          FROM LOCATION

          WHERE LOC_ID = 10 OR REGION = “MELBOURNE”

          如果你堅持要用OR, 那就需要返回記錄最少的索引列寫在最前面.

          25 IN來替換OR 

          這是一條簡單易記的規則,但是實際的執行效果還須檢驗,在ORACLE8i下,兩者的執行路徑似乎是相同的. 

          低效:

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

          高效

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

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

          避免在索引中使用任何可以為空的列,ORACLE將無法使用該索引.對于單列索引,如果列包含空值,索引中將不存在此記錄. 對于復合索引,如果每個列都為空,索引中同樣不存在此記錄. 如果至少有一個列不為空,則記錄存在于索引中.舉例: 如果唯一性索引建立在表的A列和B列上, 并且表中存在一條記錄的A,B值為(123,null) , ORACLE將不接受下一條具有相同A,B值(123,null)的記錄(插入). 然而如果所有的索引列都為空,ORACLE將認為整個鍵值為空而空不等于空. 因此你可以插入1000 條具有相同鍵值的記錄,當然它們都是空! 因為空值不存在于索引列中,所以WHERE子句中對索引列進行空值比較將使ORACLE停用該索引.

          低效: (索引失效)

          SELECT … FROM DEPARTMENT WHERE DEPT_CODE IS NOT NULL;

          高效: (索引有效)

          SELECT … FROM DEPARTMENT WHERE DEPT_CODE >=0;

          27 總是使用索引的第一個列:

          如果索引是建立在多個列上, 只有在它的第一個列(leading column)where子句引用時,優化器才會選擇使用該索引. 這也是一條簡單而重要的規則,當僅引用索引的第二個列時,優化器使用了全表掃描而忽略了索引

          28 UNION-ALL 替換UNION ( 如果有可能的話)

          SQL 語句需要UNION兩個查詢結果集合時,這兩個結果集合會以UNION-ALL的方式被合并, 然后在輸出最終結果前進行排序. 如果用UNION ALL替代UNION, 這樣排序就不是必要了. 效率就會因此得到提高. 需要注意的是,UNION ALL 將重復輸出兩個結果集合中相同記錄. 因此各位還是要從業務需求分析使用UNION ALL的可行性. UNION 將對結果集合排序,這個操作會使用到SORT_AREA_SIZE這塊內存. 對于這塊內存的優化也是相當重要的. 下面的SQL可以用來查詢排序的消耗量

          低效:

          SELECT ACCT_NUM, BALANCE_AMT

          FROM DEBIT_TRANSACTIONS

          WHERE TRAN_DATE = '31-DEC-95'

          UNION

          SELECT ACCT_NUM, BALANCE_AMT

          FROM DEBIT_TRANSACTIONS

          WHERE TRAN_DATE = '31-DEC-95'

          高效:

          SELECT ACCT_NUM, BALANCE_AMT

          FROM DEBIT_TRANSACTIONS

          WHERE TRAN_DATE = '31-DEC-95'

          UNION ALL

          SELECT ACCT_NUM, BALANCE_AMT

          FROM DEBIT_TRANSACTIONS

          WHERE TRAN_DATE = '31-DEC-95'

          29 WHERE替代ORDER BY

          ORDER BY 子句只在兩種嚴格的條件下使用索引.

          ORDER BY中所有的列必須包含在相同的索引中并保持在索引中的排列順序.

          ORDER BY中所有的列必須定義為非空.

          WHERE子句使用的索引和ORDER BY子句中所使用的索引不能并列.

          例如:

          DEPT包含以下列:

          DEPT_CODE PK NOT NULL

          DEPT_DESC NOT NULL

          DEPT_TYPE NULL

          低效: (索引不被使用)

          SELECT DEPT_CODE FROM DEPT ORDER BY DEPT_TYPE

          高效: (使用索引)

          SELECT DEPT_CODE FROM DEPT WHERE DEPT_TYPE > 0

          30 避免改變索引列的類型.:

          當比較不同數據類型的數據時, ORACLE自動對列進行簡單的類型轉換.

          假設 EMPNO是一個數值類型的索引列.

          SELECT … FROM EMP WHERE EMPNO = ‘123'

          實際上,經過ORACLE類型轉換, 語句轉化為:

          SELECT … FROM EMP WHERE EMPNO = TO_NUMBER(‘123')

          幸運的是,類型轉換沒有發生在索引列上,索引的用途沒有被改變.

          現在,假設EMP_TYPE是一個字符類型的索引列.

          SELECT … FROM EMP WHERE EMP_TYPE = 123

          這個語句被ORACLE轉換為:

          SELECT … FROM EMP WHERETO_NUMBER(EMP_TYPE)=123

          因為內部發生的類型轉換, 這個索引將不會被用到! 為了避免ORACLE對你的SQL進行隱式的類型轉換, 最好把類型轉換用顯式表現出來. 注意當字符和數值比較時, ORACLE會優先轉換數值類型到字符類型

          31 需要當心的WHERE子句:

          某些SELECT 語句中的WHERE子句不使用索引. 這里有一些例子.

          在下面的例子里, (1)!=' 將不使用索引. 記住, 索引只能告訴你什么存在于表中, 而不能告訴你什么不存在于表中. (2) ||'是字符連接函數. 就象其他函數那樣, 停用了索引. (3) +'是數學函數. 就象其他數學函數那樣, 停用了索引. (4)相同的索引列不能互相比較,這將會啟用全表掃描.

          32 a. 如果檢索數據量超過30%的表中記錄數.使用索引將沒有顯著的效率提高.

          b. 在特定情況下, 使用索引也許會比全表掃描慢, 但這是同一個數量級上的區別. 而通常情況下,使用索引比全表掃描要塊幾倍乃至幾千倍!

          33 避免使用耗費資源的操作:

          帶有DISTINCT,UNION,MINUS,INTERSECT,ORDER BYSQL語句會啟動SQL引擎

          執行耗費資源的排序(SORT)功能. DISTINCT需要一次排序操作, 而其他的至少需要執行兩次排序. 通常, 帶有UNION, MINUS , INTERSECTSQL語句都可以用其他方式重寫. 如果你的數據庫的SORT_AREA_SIZE調配得好, 使用UNION , MINUS, INTERSECT也是可以考慮的, 畢竟它們的可讀性很強

          34 優化GROUP BY:

          提高GROUP BY 語句的效率, 可以通過將不需要的記錄在GROUP BY 之前過濾掉.下面兩個查詢返回相同結果但第二個明顯就快了許多.

          低效:

          SELECT JOB , AVG(SAL)

          FROM EMP

          GROUP JOB

          HAVING JOB = ‘PRESIDENT'

          OR JOB = ‘MANAGER'

          高效:

          SELECT JOB , AVG(SAL)

          FROM EMP

          WHERE JOB = ‘PRESIDENT'

          OR JOB = ‘MANAGER'

          GROUP JOB

          posted @ 2008-12-02 10:42 竹子 閱讀(208) | 評論 (0)編輯 收藏
          主站蜘蛛池模板: 华池县| 阜宁县| 柞水县| 德清县| 泰和县| 乐业县| 淮南市| 军事| 麟游县| 淳安县| 九寨沟县| 福州市| 中宁县| 万荣县| 易门县| 西藏| 长治市| 肥西县| 涟源市| 仪陇县| 泸定县| 屯昌县| 禹州市| 许昌市| 石楼县| 肇东市| 霍林郭勒市| 北票市| 临漳县| 武威市| 潼南县| 西乌珠穆沁旗| 正定县| 曲靖市| 武强县| 莱芜市| 白水县| 怀安县| 徐州市| 宿州市| 黎川县|