數據庫優化(超級詳細),轉文,值得一讀

性能調節的目的是通過將網絡流通、磁盤 I/O CPU 時間減到最小,使每個查詢的響應時間最短並最大限度地提高整個數據庫服務器的吞吐量。爲達到此目的,需要了解應用程序的需求和數據的邏輯和物理結構,並在相互衝突的數據庫使用之間(如聯機事務處理 (OLTP) 與決策支持)權衡。
對性能問題的考慮應貫穿於開發階段的全過程,不應只在最後實現系統時才考慮性能問題。許多使性能得到顯著提高的性能事宜可通過開始時仔細設計得以實現。爲最有效地優化 Microsoft® SQL Server™ 2000 的性能,必須在極爲多樣化的情形中識別出會使性能提升最多的區域,並對這些區域集中分析。
雖然其它系統級性能問題(如內存、硬件等)也是研究對象,但經驗表明從這些方面獲得的性能收益通常會增長。通常情況下,SQL Server 自動管理可用的硬件資源,從而減少對大量的系統級手動調節任務的需求(以及從中所得的收益)。
目錄:
操作系統相關優化:描述操作系統和數據庫之間可改善的方面…………………………………………………7
設計聯合數據庫服務器
爲達到大型 Web 站點所需的高性能級別,多層系統一般在多個服務器之間平衡每一層的處理負荷。Microsoft® SQL Server™ 2000 通過對 SQL Server 數據進行水平分區,在一組服務器之間分攤數據庫處理負荷。這些服務器相互獨立,但也可以相互協作以處理來自應用程序的數據庫請求;這樣的一組協作服務器稱爲聯合體。
只有當應用程序將每個 SQL 語句發送到擁有該語句所需的大部分數據的成員服務器時,聯合數據庫層纔可以達到非常高的性能級別。這稱爲使用語句所需的數據配置 SQL 語句。使用所需的數據配置 SQL 語句不是聯合服務器所獨有的要求;在羣集系統中同樣有此要求。
雖然服務器聯合體與單個數據庫服務器呈現給應用程序的圖像相同,但在實現數據庫服務層的方式上存在內部差異。
單個服務器層
聯合服務器層
生產服務器上有一個 SQL Server 實例。
每個成員服務器上都有一個 SQL Server 實例。
生產數據存儲在一個數據庫中。
每個成員服務器都有一個成員數據庫。數據分佈在成員數據庫之間。
一般每個表都是單個實體。
原始數據庫中的表被水平分區爲成員表。一個成員數據庫有一個成員表,而且使用分佈式分區視圖使每個成員服務器上看起來似乎都有原始表的完整複本。
與單個服務器的所有連接和所有 SQL 語句都由 SQL Server 的同一個實例處理。
應用程序層必須能夠在包含語句所引用的大部分數據的成員服務器上配置 SQL 語句。
雖然目的是設計數據庫服務器聯合體來處理全部的工作負荷,但是可通過設計一組在不同的服務器之間分佈數據的分佈式分區視圖來達到此目的。
數據庫設計
數據庫的設計包括兩個組成部分:邏輯設計和物理設計。邏輯數據庫設計包括使用數據庫組件(如表和約束)爲業務需求和數據建模,而無須考慮如何或在哪裏物理存儲這些數據。物理數據庫設計包括將邏輯設計映射到物理媒體上、利用可用的硬件和軟件功能使得儘可能快地對數據進行物理訪問和維護,還包括生成索引。要在設計後更改這些組件很困難,因此在數據庫應用程序開發的早期階段正確設計數據庫、使其爲業務需求建模並利用硬件和軟件功能很重要。
實現SQL Server數據庫的優化,首先要有一個好的數據庫設計方案。在實際工作中,許多SQL Server方案往往是由於數據庫設計得不好導致性能很差。實現良好的數據庫設計必須考慮這些問題:
1.1 邏輯庫規範化問題
一般來說,邏輯數據庫設計會滿足規範化的前3級標準:
1.1規範:沒有重複的組或多值的列。
2.2規範:每個非關鍵字段必須依賴於主關鍵字,不能依賴於1個組合式主關鍵字的某些組成部分。
3.3規範:1個非關鍵字段不能依賴於另1個非關鍵字段。
  遵守這些規則的設計會產生較少的列和更多的表,因而也就減少了數據冗餘,也減少了用於存儲數據的頁。但表關係也許需要通過複雜的合併來處理,這樣會降低系統的性能。某種程度上的非規範化可以改善系統的性能,非規範化過程可以根據性能方面不同的考慮用多種不同的方法進行,但以下方法經實踐驗證往往能提高性能。
1.如果規範化設計產生了許多4路或更多路合併關係,就可以考慮在數據庫實體()中加入重複屬性()
2.常用的計算字段(如總計、最大值等)可以考慮存儲到數據庫實體中。
  比如某一個項目的計劃管理系統中有計劃表,其字段爲:項目編號、年初計劃、二次計劃、調整計劃、補列計劃,而計劃總數(年初計劃+二次計劃+調整計劃+補列計劃)是用戶經常需要在查詢和報表中用到的,在表的記錄量很大時,有必要把計劃總數作爲1個獨立的字段加入到表中。這裏可以採用觸發器以在客戶端保持數據的一致性。
3.重新定義實體以減少外部屬性數據或行數據的開支。相應的非規範化類型是:
  (1)1個實體()分割成2個表(把所有的屬性分成2)。這樣就把頻繁被訪問的數據同較少被訪問的數據分開了。這種方法要求在每個表中複製首要關鍵字。這樣產生的設計有利於並行處理,並將產生列數較少的表。
  (2)1個實體()分割成2個表(把所有的行分成2)。這種方法適用於那些將包含大量數據的實體()。在應用中常要保留歷史記錄,但是歷史記錄很少用到。因此可以把頻繁被訪問的數據同較少被訪問的歷史數據分開。而且如果數據行是作爲子集被邏輯工作組(部門、銷售分區、地理區域等)訪問的,那麼這種方法也是很有好處的。
 1.2 生成物理數據庫
  要想正確選擇基本物理實現策略,必須懂得數據庫訪問格式和硬件資源的操作特點,主要是內存和磁盤子系統I/O。這是一個範圍廣泛的話題,但以下的準則可能會有所幫助。
  1.與每個表列相關的數據類型應該反映數據所需的最小存儲空間,特別是對於被索引的列更是如此。比如能使用smallint類型就不要用integer類型,這樣索引字段可以被更快地讀取,而且可以在1個數據頁上放置更多的數據行,因而也就減少了I/O操作。
  2.1個表放在某個物理設備上,再通過SQL Server段把它的不分簇索引放在1個不同的物理設備上,這樣能提高性能。尤其是系統採用了多個智能型磁盤控制器和數據分離技術的情況下,這樣做的好處更加明顯。
  3.SQL Server段把一個頻繁使用的大表分割開,並放在2個單獨的智能型磁盤控制器的數據庫設備上,這樣也可以提高性能。因爲有多個磁頭在查找,所以數據分離也能提高性能。
  4.SQL Server段把文本或圖像列的數據存放在1個單獨的物理設備上可以提高性能。1個專用的智能型的控制器能進一步提高性能。
查詢優化
查詢速度慢的原因很多,常見如下幾種:  
  1、沒有索引或者沒有用到索引(這是查詢慢最常見的問題,是程序設計的缺陷)  
  2I/O吞吐量小,形成了瓶頸效應。  
  3、沒有創建計算列導致查詢不優化。  
  4、內存不足  
  5、網絡速度慢  
  6、查詢出的數據量過大(可以採用多次查詢,其他的方法降低數據量)  
  7、鎖或者死鎖(這也是查詢慢最常見的問題,是程序設計的缺陷)  
  8sp_lock,sp_who,活動的用戶查看,原因是讀寫競爭資源。  
  9、返回了不必要的行和列  
10、查詢語句不好,沒有優化
可以通過如下方法來優化查詢 :  
  1、把數據、日誌、索引放到不同的I/O設備上,增加讀取速度,以前可以將Tempdb應放在RAID0上,SQL2000不在支持。數據量(尺寸)越大,提高I/O越重要.  
  2、縱向、橫向分割表,減少表的尺寸(sp_spaceuse)  
  3、升級硬件  
  4、根據查詢條件,建立索引,優化索引、優化訪問方式,限制結果集的數據量。注意填充因子要適當(最好是使用默認值0)。索引應該儘量小,使用字節數小的列建索引好(參照索引的創建),不要對有限的幾個值的字段建單一索引如性別字段  
  5、提高網速;  
  6、擴大服務器的內存,Windows 2000SQL server 2000能支持4-8G的內存。配置虛擬內存:虛擬內存大小應基於計算機上併發運行的服務進行配置。運行 Microsoft SQL Server? 2000 時,可考慮將虛擬內存大小設置爲計算機中安裝的物理內存的 1.5 倍。如果另外安裝了全文檢索功能,並打算運行 Microsoft 搜索服務以便執行全文索引和查詢,可考慮:將虛擬內存大小配置爲至少是計算機中安裝的物理內存的 3 倍。將 SQL Server max server memory 服務器配置選項配置爲物理內存的 1.5 倍(虛擬內存大小設置的一半)。  
  7、增加服務器 CPU個數;但是必須明白並行處理串行處理更需要資源例如內存。使用並行還是串行程是MsSQL自動評估選擇的。單個任務分解成多個任務,就可以在處理器上運行。例如耽擱查詢的排序、連接、掃描和GROUP BY字句同時執行,SQL SERVER根據系統的負載情況決定最優的並行等級,複雜的需要消耗大量的CPU的查詢最適合並行處理。但是更新操作Update,Insert Delete還不能並行處理。  
  8、如果是使用like進行查詢的話,簡單的使用index是不行的,但是全文索引,耗空間。 like 'a%' 使用索引 like '%a' 不使用索引用 like '%a%' 查詢時,查詢耗時和字段值總長度成正比,所以不能用CHAR類型,而是VARCHAR。對於字段的值很長的建全文索引。  
  9DB Server APPLication Server 分離;OLTPOLAP分離  
  10、分佈式分區視圖可用於實現數據庫服務器聯合體。聯合體是一組分開管理的服務器,但它們相互協作分擔系統的處理負荷。這種通過分區數據形成數據庫服務器聯合體的機制能夠擴大一組服務器,以支持大型的多層 Web 站點的處理需要。有關更多信息,參見設計聯合數據庫服務器。(參照SQL幫助文件'分區視圖')  
  a、在實現分區視圖之前,必須先水平分區表  
  b、在創建成員表後,在每個成員服務器上定義一個分佈式分區視圖,並且每個視圖具有相同的名稱。這樣,引用分佈式分區視圖名的查詢可以在任何一個成員服務器上運行。系統操作如同每個成員服務器上都有一個原始表的複本一樣,但其實每個服務器上只有一個成員表和一個分佈式分區視圖。數據的位置對應用程序是透明的。  
  11、重建索引 DBCC REINDEX ,DBCC INDEXDEFRAG,收縮數據和日誌 DBCC SHRINKDB,DBCC SHRINKFILE. 設置自動收縮日誌.對於大的數據庫不要設置數據庫自動增長,它會降低服務器的性能。在T-sql的寫法上有很大的講究,下面列出常見的要點:首先,DBMS處理查詢計劃的過程是這樣的:  
   1查詢語句的詞法、語法檢查  
   2將語句提交給DBMS的查詢優化器  
   3優化器做代數優化和存取路徑的優化  
   4由預編譯模塊生成查詢規劃  
   5然後在合適的時間提交給系統處理執行  
   6最後將執行結果返回給用戶其次,看一下SQL SERVER的數據存放的結構:一個頁面的大小爲8K(8060)字節,8個頁面爲一個盤區,按照B樹存放。  
  12Commitrollback的區別 Rollback:回滾所有的事物。 Commit:提交當前的事物. 沒有必要在動態SQL裏寫事物,如果要寫請寫在外面如: begin tran exec(@s) commit trans 或者將動態SQL 寫成函數或者存儲過程。  
  13、在查詢Select語句中用Where字句限制返回的行數,避免表掃描,如果返回不必要的數據,浪費了服務器的I/O資源,加重了網絡的負擔降低性能。如果表很大,在表掃描的期間將表鎖住,禁止其他的聯接訪問表,後果嚴重。  
  14SQL的註釋申明對執行沒有任何影響
  15、儘可能不使用光標,它佔用大量的資源。如果需要row-by-row地執行,儘量採用非光標技術,如:在客戶端循環,用臨時表,Table變量,用子查詢,用Case語句等等。遊標可以按照它所支持的提取選項進行分類:只進必須按照從第一行到最後一行的順序提取行。FETCH NEXT 是唯一允許的提取操作,也是默認方式。可滾動性可以在遊標中任何地方隨機提取任意行。遊標的技術在SQL2000下變得功能很強大,他的目的是支持循環。有四個併發選項 READ_ONLY:不允許通過遊標定位更新(Update),且在組成結果集的行中沒有鎖。 OPTIMISTIC WITH valueS:樂觀併發控制是事務控制理論的一個標準部分。樂觀併發控制用於這樣的情形,即在打開遊標及更新行的間隔中,只有很小的機會讓第二個用戶更新某一行。當某個遊標以此選項打開時,沒有鎖控制其中的行,這將有助於最大化其處理能力。如果用戶試圖修改某一行,則此行的當前值會與最後一次提取此行時獲取的值進行比較。如果任何值發生改變,則服務器就會知道其他人已更新了此行,並會返回一個錯誤。如果值是一樣的,服務器就執行修改。選擇這個併發選項OPTIMISTIC WITH ROW VERSIONING:此樂觀併發控制選項基於行版本控制。使用行版本控制,其中的表必須具有某種版本標識符,服務器可用它來確定該行在讀入遊標後是否有所更改。在 SQL Server 中,這個性能由 timestamp 數據類型提供,它是一個二進制數字,表示數據庫中更改的相對順序。每個數據庫都有一個全局當前時間戳值:@@DBTS。每次以任何方式更改帶有 timestamp 列的行時,SQL Server 先在時間戳列中存儲當前的 @@DBTS 值,然後增加 @@DBTS 的值。如果某個表具有 timestamp 列,則時間戳會被記到行級。服務器就可以比較某行的當前時間戳值和上次提取時所存儲的時間戳值,從而確定該行是否已更新。服務器不必比較所有列的值,只需比較 timestamp 列即可。如果應用程序對沒有 timestamp 列的表要求基於行版本控制的樂觀併發,則遊標默認爲基於數值的樂觀併發控制。 SCROLL LOCKS 這個選項實現悲觀併發控制。在悲觀併發控制中,在把數據庫的行讀入遊標結果集時,應用程序將試圖鎖定數據庫行。在使用服務器遊標時,將行讀入遊標時會在其上放置一個更新鎖。如果在事務內打開遊標,則該事務更新鎖將一直保持到事務被提交或回滾;當提取下一行時,將除去遊標鎖。如果在事務外打開遊標,則提取下一行時,鎖就被丟棄。因此,每當用戶需要完全的悲觀併發控制時,遊標都應在事務內打開。更新鎖將阻止任何其它任務獲取更新鎖或排它鎖,從而阻止其它任務更新該行。然而,更新鎖並不阻止共享鎖,所以它不會阻止其它任務讀取行,除非第二個任務也在要求帶更新鎖的讀取。滾動鎖根據在遊標定義的 Select 語句中指定的鎖提示,這些遊標併發選項可以生成滾動鎖。滾動鎖在提取時在每行上獲取,並保持到下次提取或者遊標關閉,以先發生者爲準。下次提取時,服務器爲新提取中的行獲取滾動鎖,並釋放上次提取中行的滾動鎖。滾動鎖獨立於事務鎖,並可以保持到一個提交或回滾操作之後。如果提交時關閉遊標的選項爲關,則 COMMIT 語句並不關閉任何打開的遊標,而且滾動鎖被保留到提交之後,以維護對所提取數據的隔離。所獲取滾動鎖的類型取決於遊標併發選項和遊標 Select 語句中的鎖提示。鎖提示只讀樂觀數值樂觀行版本控制鎖定無提示未鎖定未鎖定未鎖定更新 NOLOCK 未鎖定未鎖定未鎖定未鎖定 HOLDLOCK 共享共享共享更新 UPDLOCK 錯誤更新更新更新 TABLOCKX 錯誤未鎖定未鎖定更新其它未鎖定未鎖定未鎖定更新 *指定 NOLOCK 提示將使指定了該提示的表在遊標內是隻讀的。  
  16、用Profiler來跟蹤查詢,得到查詢所需的時間,找出SQL的問題所在;用索引優化器優化索引  
  17、注意UNionUNion all 的區別。UNION all好  
  18、注意使用DISTINCT,在沒有必要時不要用,它同UNION一樣會使查詢變慢。重複的記錄在查詢裏是沒有問題的  
  19、查詢時不要返回不需要的行、列  
  20、用sp_configure 'query governor cost limit'或者SET QUERY_GOVERNOR_COST_LIMIT來限制查詢消耗的資源。當評估查詢消耗的資源超出限制時,服務器自動取消查詢,在查詢之前就扼殺掉。 SET LOCKTIME設置鎖的時間  
  21、用select top 100 / 10 Percent 來限制用戶返回的行數或者SET ROWCOUNT來限制操作的行  
  22、在SQL2000以前,一般不要用如下的字句: "IS NULL", "<>", "!=", "!>", "!<", "NOT", "NOT EXISTS", "NOT IN", "NOT LIKE", and "LIKE '%500'",因爲他們不走索引全是表掃描。也不要在Where字句中的列名加函數,如Convertsubstring,如果必須用函數的時候,創建計算列再創建索引來替代.還可以變通寫法:Where SUBSTRING(firstname,1,1) = 'm'改爲Where firstname like 'm%'(索引掃描),一定要將函數和列名分開。並且索引不能建得太多和太大。NOT IN會多次掃描表,使用EXISTSNOT EXISTS IN , LEFT OUTER JOIN 來替代,特別是左連接,ExistsIN更快,最慢的是NOT操作.如果列的值含有空,以前它的索引不起作用,現在2000的優化器能夠處理了。相同的是IS NULL"NOT", "NOT EXISTS", "NOT IN"能優化她,而"<>"等還是不能優化,用不到索引。  
  23、使用Query Analyzer,查看SQL語句的查詢計劃和評估分析是否是優化的SQL。一般的20%的代碼佔據了80%的資源,我們優化的重點是這些慢的地方。  
  24、如果使用了IN或者OR等時發現查詢沒有走索引,使用顯示申明指定索引: Select * FROM PersonMember (INDEX = IX_Title) Where processid IN ('''')  
  25、將需要查詢的結果預先計算好放在表中,查詢的時候再Select。這在SQL7.0以前是最重要的手段。例如醫院的住院費計算。  
  26MIN() MAX()能使用到合適的索引。  
  27、數據庫有一個原則是代碼離數據越近越好,所以優先選擇Default,依次爲Rules,Triggers, Constraint(約束如外健主健CheckUNIQUE……,數據類型的最大長度等等都是約束),Procedure.這樣不僅維護工作小,編寫程序質量高,並且執行的速度快。  
  28、如果要插入大的二進制值到Image列,使用存儲過程,千萬不要用內嵌Insert來插入(不知JAVA是否)。因爲這樣應用程序首先將二進制值轉換成字符串(尺寸是它的兩倍),服務器受到字符後又將他轉換成二進制值.存儲過程就沒有這些動作: 方法:Create procedure p_insert as insert into table(Fimage) values (@image), 在前臺調用這個存儲過程傳入二進制參數,這樣處理速度明顯改善。  
  29Between在某些時候比IN 速度更快,Between能夠更快地根據索引找到範圍。用查詢優化器可見到差別。 select * from chineseresume where title in ('','') Select * from chineseresume where between '' and '' 是一樣的。由於in會在比較多次,所以有時會慢些。  
  30、在必要是對全局或者局部臨時表創建索引,有時能夠提高速度,但不是一定會這樣,因爲索引也耗費大量的資源。他的創建同是實際表一樣。  
  31、不要建沒有作用的事物例如產生報表時,浪費資源。只有在必要使用事物時使用它。  
  32、用OR的字句可以分解成多個查詢,並且通過UNION 連接多個查詢。他們的速度只同是否使用索引有關,如果查詢需要用到聯合索引,用UNION all執行的效率更高.多個OR的字句沒有用到索引,改寫成UNION的形式再試圖與索引匹配。一個關鍵的問題是否用到索引。  
   33、儘量少用視圖,它的效率低。對視圖操作比直接對錶操作慢,可以用stored procedure來代替她。特別的是不要用視圖嵌套,嵌套視圖增加了尋找原始資料的難度。我們看視圖的本質:它是存放在服務器上的被優化好了的已經產生了查詢規劃的SQL。對單個表檢索數據時,不要使用指向多個表的視圖,直接從表檢索或者僅僅包含這個表的視圖上讀,否則增加了不必要的開銷,查詢受到干擾.爲了加快視圖的查詢,MsSQL增加了視圖索引的功能。  
  34、沒有必要時不要用DISTINCTORDER BY,這些動作可以改在客戶端執行。它們增加了額外的開銷。這同UNION UNION ALL一樣的道理。   
  35、在IN後面值的列表中,將出現最頻繁的值放在最前面,出現得最少的放在最後面,減少判斷的次數。  
  36、當用Select INTO時,它會鎖住系統表(sysobjectssysindexes等等),阻塞其他的連接的存取。創建臨時表時用顯示申明語句,而不是 select INTO. drop table t_lxh begin tran select * into t_lxh from chineseresume where name = 'XYZ' --commit 在另一個連接中Select * from sysobjects可以看到 Select INTO 會鎖住系統表,Create table 也會鎖系統表(不管是臨時表還是系統表)。所以千萬不要在事物內使用它!!!這樣的話如果是經常要用的臨時表請使用實表,或者臨時表變量。  
  37、一般在GROUP BY HAVING字句之前就能剔除多餘的行,所以儘量不要用它們來做剔除行的工作。他們的執行順序應該如下最優:select Where字句選擇所有合適的行,Group By用來分組個統計行,Having字句用來剔除多餘的分組。這樣Group By Having的開銷小,查詢快.對於大的數據行進行分組和Having十分消耗資源。如果Group BY的目的不包括計算,只是分組,那麼用Distinct更快  
  38、一次更新多條記錄比分多次更新每次一條快,就是說批處理好  
  39、少用臨時表,儘量用結果集和Table類性的變量來代替它,Table 類型的變量比臨時表好  
  40、在SQL2000下,計算字段是可以索引的,需要滿足的條件如下:  
  a、計算字段的表達是確定的  
  b、不能用在TEXT,NtextImage數據類型  
  c、必須配製如下選項 ANSI_NULLS = ON, ANSI_PADDINGS = ON, …….  
  41、儘量將數據的處理工作放在服務器上,減少網絡的開銷,如使用存儲過程。存儲過程是編譯好、優化過、並且被組織到一個執行規劃裏、且存儲在數據庫中的SQL語句,是控制流語言的集合,速度當然快。反覆執行的動態SQL,可以使用臨時存儲過程,該過程(臨時表)被放在Tempdb中。以前由於SQL SERVER對複雜的數學計算不支持,所以不得不將這個工作放在其他的層上而增加網絡的開銷。SQL2000支持UDFs,現在支持複雜的數學計算,函數的返回值不要太大,這樣的開銷很大。用戶自定義函數象光標一樣執行的消耗大量的資源,如果返回大的結果採用存儲過程  
  42、不要在一句話裏再三的使用相同的函數,浪費資源,將結果放在變量裏再調用更快  
  43Select COUNT(*)的效率教低,儘量變通他的寫法,而EXISTS.同時請注意區別: select count(Field of null) from Table select count(Field of NOT null) from Table 的返回值是不同的!!!  
  44、當服務器的內存夠多時,配製線程數量 = 最大連接數+5,這樣能發揮最大的效率;否則使用配製線程數量<最大連接數啓用SQL SERVER的線程池來解決,如果還是數量 = 最大連接數+5,嚴重的損害服務器的性能。  
  45、按照一定的次序來訪問你的表。如果你先鎖住表A,再鎖住表B,那麼在所有的存儲過程中都要按照這個順序來鎖定它們。如果你(不經意的)某個存儲過程中先鎖定表B,再鎖定表A,這可能就會導致一個死鎖。如果鎖定順序沒有被預先詳細的設計好,死鎖很難被發現  
  46、通過SQL Server Performance Monitor監視相應硬件的負載 Memory: Page Faults / sec計數器如果該值偶爾走高,表明當時有線程競爭內存。如果持續很高,則內存可能是瓶頸。
  Process:  
  1% DPC Time 指在範例間隔期間處理器用在緩延程序調用(DPC)接收和提供服務的百分比。(DPC 正在運行的爲比標準間隔優先權低的間隔)由於 DPC 是以特權模式執行的,DPC 時間的百分比爲特權時間百分比的一部分。這些時間單獨計算並且不屬於間隔計算總數的一部分。這個總數顯示了作爲實例時間百分比的平均忙時。  
  2%Processor Time計數器 如果該參數值持續超過95%,表明瓶頸是CPU。可以考慮增加一個處理器或換一個更快的處理器。  
  3% Privileged Time 指非閒置處理器時間用於特權模式的百分比。(特權模式是爲操作系統組件和操縱硬件驅動程序而設計的一種處理模式。它允許直接訪問硬件和所有內存。另一種模式爲用戶模式,它是一種爲應用程序、環境分系統和整數分系統設計的一種有限處理模式。操作系統將應用程序線程轉換成特權模式以訪問操作系統服務)。特權時間的 % 包括爲間斷和 DPC 提供服務的時間。特權時間比率高可能是由於失敗設備產生的大數量的間隔而引起的。這個計數器將平均忙時作爲樣本時間的一部分顯示。  
  4% User Time表示耗費CPU的數據庫操作,如排序,執行aggregate functions等。如果該值很高,可考慮增加索引,儘量使用簡單的表聯接,水平分割大表格等方法來降低該值。 Physical Disk: Curretn Disk Queue Length計數器該值應不超過磁盤數的1.5~2倍。要提高性能,可增加磁盤。 SQLServer:Cache Hit Ratio計數器該值越高越好。如果持續低於80%,應考慮增加內存。注意該參數值是從SQL Server啓動後,就一直累加記數,所以運行經過一段時間後,該值將不能反映系統當前值。  
  47、分析select emp_name form employee where salary > 3000 在此語句中若salaryFloat類型的,則優化器對其進行優化爲Convert(float,3000),因爲3000是個整數,我們應在編程時使用3000.0而不要等運行時讓DBMS進行轉化。同樣字符和整型數據的轉換。  
  48、查詢的關聯同寫的順序  
  select a.personMemberID, * from chineseresume a,personmember b where personMemberID = b.referenceid and a.personMemberID = 'JCNPRH39681' A = B ,B = '號碼')  
  select a.personMemberID, * from chineseresume a,personmember b where a.personMemberID = b.referenceid and a.personMemberID = 'JCNPRH39681' and b.referenceid = 'JCNPRH39681' A = B ,B = '號碼' A = '號碼')  
  select a.personMemberID, * from chineseresume a,personmember b where b.referenceid = 'JCNPRH39681' and a.personMemberID = 'JCNPRH39681' B = '號碼' A = '號碼')  
  49、  
  (1)IF 沒有輸入負責人代碼 THEN code1=0 code2=9999 ELSE code1=code2=負責人代碼 END IF 執行SQL語句爲: Select 負責人名 FROM P2000 Where 負責人代碼>=:code1 AND負責人代碼 <=:code2  
  (2)IF 沒有輸入負責人代碼 THEN  Select 負責人名 FROM P2000 ELSE code= 負責人代碼 Select 負責人代碼 FROM P2000 Where 負責人代碼=:code END IF 第一種方法只用了一條SQL語句,第二種方法用了兩條SQL語句。在沒有輸入負責人代碼時,第二種方法顯然比第一種方法執行效率高,因爲它沒有限制條件; 在輸入了負責人代碼時,第二種方法仍然比第一種方法效率高,不僅是少了一個限制條件,還因相等運算是最快的查詢運算。我們寫程序不要怕麻煩  
  50、關於JOBCN現在查詢分頁的新方法(如下),用性能優化器分析性能的瓶頸,如果在I/O或者網絡的速度上,如下的方法優化切實有效,如果在CPU或者內存上,用現在的方法更好。請區分如下的方法,說明索引越小越好。  
  begin  
  DECLARE @local_variable table (FID int identity(1,1),ReferenceID varchar(20))  
  insert into @local_variable (ReferenceID)  
  select top 100000 ReferenceID from chineseresume order by ReferenceID  
  select * from @local_variable where Fid > 40 and fid <= 60  
  end
  
  begin  
  DECLARE @local_variable table (FID int identity(1,1),ReferenceID varchar(20))  
  insert into @local_variable (ReferenceID)  
  select top 100000 ReferenceID from chineseresume order by updatedate  
  select * from @local_variable where Fid > 40 and fid <= 60  
  end 的不同
  
  begin  
  create table #temp (FID int identity(1,1),ReferenceID varchar(20))  
  insert into #temp (ReferenceID)  
  select top 100000 ReferenceID from chineseresume order by updatedate  
  select * from #temp where Fid > 40 and fid <= 60 drop table #temp  
  end
完全通過系統級服務器性能優化(如內存大小、文件系統類型、處理器的數目及類型等)解決性能問題可能很誘人。但經驗表明大多數性能問題不能用這種方法解決。必須通過這些方法解決性能問題:分析應用程序以及應用程序提交給數據庫的查詢和更新,並分析這些查詢和更新如何與數據庫架構交互。
持續時間意外地長的查詢和更新可能由下列原因引起:
·                     網絡通訊速度慢。
·                     服務器計算機的內存不足或 Microsoft® SQL Server™ 2000 可用的內存不足。
·                     缺少有用的統計數據。
·                     統計數據過期。
·                     缺少有用的索引
·                     缺少有用的數據條帶化。
當查詢或更新花費的時間比預期的長時,使用下面的檢查清單提高性能
說明  建議在與技術支持提供商聯繫之前先參考該檢查清單。
1.              性能問題與查詢以外的組件是否有關?例如,問題是否爲網絡性能慢?是否有任何其它可能引起或間接導致性能下降的組件?可以使用 Windows NT 性能監視器監視與 SQL Server 相關和與 SQL Server 不相關的組件性能。有關更多信息,請參見使用系統監視器進行監視。
2.              如果性能問題與查詢相關,涉及哪個查詢或哪組查詢?使用 SQL 事件探查器幫助識別慢速查詢。有關更多信息,請參見使用 SQL 事件探查器進行監視。
通過使用 SET 語句啓用 SHOWPLANSTATISTICS IOSTATISTICS TIME STATISTICS PROFILE 選項,可以確定數據庫查詢性能。
·                             SHOWPLAN 描述 SQL Server 查詢優化器選擇的數據檢索方法。有關更多信息,請參見 SET SHOWPLAN_ALL
·                             STATISTICS IO 報告與語句內引用的每個表的掃描數、邏輯讀取數(在高速緩存中訪問的頁數)和物理讀取數(訪問磁盤的次數)有關的信息。有關更多信息,請參見 SET STATISTICS IO
·                             STATISTICS TIME 顯示分析、編譯和執行查詢所需的時間(以毫秒爲單位)。有關更多信息,請參見 SET STATISTICS TIME
·                             STATISTICS PROFILE 顯示每個查詢執行後的結果集,代表查詢執行的配置文件。有關更多信息,請參見 SET STATISTICS PROFILE
SQL 查詢分析器中,還可以打開 graphical execution plan 選項查看關於 SQL Server 如何檢索數據的圖形表示。
由這些工具收集的信息使您得以確定 SQL Server 查詢優化器正在如何執行查詢以及正在使用哪些索引。利用這些信息,可以確定通過重寫查詢、更改表上的索引或修改數據庫設計等方法能否提高性能。有關更多信息,請參見分析查詢。
3.              是否已經用有用的統計數據優化查詢?
SQL Server 自動在索引列上創建對列內的值分佈情況的統計。也可以使用 SQL 查詢分析器或 CREATE STATISTICS 語句在非索引列上手動創建統計;或者如果將 auto create statistics 數據庫選項設置爲 true,則自動在非索引列上創建統計。查詢處理器可以利用這些統計確定最佳的查詢評估策略。在聯接操作所涉及的非索引列上維護附加的統計信息可以提高查詢性能。有關更多信息,請參見統計信息。
使用 SQL 事件探查器或 SQL 查詢分析器內的圖形執行計劃來監視查詢,以確定查詢是否有足夠的統計信息。有關更多信息,請參見錯誤和警告事件分類。
4.              查詢統計信息是否爲最新?統計信息是否自動更新?
SQL Server 自動在索引列上創建並更新查詢統計(只要沒有禁用自動查詢統計更新特性)。另外,可以使用 SQL 查詢分析器或 UPDATE STATISTICS 語句在非索引列上手工更新統計;或者如果 auto update statistics 數據庫選項設置爲 true,則自動在非索引列上更新統計。最新的統計不取決於日期或時間數據。如果尚未進行 UPDATE 操作,則查詢統計信息仍是最新的。
如果沒有將統計設置爲自動更新,則應設置爲自動更新。有關更多信息,請參見統計信息
5.              是否有合適的索引?添加一個或多個索引是否會提高查詢性能?有關更多信息,請參見索引優化建議
6.              是否有任何數據熱點或索引熱點?如果有,考慮使用磁盤條帶化。有關更多信息,請參見使用文件組放置數據 RAID
7.              是否爲查詢優化器提供了優化複雜查詢的最有利條件?有關更多信息,請參見查詢優化建議
存儲過程的優化:
一、前言:在經過一段時間的存儲過程開發之後,寫下了一些開發時候的小結和經驗與大家共享,希望對大家有益,主要是針對SybaseSQL Server數據庫,但其它數據庫應該有一些共性。
二、適合讀者對象:數據庫開發程序員,數據庫的數據量很多,涉及到對SP(存儲過程)的優化的項目開發人員,對數據庫有濃厚興趣的人。
三、介紹:在數據庫的開發過程中,經常會遇到複雜的業務邏輯和對數據庫的操作,這個時候就會用SP來封裝數據庫操作。如果項目的SP較多,書寫又沒有一定的規範,將會影響以後的系統維護困難和大SP邏輯的難以理解,另外如果數據庫的數據量大或者項目對SP的性能要求很,就會遇到優化的問題,否則速度有可能很慢,經過親身經驗,一個經過優化過的SP要比一個性能差的SP的效率甚至高几百倍。
四、內容:
1、開發人員如果用到其他庫的TableView,務必在當前庫中建立View來實現跨庫操作,最好不要直接使用“databse.dbo.table_name”,因爲sp_depends不能顯示出該SP所使用的跨庫tableview,不方便校驗。
2、開發人員在提交SP前,必須已經使用set showplan on分析過查詢計劃,做過自身的查詢優化檢查。
3、高程序運行效率,優化應用程序,在SP編寫過程中應該注意以下幾點:
a) SQL的使用規範:
i. 儘量避免大事務操作,慎用holdlock子句,提高系統併發能力。
ii. 儘量避免反覆訪問同一張或幾張表,尤其是數據量較大的表,可以考慮先根據條件提取數據到臨時表中,然後再做連接。
iii.儘量避免使用遊標,因爲遊標的效率較差,如果遊標操作的數據超過1萬行,那麼就應該改寫;如果使用了遊標,就要儘量避免在遊標循環中再進行表連接的操作。
iv. 注意where字句寫法,必須考慮語句順序,應該根據索引順序、範圍大小來確定條件子句的前後順序,儘可能的讓字段順序與索引順序相一致,範圍從大到小。
v. 不要在where子句中的“=”左邊進行函數、算術運算或其他表達式運算,否則系統將可能無法正確使用索引。
vi. 儘量使用exists代替select count(1)來判斷是否存在記錄,count函數只有在統計表中所有行數時使用,而且count(1)count(*)更有效率。
vii.儘量使用“>=”,不要使用“>”。
viii.注意一些or子句和union子句之間的替換
ix.注意表之間連接的數據類型,避免不同類型數據之間的連接。
x. 注意存儲過程中參數和數據類型的關係。
xi.注意insertupdate操作的數據量,防止與其他應用衝突。如果數據量超過200個數據頁面(400k),那麼系統將會進行鎖升級,頁級鎖會升級成表級鎖。
b) 索引的使用規範:
i. 索引的創建要與應用結合考慮,建議大的OLTP表不要超過6個索引。
ii. 儘可能的使用索引字段作爲查詢條件,尤其是聚簇索引,必要時可以通過index index_name來強制指定索引
iii.避免對大表查詢時進行table scan,必要時考慮新建索引。
iv. 在使用索引字段作爲條件時,如果該索引是聯合索引,那麼必須使用到該索引中的第一個字段作爲條件時才能保證系統使用該索引,否則該索引將不會被使用。
v. 要注意索引的維護,週期性重建索引,重新編譯存儲過程。
c)tempdb的使用規範:
i. 儘量避免使用distinctorder bygroup byhavingjoincumpute,因爲這些語句會加重tempdb的負擔。
ii. 避免頻繁創建和刪除臨時表,減少系統表資源的消耗。
iii.在新建臨時表時,如果一次性插入數據量很大,那麼可以使用select into代替create table,避免log,提高速度;如果數據量不大,爲了緩和系統表的資源,建議先create table,然後insert
iv. 如果臨時表的數據量較大,需要建立索引,那麼應該將創建臨時表和建立索引的過程放在單獨一個子存儲過程中,這樣才能保證系統能夠很好的使用到該臨時表的索引。
v. 如果使用到了臨時表,在存儲過程的最後務必將所有的臨時表顯式刪除,先truncate table,然後drop table,這樣可以避免系統表的較長時間鎖定。
vi. 慎用大的臨時表與其他大表的連接查詢和修改,減低系統表負擔,因爲這種操作會在一條語句中多次使用tempdb的系統表。
d)合理的算法使用:
根據上面已提到的SQL優化技術和ASE Tuning手冊中的SQL優化內容,結合實際應用,採用多種算法進行比較,以獲得消耗資源最少、效率最高的方法。具體可用ASE調優命令:set statistics io on, set statistics time on , set showplan on 等。
以下是一些常用的優化需要注意的方面:
操作符優化
IN
操作符
IN寫出來的SQL的優點是比較容易寫及清晰易懂,這比較適合現代軟件開發的風格。
但是用INSQL性能總是比較低的,從ORACLE執行的步驟來分析用INSQL與不用INSQL有以下區別:
ORACLE
試圖將其轉換成多個表的連接,如果轉換不成功則先執行IN裏面的子查詢,再查詢外層的表記錄,如果轉換成功則直接採用多個表的連接方式查詢。由此可見用INSQL至少多了一個轉換的過程。一般的SQL都可以轉換成功,但對於含有分組統計等方面的SQL就不能轉換了。
推薦方案:在業務密集的SQL當中儘量不採用IN操作符。
NOT IN
操作符
此操作是強列推薦不使用的,因爲它不能應用表的索引。
推薦方案:用NOT EXISTS 或(外連接+判斷爲空)方案代替
<>
操作符(不等於)
不等於操作符是永遠不會用到索引的,因此對它的處理只會產生全表掃描。
推薦方案:用其它相同功能的操作運算代替,如
a<>0
改爲 a>0 or a<0
a<>’’
改爲 a>’’
IS NULL
IS NOT NULL操作(判斷字段是否爲空)
判斷字段是否爲空一般是不會應用索引的,因爲B樹索引是不索引空值的。
推薦方案:
用其它相同功能的操作運算代替,如
a is not null
改爲 a>0 a>’’等。
不允許字段爲空,而用一個缺省值代替空值,如業擴申請中狀態字段不允許爲空,缺省爲申請。
建立位圖索引(有分區的表不能建,位圖索引比較難控制,如字段值太多索引會使性能下降,多人更新操作會增加數據塊鎖的現象)
>
< 操作符(大於或小於操作符)
大於或小於操作符一般情況下是不用調整的,因爲它有索引就會採用索引查找,但有的情況下可以對它進行優化,如一個表有100萬記錄,一個數值型字段A30萬記錄的A=030萬記錄的A=139萬記錄的A=21萬記錄的A=3。那麼執行A>2A>=3的效果就有很大的區別了,因爲A>2ORACLE會先找出爲2的記錄索引再進行比較,而A>=3ORACLE則直接找到=3的記錄索引。
LIKE
操作符
LIKE
操作符可以應用通配符查詢,裏面的通配符組合可能達到幾乎是任意的查詢,但是如果用得不好則會產生性能上的問題,如LIKE ‘%5400%’ 這種查詢不會引用索引,而LIKE ‘X5400%’則會引用範圍索引。一個實際例子:用YW_YHJBQK表中營業編號後面的戶標識號可來查詢營業編號 YY_BH LIKE ‘%5400%’ 這個條件會產生全表掃描,如果改成YY_BH LIKE ’X5400%’ OR YY_BH LIKE ’B5400%’ 則會利用YY_BH的索引進行兩個範圍的查詢,性能肯定大大提高。
UNION
操作符
UNION
在進行錶鏈接後會篩選掉重複的記錄,所以在錶鏈接後會對所產生的結果集進行排序運算,刪除重複的記錄再返回結果。實際大部分應用中是不會產生重複的記錄,最常見的是過程表與歷史表UNION。如:
select * from gc_dfys
union
select * from ls_jg_dfys
這個SQL在運行時先取出兩個表的結果,再用排序空間進行排序刪除重複的記錄,最後返回結果集,如果表數據量大的話可能會導致用磁盤進行排序。
推薦方案:採用UNION ALL操作符替代UNION,因爲UNION ALL操作只是簡單的將兩個結果合併後就返回。
select * from gc_dfys
union all
select * from ls_jg_dfys
SQL
書寫的影響
同一功能同一性能不同寫法SQL的影響
如一個SQLA程序員寫的爲
Select * from zl_yhjbqk
B
程序員寫的爲
Select * from dlyx.zl_yhjbqk
(帶表所有者的前綴)
C
程序員寫的爲
Select * from DLYX.ZLYHJBQK
(大寫表名)
D
程序員寫的爲
Select * from DLYX.ZLYHJBQK
(中間多了空格)
以上四個SQLORACLE分析整理之後產生的結果及執行的時間是一樣的,但是從ORACLE共享內存SGA的原理,可以得出ORACLE對每個SQL 都會對其進行一次分析,並且佔用共享內存,如果將SQL的字符串及格式寫得完全相同則ORACLE只會分析一次,共享內存也只會留下一次的分析結果,這不僅可以減少分析SQL的時間,而且可以減少共享內存重複的信息,ORACLE也可以準確統計SQL的執行頻率。
WHERE
後面的條件順序影響
WHERE
子句後面的條件順序對大數據量表的查詢會產生直接的影響,如
Select * from zl_yhjbqk where dy_dj = '1KV
以下' and xh_bz=1
Select * from zl_yhjbqk where xh_bz=1 and dy_dj = '1KV
以下'
以上兩個SQLdy_dj(電壓等級)及xh_bz(銷戶標誌)兩個字段都沒進行索引,所以執行的時候都是全表掃描,第一條SQLdy_dj = '1KV以下'條件在記錄集內比率爲99%,而xh_bz=1的比率只爲0.5%,在進行第一條SQL的時候99%條記錄都進行dy_djxh_bz的比較,而在進行第二條SQL的時候0.5%條記錄都進行dy_djxh_bz的比較,以此可以得出第二條SQLCPU佔用率明顯比第一條低。
查詢表順序的影響
FROM後面的表中的列表順序會對SQL執行性能影響,在沒有索引及ORACLE沒有對錶進行統計分析的情況下ORACLE會按表出現的順序進行鏈接,由此因爲表的順序不對會產生十分耗服務器資源的數據交叉。(注:如果對錶進行了統計分析,ORACLE會自動先進小表的鏈接,再進行大表的鏈接)
SQL
語句索引的利用
對操作符的優化(見上節)
對條件字段的一些優化
採用函數處理的字段不能利用索引,如:
substr(hbs_bh,1,4)=’5400’
,優化處理:hbs_bh like ‘5400%’
trunc(sk_rq)=trunc(sysdate)
優化處理:
sk_rq>=trunc(sysdate) and sk_rq<trunc(sysdate+1)
進行了顯式或隱式的運算的字段不能進行索引,如:
ss_df+20>50
,優化處理:ss_df>30
‘X’||hbs_bh>’X5400021452’
,優化處理:hbs_bh>’5400021542’
sk_rq+5=sysdate
,優化處理:sk_rq=sysdate-5
hbs_bh=5401002554
,優化處理:hbs_bh=’ 5401002554’,注:此條件對hbs_bh 進行隱式的to_number轉換,因爲hbs_bh字段是字符型。
條件內包括了多個本表的字段運算時不能進行索引,如:
ys_df>cx_df
,無法進行優化
qc_bh||kh_bh=’5400250000’
,優化處理:qc_bh=’5400’ and kh_bh=’250000’
應用ORACLEHINT(提示)處理
提示處理是在ORACLE產生的SQL分析執行路徑不滿意的情況下要用到的。它可以對SQL進行以下方面的提示
目標方面的提示:
COST
(按成本優化)
RULE
(按規則優化)
CHOOSE
(缺省)(ORACLE自動選擇成本或規則進行優化)
ALL_ROWS
(所有的行儘快返回)
FIRST_ROWS
(第一行數據儘快返回)
執行方法的提示:
USE_NL
(使用NESTED LOOPS方式聯合)
USE_MERGE
(使用MERGE JOIN方式聯合)
USE_HASH
(使用HASH JOIN方式聯合)
索引提示:
INDEX
TABLE INDEX)(使用提示的表索引進行查詢)
其它高級提示(如並行處理等等)
ORACLE
的提示功能是比較強的功能,也是比較複雜的應用,並且提示只是給ORACLE執行的一個建議,有時如果出於成本方面的考慮ORACLE也可能不會按提示進行。根據實踐應用,一般不建議開發人員應用ORACLE提示,因爲各個數據庫及服務器性能情況不一樣,很可能一個地方性能提升了,但另一個地方卻下降了,ORACLESQL執行分析方面已經比較成熟,如果分析執行的路徑不對首先應在數據庫結構(主要是索引)、服務器當前性能(共享內存、磁盤文件碎片)、數據庫對象(表、索引)統計信息是否正確這幾方面分析。
與沒有優化數據庫的網站相比,數據庫的存取會降低你的系統性能。但是大多數情況下,網站和數據庫有密不可分的關係,正是數據庫給站點提供了大容量、多樣性、個性化等特色,並實現了很多特殊的功能。
1
不要忘記給數據庫做索引。
合理的索引能立即顯著地提高數據庫整個系統的性能。可以參考有關SQL性能調試書籍,學會根據所需查詢方式合理製作索引和根據索引方式改進查詢語句。
2
在適當的情況下,儘可能的用存儲過程而不是SQL查詢。
因爲前者已經過了預編譯,運行速度更快。同時讓數據庫僅僅返回你所需要的那些數據,而不是返回大量數據再讓ASP程序過濾。總之要充分和有效地發揮數據庫的強大功能,讓它按照我們的要求反饋給我們最合適和最精練的信息。
3
在可能情況下我們應該使用SQL Server而不是Access。因爲Access僅僅是基於文件的數據庫,多用戶性能很差。數據庫連接儘量使用OLEDB和非DSN方式,因爲這種連接方式有更好的併發性能。

4
避免使用DAOData Access Objects)和RDORemote Data Objects)數據源。因爲他們主要應用在單用戶的處理系統裏,ADOActiveX Data Objects)纔是爲Web應用設計的。
5
建立記錄集Rescordset的時候要清晰合理地設置數據遊標(cursort)和鎖定方式(locktype)
因爲在不同的方式下ASP會以不同的方式操縱數據庫,其執行速度也有很大區別,尤其在大數據量的時候。如果你只想遍歷數據,那麼默認遊標(前進、只讀)會帶來最好的性能。
6
當你引用ADO變量的時候,會消耗較多的CPU週期。因此,如果在一個ASP頁面中多次引用數據庫的字段變量,一個較好的方式是將字段值先放入本地變量,然後可以直接調用本地變量來計算和顯示數據。
7
緩存ADO Connection對象也許不是一個好主意。
如果一個連接(Connection)對象被存儲在Application對象中而被所有ASP頁面使用,那麼所有頁面就會爭着使用這個連接。但是如果連接對象被存儲在Session對象中,就要爲每個用戶創建一個數據庫連接,這就減小了連接池的作用,並且增大了Web服務器和數據庫服務器的壓力。可以用在每個使用ADOASP頁創建和釋放ADO對象來替代緩存數據庫連接。因爲IIS內建了數據庫連接池,所以這種方法非常有效,缺點是每個ASP頁面都需要進行一些創建和釋放操作。
8 ASP
最強大和主要的用途之一就是對數據庫進行操作,在數據庫操作中我們要注意:不要任意使用“SELECT ......” 形式的SQL查詢語句。應該儘量檢索你所需要的那些字段。比如一個表中有10個字段,但是你只會用到其中的一個字段(name),就該使用“select name from mytable”,而不是用“select from mytable”。在字段數比較少的時候,兩者的區別可能並不明顯,但是當一個表中擁有幾十個字段的時候,數據庫會多檢索很多你並不需要的數據。在這種情況下你最好不要爲了節省打字時間或者害怕查找對應字段名稱的麻煩,而要老老實實地使用“select id,name,age... from mytable”
9
及時關閉打開的記錄集對象以及連接(Connection)對象。
記錄集對象和連接對象耗費系統資源相當大,因此它們的可用數量是有限的。如果你打開了太多的記錄集對象以及連接對象而最後卻沒有關閉它們,可能會出現ASP程序剛開始的時候運行速度很快,而多運行幾遍就越來越慢的現象,甚至導致服務器死機。請使用如下方法進行關閉:
MyRecordSet.closeSet MyRecordSet=Nothing
Set MyConnection=Nothing
10
連接數據庫
仍然使用ODBC系統或者文件DSN來連接數據庫,或者使用很快的OLEDB技術來連接。使用後者,當移動Web文件時,不再需要修改配置。
OLEDB
位於應用程序與ODBC層之間。在ASP頁面中,ADO就是位於OLEDB之上的程序。調用ADO時,首先發送給OLEDB,然後再發送給ODBC層。可以直接連接到OLEDB層,這麼做後,將提高服務器端的性能。怎麼直接連接到OLEDB呢?
如果使用SQLServer 7,使用下面的代碼做爲連接字符串:
strConnString = "DSN='';DRIVER={SQL SERVER};" & _
"UID=myuid;PWD=mypwd;" & _
"DATABASE=MyDb;SERVER=MyServer;"
最重要的參數就是“DRIVER=”部分。如果你想繞過ODBC而使用OLEDB來訪問SQL Server,使用下面的語法:
strConnString ="Provider=SQLOLEDB.1;Password=mypassword;" & _
"Persist Security Info=True;User ID=myuid;" & _
"Initial Catalog=mydbname;" & _
"Data Source=myserver;Connect Timeout=15"
爲什麼這很重要
現在你可能奇怪爲什麼學習這種新的連接方法很關鍵?爲什麼不使用標準的DSN或者系統DSN方法?好,根據Wrox在他們的ADO 2.0程序員參考書籍中所做的測試,如果使用OLEDB連接,要比使用DSN或者DSNless連接,有以下的性能提高表現:
性能比較:
----------------------------------------------------------------------
SQL Access
連接時間: 18 82
重複1000個記錄的時間:2900 5400
OLEDB DSN OLEDB DSN
連接時間:62 99
重複1000個記錄的時間:100 950
----------------------------------------------------------------------
這個結論在WroxADO 2.0程序員參考發表。時間是以毫秒爲單位,重複1000個記錄的時間是以服務器油標的方式計算的。
有一個例子:
select a. *, m.amount
from tableA a,
(
select b.fieldD, sum(c.total_amount) amount
from tableA b, tableB c
where b.fieldC = 100 and
b.fieldA in ('AA', 'BB', 'CC', 'DD', 'EE', 'FF') and
b.fieldId = c.fieldId
group by b.fieldD
) m
where a.fieldC = 100 and a.fieldD = m.fieldD and
a.fieldA = 'GG'


這句sql當中對同一個表掃描了兩次,所以效率太低,有什麼辦法可以避免這種寫法?
tableA,tableB
是主從表關係。
請不要用sql server 中太特殊的語法,因爲要用到oracle中。
oracle中無人回答。

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

SQL
語句的寫法是根據你的業務要求,改寫起來效果不能很明顯。

先分析一下你的SQL的執行路徑:

1

首先會分別對tableAtableB應用filter動作(使用m子查詢中的where條件)。然後進行連接,可能會是nestloophash join...這取決於你

的兩個表數據過濾情況。然後進行彙總(group by)輸出m結果集。

2
、接下來會將m結果集與tableA(外層)過濾後(a.fieldC = 100 and a.fieldA = 'GG')的結果集進行連接,還是有多種連接方式。最後輸

a. *, m.amount

大致分析了一下執行的路徑,就會對你的描述產生疑惑:對同一個表掃描了兩次肯定指的是tableA了。但是你沒有建立相關的索引嗎?如

果說外層的查詢就算建立索引也會通過rowid定位到表中,我們權當這是表掃描,但是內層的查詢應該不會發生產生表掃描(all table

access
)的情況!應該是索引掃描(index scan)纔對。根據這一點,我們可以首先考慮建立索引來提高效率。

可以考慮建立的索引:

create index idx_1 on tableA(fieldC,fieldA,fieldId,fieldD)

create index idx_2 on tableB(fieldId,total_amount)


建立完這兩個索引後別忘了重新執行分析,以保證統計值準確。

建立完這兩個索引後,內層的執行計劃應該是對idx_1idx_2進行索引掃描(index scan)然後連接輸出m結果集,再與外層的經過索引掃描(

index scan + rowid to table
)的結果集進行連接。

如果查詢計劃不對,請檢查你的優化器參數設置,不要使用rbo要使用cbo。如果還是沒有采用請用/* index*/提示強制指定....


上面的是單純從索引方面考慮。如果還是不能提高速度,考慮建立實體化視圖(物化視圖)。可以只將m部分進行實體化。如果tableAtableB

基本屬於靜態表,可以考慮將整條語句實體化。
這裏有個非常好的例子並總結了:
SERVER數據庫中實現快速的數據提取和數據分頁。以下代碼說明了我們實例中數據庫的紅頭文件一表的部分數據結構:

CREATE table [dbo].[TGongwen] (
  --TGongwen是紅頭文件表名

[Gid] [int] ideNTITY (1, 1) NOT NULL ,
--
本表的id號,也是主鍵

[title] [varchar] (80) COLLATE Chinese_PRC_CI_AS NULL ,
--
紅頭文件的標題

[fariqi] [datetime] NULL ,
--
發佈日期

[neibuYonghu] [varchar] (70) COLLATE Chinese_PRC_CI_AS NULL ,
--
發佈用戶

[reader] [varchar] (900) COLLATE Chinese_PRC_CI_AS NULL ,

--
需要瀏覽的用戶。每個用戶中間用分隔符“,”分開

) ON [PRIMARY] TEXTimage_ON [PRIMARY]

GO

下面,我們來往數據庫中添加1000萬條數據:

declare @i int

set @i=1

while @i<=250000

begin

insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-2-5','
通信科','通信科,辦公室,王局長,劉局長,張局長,admin,刑偵支隊,特勤支隊,交巡警支隊,經偵支隊,戶政科,治安支隊,外事科','這是最先的25萬條記錄')

set @i=@i+1

end

GO

declare @i int

set @i=1

while @i<=250000

begin

insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-9-16','
辦公室','辦公室,通信科,王局長,劉局長,張局長,admin,刑偵支隊,特勤支隊,交巡警支隊,經偵支隊,戶政科,外事科','這是中間的25萬條記錄')

set @i=@i+1

end

GO

declare @h int

set @h=1

while @h<=100

begin

declare @i int

set @i=2002

while @i<=2003

begin

declare @j int

set @j=0

while @j<50

begin

declare @k int

set @k=0

while @k<50

begin

insert into Tgongwen(fariqi,neibuyonghu,reader,title) values(cast(@i as varchar(4))+'-8-15 3:'+cast(@j as varchar(2))+':'+cast(@j as varchar(2)),'
通信科','辦公室,通信科,王局長,劉局長,張局長,admin,刑偵支隊,特勤支隊,交巡警支隊,經偵支隊,戶政科,外事科','這是最後的50萬條記錄')

set @k=@k+1

end

set @j=@j+1

end

set @i=@i+1

end

set @h=@h+1

end

GO

declare @i int

set @i=1

while @i<=9000000

begin

insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-5-5','
通信科','通信科,辦公室,王局長,劉局長,張局長,admin,刑偵支隊,特勤支隊,交巡警支隊,經偵支隊,戶政科,治安支隊,外事科','這是最後添加的900萬條記錄')

set @i=@i+1000000

end

GO

通過以上語句,我們創建了25萬條由於200425發佈的記錄,25萬條由辦公室於200496發佈的記錄,2002年和2003年各1002500條相同日期、不同分秒的記錄(共50萬條),還有由通信科於200455發佈的900萬條記錄,合計1000萬條。


一、因情制宜,建立適當索引
建立適當的索引是實現查詢優化的首要前提。

索引(index)是除表之外另一重要的、用戶定義的存儲在物理介質上的數據結構。當根據索引碼的值搜索數據時,索引提供了對數據的快速訪問。事實上,沒有索引,數據庫也能根據select語句成功地檢索到結果,但隨着表變得越來越大,使用適當的索引的效果就越來越明顯。注意,在這句話中,我們用了適當這個詞,這是因爲,如果使用索引時不認真考慮其實現過程,索引既可以提高也會破壞數據庫的工作性能

(一)深入淺出理解索引結構

實際上,您可以把索引理解爲一種特殊的目錄。微軟SQL SERVER提供了兩種索引:聚集索引(clustered index,也稱聚類索引、簇集索引)和非聚集索引(nonclustered index,也稱非聚類索引、非簇集索引)。下面,我們舉例來說明一下聚集索引和非聚集索引的區別:

其實,我們的漢語字典的正文本身就是一個聚集索引。比如,我們要查字,就會很自然地翻開字典的前幾頁,因爲的拼音是“an”,而按照拼音排序漢字的字典是以英文字母“a”開頭並以“z”結尾的,那麼字就自然地排在字典的前部。如果您翻完了所有以“a”開頭的部分仍然找不到這個字,那麼就說明您的字典中沒有這個字;同樣的,如果查字,那您也會將您的字典翻到最後部分,因爲的拼音是“zhang”。也就是說,字典的正文部分本身就是一個目錄,您不需要再去查其他目錄來找到您需要找的內容。

我們把這種正文內容本身就是一種按照一定規則排列的目錄稱爲聚集索引

如果您認識某個字,您可以快速地從自動中查到這個字。但您也可能會遇到您不認識的字,不知道它的發音,這時候,您就不能按照剛纔的方法找到您要查的字,而需要去根據偏旁部首查到您要找的字,然後根據這個字後的頁碼直接翻到某頁來找到您要找的字。但您結合部首目錄檢字表而查到的字的排序並不是真正的正文的排序方法,比如您查字,我們可以看到在查部首之後的檢字表中的頁碼是672頁,檢字表中的上面是字,但頁碼卻是63頁,的下面是字,頁面是390頁。很顯然,這些字並不是真正的分別位於字的上下方,現在您看到的連續的馳、張、弩三字實際上就是他們在非聚集索引中的排序,是字典正文中的字在非聚集索引中的映射。我們可以通過這種方式來找到您所需要的字,但它需要兩個過程,先找到目錄中的結果,然後再翻到您所需要的頁碼。

我們把這種目錄純粹是目錄,正文純粹是正文的排序方式稱爲非聚集索引

通過以上例子,我們可以理解到什麼是聚集索引非聚集索引

進一步引申一下,我們可以很容易的理解:每個表只能有一個聚集索引,因爲目錄只能按照一種方法進行排序。

(二)何時使用聚集索引或非聚集索引

下面的表總結了何時使用聚集索引或非聚集索引(很重要)。

動作描述
使用聚集索引
使用非聚集索引

列經常被分組排序



返回某範圍內的數據

不應

一個或極少不同值
不應
不應

小數目的不同值

不應

大數目的不同值
不應


頻繁更新的列
不應


外鍵列



主鍵列



頻繁修改索引列
不應


事實上,我們可以通過前面聚集索引和非聚集索引的定義的例子來理解上表。如:返回某範圍內的數據一項。比如您的某個表有一個時間列,恰好您把聚合索引建立在了該列,這時您查詢2004112004101之間的全部數據時,這個速度就將是很快的,因爲您的這本字典正文是按日期進行排序的,聚類索引只需要找到要檢索的所有數據中的開頭和結尾數據即可;而不像非聚集索引,必須先查到目錄中查到每一項數據對應的頁碼,然後再根據頁碼查到具體內容。

(三)結合實際,談索引使用的誤區

理論的目的是應用。雖然我們剛纔列出了何時應使用聚集索引或非聚集索引,但在實踐中以上規則卻很容易被忽視或不能根據實際情況進行綜合分析。下面我們將根據在實踐中遇到的實際問題來談一下索引使用的誤區,以便於大家掌握索引建立的方法。

1
、主鍵就是聚集索引

這種想法筆者認爲是極端錯誤的,是對聚集索引的一種浪費。雖然SQL SERVER默認是在主鍵上建立聚集索引的。

通常,我們會在每個表中都建立一個ID列,以區分每條數據,並且這個ID列是自動增大的,步長一般爲1。我們的這個辦公自動化的實例中的列Gid就是如此。此時,如果我們將這個列設爲主鍵,SQL SERVER會將此列默認爲聚集索引。這樣做有好處,就是可以讓您的數據在數據庫中按照ID進行物理排序,但筆者認爲這樣做意義不大。

顯而易見,聚集索引的優勢是很明顯的,而每個表中只能有一個聚集索引的規則,這使得聚集索引變得更加珍貴。

從我們前面談到的聚集索引的定義我們可以看出,使用聚集索引的最大好處就是能夠根據查詢要求,迅速縮小查詢範圍,避免全表掃描。在實際應用中,因爲ID號是自動生成的,我們並不知道每條記錄的ID號,所以我們很難在實踐中用ID號來進行查詢。這就使讓ID號這個主鍵作爲聚集索引成爲一種資源浪費。其次,讓每個ID號都不同的字段作爲聚集索引也不符合大數目的不同值情況下不應建立聚合索引規則;當然,這種情況只是針對用戶經常修改記錄內容,特別是索引項的時候會負作用,但對於查詢速度並沒有影響。

在辦公自動化系統中,無論是系統首頁顯示的需要用戶簽收的文件、會議還是用戶進行文件查詢等任何情況下進行數據查詢都離不開字段的是日期還有用戶本身的用戶名

通常,辦公自動化的首頁會顯示每個用戶尚未簽收的文件或會議。雖然我們的where語句可以僅僅限制當前用戶尚未簽收的情況,但如果您的系統已建立了很長時間,並且數據量很大,那麼,每次每個用戶打開首頁的時候都進行一次全表掃描,這樣做意義是不大的,絕大多數的用戶1個月前的文件都已經瀏覽過了,這樣做只能徒增數據庫的開銷而已。事實上,我們完全可以讓用戶打開系統首頁時,數據庫僅僅查詢這個用戶近3個月來未閱覽的文件,通過日期這個字段來限制表掃描,提高查詢速度。如果您的辦公自動化系統已經建立的2年,那麼您的首頁顯示速度理論上將是原來速度8倍,甚至更快。

在這裏之所以提到理論上三字,是因爲如果您的聚集索引還是盲目地建在ID這個主鍵上時,您的查詢速度是沒有這麼高的,即使您在日期這個字段上建立的索引(非聚合索引)。下面我們就來看一下在1000萬條數據量的情況下各種查詢的速度表現(3個月內的數據爲25萬條):

1)僅在主鍵上建立聚集索引,並且不劃分時間段:

Select gid,fariqi,neibuyonghu,title from tgongwen

用時:128470毫秒(即:128秒)

2)在主鍵上建立聚集索引,在fariq上建立非聚集索引:

select gid,fariqi,neibuyonghu,title from Tgongwen

where fariqi> dateadd(day,-90,getdate())

用時:53763毫秒(54秒)

3)將聚合索引建立在日期列(fariqi)上:

select gid,fariqi,neibuyonghu,title from Tgongwen

where fariqi> dateadd(day,-90,getdate())

用時:2423毫秒(2秒)

雖然每條語句提取出來的都是25萬條數據,各種情況的差異卻是巨大的,特別是將聚集索引建立在日期列時的差異。事實上,如果您的數據庫真的有1000萬容量的話,把主鍵建立在ID列上,就像以上的第12種情況,在網頁上的表現就是超時,根本就無法顯示。這也是我摒棄ID列作爲聚集索引的一個最重要的因素。

得出以上速度的方法是:在各個select語句前加:declare @d datetime

set @d=getdate()

並在select語句後加:

select [
語句執行花費時間(毫秒)]=datediff(ms,@d,getdate())

2
、只要建立索引就能顯著提高查詢速度

事實上,我們可以發現上面的例子中,第23條語句完全相同,且建立索引的字段也相同;不同的僅是前者在fariqi字段上建立的是非聚合索引,後者在此字段上建立的是聚合索引,但查詢速度卻有着天壤之別。所以,並非是在任何字段上簡單地建立索引就能提高查詢速度。

從建表的語句中,我們可以看到這個有着1000萬數據的表中fariqi字段有5003個不同記錄。在此字段上建立聚合索引是再合適不過了。在現實中,我們每天都會發幾個文件,這幾個文件的發文日期就相同,這完全符合建立聚集索引要求的:既不能絕大多數都相同,又不能只有極少數相同的規則。由此看來,我們建立適當的聚合索引對於我們提高查詢速度是非常重要的。

3
、把所有需要提高查詢速度的字段都加進聚集索引,以提高查詢速度

上面已經談到:在進行數據查詢時都離不開字段的是日期還有用戶本身的用戶名。既然這兩個字段都是如此的重要,我們可以把他們合併起來,建立一個複合索引(compound index)。

很多人認爲只要把任何字段加進聚集索引,就能提高查詢速度,也有人感到迷惑:如果把複合的聚集索引字段分開查詢,那麼查詢速度會減慢嗎?帶着這個問題,我們來看一下以下的查詢速度(結果集都是25萬條數據):(日期列fariqi首先排在複合聚集索引的起始列,用戶名neibuyonghu排在後列)

1select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>'2004-5-5'

查詢速度:2513毫秒

2select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi>'2004-5-5' and neibuyonghu='辦公室'

查詢速度:2516毫秒

3select gid,fariqi,neibuyonghu,title from Tgongwen where neibuyonghu='辦公室'

查詢速度:60280毫秒

從以上試驗中,我們可以看到如果僅用聚集索引的起始列作爲查詢條件和同時用到複合聚集索引的全部列的查詢速度是幾乎一樣的,甚至比用上全部的複合索引列還要略快(在查詢結果集數目一樣的情況下);而如果僅用複合聚集索引的非起始列作爲查詢條件的話,這個索引是不起任何作用的。當然,語句12的查詢速度一樣是因爲查詢的條目數一樣,如果複合索引的所有列都用上,而且查詢結果少的話,這樣就會形成索引覆蓋,因而性能可以達到最優。同時,請記住:無論您是否經常使用聚合索引的其他列,但其前導列一定要是使用最頻繁的列。

(四)其他書上沒有的索引使用經驗總結

1
、用聚合索引比用不是聚合索引的主鍵速度快

下面是實例語句:(都是提取25萬條數據)

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'

使用時間:3326毫秒

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid<=250000

使用時間:4470毫秒

這裏,用聚合索引比用不是聚合索引的主鍵速度快了近1/4

2
、用聚合索引比用一般的主鍵作order by時速度快,特別是在小數據量情況下

select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by fariqi

用時:12936

select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by gid

用時:18843

這裏,用聚合索引比用一般的主鍵作order by時,速度快了3/10。事實上,如果數據量很小的話,用聚集索引作爲排序列要比使用非聚集索引速度快得明顯的多;而數據量如果很大的話,如10萬以上,則二者的速度差別不明顯。

3
、使用聚合索引內的時間段,搜索時間會按數據佔整個數據表的百分比成比例減少,而無論聚合索引使用了多少個

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1'

用時:6343毫秒(提取100萬條)

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-6-6'

用時:3170毫秒(提取50萬條)

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'

用時:3326毫秒(和上句的結果一模一樣。如果採集的數量一樣,那麼用大於號和等於號是一樣的)

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1' and fariqi<'2004-6-6'

用時:3280毫秒
  4 、日期列不會因爲有分秒的輸入而減慢查詢速度

下面的例子中,共有100萬條數據,200411以後的數據有50萬條,但只有兩個不同的日期,日期精確到日;之前有數據50萬條,有5000個不同的日期,日期精確到秒。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi>'2004-1-1' order by fariqi

用時:6390毫秒

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi<'2004-1-1' order by fariqi

用時:6453毫秒

(五)其他注意事項

水可載舟,亦可覆舟,索引也一樣。索引有助於提高檢索性能,但過多或不當的索引也會導致系統低效。因爲用戶在表中每加進一個索引,數據庫就要做更多的工作。過多的索引甚至會導致索引碎片。

所以說,我們要建立一個適當的索引體系,特別是對聚合索引的創建,更應精益求精,以使您的數據庫能得到高性能的發揮。

當然,在實踐中,作爲一個盡職的數據庫管理員,您還要多測試一些方案,找出哪種方案效率最高、最爲有效。

二、改善SQL語句
很多人不知道SQL語句在SQL SERVER中是如何執行的,他們擔心自己所寫的SQL語句會被SQL SERVER誤解。比如:

select * from table1 where name='zhangsan' and tID > 10000

和執行:

select * from table1 where tID > 10000 and name='zhangsan'

一些人不知道以上兩條語句的執行效率是否一樣,因爲如果簡單的從語句先後上看,這兩個語句的確是不一樣,如果tID是一個聚合索引,那麼後一句僅僅從表的10000條以後的記錄中查找就行了;而前一句則要先從全表中查找看有幾個name='zhangsan'的,而後再根據限制條件條件tID>10000來提出查詢結果。

事實上,這樣的擔心是不必要的。SQL SERVER中有一個查詢分析優化器,它可以計算出where子句中的搜索條件並確定哪個索引能縮小表掃描的搜索空間,也就是說,它能實現自動優化。

雖然查詢優化器可以根據where子句自動的進行查詢優化,但大家仍然有必要了解一下查詢優化器的工作原理,如非這樣,有時查詢優化器就會不按照您的本意進行快速查詢。

在查詢分析階段,查詢優化器查看查詢的每個階段並決定限制需要掃描的數據量是否有用。如果一個階段可以被用作一個掃描參數(SARG),那麼就稱之爲可優化的,並且可以利用索引快速獲得所需數據。

SARG
的定義:用於限制搜索的一個操作,因爲它通常是指一個特定的匹配,一個值得範圍內的匹配或者兩個以上條件的AND連接。形式如下:

列名操作符 <常數變量>



<
常數變量> 操作符列名

列名可以出現在操作符的一邊,而常數或變量出現在操作符的另一邊。如:

Name=’
張三

價格>5000

5000<
價格

Name=’
張三’ and 價格>5000

如果一個表達式不能滿足SARG的形式,那它就無法限制搜索的範圍了,也就是SQL SERVER必須對每一行都判斷它是否滿足WHERE子句中的所有條件。所以一個索引對於不滿足SARG形式的表達式來說是無用的。

介紹完SARG後,我們來總結一下使用SARG以及在實踐中遇到的和某些資料上結論不同的經驗:

1
Like語句是否屬於SARG取決於所使用的通配符的類型

如:name like ‘%’ ,這就屬於SARG

而:name like ‘%’ ,就不屬於SARG

原因是通配符%在字符串的開通使得索引無法使用。

2
or 會引起全表掃描

Name=’
張三’ and 價格>5000 符號SARG,而:Name=’張三’ or 價格>5000 則不符合SARG。使用or會引起全表掃描。

3
、非操作符、函數引起的不滿足SARG形式的語句

不滿足SARG形式的語句最典型的情況就是包括非操作符的語句,如:NOT!=<>!<!>NOT EXISTSNOT INNOT LIKE等,另外還有函數。下面就是幾個不滿足SARG形式的例子:

ABS(
價格)<5000

Name like ‘%


有些表達式,如:

WHERE
價格*2>5000

SQL SERVER
也會認爲是SARGSQL SERVER會將此式轉化爲:

WHERE
價格>2500/2

但我們不推薦這樣使用,因爲有時SQL SERVER不能保證這種轉化與原始表達式是完全等價的。

4
IN 的作用相當與OR

語句:

Select * from table1 where tid in (2,3)



Select * from table1 where tid=2 or tid=3

是一樣的,都會引起全表掃描,如果tid上有索引,其索引也會失效。

5
、儘量少用NOT

6
exists in 的執行效率是一樣的

很多資料上都顯示說,exists要比in的執行效率要高,同時應儘可能的用not exists來代替not in。但事實上,我試驗了一下,發現二者無論是前面帶不帶not,二者之間的執行效率都是一樣的。因爲涉及子查詢,我們試驗這次用SQL SERVER自帶的pubs數據庫。運行前我們可以把SQL SERVERstatistics I/O狀態打開。

1select title,price from titles where title_id in (select title_id from sales where qty>30)

該句的執行結果爲:

'sales'。掃描計數 18,邏輯讀 56 次,物理讀 0 次,預讀 0 次。

'titles'。掃描計數 1,邏輯讀 2 次,物理讀 0 次,預讀 0 次。

2select title,price from titles where exists (select * from sales where sales.title_id=titles.title_id and qty>30)

第二句的執行結果爲:

'sales'。掃描計數 18,邏輯讀 56 次,物理讀 0 次,預讀 0 次。

'titles'。掃描計數 1,邏輯讀 2 次,物理讀 0 次,預讀 0 次。

我們從此可以看到用exists和用in的執行效率是一樣的。

7
、用函數charindex()和前面加通配符%LIKE執行效率一樣

前面,我們談到,如果在LIKE前面加上通配符%,那麼將會引起全表掃描,所以其執行效率是低下的。但有的資料介紹說,用函數charindex()來代替LIKE速度會有大的提升,經我試驗,發現這種說明也是錯誤的:

select gid,title,fariqi,reader from tgongwen where charindex('
刑偵支隊',reader)>0 and fariqi>'2004-5-5'

用時:7秒,另外:掃描計數 4,邏輯讀 7155 次,物理讀 0 次,預讀 0 次。

select gid,title,fariqi,reader from tgongwen where reader like '%' + '
刑偵支隊' + '%' and fariqi>'2004-5-5'

用時:7秒,另外:掃描計數 4,邏輯讀 7155 次,物理讀 0 次,預讀 0 次。

8
union並不絕對比or的執行效率高

我們前面已經談到了在where子句中使用or會引起全表掃描,一般的,我所見過的資料都是推薦這裏用union來代替or。事實證明,這種說法對於大部分都是適用的。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' or gid>9990000

用時:68秒。掃描計數 1,邏輯讀 404008 次,物理讀 283 次,預讀 392163 次。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'

union

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid>9990000

用時:9秒。掃描計數 8,邏輯讀 67489 次,物理讀 216 次,預讀 7499 次。

看來,用union在通常情況下比用or的效率要高的多。

但經過試驗,筆者發現如果or兩邊的查詢列是一樣的話,那麼用union則反倒和用or的執行速度差很多,雖然這裏union掃描的是索引,而or掃描的是全表。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' or fariqi='2004-2-5'

用時:6423毫秒。掃描計數 2,邏輯讀 14726 次,物理讀 1 次,預讀 7176 次。

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'

union

select gid,fariqi,neibuyonghu,reader,title from Tgongwen where
 fariqi='2004-2-5'

用時:11640毫秒。掃描計數 8,邏輯讀 14806 次,物理讀 108 次,預讀 1144 次。

9
、字段提取要按照需多少、提多少的原則,避免“select *”

我們來做一個試驗:

select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc

用時:4673毫秒

select top 10000 gid,fariqi,title from tgongwen order by gid desc

用時:1376毫秒

select top 10000 gid,fariqi from tgongwen order by gid desc

用時:80毫秒

由此看來,我們每少提取一個字段,數據的提取速度就會有相應的提升。提升的速度還要看您捨棄的字段的大小來判斷。

10
count(*)不比count(字段)

某些資料上說:用*會統計所有列,顯然要比一個世界的列名效率低。這種說法其實是沒有根據的。我們來看:

select count(*) from Tgongwen

用時:1500毫秒

select count(gid) from Tgongwen

用時:1483毫秒

select count(fariqi) from Tgongwen

用時:3140毫秒

select count(title) from Tgongwen

用時:52050毫秒

從以上可以看出,如果用count(*)和用count(主鍵)的速度是相當的,而count(*)卻比其他任何除主鍵以外的字段彙總速度要快,而且字段越長,彙總的速度就越慢。我想,如果用count(*) SQL SERVER可能會自動查找最小字段來彙總的。當然,如果您直接寫count(主鍵)將會來的更直接些。

11
order by按聚集索引列排序效率最高

我們來看:(gid是主鍵,fariqi是聚合索引列)

select top 10000 gid,fariqi,reader,title from tgongwen

用時:196 毫秒。掃描計數 1,邏輯讀 289 次,物理讀 1 次,預讀 1527 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by gid asc

用時:4720毫秒。掃描計數 1,邏輯讀 41956 次,物理讀 0 次,預讀 1287 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc

用時:4736毫秒。掃描計數 1,邏輯讀 55350 次,物理讀 10 次,預讀 775 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi asc

用時:173毫秒。掃描計數 1,邏輯讀 290 次,物理讀 0 次,預讀 0 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi desc

用時:156毫秒。掃描計數 1,邏輯讀 289 次,物理讀 0 次,預讀 0 次。

從以上我們可以看出,不排序的速度以及邏輯讀次數都是和“order by 聚集索引列 的速度是相當的,但這些都比“order by 非聚集索引列的查詢速度是快得多的。

同時,按照某個字段進行排序的時候,無論是正序還是倒序,速度是基本相當的。

12
、高效的TOP

事實上,在查詢和提取超大容量的數據集時,影響數據庫響應時間的最大因素不是數據查找,而是物理的I/0操作。如:

select top 10 * from (

select top 10000 gid,fariqi,title from tgongwen

where neibuyonghu='
辦公室'

order by gid desc) as a

order by gid asc

這條語句,從理論上講,整條語句的執行時間應該比子句的執行時間長,但事實相反。因爲,子句執行後返回的是10000條記錄,而整條語句僅返回10條語句,所以影響數據庫響應時間最大的因素是物理I/O操作。而限制物理I/O操作此處的最有效方法之一就是使用TOP關鍵詞了。TOP關鍵詞是SQL SERVER中經過系統優化過的一個用來提取前幾條或前幾個百分比數據的詞。經筆者在實踐中的應用,發現TOP確實很好用,效率也很高。但這個詞在另外一個大型數據庫ORACLE中卻沒有,這不能說不是一個遺憾,雖然在ORACLE中可以用其他方法(如:rownumber)來解決。在以後的關於實現千萬級數據的分頁顯示存儲過程的討論中,我們就將用到TOP這個關鍵詞。

到此爲止,我們上面討論瞭如何實現從大容量的數據庫中快速地查詢出您所需要的數據方法。當然,我們介紹的這些方法都是方法,在實踐中,我們還要考慮各種因素,如:網絡性能、服務器的性能、操作系統的性能,甚至網卡交換機等。
  三、實現小數據量和海量數據的通用分頁顯示存儲過程
建立一個web 應用,分頁瀏覽功能必不可少。這個問題是數據庫處理中十分常見的問題。經典的數據分頁方法是:ADO 紀錄集分頁法,也就是利用ADO自帶的分頁功能(利用遊標)來實現分頁。但這種分頁方法僅適用於較小數據量的情形,因爲遊標本身有缺點:遊標是存放在內存中,很費內存。遊標一建立,就將相關的記錄鎖住,直到取消遊標。遊標提供了對特定集合中逐行掃描的手段,一般使用遊標來逐行遍歷數據,根據取出數據條件的不同進行不同的操作。而對於多表和大表中定義的遊標(大的數據集合)循環很容易使程序進入一個漫長的等待甚至死機。

更重要的是,對於非常大的數據模型而言,分頁檢索時,如果按照傳統的每次都加載整個數據源的方法是非常浪費資源的。現在流行的分頁方法一般是檢索頁面大小的塊區的數據,而非檢索所有的數據,然後單步執行當前行。

最早較好地實現這種根據頁面大小和頁碼來提取數據的方法大概就是俄羅斯存儲過程。這個存儲過程用了遊標,由於遊標的侷限性,所以這個方法並沒有得到大家的普遍認可。

後來,網上有人改造了此存儲過程,下面的存儲過程就是結合我們的辦公自動化實例寫的分頁存儲過程:

CREATE procedure pagination1

(@pagesize int,
 --頁面大小,如每頁存儲20條記錄

@pageindex int
  --當前頁碼

)

as

set nocount on

begin

declare @indextable table(id int identity(1,1),nid int)
 --定義表變量

declare @PageLowerBound int
 --定義此頁的底碼

declare @PageUpperBound int
 --定義此頁的頂碼

set @PageLowerBound=(@pageindex-1)*@pagesize

set @PageUpperBound=@PageLowerBound+@pagesize

set rowcount @PageUpperBound

insert into @indextable(nid) select gid from TGongwen where fariqi >dateadd(day,-365,getdate()) order by fariqi desc

select O.gid,O.mid,O.title,O.fadanwei,O.fariqi from TGongwen O,@indextable t where O.gid=t.nid

and t.id>@PageLowerBound and t.id<=@PageUpperBound order by t.id

end

set nocount off

以上存儲過程運用了SQL SERVER的最新技術――表變量。應該說這個存儲過程也是一個非常優秀的分頁存儲過程。當然,在這個過程中,您也可以把其中的表變量寫成臨時表:CREATE TABLE #Temp。但很明顯,在SQL SERVER中,用臨時表是沒有用表變量快的。所以筆者剛開始使用這個存儲過程時,感覺非常的不錯,速度也比原來的ADO的好。但後來,我又發現了比此方法更好的方法。

筆者曾在網上看到了一篇小短文《從數據表中取出第n條到第m條的記錄的方法》,全文如下:

publish 表中取出第 n 條到第 m 條的記錄:
SELECT TOP m-n+1 *
FROM publish
WHERE (id NOT IN
(SELECT TOP n-1 id
FROM publish))

id
publish 表的關鍵字

我當時看到這篇文章的時候,真的是精神爲之一振,覺得思路非常得好。等到後來,我在作辦公自動化系統(ASP.net+ C#SQL SERVER)的時候,忽然想起了這篇文章,我想如果把這個語句改造一下,這就可能是一個非常好的分頁存儲過程。於是我就滿網上找這篇文章,沒想到,文章還沒找到,卻找到了一篇根據此語句寫的一個分頁存儲過程,這個存儲過程也是目前較爲流行的一種分頁存儲過程,我很後悔沒有爭先把這段文字改造成存儲過程:

CREATE PROCEDURE pagination2
(
@SQL nVARCHAR(4000),
  --不帶排序語句的SQL語句
@Page int,
       --頁碼
@RecsPerPage int,
    --每頁容納的記錄數
@ID VARCHAR(255),
    --需要排序的不重複的ID
@Sort VARCHAR(255)
   --排序字段及規則
)
AS

DECLARE @Str nVARCHAR(4000)

SET @Str='SELECT
  TOP '+CAST(@RecsPerPage AS VARCHAR(20))+' * FROM ('+@SQL+') T WHERE T.'+@ID+'NOT IN
(SELECT
  TOP '+CAST((@RecsPerPage*(@Page-1)) AS VARCHAR(20))+' '+@ID+' FROM ('+@SQL+') T9 ORDER BY '+@Sort+') ORDER BY '+@Sort

PRINT @Str

EXEC sp_ExecuteSql @Str
GO

其實,以上語句可以簡化爲:

SELECT TOP
頁大小 *

FROM Table1

WHERE (ID NOT IN

(SELECT TOP
頁大小*頁數 id

FROM


ORDER BY id))

ORDER BY ID

但這個存儲過程有一個致命的缺點,就是它含有NOT IN字樣。雖然我可以把它改造爲:

SELECT TOP
頁大小 *

FROM Table1

WHERE not exists

(select * from (select top (
頁大小*頁數) * from table1 order by id) b where b.id=a.id )

order by id

即,用not exists來代替not in,但我們前面已經談過了,二者的執行效率實際上是沒有區別的。

既便如此,用TOP 結合NOT IN的這個方法還是比用遊標要來得快一些。

雖然用not exists並不能挽救上個存儲過程的效率,但使用SQL SERVER中的TOP關鍵字卻是一個非常明智的選擇。因爲分頁優化的最終目的就是避免產生過大的記錄集,而我們在前面也已經提到了TOP的優勢,通過TOP 即可實現對數據量的控制。

在分頁算法中,影響我們查詢速度的關鍵因素有兩點:TOPNOT INTOP可以提高我們的查詢速度,而NOT IN會減慢我們的查詢速度,所以要提高我們整個分頁算法的速度,就要徹底改造NOT IN,同其他方法來替代它。

我們知道,幾乎任何字段,我們都可以通過max(字段)min(字段)來提取某個字段中的最大或最小值,所以如果這個字段不重複,那麼就可以利用這些不重複的字段的maxmin作爲分水嶺,使其成爲分頁算法中分開每頁的參照物。在這裏,我們可以用操作符“>”“<”號來完成這個使命,使查詢語句符合SARG形式。如:

Select top 10 * from table1 where id>200

於是就有了如下分頁方案:

select top
頁大小 *

from table1

where id>

(select max (id) from

(select top ((
頁碼-1)*頁大小) id from table1 order by id) as T

)

order by id

在選擇即不重複值,又容易分辨大小的列時,我們通常會選擇主鍵。下表列出了筆者用有着1000萬數據的辦公自動化系統中的表,在以GIDGID是主鍵,但並不是聚集索引。)爲排序列、提取gid,fariqi,title字段,分別以第11010050010001萬、10萬、25萬、50萬頁爲例,測試以上三種分頁方案的執行速度:(單位:毫秒)

頁 碼
方案1
方案2
方案3

1
60
30
76

10
46
16
63

100
1076
720
130

500
540
12943
83

1000
17110
470
250

1

24796
4500
140

10

38326
42283
1553

25

28140
128720
2330

50

121686
127846
7168

從上表中,我們可以看出,三種存儲過程在執行100頁以下的分頁命令時,都是可以信任的,速度都很好。但第一種方案在執行分頁1000頁以上後,速度就降了下來。第二種方案大約是在執行分頁1萬頁以上後速度開始降了下來。而第三種方案卻始終沒有大的降勢,後勁仍然很足。

在確定了第三種分頁方案後,我們可以據此寫一個存儲過程。大家知道SQL SERVER的存儲過程是事先編譯好的SQL語句,它的執行效率要比通過WEB頁面傳來的SQL語句的執行效率要高。下面的存儲過程不僅含有分頁方案,還會根據頁面傳來的參數來確定是否進行數據總數統計。

--
獲取指定頁的數據

CREATE PROCEDURE pagination3

@tblName
  varchar(255),    -- 表名

@strGetFields varchar(1000) = '*',
 -- 需要返回的列

@fldName varchar(255)='',
   -- 排序的字段名

@PageSize
  int = 10,     -- 頁尺寸

@PageIndex
 int = 1,      -- 頁碼

@doCount
 bit = 0,  -- 返回記錄總數, 0 值則返回

@OrderType bit = 0,
 -- 設置排序類型, 0 值則降序

@strWhere
 varchar(1500) = '' -- 查詢條件 (注意: 不要加 where)

AS

declare @strSQL
  varchar(5000)    -- 主語句

declare @strTmp
  varchar(110)    -- 臨時變量

declare @strOrder varchar(400)
    -- 排序類型

if @doCount != 0

begin

if @strWhere !=''

set @strSQL = "select count(*) as Total from [" + @tblName + "] where "+@strWhere

else

set @strSQL = "select count(*) as Total from [" + @tblName + "]"

end

--
以上代碼的意思是如果@doCount傳遞過來的不是0,就執行總數統計。以下的所有代碼都是@doCount0的情況

else

begin

if @OrderType != 0

begin

set @strTmp = "<(select min"

set @strOrder = " order by [" + @fldName +"] desc"

--
如果@OrderType不是0,就執行降序,這句很重要!

end

else

begin

set @strTmp = ">(select max"

set @strOrder = " order by [" + @fldName +"] asc"

end

if @PageIndex = 1

begin

if @strWhere != ''

set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ "
 from [" + @tblName + "] where " + @strWhere + " " + @strOrder

else

set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ "
 from ["+ @tblName + "] "+ @strOrder

--
如果是第一頁就執行以上代碼,這樣會加快執行速度

end

else

begin

--
以下代碼賦予了@strSQL以真正執行的SQL代碼

set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ "
 from ["

+ @tblName + "] where [" + @fldName + "]" + @strTmp + "(["+ @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["+ @fldName + "] from [" + @tblName + "]" + @strOrder + ") as tblTmp)"+ @strOrder

if @strWhere != ''

set @strSQL = "select top " + str(@PageSize) +" "+@strGetFields+ "
 from ["

+ @tblName + "] where [" + @fldName + "]" + @strTmp + "(["

+ @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["

+ @fldName + "] from [" + @tblName + "] where " + @strWhere + " "

+ @strOrder + ") as tblTmp) and " + @strWhere + " " + @strOrder

end

end

exec (@strSQL)

GO

上面的這個存儲過程是一個通用的存儲過程,其註釋已寫在其中了。

在大數據量的情況下,特別是在查詢最後幾頁的時候,查詢時間一般不會超過9秒;而用其他存儲過程,在實踐中就會導致超時,所以這個存儲過程非常適用於大容量數據庫的查詢。

筆者希望能夠通過對以上存儲過程的解析,能給大家帶來一定的啓示,並給工作帶來一定的效率提升,同時希望同行提出更優秀的實時數據分頁算法。

四、聚集索引的重要性和如何選擇聚集索引
在上一節的標題中,筆者寫的是:實現小數據量和海量數據的通用分頁顯示存儲過程。這是因爲在將本存儲過程應用於辦公自動化系統的實踐中時,筆者發現這第三種存儲過程在小數據量的情況下,有如下現象:

1
、分頁速度一般維持在1秒和3秒之間。

2
、在查詢最後一頁時,速度一般爲5秒至8秒,哪怕分頁總數只有3頁或30萬頁。

雖然在超大容量情況下,這個分頁的實現過程是很快的,但在分前幾頁時,這個13秒的速度比起第一種甚至沒有經過優化的分頁方法速度還要慢,借用戶的話說就是還沒有ACCESS數據庫速度快,這個認識足以導致用戶放棄使用您開發的系統。

筆者就此分析了一下,原來產生這種現象的癥結是如此的簡單,但又如此的重要:排序的字段不是聚集索引!

本篇文章的題目是:查詢優化及分頁算法方案。筆者只所以把查詢優化分頁算法這兩個聯繫不是很大的論題放在一起,就是因爲二者都需要一個非常重要的東西――聚集索引。

在前面的討論中我們已經提到了,聚集索引有兩個最大的優勢:

1
、以最快的速度縮小查詢範圍。

2
、以最快的速度進行字段排序。

1條多用在查詢優化時,而第2條多用在進行分頁時的數據排序。

而聚集索引在每個表內又只能建立一個,這使得聚集索引顯得更加的重要。聚集索引的挑選可以說是實現查詢優化高效分頁的最關鍵因素。

但要既使聚集索引列既符合查詢列的需要,又符合排序列的需要,這通常是一個矛盾。

筆者前面索引的討論中,將fariqi,即用戶發文日期作爲了聚集索引的起始列,日期的精確度爲。這種作法的優點,前面已經提到了,在進行劃時間段的快速查詢中,比用ID主鍵列有很大的優勢。

但在分頁時,由於這個聚集索引列存在着重複記錄,所以無法使用maxmin來最爲分頁的參照物,進而無法實現更爲高效的排序。而如果將ID主鍵列作爲聚集索引,那麼聚集索引除了用以排序之外,沒有任何用處,實際上是浪費了聚集索引這個寶貴的資源。

爲解決這個矛盾,筆者後來又添加了一個日期列,其默認值爲getdate()。用戶在寫入記錄時,這個列自動寫入當時的時間,時間精確到毫秒。即使這樣,爲了避免可能性很小的重合,還要在此列上創建UNIQUE約束。將此日期列作爲聚集索引列。

有了這個時間型聚集索引列之後,用戶就既可以用這個列查找用戶在插入數據時的某個時間段的查詢,又可以作爲唯一列來實現maxmin,成爲分頁算法的參照物。

經過這樣的優化,筆者發現,無論是大數據量的情況下還是小數據量的情況下,分頁速度一般都是幾十毫秒,甚至0毫秒。而用日期段縮小範圍的查詢速度比原來也沒有任何遲鈍。

聚集索引是如此的重要和珍貴,所以筆者總結了一下,一定要將聚集索引建立在:

1
、您最頻繁使用的、用以縮小查詢範圍的字段上;

2
、您最頻繁使用的、需要排序的字段上。
應用程序設計
應用程序設計在決定使用 Microsoft® SQL Server™ 2000 的系統的性能方面起關鍵作用。將客戶端視爲控制實體而非數據庫服務器。客戶端確定查詢類型、何時提交查詢以及如何處理查詢結果。這反過來對服務器上的鎖類型和持續時間、I/O 活動量以及處理 (CPU) 負荷等產生主要影響,並由此影響總體性能的優劣。
正因爲如此,在應用程序的設計階段做出正確決策十分重要。然而,即使在使用總控應用程序時(這種情況下似乎不可能更改客戶端應用程序)出現性能問題,也不會改變影響性能的根本因素:客戶端具有支配作用,如果不更改客戶端則許多性能問題都無法解決。設計優秀的應用程序允許 SQL Server 支持成千上萬的併發用戶。反之,設計差的應用程序會防礙即使是最強大的服務器平臺處理少數用戶的請求。
客戶端應用程序的設計準則包括:
·                     消除過多的網絡流量。
客戶端和 SQL Server 之間的網絡往返通常是數據庫應用程序性能較差的首要原因,甚至超過了服務器和客戶端之間傳送的數據量這一因素的影響。網絡往返描述在客戶端應用程序和 SQL Server 之間爲每個批處理和結果集發送的會話流量。通過使用存儲過程,可以將網絡往返減到最小。例如,如果應用程序根據從 SQL Server 收到的數據值採取不同的操作,只要可能就應直接在存儲過程中做出決定,從而消除過多的網絡流量。
如果存儲過程中有多個語句,則默認情況下,SQL Server 在每個語句完成時給客戶端應用程序發送一條消息,詳細說明每個語句所影響的行數。大多數應用程序不需要這些消息。如果確信應用程序不需要它們,可以禁用這些消息,以提高慢速網絡的性能。請使用 SET NOCOUNT 會話設置爲應用程序禁用這些消息。有關更多信息,請參見 SET NOCOUNT
·                     使用小結果集。
檢索沒必要大的結果集(如包含上千行)並在客戶端瀏覽將增加 CPU 和網絡 I/O 的負載,使應用程序的遠程使用能力降低並限制多用戶可伸縮性。最好將應用程序設計爲提示用戶輸入足夠的信息,以便查詢提交後生成大小適中的結果集。有關更多信息,請參見使用高效數據檢索優化應用程序性能
可幫助實現上述目標的應用程序設計技術包括:在生成查詢時對通配符進行控制,強制某些輸入字段,不允許特殊查詢,以及使用 TOPPERCENT SET ROWCOUNT Transact-SQL 語句限制查詢返回的行數。有關更多信息,請參見使用 TOP PERCENT 限制結果集 SET ROWCOUNT
·                     允許在用戶需要重新控制應用程序時取消正在執行的查詢。
應用程序決不應強迫用戶重新啓動客戶機以取消查詢。無視這一點將導致無法解決的性能問題。如果應用程序取消查詢(例如使用開放式數據庫連接 (ODBC) sqlcancel 函數取消查詢),應對事務級別予以適當的考慮。例如,取消查詢並不會提交或回滾用戶定義的事務。取消查詢後,所有在事務內獲取的鎖都將保留。因此,在取消查詢後始終要提交或回滾事務。同樣的情況也適用於可用於取消查詢的 DB-Library 和其它應用程序接口 (API)
·                     始終實現查詢或鎖定超時。
不要讓查詢無限期運行。調用適當的 API 以設置查詢超時。例如,使用 ODBC SQLSetStmtOption 函數。
有關設置查詢超時的更多信息,請參見 ODBC API 文檔。
有關設置鎖定超時的更多信息,請參見自定義鎖超時
·                     不要使用不允許顯式控制發送到 SQL Server SQL 語句的應用程序開發工具。
如果工具基於更高級的對象透明地生成 Transact-SQL 語句,而且不提供諸如查詢取消、查詢超時和完全事務控制等關鍵功能,則不要使用這類工具。如果應用程序生成透明的 SQL 語句,通常不可能維護好的性能或解決性能問題,因爲在這種情況下不允許對事務和鎖定問題進行顯式控制,而這一點對性能狀況至關重要。
·                     不要將決策支持和聯機事務處理 (OLTP) 查詢混在一起。有關更多信息,請參見聯機事務處理與決策支持
·                     只在必要時才使用遊標。
遊標是關係數據庫中的有用工具,但使用遊標完成任務始終比使用面向集合的 SQL 語句花費多。
當使用面向集合的 SQL 語句時,客戶端應用程序讓服務器更新滿足指定條件的記錄集。服務器決定如何作爲單個工作單元完成更新。當通過遊標更新時,客戶端應用程序要求服務器爲每行維護行鎖或版本信息,而這只是爲了客戶端在提取行後請求更新行。
而且,使用遊標意味着服務器通常要在臨時存儲中維護客戶端的狀態信息,如用戶在服務器上的當前行集。爲衆多客戶端維護這類狀態信息需消耗大量的服務器資源。對於關係數據庫,更好的策略是讓客戶端應用程序快速進出,以便在各次調用之間不在服務器上維護客戶端的狀態信息。面向集合的 SQL 語句支持此策略。
然而,如果查詢使用遊標,請確定如果使用更高效的遊標類型(如快速只進遊標)或單個查詢能否更高效地編寫遊標查詢。有關更多信息,請參見使用高效數據檢索優化應用程序性能
·                     使事務儘可能簡短。有關更多信息,請參見事務和批處理對應用程序性能的影響
·                     使用存儲過程。有關更多信息,請參見存儲過程對應用程序性能的影響
·                     使用 Prepared Execution 來執行參數化 SQL 語句。有關更多信息,請參見 Prepared Execution (ODBC)
·                     始終處理完所有結果。
不要設計或使用在未取消查詢時就停止處理結果行的應用程序。否則通常會導致阻塞和降低性能。有關更多信息,請參見瞭解和避免阻塞
·                     確保將應用程序設計爲可避免死鎖。有關更多信息,請參見將死鎖減至最少
·                     確保已設置所有能夠優化分佈式查詢性能的適當選項。有關更多信息,請參見優化分佈式查詢
在應用系統的設計中,要着重考慮以下幾點:
  1.合理使用索引
索引是數據庫中重要的數據結構,它的根本目的就是爲了提高查詢效率。現在大多數的數據庫產品都採用IBM最先提出的ISAM索引結構。索引的使用要恰到好處,其使用原則如下:
在經常進行連接,但是沒有指定爲外鍵的列上建立索引,而不經常連接的字段則由優化器自動生成索引。
在頻繁進行排序或分組(即進行group byorder by操作)的列上建立索引。
在條件表達式中經常用到的不同值較多的列上建立檢索,在不同值少的列上不要建立索引。比如在僱員表的性別列上只有兩個不同值,因此就無必要建立索引。如果建立索引不但不會提高查詢效率,反而會嚴重降低更新速度。
如果待排序的列有多個,可以在這些列上建立複合索引(compound index)。
使用系統工具。如Informix數據庫有一個tbcheck工具,可以在可疑的索引上進行檢查。在一些數據庫服務器上,索引可能失效或者因爲頻繁操作而使得讀取效率降低,如果一個使用索引的查詢不明不白地慢下來,可以試着用tbcheck工具檢查索引的完整性,必要時進行修復。另外,當數據庫表更新大量數據後,刪除並重建索引可以提高查詢速度。
2.避免或簡化排序
應當簡化或避免對大型表進行重複的排序。當能夠利用索引自動以適當的次序產生輸出時,優化器就避免了排序的步驟。以下是一些影響因素:
索引中不包括一個或幾個待排序的列;
●group byorder by子句中列的次序與索引的次序不一樣;
排序的列來自不同的表。
爲了避免不必要的排序,就要正確地增建索引,合理地合併數據庫表(儘管有時可能影響表的規範化,但相對於效率的提高是值得的)。如果排序不可避免,那麼應當試圖簡化它,如縮小排序的列的範圍等。
3.消除對大型錶行數據的順序存取
在嵌套查詢中,對錶的順序存取對查詢效率可能產生致命的影響。比如採用順序存取策略,一個嵌套3層的查詢,如果每層都查詢1000行,那麼這個查詢就要查詢10億行數據。避免這種情況的主要方法就是對連接的列進行索引。例如,兩個表:學生表(學號、姓名、年齡……)和選課表(學號、課程號、成績)。如果兩個表要做連接,就要在學號這個連接字段上建立索引。
還可以使用並集來避免順序存取。儘管在所有的檢查列上都有索引,但某些形式的where子句強迫優化器使用順序存取。下面的查詢將強迫對orders表執行順序操作:
SELECT FROM orders WHERE (customer_num=104 AND order_num>1001) OR order_num=1008
雖然在customer_numorder_num上建有索引,但是在上面的語句中優化器還是使用順序存取路徑掃描整個表。因爲這個語句要檢索的是分離的行的集合,所以應該改爲如下語句:
SELECT FROM orders WHERE customer_num=104 AND order_num>1001
UNION
SELECT FROM orders WHERE order_num=1008
這樣就能利用索引路徑處理查詢。
4.避免相關子查詢
一個列的標籤同時在主查詢和where子句中的查詢中出現,那麼很可能當主查詢中的列值改變之後,子查詢必須重新查詢一次。查詢嵌套層次越多,效率越低,因此應當儘量避免子查詢。如果子查詢不可避免,那麼要在子查詢中過濾掉儘可能多的行。
5.避免困難的正規表達式
MATCHESLIKE關鍵字支持通配符匹配,技術上叫正規表達式。但這種匹配特別耗費時間。例如:SELECT FROM customer WHERE zipcode LIKE “98_ _ _”
即使在zipcode字段上建立了索引,在這種情況下也還是採用順序掃描的方式。如果把語句改爲SELECT FROM customer WHERE zipcode >“98000”,在執行查詢時就會利用索引來查詢,顯然會大大提高速度。
另外,還要避免非開始的子串。例如語句:SELECT FROM customer WHERE zipcode[23] >“80”,在where子句中採用了非開始子串,因而這個語句也不會使用索引。
6.使用臨時表加速查詢
把表的一個子集進行排序並創建臨時表,有時能加速查詢。它有助於避免多重排序操作,而且在其他方面還能簡化優化器的工作。例如:
SELECT cust.namercvbles.balance……other columns
FROM custrcvbles
WHERE cust.customer_id = rcvlbes.customer_id
AND rcvblls.balance>0
AND cust.postcode>“98000”
ORDER BY cust.name
如果這個查詢要被執行多次而不止一次,可以把所有未付款的客戶找出來放在一個臨時文件中,並按客戶的名字進行排序:
SELECT cust.namercvbles.balance……other columns
FROM custrcvbles
WHERE cust.customer_id = rcvlbes.customer_id
優化實用工具和工具性能
可在生產數據庫上執行以獲得最佳性能收益的三個操作包括:
·                     備份和還原操作。
·                     將數據大容量複製到表中。
·                     執行數據庫控制檯命令 (DBCC) 操作。
一般情況下,不需要優化這些操作。然而,在性能很關鍵的情形中,可採用一些技巧優化性能。
 Microsoft SQL Server數據庫內核用1個基於費用的查詢優化器自動優化向SQL提交的數據查詢操作。數據操作查詢是指支持SQL關鍵字WHEREHAVING的查詢,如SELECTDELETEUPDATE。基於費用的查詢優化器根據統計信息產生子句的費用估算。
  瞭解優化器數據處理過程的簡單方法是檢測SHOWPLAN命令的輸出結果。如果用基於字符的工具(例如isql),可以通過鍵入SHOW SHOWPLAN ON來得到SHOWPLAN命令的輸出。如果使用圖形化查詢,比如SQL Enterprise Manager中的查詢工具或isql/w,可以設定配置選項來提供這一信息。
 SQL Server的優化通過3個階段完成:查詢分析、索引選擇、合併選擇:
1.查詢分析
  在查詢分析階段,SQL Server優化器查看每一個由正規查詢樹代表的子句,並判斷它是否能被優化。SQL Server一般會盡量優化那些限制掃描的子句。例如,搜索和/或合併子句。但是不是所有合法的SQL語法都可以分成可優化的子句,如含有SQL不等關係符“<>”的子句。因爲“<>”1個排斥性的操作符,而不是1個包括性的操作符,所在掃描整個表之前無法確定子句的選擇範圍會有多大。當1個關係型查詢中含有不可優化的子句時,執行計劃用表掃描來訪問查詢的這個部分,對於查詢樹中可優化的SQL Server子句,則由優化器執行索引選擇。
2.索引選擇
  對於每個可優化的子句,優化器都查看數據庫系統表,以確定是否有相關的索引能用於訪問數據。只有當索引中的列的1個前綴與查詢子句中的列完全匹配時,這個索引才被認爲是有用的。因爲索引是根據列的順序構造的,所以要求匹配是精確的匹配。對於分簇索引,原來的數據也是根據索引列順序排序的。想用索引的次要列訪問數據,就像想在電話本中查找所有姓爲某個姓氏的條目一樣,排序基本上沒有什麼用,因爲你還是得查看每一行以確定它是否符合條件。如果1個子句有可用的索引,那麼優化器就會爲它確定選擇性。
  所以在設計過程中,要根據查詢設計準則仔細檢查所有的查詢,以查詢的優化特點爲基礎設計索引。
  (1)比較窄的索引具有比較高的效率。對於比較窄的索引來說,每頁上能存放較多的索引行,而且索引的級別也較少。所以,緩存中能放置更多的索引頁,這樣也減少了I/O操作。
  (2)SQL Server優化器能分析大量的索引和合並可能性。所以與較少的寬索引相比,較多的窄索引能向優化器提供更多的選擇。但是不要保留不必要的索引,因爲它們將增加存儲和維護的開支。對於複合索引、組合索引或多列索引,SQL Se
優化服務器性能
Microsoft® SQL Server™ 2000 自動調整很多服務器配置選項,因此係統管理員只需做很少的調整(如果有)。這些配置選項可以由系統管理員修改,但一般建議保留爲默認值,以使 SQL Server 能根據運行時的情況自動對自身進行調整。
不過,如果需要,可以配置下列組件以優化服務器性能:
·                     SQL Server 內存
·                     I/O 子系統
·                     Microsoft Windows NT® 選項
MSSQL是怎樣使用內存的:

  最大的開銷一般是用於數據緩存,如果內存足夠,它會把用過的數據和覺得你會用到的數據統統扔到內存中,直到內存不足的時候,才把命中率低的數據給清掉。所以一般我們在看statistics io的時候,看到的physics read都是0

  其次就是查詢的開銷,一般地說,hash join是會帶來比較大的內存開銷的,而merge joinnested loop的開銷比較小,還有排序和中間表、遊標也是會有比較大的開銷的。

  所以用於關聯和排序的列上一般需要有索引。

  再其次就是對執行計劃、系統數據的存儲,這些都是比較小的。
  我們先來看數據緩存對性能的影響,如果系統中沒有其它應用程序來爭奪內存,數據緩存一般是越多越好,甚至有些時候我們會強行把一些數據pin在高速緩存中。但是如果有其它應用程序,雖然在需要的時候MSSQL會釋放內存,但是線程切換、IO等待這些工作也是需要時間的,所以就會造成性能的降低。這樣我們就必須設置MSSQL的最大內存使用。可以在SQL Server 屬性(內存選項卡)中找到配置最大使用內存的地方,或者也可以使用sp_configure來完成。如果沒有其它應用程序,那麼就不要限制MSSQL對內存的使用。
  然後來看查詢的開銷,這個開銷顯然是越低越好,因爲我們不能從中得到好處,相反,使用了越多的內存多半意味着查詢速度的降低。所以我們一般要避免中間表和遊標的使用,在經常作關聯和排序的列上建立索引。
不更改代碼的情況下如何優化數據庫系統
這個問題很多DBA可能都碰到過吧:比如剛接手一箇舊有系統,原來的廠商不允許對代碼修改,或者是系統應用比較關鍵。不允許作修改,或者是源代碼出於商業目的,進行了一定程度的加密,還有的時候可能是行政因素--領導爲了避免責任,不允許你這樣做,但這個時候,系統的性能上的問題還比較嚴重,還有其他辦法怎麼對系統進行優化麼?
在這裏我嘗試總結一下可能有的途徑。
針對特定的SQL進行"外科手術" (Metalink 122812.1)改進執行計劃
·                                 更新統計信息 (調整採樣率/柱狀圖統計)
·                                 調整索引 (添加或調整合適的索引,刪除不必要的索引)
·                                 創建物化試圖(用空間開銷來換取時間收益)
優化OS和數據庫以外的其他東西
首先優化操作系統-比如核心參數的合理調整,操作系統資源的合理分配; 磁盤IO的調整,這是很重要的一部分,因爲磁盤IO速度很容易造成系統瓶頸;網絡資源的優化-TCP/IP的參數調整;
調整Oracle初始化參數
優化器模式的設定,db_cache 參數等設定,sga 大小等參數設定,都對數據庫性能有着重要的影響。
合理的系統資源調度
在一些批處理操作爲主的系統中,系統資源的調度是比較重要的,調度不合理,很容易造成資源爭用。有的系統可能在系統創建之初調度是比較合理的,經過一段時間運行之後,可能因爲數據量的變化,SQL語句的執行計劃變化等會造成操作時間上的重疊,這肯定會給系統帶來壓力上的問題。
調整數據庫對象
·                                 調整pctfree ,freelist ,存儲參數
·                                 調整表空間文件和數據庫對象(表、索引)的磁盤分佈。
·                                 cache 一些常用的數據庫對象。
系統Bug問題帶來的影響/升級改進性能
Oracle軟件Bug多多,系統運行初期有的Bug帶來的危害還不夠明顯,隨着時間的推移,個別的Bug會給系統性能造成問題。這個時候對系統的Bug 修復已經對數據庫系統進行升級就是必要的。通過升級,修正Oracle軟件缺陷,同時在升級後也可能會增強數據庫引擎的效率。當然,也要注意升級可能帶來的不良的影響。
·                    
                        操作系統相關優化
1. 操作系統性能的好壞直接影響數據庫的使用性能,如果操作系統存在問題,如CPU過載、過度內存交換、磁盤I/O瓶頸等,在這種情況下,單純進行數據庫內部性能調整是不會改善系統性能的。我們可以通過Windows NT的系統監視器(System Monitor)來監控各種設備,發現性能瓶頸。  CPU 一種常見的性能問題就是缺乏處理能力。系統的處理能力是由系統的CPU數量、類型和速度決定的。如果系統沒有足夠的CPU處理能力,它就不能足夠快地處理事務以滿足需要。我們可以使用System Monitor確定CPU的使用率,如果以75%或更高的速率長時間運行,就可能碰到了CPU瓶頸問題,這時應該升級CPU。但是升級前必須監視系統的其他特性,如果是因爲SQL語句效率非常低,優化語句就有助於解決較低的CPU利用率。而當確定需要更強的處理能力,可以添加CPU或者用更快的CPU 替換。  內存 SQL Server可使用的內存量是SQL Server性能最關鍵因素之一。而內存同I/O子系統的關係也是一個非常重要的因素。例如,在I/O操作頻繁的系統中,SQL Server用來緩存數據的可用內存越多,必須執行的物理I/O也就越少。這是因爲數據將從數據緩存中讀取而不是從磁盤讀取。同樣,內存量的不足會引起明顯的磁盤讀寫瓶頸,因爲系統緩存能力不足會引起更多的物理磁盤I/O  可以利用System Monitor檢查SQL ServerBuffer Cache Hit Ratio計數器,如果命中率經常低於90%,就應該添加更多的內存。  I/O子系統I/O子系統發生的瓶頸問題是數據庫系統可能遇到的最常見的同硬件有關的問題。配置很差的I/O子系統引起性能問題的嚴重程度僅次於編寫很差的SQL語句。I/O子系統問題是這樣產生的,一個磁盤驅動器能夠執行的I/O操作是有限的,一般一個普通的磁盤驅動器每秒只能處理85I/O操作,如果磁盤驅動器超載,到這些磁盤驅動器的I/O操作就要排隊,SQLI/O延遲將很長。這可能會使鎖持續的時間更長,或者使線程在等待資源的過程中保持空閒狀態,其結果就是整個系統的性能受到影響。
解決I/O子系統有關的問題也許是最容易的,多數情況下,增加磁盤驅動器就可以解決這個性能問題。 
 當然,影響性能的因素很多,而應用又各不相同,找出一個通用的優化方案是很困難的,只能是在系統開發和維護的過程中針對運行的具體情況,不斷加以調整。
2 與SQL Server相關的硬件系統
  與SQL Server有關的硬件設計包括系統處理器、內存、磁盤子系統和網絡,這4個部分基本上構成了硬件平臺,Windows NTSQL Server運行於其上。
2.1 系統處理器(CPU)
  根據自己的具體需要確定CPU結構的過程就是估計在硬件平臺上佔用CPU的工作量的過程。從以往的經驗看,CPU配置最少應是180586/100處理器。如果只有23個用戶,這就足夠了,但如果打算支持更多的用戶和關鍵應用,推薦採用Pentium ProPCPU
2.2 內存(RAM)
  爲SQL Server方案確定合適的內存設置對於實現良好的性能是至關重要的。SQL Server用內存做過程緩存、數據和索引項緩存、靜態服務器開支和設置開支。SQL Server最多能利用2GB虛擬內存,這也是最大的設置值。還有一點必須考慮的是Windows NT和它的所有相關的服務也要佔用內存。
  Windows NT爲每個WIN32應用程序提供了4GB的虛擬地址空間。這個虛擬地址空間由Windows NT虛擬內存管理器(VMM)映射到物理內存上,在某些硬件平臺上可以達到4GBSQL Server應用程序只知道虛擬地址,所以不能直接訪問物理內存,這個訪問是由VMM控制的。Windows NT允許產生超出可用的物理內存的虛擬地址空間,這樣當給SQL Server分配的虛擬內存多於可用的物理內存時,會降低SQL Server的性能。
  這些地址空間是專門爲SQL Server系統設置的,所以如果在同一硬件平臺上還有其它軟件(如文件和打印共享,應用程序服務等)在運行,那麼應該考慮到它們也佔用一部分內存。一般來說硬件平臺至少要配置32MB的內存,其中,Windows NT至少要佔用16MB1個簡單的法則是,給每一個併發的用戶增加100KB的內存。例如,如果有100個併發的用戶,則至少需要32MB+100用戶*100KB=42MB內存,實際的使用數量還需要根據運行的實際情況調整。可以說,提高內存是提高系統性能的最經濟的途徑。
 2.3 磁盤子系統
  設計1個好的磁盤I/O系統是實現良好的SQL Server方案的一個很重要的方面。這裏討論的磁盤子系統至少有1個磁盤控制設備和1個或多個硬盤單元,還有對磁盤設置和文件系統的考慮。智能型SCSI-2磁盤控制器或磁盤組控制器是不錯的選擇,其特點如下:
  (1)控制器高速緩存。  (2)總線主板上有處理器,可以減少對系統CPU的中斷。  (3)異步讀寫支持。  (4)32RAID支持。  (5)快速SCSI—2驅動。  (6)超前讀高速緩存(至少1個磁道)
3 檢索策略
  在精心選擇了硬件平臺,又實現了1個良好的數據庫方案,並且具備了用戶需求和應用方面的知識後,現在應該設計查詢和索引了。有2個方面對於在SQL Server上取得良好的查詢和索引性能是十分重要的,第1是根據SQL Server優化器方面的知識生成查詢和索引;2是利用SQL Server的性能特點,加強數據訪問操作。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章