SQL分區技術

SQL分區技術

 

 

你是否在千方百計優化SQL Server 數據庫的性能?如果你的數據庫中含有大量的表格,把這些表格分區放入獨立的文件組可能會讓你受益匪淺。SQL Server 2005引入的表分區技術,讓用戶能夠把數據分散存放到不同的物理磁盤中,提高這些磁盤的並行處理性能以優化查詢性能。

  SQL Server數據庫表分區操作過程由三個步驟組成:

  1. 創建分區函數

  2. 創建分區架構

  3. 對錶進行分區

  下面將對每個步驟進行詳細介紹。

  步驟一:創建一個分區函數

  此分區函數用於定義你希望SQL Server如何對數據進行分區的參數值(how)。這個操作並不涉及任何表格,只是單純的定義了一項技術來分割數據。

  我們可以通過指定每個分區的邊界條件來定義分區。例如,假定我們有一份Customers表,其中包含了關於所有客戶的信息,以一一對應的客戶編號(從1到1,000,000)來區分。我們將通過以下的分區函數把這個表分爲四個大小相同的分區:  

CREATE PARTITION FUNCTION customer_partfunc (int)
  AS RANGE RIGHT
  FOR VALUES (250000, 500000, 750000)

  這些邊界值定義了四個分區。第一個分區包括所有值小於250,000的數據,第二個分區包括值在250,000到49,999之間的數據。第三個分區包括值在500,000到7499,999之間的數據。所有值大於或等於750,000的數據被歸入第四個分區。

  請注意,這裏調用的"RANGE RIGHT"語句表明每個分區邊界值是右界。類似的,如果使用"RANGELEFT"語句,則上述第一個分區應該包括所有值小於或等於250,000的數據,第二個分區的數據值在250,001到500,000之間,以此類推。

  步驟二:創建一個分區架構

  一旦給出描述如何分割數據的分區函數,接着就要創建一個分區架構,用來定義分區位置(where)。創建過程非常直截了當,只要將分區連接到指定的文件組就行了。例如,如果有四個文件組,組名從"fg1"到"fg4",那麼以下的分區架構就能達到想要的效果:  

CREATE PARTITION SCHEME customer_partscheme
  AS PARTITION customer_partfunc
  TO (fg1, fg2, fg3, fg4)

  注意,這裏將一個分區函數連接到了該分區架構,但並沒有將分區架構連接到任何數據表。這就是可複用性起作用的地方了。無論有多少數據庫表,我們都可以使用該分區架構(或僅僅是分區函數)。

  步驟三:對一個表進行分區

  定義好一個分區架構後,就可以着手創建一個分區表了。這是整個分區操作過程中最簡單的一個步驟。只需要在表創建指令中添加一個"ON"語句,用來指定分區架構以及應用該架構的表列。因爲分區架構已經識別了分區函數,所以不需要再指定分區函數了。

  例如,使用以上的分區架構創建一個客戶表,可以調用以下的Transact-SQL指令:  

CREATE TABLE customers (FirstName nvarchar(40), LastName nvarchar(40), CustomerNumber int)
  ON customer_partscheme (CustomerNumber)

  關於SQL Server的表分區功能,你知道上述的相關知識就足夠了。記住!編寫能夠用於多個表的一般的分區函數和分區架構就能夠大大提高可複用性。

數據庫性能調優是每一個優秀SQL Server管理員最終的責任。雖然保證數據的安全和可用性是我們的最高的目標,但是假如數據庫應用程序無法滿足用戶的要求,那麼DBA們會因爲性能低下的設計和實現而受到指責。SQL Server 2005在數據庫性能方面得到了很多提高,尤其是表分區的技術。如果你還沒不瞭解表分區的特徵,那麼請你花點時間讀這篇文章。

  表分區的概念不是一個新的概念;只要你當過一段時間的SQL Server DBA,那麼你可能已經對一些頻繁訪問的表進行過歸檔,當這個表中的歷史數據變的不再經常被訪問的時候。比如,假設你有一個打印時間報表的應用,你的報告很少會查詢1995年的數據,因爲絕大部分的預算規劃會基於最近幾年的數據。

  在SQL Server的早期版本中,你可以創建多個表。每一個表都具有相同的列結構,用來保存不同年份的數據。這樣,當存在着對歷史數據訪問的必要的時候,你可以創建一個視圖來對這些表進行查詢處理。將數據保存在多個表中是很方便的,因爲相對於查詢時掃描整個大表,掃描小表會更快。但是這種好處只有在你預先知道哪些時間段的數據會被訪問。同時,一旦數據過期,你還需要創建新表並且轉移新產生的歷史數據。

  SQL Server 7和SQL Server 2000支持分佈式分區視圖(distributedpartitioned views,又稱爲物化視圖,materialized views).分佈式分區視圖由分佈於多臺服務器上的、具有相同表結構的表構成,而且你還需要爲每一個服務器增加鏈接服務器定義(linked serverdefinitions),最後在其中一臺服務器上創建一個視圖將每臺服務器上返回的數據合併起來。這裏的設計思想是數據庫引擎可以利用多臺服務器的處理能力來滿足查詢。

  但是,分佈式分區視圖(DPV)受到很多限制,你可以在SQL Server的在線幫助文檔中閱讀到。雖然DPV在一些情況下能夠提供性能上的提高,但是這種技術不能被廣泛的應用。已經被證明它們不能滿足逐步增長的企業級應用的要求。何況,DPV的實現是一個費力的過程,需要DBA進行很多工作。

  SQL Server 2005開始支持表分區,這種技術允許所有的表分區都保存在同一臺服務器上。每一個表分區都和在某個文件組(filegroup)中的單個文件關聯。同樣的一個文件/文件組可以容納多個分區表。

  在這種設計架構下,數據庫引擎能夠判定查詢過程中應該訪問哪個分區,而不用掃描整個表。如果查詢需要的數據行分散在多個分區中,SQL Server使用多個處理器對多個分區進行並行查詢。你可以爲在創建表的時候就定義分區的索引。 對小索引的搜索或者掃描要比掃描整個表或者一張大表上的索引要快很多。因此,當對大表進行查詢,表分區可以產生相當大的性能提升。

  現在讓我們通過一個簡單的例子來了解表分區是如何發揮作用的。在這篇文章中,我不想深入到分區的語法細節當中,這些你可以在SQL Server的在線幫助文檔中找到。下面的例子基於存儲着一個時間報表系統的數據的數據倉庫。除了默認的文件組,我另外創建了7個文件組,每一個文件組僅包含一個文件,這個文件將存儲由分區函數定義的一部分數據。

  爲了測試表分區的性能提升,我向這個分區表中插入了一千五百萬行,同時向另外一個具有相同表結構、但是沒有進行分區的表插入了同樣的數據。對分區表執行的INSERT語句運行的更快一些。甚至在我的內存不到1G的筆記本電腦上,對分區表的INSERT語句比不分區的表的INSERT語句要快上三倍。當然,查詢的執行時間依據硬件資源的差異而所有變化,但是你還是能夠在你的環境中感到不同程度的提升。

  我將檢查更深入了一步,通過分別檢查同一條返回所有行的、簡單SELECT語句在分區表和非分區表上的執行計劃,返回的數據範圍通過WHERE語句來指定。同一條語句在這兩個不同的表上有不同的執行計劃。對於分區表的查詢顯示出一個嵌套的循環和索引的掃描。從本質上來說,SQL Server將兩個分區視爲獨立的表,因此使用一個嵌套循環將它們連接起來。對非分區的表的同一個查詢則使用索引掃描來返回同樣的列。當你使用同樣的分區策略創建多個表,同時在查詢中連接這些表,那麼性能上的提升會更加明顯

你可以使用下面的查詢來了解每一個分區中的行的個數:

  SELECT $PARTITION.TimeEntryDateRangePFN(time_entry_date) AS Partition,
  COUNT(*) AS [COUNT] FROM fact_time_entry
  GROUP BY $PARTITION.TimeEntryDateRangePFN(time_entry_date)
  ORDER BY Partition

  表分區對交易環境和數據倉庫環境來說,都是一個重要的特徵。數據倉庫用戶最主要的抱怨是移動事實表(fact table)會花費太多時間。當裝載數據到事實表的時候,用戶查詢(立方體處理查詢)的性能會明顯下降,甚至是完全無法成功。因此,裝載大量的數據到事實表的時候常常需要停機。如果使用表分區,就不再出現這樣的情況——確切的講,你一眨眼的工夫就可以移動事實表。爲了演示這是如何生效的,我使用上面例子中相同的分區函數和表結構來創建一個新的表,這個表叫做fact_time_entry2。表的主鍵從五千萬開始,這樣fact_time_entry2就不會包含表fact_time_entry中已經有的數據。

  現在我把2007年的數據移動到這張fact_time_entry2中。同時讓我們假設fact_time_entry表中包含着2007年之前的數據。在fact_time_entry2表完成數據的轉移,我執行下面的語句:

  ALTER TABLE fact_time_entry2
  SWITCH PARTITION 8 TO fact_time_entry PARTITION 8

  這條語句將編號爲8的分區,這個分區恰好包含着2007年的數據,從fact_time_entry2移動到了fact_time_entry表中,在我的筆記本電腦上,這個過程只花費了3毫秒。在這短短的3毫秒中,我的事實表就增加了五百萬條記錄!的確,我需要在交換分區之前,將數據移動到中間表,但是我的用戶不需要擔心——事實表隨時都可以查詢!在這幕後,實際上沒有數據移動——只是兩張表的元數據發生了變化。

  我可以使用類似的查詢刪除事實表中不在需要的數據。例如,假設我們決定我們不再關心2004年的記錄。下面的語句可以將這些記錄轉移到我們創建的工作表中:

  ALTER TABLE fact_time_entry
  SWITCH PARTITION 2 TO fact_time_entry2 PARTITION 2

  這樣的語句依舊在毫秒級內完成了。現在,我可以刪除fact_time_entry2或者將它移到其他的服務器上。我的事實表不會包含2004年的任何記錄。這個分區還是需要在目的表中存在,而且它必須是空的。你不能將分區轉移到一個包含重複數據的表中。源表和目的表的分區必須一致,同時被轉移的數據必須在同一個文件組中。即使受到這麼多的限制,轉換分區和無需停機就可以移動數據表的功能必將讓數據倉庫的實現變的前所未有的輕鬆。

SQL Server 表分區(partitioned table/Data Partitioning)
  Partitioned Table
  可伸縮性性是數據庫管理系統的一個很重要的方面,在SQL Server 2005中可伸縮性方面提供了表分區功能。
  其實對於有關係弄數據庫產品來說,對錶、數據庫和服務器進行數據分區的從而提供大數據量的支持並不是什麼新鮮事,但 SQL Server 2005 提供了一個新的體系結構功能,用於對數據庫中的文件組進行表分區。水平分區可根據分區架構,將一個表劃分爲幾個較小的分組。表分區功能是針對超大型數據庫(從數百吉字節到數千吉字節或更大)而設計的。超大型數據庫 (VLDB) 查詢性能通過分區得到了改善。通過對廣大分區列值進行分區,可以對數據的子集進行管理,並將其快速、高效地重新分配給其他表。
  設想一個大致的電子交易網站,有一個表存儲了此網站的歷史交易數據,這此數據量可能有上億條,在以前的SQL Server版本中存儲在一個表中不管對於查詢性能還是維護都是件麻煩事,下面我們來看一下在SQL Server2005怎麼提高性能和可管理性:
  -- 創建要使用的測試數據庫,Demo
  USE [master]
  IF EXISTS (SELECT name FROMmaster.dbo.sysdatabases WHERE name = N'DEMO')
  DROP DATABASE [DEMO]
  CREATE DATABASE [DEMO]
  --由於表分區使用使用新的體系結構,使用文件組來進行表分區,所以我們創建將要用到的6個文件組,來存儲6個時間段的交易數據[<2000],[2001], [2002], [2003], [2004], [>2005]
  ALTER DATABASE Demo ADD FILEGROUP YEARFG1;
  ALTER DATABASE Demo ADD FILEGROUP YEARFG2;
  ALTER DATABASE Demo ADD FILEGROUP YEARFG3;
  ALTER DATABASE Demo ADD FILEGROUP YEARFG4;
  ALTER DATABASE Demo ADD FILEGROUP YEARFG5;
  ALTER DATABASE Demo ADD FILEGROUP YEARFG6;
  -- 下面爲這些文件組添加文件來進行物理的數據存儲
  ALTER DATABASE Demo ADD FILE (NAME ='YEARF1', FILENAME = 'C:\ADVWORKSF1.NDF') TO FILEGROUP YEARFG1;
  ALTER DATABASE Demo ADD FILE (NAME ='YEARF2', FILENAME = 'C:\ADVWORKSF2.NDF') TO FILEGROUP YEARFG2;
  ALTER DATABASE Demo ADD FILE (NAME ='YEARF3', FILENAME = 'C:\ADVWORKSF3.NDF') TO FILEGROUP YEARFG3;
  ALTER DATABASE Demo ADD FILE (NAME ='YEARF4', FILENAME = 'C:\ADVWORKSF4.NDF') TO FILEGROUP YEARFG4;
  ALTER DATABASE Demo ADD FILE (NAME ='YEARF5', FILENAME = 'C:\ADVWORKSF5.NDF') TO FILEGROUP YEARFG5;
  ALTER DATABASE Demo ADD FILE (NAME ='YEARF6', FILENAME = 'C:\ADVWORKSF6.NDF') TO FILEGROUP YEARFG6;
  -- HERE WE ASSOCIATE THE PARTITION FUNCTIONTO
  -- THE CREATED FILEGROUP VIA A PARTITIONINGSCHEME
  USE DEMO;
  GO
  -------------------------------------------------------
  -- 創建分區函數
  -------------------------------------------------------
  CREATE PARTITION FUNCTION YEARPF(datetime)
  AS
  RANGE LEFT FOR VALUES ('01/01/2000'
   ,'01/01/2001'
   ,'01/01/2002'
   ,'01/01/2003'
   ,'01/01/2004')
  -------------------------------------------------------
  -- 創建分區架構
  -------------------------------------------------------
  CREATE PARTITION SCHEME YEARPS
  AS PARTITION YEARPF TO (YEARFG1,YEARFG2,YEARFG3,YEARFG4,YEARFG5,YEARFG6)
  -- 創建使用此Schema的表
  CREATE TABLE PARTITIONEDORDERS
  (
  ID INT NOT NULL IDENTITY(1,1),
  DUEDATE DATETIME NOT NULL,
  ) ON YEARPS(DUEDATE)
  --爲此表填充數據
  declare @DT datetime
  SELECT @DT = '1999-01-01'
  --start looping, stop at ending date
  WHILE (@DT <= '2005-12-21')
  BEGIN
   INSERT INTO PARTITIONEDORDERS VALUES(@DT)
   SET @DT=dateadd(yy,1,@DT)
  END
  -- 現在我們可以看一下我們剛纔插入的行都分佈在哪個Partition
  SELECT *, $PARTITION.YEARPF(DUEDATE) FROMPARTITIONEDORDERS
  --我們可以看一下我們現在PARTITIONEDORDERS表的數據存儲在哪此partition中,以及在這些分區中數據量的分佈
  SELECT * FROM SYS.PARTITIONS WHERE OBJECT_ID= OBJECT_ID('PARTITIONEDORDERS')
  --
  --現在我們設想一下,如果我們隨着時間的流逝,現在已經到了2005年,按照我們先前的設定,我們想再想入一個分區,這時是不是重新創建表分區架構然後重新把數據導放到新的分區架構呢,答案是完全不用。下面我們就看如果新加一個分區。
  --更改分區架構定義語言,讓下一個分區使用和現在已經存在的分區YEARFG6分區中,這樣此分區就存儲了兩段partition的數據。
  ALTER PARTITION SCHEME YEARPS
  NEXT USED YEARFG6;
  --更改分區函數
  ALTER PARTITION FUNCTION YEARPF()
  SPLIT RANGE ('01/01/2005')
  --現在我們可以看一下我們剛纔插入的行都分佈在哪個Partition?
  SELECT *, $PARTITION.YEARPF(DUEDATE) FROMPARTITIONEDORDERS
  --我們可以看一下我們現在PARTITIONEDORDERS表的數據存儲在哪此partition中,以及在這些分區中數據量的分佈
  SELECT * FROM SYS.PARTITIONS WHERE OBJECT_ID= OBJECT_ID('PARTITIONEDORDERS')

SQL Server 表分區(partitioned table/DataPartitioning) Partitioned Table 可伸縮性性是數據庫管理系統的一個很重要的方面,在SQL Server 2005中可伸縮性方面提供了表分區功能。 其實對於有關係弄數據庫產品來說,對錶、數據庫和服務器進行數據分區的從而提供大數據量的支持並不是什麼新鮮事,但 SQL Server 2005 提供了一個新的體系結構功能,用於對數據庫中的文件組進行表分區。水平分區可根據分區架構,將一個表劃分爲幾個較小的分組。表分區功能是針對超大型數據庫(從數百吉字節到數千吉字節或更大)而設計的。超大型數據庫 (VLDB) 查詢性能通過分區得到了改善。通過對廣大分區列值進行分區,可以對數據的子集進行管理,並將其快速、高效地重新分配給其他表。 設想一個大致的電子交易網站,有一個表存儲了此網站的歷史交易數據,這此數據量可能有上億條,在以前的SQL Server版本中存儲在一個表中不管對於查詢性能還是維護都是件麻煩事,下面我們來看一下在SQL Server2005怎麼提高性能和可管理性: -- 創建要使用的測試數據庫,Demo USE [master] IF EXISTS (SELECT name FROMmaster.dbo.sysdatabases WHERE name = N'DEMO') DROP DATABASE [DEMO] CREATEDATABASE [DEMO] --由於表分區使用使用新的體系結構,使用文件組來進行表分區,所以我們創建將要用到的6個文件組,來存儲6個時間段的交易數據[<2000],[ 2001],[2002], [2003], [2004], [>2005] ALTER DATABASE Demo ADD FILEGROUP YEARFG1;ALTER DATABASE Demo ADD FILEGROUP YEARFG2; ALTER DATABASE Demo ADD FILEGROUPYEARFG3; ALTER DATABASE Demo ADD FILEGROUP YEARFG4; ALTER DATABASE Demo ADDFILEGROUP YEARFG5; ALTER DATABASE Demo ADD FILEGROUP YEARFG6; -- 下面爲這些文件組添加文件來進行物理的數據存儲 ALTER DATABASE Demo ADD FILE (NAME = 'YEARF1', FILENAME ='C:\ADVWORKSF1.NDF') TO FILEGROUP YEARFG1; ALTER DATABASE Demo ADD FILE (NAME ='YEARF2', FILENAME = 'C:\ADVWORKSF2.NDF') TO FILEGROUP YEARFG2; ALTER DATABASEDemo ADD FILE (NAME = 'YEARF3', FILENAME = 'C:\ADVWORKSF3.NDF') TO FILEGROUPYEARFG3; ALTER DATABASE Demo ADD FILE (NAME = 'YEARF4', FILENAME ='C:\ADVWORKSF4.NDF') TO FILEGROUP YEARFG4; ALTER DATABASE Demo ADD FILE (NAME ='YEARF5', FILENAME = 'C:\ADVWORKSF5.NDF') TO FILEGROUP YEARFG5; ALTER DATABASEDemo ADD FILE (NAME = 'YEARF6', FILENAME = 'C:\ADVWORKSF6.NDF') TO FILEGROUPYEARFG6; -- HERE WE ASSOCIATE THE PARTITION FUNCTION TO -- THE CREATEDFILEGROUP VIA A PARTITIONING SCHEME USE DEMO; GO------------------------------------------------------- -- 創建分區函數 ------------------------------------------------------- CREATEPARTITION FUNCTION YEARPF(datetime) AS RANGE LEFT FOR VALUES ('01/01/2000','01/01/2001' ,'01/01/2002' ,'01/01/2003' ,'01/01/2004')------------------------------------------------------- -- 創建分區架構 ------------------------------------------------------- CREATEPARTITION SCHEME YEARPS AS PARTITION YEARPF TO (YEARFG1,YEARFG2,YEARFG3,YEARFG4,YEARFG5,YEARFG6) -- 創建使用此Schema的表 CREATE TABLE PARTITIONEDORDERS ( ID INT NOT NULLIDENTITY(1,1), DUEDATE DATETIME NOT NULL, ) ON YEARPS(DUEDATE) --爲此表填充數據 declare @DT datetime SELECT @DT = '1999-01-01' --start looping,stop at ending date WHILE (@DT <= '2005-12-21') BEGIN INSERT INTOPARTITIONEDORDERS VALUES(@DT) SET @DT=dateadd(yy,1,@DT) END -- 現在我們可以看一下我們剛纔插入的行都分佈在哪個Partition SELECT *, $PARTITION.YEARPF(DUEDATE) FROMPARTITIONEDORDERS --我們可以看一下我們現在PARTITIONEDORDERS表的數據存儲在哪此partition中,以及在這些分區中數據量的分佈 SELECT * FROMSYS.PARTITIONS WHERE OBJECT_ID = OBJECT_ID('PARTITIONEDORDERS') -- --現在我們設想一下,如果我們隨着時間的流逝,現在已經到了2005年,按照我們先前的設定,我們想再想入一個分區,這時是不是重新創建表分區架構然後重新把數據導放到新的分區架構呢,答案是完全不用。下面我們就看如果新加一個分區。 --更改分區架構定義語言,讓下一個分區使用和現在已經存在的分區YEARFG6分區中,這樣此分區就存儲了兩段partition的數據。 ALTER PARTITION SCHEME YEARPSNEXT USED YEARFG6; --更改分區函數 ALTER PARTITIONFUNCTION YEARPF() SPLIT RANGE ('01/01/2005') --現在我們可以看一下我們剛纔插入的行都分佈在哪個Partition? SELECT *, $PARTITION.YEARPF(DUEDATE)FROM PARTITIONEDORDERS --我們可以看一下我們現在PARTITIONEDORDERS表的數據存儲在哪此partition中,以及在這些分區中數據量的分佈 SELECT * FROMSYS.PARTITIONS WHERE OBJECT_ID = OBJECT_ID('PARTITIONEDORDERS')

這就是對數據庫的橫向分割,把一個大表表分割成不同的小表,並把小表並分步到不同的物理文件(由不同的物理磁盤構成)組上。這是做海量數據查詢的基礎理論,sql2000以前不支持這個技術,不過有個分區視圖有類似的功能。但是sql2005直接有這個功能,我想ax應該很快會支持這個功能。

其實,googl的查詢原理也是這樣的,它把每次查詢同時提交給googl的50萬臺服務器,每個服務器上只保留了不多的信息,這個每個服務器能很快的返回自己找到的數據,並提供給集羣服務器整理排序後顯示給用戶。理論上講,若有經濟條件,把數據分割成無限多個節點,這樣每個節點查詢自己的數據時的速度幾乎是不耗時的,這樣速度就會大大的加快

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

深入淺出理解索引結構

 

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

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

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

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

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

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

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

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

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

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

動作描述

使用聚集索引

使用非聚集索引

外鍵列

主鍵列

列經常被分組排序(order by)

返回某範圍內的數據

不應

小數目的不同值

不應

大數目的不同值

不應

頻繁更新的列

不應

頻繁修改索引列

不應

一個或極少不同值

不應

不應

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

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

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

  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 fromtgongwen

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

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

  select gid,fariqi,neibuyonghu,title fromTgongwen

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

  用時:53763毫秒(54秒)

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

  select gid,fariqi,neibuyonghu,title fromTgongwen

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

  用時:2423毫秒(2秒)

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

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

  declare @d datetime

  set @d=getdate()

  並在select語句後加:

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

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

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

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

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

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

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

  (1)select gid,fariqi,neibuyonghu,title fromTgongwen

  where fariqi>'2004-5-5'

  查詢速度:2513毫秒

  (2)select gid,fariqi,neibuyonghu,title fromTgongwen

  where fariqi>'2004-5-5' and neibuyonghu='辦公室'

  查詢速度:2516毫秒

  (3)select gid,fariqi,neibuyonghu,title fromTgongwen

  where neibuyonghu='辦公室'

  查詢速度:60280毫秒

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

 

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

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

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

  select gid,fariqi,neibuyonghu,reader,titlefrom Tgongwen

  where fariqi='2004-9-16'

  使用時間:3326毫秒

  select gid,fariqi,neibuyonghu,reader,titlefrom Tgongwen where gid<=250000

  使用時間:4470毫秒

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

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

  select gid,fariqi,neibuyonghu,reader,titlefrom Tgongwen order by fariqi

  用時:12936

  select gid,fariqi,neibuyonghu,reader,titlefrom Tgongwen order by gid

  用時:18843

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

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

  select gid,fariqi,neibuyonghu,reader,titlefrom Tgongwen

  where fariqi>'2004-1-1'

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

  select gid,fariqi,neibuyonghu,reader,titlefrom Tgongwen

  where fariqi>'2004-6-6'

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

  select gid,fariqi,neibuyonghu,reader,titlefrom Tgongwen

  where fariqi='2004-9-16'

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

  select gid,fariqi,neibuyonghu,reader,titlefrom Tgongwen

  where fariqi>'2004-1-1' andfariqi<'2004-6-6'

  用時:3280毫秒

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

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

  select gid,fariqi,neibuyonghu,reader,titlefrom Tgongwen

  where fariqi>'2004-1-1' order by fariqi

  用時:6390毫秒

  select gid,fariqi,neibuyonghu,reader,titlefrom Tgongwen

  where fariqi<'2004-1-1' order by fariqi

  用時:6453毫秒

  (五)其他注意事項

  “水可載舟,亦可覆舟”,索引也一樣。索引有助於提高檢索性能,但過多或不當的索引也會導致系統低效。過多的索引甚至會導致索引碎片。

  索引是從數據庫中獲取數據的最高效方式之一。95%的數據庫性能問題都可以採用索引技術得到解決。

  1. 不要索引常用的小型表

  不要爲小型數據表設置任何鍵,假如它們經常有插入和刪除操作就更別這樣作了。對這些插入和刪除操作的索引維護可能比掃描表空間消耗更多的時間。

  2. 不要把社會保障號碼(SSN)或身份證號碼(ID)選作鍵

  永遠都不要使用 SSN 或 ID 作爲數據庫的鍵。除了隱私原因以外,SSN 或 ID 需要手工輸入。永遠不要使用手工輸入的鍵作爲主鍵,因爲一旦你輸入錯誤,你唯一能做的就是刪除整個記錄然後從頭開始。

  3. 不要用用戶的鍵

  在確定採用什麼字段作爲表的鍵的時候,可一定要小心用戶將要編輯的字段。通常的情況下不要選擇用戶可編輯的字段作爲鍵。這樣做會迫使你採取以下兩個措施:

  4. 不要索引 memo/notes 字段和不要索引大型文本字段(許多字符)

  這樣做會讓你的索引佔據大量的數據庫空間

  5. 使用系統生成的主鍵

  假如你總是在設計數據庫的時候採用系統生成的鍵作爲主鍵,那麼你實際控制了數據庫的索引完整性。這樣,數據庫和非人工機制就有效地控制了對存儲數據中每一行的訪問。

  採用系統生成鍵作爲主鍵還有一個優點:當你擁有一致的鍵結構時,找到邏輯缺陷很容易

二、改善SQL語句

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

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

  和執行:

  select * from table1 where tID > 10000 andname='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 EXISTS、NOT IN、NOT LIKE等,另外還有函數。下面就是幾個不滿足SARG形式的例子:

  ABS(價格)<5000

  Name like ‘%三’

  有些表達式,如:

  WHERE 價格*2>5000

  SQL SERVER也會認爲是SARG,SQLSERVER會將此式轉化爲:

  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數據庫。運行前我們可以把SQLSERVER的statistics I/O狀態打開。

  (1)select title,price from titles wheretitle_id in

  (select title_id from sales where qty>30)

  該句的執行結果爲:

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

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

  (2)select title,price from titles whereexists

  (select * from sales wheresales.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,titlefrom Tgongwen

  where fariqi='2004-9-16' or gid>9990000

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

  select gid,fariqi,neibuyonghu,reader,titlefrom Tgongwen

  where fariqi='2004-9-16'

  union

  select gid,fariqi,neibuyonghu,reader,titlefrom Tgongwen where gid>9990000

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

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

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

  select gid,fariqi,neibuyonghu,reader,titlefrom Tgongwen

  where fariqi='2004-9-16' or fariqi='2004-2-5'

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

  select gid,fariqi,neibuyonghu,reader,titlefrom Tgongwen

  where fariqi='2004-9-16'

  union

  select gid,fariqi,neibuyonghu,reader,titlefrom Tgongwen

  where fariqi='2004-2-5'

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

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

  我們來做一個試驗:

  select top 10000 gid,fariqi,reader,title fromtgongwen order by gid desc

  用時:4673毫秒

  select top 10000 gid,fariqi,title fromtgongwen order by gid desc

  用時:1376毫秒

  select top 10000 gid,fariqi from tgongwenorder 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 fromtgongwen

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

  select top 10000 gid,fariqi,reader,title fromtgongwen order by gid asc

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

  select top 10000 gid,fariqi,reader,title fromtgongwen order by gid desc

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

  select top 10000 gid,fariqi,reader,title fromtgongwen order by fariqi asc

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

  select top 10000 gid,fariqi,reader,title fromtgongwen order by fariqi desc

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

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

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

12、高效的TOP

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

  select top 10 * from (

  select top 10000 gid,fariqi,title fromtgongwen

  where neibuyonghu='辦公室'order by gid desc) as a

  order by gid asc

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

到此爲止,我們上面討論瞭如何實現從大容量的數據庫中快速地查詢出您所需要的數據方法。當然,我們介紹的這些方法都是“軟”方法,在實踐中,我們還要考慮各種“硬”因素,如:網絡性能、服務器的性能、操作系統的性能,甚至網卡、交換機等。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

寫出性能優良的SQL

我們要做到不但會寫SQL,還要做到寫出性能優良的SQL,以下爲筆者學習、摘錄、並彙總部分資料與大家分享!
(1)選擇最有效率的表名順序(只在基於規則的優化器中有效):
ORACLE 的解析器按照從右到左的順序處理FROM子句中的表名,FROM子句中寫在最後的表(基礎表 driving table)將被最先處理,在FROM子句中包含多個表的情況下,你必須選擇記錄條數最少的表作爲基礎表。如果有3個以上的表連接查詢, 那就需要選擇交叉表(intersectiontable)作爲基礎表, 交叉表是指那個被其他表所引用的表.
(2)WHERE子句中的連接順序.:
ORACLE採用自下而上的順序解析WHERE子句,根據這個原理,表之間的連接必須寫在其他WHERE條件之前, 那些可以過濾掉最大數量記錄的條件必須寫在WHERE子句的末尾.
(3)SELECT子句中避免使用 ‘ * ‘:
ORACLE在解析的過程中, 會將'*' 依次轉換成所有的列名,這個工作是通過查詢數據字典完成的, 這意味着將耗費更多的時間
(4)減少訪問數據庫的次數:
ORACLE在內部執行了許多工作: 解析SQL語句,估算索引的利用率, 綁定變量, 讀數據塊等;
(5)在SQL*Plus , SQL*Forms和Pro*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只在刪除全表適用,TRUNCATE是DDL不是DML)
(10)儘量多使用COMMIT:
只要有可能,在程序中儘量多使用COMMIT,這樣程序的性能得到提高,需求也會因爲COMMIT所釋放的資源而減少:
COMMIT所釋放的資源:
a. 回滾段上用於恢復數據的信息.
b. 被程序語句獲得的鎖
c. redo log buffer 中的空間
d. ORACLE爲管理上述3種資源中的內部花費
(11)用Where子句替換HAVING子句:
避免使用HAVING子句, HAVING 只會在檢索出所有記錄之後纔對結果集進行過濾. 這個處理需要排序,總計等操作.如果能通過WHERE子句限制記錄的數目,那就能減少這方面的開銷.(非oracle中)on、where、having這三個都可以加條件的子句中,on是最先執行,where次之,having最後,因爲on是先把不符合條件的記錄過濾後才進行統計,它就可以減少中間運算要處理的數據,按理說應該速度是最快的,where也應該比having快點的,因爲它過濾數據後才進行sum,在兩個表聯接時才用on的,所以在一個表的時候,就剩下where跟having比較了。在這單表查詢統計的情況下,如果要過濾的條件沒有涉及到要計算字段,那它們的結果是一樣的,只是where可以使用rushmore技術,而having就不能,在速度上後者要慢如果要涉及到計算的字段,就表示在沒計算之前,這個字段的值是不確定的,根據上篇寫的工作流程,where的作用時間是在計算之前就完成的,而having就是在計算後才起作用的,所以在這種情況下,兩者的結果會不同。在多表聯接查詢時,on比where更早起作用。系統首先根據各個表之間的聯接條件,把多個表合成一個臨時表後,再由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)通常將提高查詢的效率. 在子查詢中,NOTIN子句將執行一個內部的排序和合並. 無論在哪種情況下,NOT IN都是最低效的 (因爲它對子查詢中的表執行了一個全表遍歷). 爲了避免使用NOT IN ,我們可以把它改寫成外連接(Outer Joins)或NOT EXISTS.
例子:
(高效)SELECT * FROM EMP (基礎表) WHERE EMPNO > 0 AND EXISTS (SELECT ‘X' FROM DEPTWHERE DEPT.DEPTNO = EMP.DEPTNO AND LOC = ‘MELB')
(低效)SELECT * FROM EMP (基礎表) WHERE EMPNO > 0 AND DEPTNO IN(SELECT DEPTNO FROMDEPT 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)的唯一性驗證.。那些LONG或LONG 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 NULL和IS 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, 這樣排序就不是必要了. 效率就會因此得到提高. 需要注意的是,UNIONALL 將重複輸出兩個結果集合中相同記錄. 因此各位還是要從業務需求分析使用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 BY的SQL語句會啓動SQL引擎
執行耗費資源的排序(SORT)功能. DISTINCT需要一次排序操作, 而其他的至少需要執行兩次排序. 通常, 帶有UNION, MINUS ,INTERSECT的SQL語句都可以用其他方式重寫. 如果你的數據庫的SORT_AREA_SIZE調配得好, 使用UNION, MINUS, INTERSECT也是可以考慮的, 畢竟它們的可讀性很強
(34)優化GROUP BY:
提高GROUP BY 語句的效率, 可以通過將不需要的記錄在GROUPBY 之前過濾掉.下面兩個查詢返回相同結果但第二個明顯就快了許多.
低效:
SELECT JOB , AVG(SAL)
FROM EMP
GROUP by JOB
HAVING JOB = ‘PRESIDENT'
OR JOB = ‘MANAGER'
高效:
SELECT JOB , AVG(SAL)
FROM EMP
WHERE JOB = ‘PRESIDENT'
OR JOB = ‘MANAGER'
GROUP by JOB

 

 

 

 

SQL Server 存儲過程的分頁

 

SQL Server 存儲過程的分頁,這個問題已經討論過幾年了,很多朋友在問我,所以在此發表一下我的觀點
建立表:

CREATE TABLE[TestTable] (
[ID] [int] IDENTITY (1, 1) NOT NULL ,
[FirstName] [nvarchar] (100) COLLATE Chinese_PRC_CI_AS NULL ,
[LastName] [nvarchar] (100) COLLATE Chinese_PRC_CI_AS NULL ,
[Country] [nvarchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
[Note] [nvarchar] (2000) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY]
GO

插入數據:(2萬條,用更多的數據測試會明顯一些)
SET IDENTITY_INSERT TestTable ON

declare @iint
set @i=1
while @i<=20000
begin
    insert into TestTable([id], FirstName, LastName,Country,Note) values(@i,'FirstName_XXX','LastName_XXX','Country_XXX','Note_XXX')
    set @i=@i+1
end

SETIDENTITY_INSERT TestTable OFF

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

分頁方案一:(利用NotIn和SELECT TOP分頁)
語句形式:
SELECT TOP 10 *
FROM TestTable
WHERE (ID NOT IN
          (SELECT TOP 20 id
         FROM TestTable
         ORDER BY id))
ORDER BY ID


SELECT TOP 頁大小 *
FROM TestTable
WHERE (ID NOT IN
          (SELECT TOP 頁大小*頁數id
         FROM 表
         ORDER BY id))
ORDER BY ID

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

分頁方案二:(利用ID大於多少和SELECT TOP分頁)
語句形式:
SELECT TOP 10 *
FROM TestTable
WHERE (ID >
          (SELECT MAX(id)
         FROM (SELECT TOP 20 id
                FROM TestTable
                ORDER BY id) AS T))
ORDER BY ID


SELECT TOP 頁大小 *
FROM TestTable
WHERE (ID >
          (SELECT MAX(id)
         FROM (SELECT TOP 頁大小*頁數id
                FROM 表
                ORDER BY id) AS T))
ORDER BY ID


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

分頁方案三:(利用SQL的遊標存儲過程分頁)
create procedure XiaoZhengGe
@sqlstr nvarchar(4000), --查詢字符串
@currentpage int, --第N頁
@pagesize int --每頁行數
as
set nocount on
declare @P1 int, --P1是遊標的id
@rowcount int
exec sp_cursoropen @P1 output,@sqlstr,@scrollopt=1,@ccopt=1,@rowcount=@rowcountoutput
select ceiling(1.0*@rowcount/@pagesize) as 總頁數--,@rowcount as 總行數,@currentpageas 當前頁
set @currentpage=(@currentpage-1)*@pagesize+1
exec sp_cursorfetch @P1,16,@currentpage,@pagesize
exec sp_cursorclose @P1
set nocount off

其它的方案:如果沒有主鍵,可以用臨時表,也可以用方案三做,但是效率會低。
建議優化的時候,加上主鍵和索引,查詢效率會提高。

通過SQL 查詢分析器,顯示比較:我的結論是:
分頁方案二:(利用ID大於多少和SELECT TOP分頁)效率最高,需要拼接SQL語句
分頁方案一:(利用NotIn和SELECT TOP分頁)   效率次之,需要拼接SQL語句
分頁方案三:(利用SQL的遊標存儲過程分頁)    效率最差,但是最爲通用

在實際情況中,要具體分析。

 

 

 

 

 

 

數據庫日常維護(參考)



數據庫日常維護工作是系統管理員的重要職責。其內容主要包括以下幾個部分:
一、備份系統數據
SYBASE 系統的備份與恢復機制保證了在系統失敗時重新獲取數據的可能性。SQL Server 提供了兩種不同類型的恢復機制:一類是系統自動完成的恢復,這種措施在每次系統啓動時都自動進行,保證了在系統癱瘓前完成的事務都寫到數據庫設備上,而未完成的事務都被回退;另一類是人工完成的恢復,這是通過 DUMP 和 LOAD 命令來執行人工備份和恢復工作。因此定期備份事務日誌和數據庫是一項十分重要的日常維護工作。

1、備份數據庫

每一個數據庫都應在創建之後卸出,從而提供一個裝入基點。在此之後按排定的時間週期表卸出。比如每週五卸出數據庫。對一般數據庫系統卸出數據庫週期建議爲每週一次。
除了按計劃週期卸出數據庫之外,還需在每次運行沒有日誌的操作後卸出數據庫。例如:
·每次強制地運行了 DUMP TRAN WITH NO_LOG (因爲數據庫的磁盤空溢出);
·每次用 sp_dboption 允許 select into/bulkcopy 做快速拷貝,或用 SELECT INTO 命令創建一個永久性的表,或使用了 WRITETEXT 命令。
卸出數據庫的命令爲:

DUMP DATABASE database_name
TO dump_device

database_name 是要卸出的數據庫名稱,dump_device 是卸出設備的名稱。用系統過程 sp_helpdevice 可以獲得設備的信息。
下面一條命令用來卸出數據庫 my_db :

DUMP DATABASE my_db
TO db_bk_dev

2、備份事務日誌

如果事務日誌與數據庫放在同一個設備上,則事務日誌不應與數據庫分開備份。master 數據庫和小於 4M 的用戶數據庫就是這種情況。一般數據庫系統的數據庫和日誌分別放在不同的設備上,因此,可以用 DUMP TRAN 命令單獨備份日誌。
備份事務日誌的週期直接影響數據的恢復程度,因此建議每天備份。
備份事務日誌的命令格式爲:

DUMP TRANsaction database_name
[TO dump_device]
[WITH TRUNCATE_ONLY|WITH NO_LOG|WITH NO_TRUNCATE]

其中 database_name 是要備份事務的數據庫名稱,dump_device 是備份設備名稱,僅當包含了 WITH TRUNCATE_ONLY 或 WITH NO_LOG 子句時,纔可以備份到設備。
注意:如果總是用 DUMP DATEBASE (備份數據庫及其日誌),而不用 DUMP TRAN ,事務日誌將不會刷新,而變得非常龐大。
對於 master 數據庫和小型數據庫每次運行 DUMP DATEBASE 之後應當運行 DUMP TRANsaction 命令刷新日誌 。
下面一條命令備份數據庫 db160 的事務日誌到備份設備上:

DUMP TRANsaction db160
TO db_log_bk_dev
WITH TRUNCATE_ONLY

3、備份數據庫及其日誌間的相互作用

在至少卸出一次數據庫前,卸出事務日誌是毫無意義的。下圖顯示了備份數據庫及其日誌間的關係

如果在星期二下午5:01出現非硬件故障,需要做的所有工作是裝入磁帶5(參見下一節:數據恢復),由於磁帶5是下午5:00剛備份的,因此只有備份和裝入之間的一分鐘內的數據損失。
但是,如果在星期二下午4:49失效會怎麼樣呢?在這種情況下,要裝入磁帶1(在星期五下午5:00的卸出)。然後,依次裝入磁帶2,3以及4。這樣,系統將恢復到星期二上午10:00點的狀態,星期二的大部分工作丟失了。此例顯示了經常卸出事務的重要性。

二、萬一系統失敗時恢復數據庫系統

如果用戶數據庫存儲的設備失效,從而數據庫被破壞或不可存取,通過裝入最新的數據庫備份以及後來的事務日誌備份可以恢復數據庫。假設當前的事務日誌存在於一個並沒有毀壞的設備上,帶着 WITH NO_TRUNCATE 選項的 DUMP TRANsaction 命令卸出它。
要恢復數據庫按如下步驟去做:
1、如果日誌存在於一個分離的設備上,用帶着 NO_TRUNCATE 選項的 DUMP TRANsaction 命令卸出被毀壞的或者不可存取的用戶數據庫事務日誌。
2、用下面的查詢檢查設備分配已毀壞數據庫的設備使用情況。必須爲同一目的賦同樣的空間塊。
下面的查詢顯示了分配給數據庫 mydb 設備使用和尺寸情況:

SELECT segmap,size FROM sysusages
WHERE dbid =
( SELECT dbid FROM sysdatabases WHERE name =“mydb”)

3、檢查查詢的輸出。在 segmap 列的 ‘3’代表數據分配,‘4’代表日誌分配。size 列代表 2K 數據塊的數目。注意此信息的次序、使用和尺寸部分。例如,輸出爲:

segmapSize
--------------------
310240//實際尺寸爲:20M
35120//實際尺寸爲:10M
45120//實際尺寸爲:10M
31024//實際尺寸爲:2M
42048//實際尺寸爲:4M

4、用 DROP DATABASE 命令刪除毀壞設備上的數據庫。如果系統報錯,用DBCC DBREPAIR 命令的 DROPDB 選項。
5、刪除數據庫後,用 sp_dropdevice 刪除毀壞了的設備。
6、用 DISK INIT 初始化新的數據庫設備。
7、重建數據庫。用 CREATE DATABASE 命令從老的 sysusages 表拷貝所有的行,幷包含第一邏輯設備。
對上例,命令爲:

CREATE DATABASE mydb
ON datadev1=20,datadev2=10
LOG ON logdev1=10

8、用 ALTER DATABASE 命令重建其餘入口。在此例中,在datadev1上分配更多的空間,命令爲:

ALTER DATABASE mydb ON datadev1=2[page]

9、用 LOAD DATABASE 重新裝入數據庫,然後用 LOAD TRAN 裝入前面卸出的日誌。
LOAD DATABASE 命令語法是:

LOAD DATABASE database_name
FROM dump_device

LOAD TRANsaction 命令的語法是:

LOAD TRANsaction database_name
FROM dump_device

卸出數據庫和事務日誌的缺省權限歸數據庫所有者,且可以傳遞給其他用戶;裝載數據庫和事務的權限也歸數據庫所有者,但不能傳遞。

二、產生用戶信息表,併爲信息表授權;
系統維護人員的另一個日常事務是爲用戶創建新的信息表,併爲之授權。創建表以及爲表授權的方法已經在講過,在此只將有關命令語法寫出來。
·創建表的命令爲:

CREATE TABLE table_name
( column_1 datatype [NULL | NOT NULL |IDENTITY],
column_2 ……

go

ALTER TABLE table_name
ADD PRIMARY KEY (column_list)
go

·刪除表的命令格式爲:

DROP TABLE table_name
go

·爲表授權的命令格式爲:

GRANT {ALL|permission_list}
ON table_name TO user_name
go

·收回權限的命令格式爲

REVOKE {ALL|permission_list}
ON table_name FROM user_name
go


三、監視系統運行狀況,及時處理系統錯誤;
系統管理員的另一項日常工作是監視系統運行情況。主要有以下幾個方面:
1、監視當前用戶以及進程的信息
使用系統過程:sp_who
說明:該命令顯示當前系統所有註冊用戶及進程信息,如下表是某系統的信息。
SpidStatusLoginamehostnameblkdbnamecmd
---------------------------------------------------------------
1RunningSascosysv0MasterSELECT
2SleepingNULL0MasterNETWORK HANDLE
3SleepingNULL0MasterDEADLOCK TUNE
4SleepingNULL0MasterMIRROR HANDLER
5SleepingNULL0MasterHOUSEKEEPER
6SleepingNULL0MasterCHECKPOINT SLEEP
從左向右依次顯示:進程號、當前狀態、註冊用戶名、主機名、佔用塊數、數據庫名以及當前命令。
如果監視時發現進程總數接近最大連接數(用系統過程:sp_configure “userconn” 查看)時,應下掉不活動或無關進程,以保證系統正常運做;另外亦可監視非法用戶或用戶使用不屬於自己使用範圍的數據庫等情況。
2、監視目標占用空間情況
使用系統過程:sp_spaceused
說明:該過程顯示行數、數據頁數以及當前數據庫中由某個目標或所有目標所佔用的空間。如下表是某數據庫日誌表的信息:

NameRow_totalreserveddataIndex_sizeunused
------------------------------------------------------------
SyslogsNot avail32KB32KB0KBNot avail

日常要監視的主要目標有:用戶數據庫、數據庫日誌表(syslogs)以及計費原始數據表等。如果發現佔用空間過大,對日誌表要進行轉儲;對其他目標則應擴充空間或清楚垃圾數據。
3、監視 SQL Server 統計數字
使用系統過程:sp_monitor
說明:sp_monitor 顯示SQL Server 的歷史統計數字,下表是某系統的統計數字:

Last_runCurrent_runSeconds
---------------------------------------------------------------
May 13 2000 1:27PMMay 13 2000 3:01PM5678
CPU_busyIO_busyIdle
---------------------------------------------------------------
16(6)-0%0(0)-0%5727(5672)-99%
Packets_receivedPackets_sentPacket_errors
---------------------------------------------------------------
21(17)100(97)0(0)
Total_readTotal_writeTotal_errorsConnections
--------------------------------------------------------
785(366)311(113)0(0)3(2)

上表依次給出該系統本次運行統計的上一次時間、本次時間、間隔秒數、CPU佔用、IO佔用、收發包情況、系統讀入寫出情況等信息

四、保證系統數據安全,週期更改用戶口令;
爲保證系統數據的安全,系統管理員必須依據系統的實際情況,執行一系列的安全保障措施。其中,週期性的更改用戶口令是比較常用且十分有效的措施。
更改用戶口令是通過調用系統過程sp_password 來實現的。Sp_password 的語法爲:
sp_password caller_password,new_password [,loginame]
其中caller_password 是登錄口令(老口令),new_password是新口令,loginame是登錄名稱。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

聚集索引的區別

 

 

 

 

聚集索引的區別

  聚集索引:物理存儲按照索引排序

  非聚集索引:物理存儲不按照索引排序

優勢與缺點

聚集索引:插入數據時速度要慢(時間花費在“物理存儲的排序”上,也就是首先要找到位置然後插入),查詢數據比非聚集數據的速度快

聚集索引的區別

  聚集索引:物理存儲按照索引排序

  非聚集索引:物理存儲不按照索引排序

優勢與缺點

聚集索引:插入數據時速度要慢(時間花費在“物理存儲的排序”上,也就是首先要找到位置然後插入),查詢數據比非聚集數據的速度快

索引是通過二叉樹的數據結構來描述的,我們可以這麼理解聚簇索引:索引的葉節點就是數據節點。而非聚簇索引的葉節點仍然是索引節點,只不過有一個指針指向對應的數據塊。如下圖:

 

非聚集索引

聚集索引

一、索引塊與數據塊的區別

大家都知道,索引可以提高檢索效率,因爲它的二叉樹結構以及佔用空間小,所以訪問速度塊。讓我們來算一道數學題:如果表中的一條記錄在磁盤上佔用1000字節的話,我們對其中10字節的一個字段建立索引,那麼該記錄對應的索引塊的大小隻有10字節。我們知道,SQLServer的最小空間分配單元是“頁(Page)”,一個頁在磁盤上佔用8K空間,那麼這一個頁可以存儲上述記錄8條,但可以存儲索引800條。現在我們要從一個有8000條記錄的表中檢索符合某個條件的記錄,如果沒有索引的話,我們可能需要遍歷8000條×1000字節/8K字節=1000個頁面才能夠找到結果。如果在檢索字段上有上述索引的話,那麼我們可以在8000條×10字節/8K字節=10個頁面中就檢索到滿足條件的索引塊,然後根據索引塊上的指針逐一找到結果數據塊,這樣IO訪問量要少的多。

二、索引優化技術

是不是有索引就一定檢索的快呢?答案是否。有些時候用索引還不如不用索引快。比如說我們要檢索上述表中的所有記錄,如果不用索引,需要訪問8000條×1000 字節/8K字節=1000個頁面,如果使用索引的話,首先檢索索引,訪問8000條×10字節/8K字節=10個頁面得到索引檢索結果,再根據索引檢索結果去對應數據頁面,由於是檢索所有數據,所以需要再訪問8000條×1000字節/8K字節=1000個頁面將全部數據讀取出來,一共訪問了1010個頁面,這顯然不如不用索引快。

SQL Server內部有一套完整的數據檢索優化技術,在上述情況下,SQL Server的查詢計劃(SearchPlan)會自動使用表掃描的方式檢索數據而不會使用任何索引。那麼SQL Server是怎麼知道什麼時候用索引,什麼時候不用索引的呢?SQL Server除了日常維護數據信息外,還維護着數據統計信息,下圖是數據庫屬性頁面的一個截圖:

聚簇索引與非聚簇索引的本質區別到底是什麼?什麼時候用聚簇索引,什麼時候用非聚簇索引?

這是一個很複雜的問題,很難用三言兩語說清楚。我在這裏從SQL Server索引優化查詢的角度簡單談談(如果對這方面感興趣的話,可以讀一讀微軟出版的《Microsoft SQL Server 2000數據庫編程》第3單元的數據結構引論以及第6、13、14單元)。

從圖中我們可以看到,SQL Server自動維護統計信息,這些統計信息包括數據密度信息以及數據分佈信息,這些信息幫助SQL Server決定如何制定查詢計劃以及查詢是是否使用索引以及使用什麼樣的索引(這裏就不再解釋它們到底如何幫助SQL Server建立查詢計劃的了)。我們還是來做個實驗。建立一張表:tabTest(ID, unqValue,intValue),其中ID是整形自動編號主索引,unqValue是uniqueidentifier類型,在上面建立普通索引,intValue 是整形,不建立索引。之所以掛上一個沒有索引的intValue字段,就是防止SQLServer使用索引覆蓋查詢優化技術,這樣實驗就起不到作用了。向表中錄入10000條隨機記錄,代碼如下:

Code

CREATE TABLE [dbo].[tabTest] (

[ID] [int] IDENTITY (1, 1) NOT NULL ,

[unqValue] [uniqueidentifier] NOT NULL ,

[intValue] [int] NOT NULL

) ON [PRIMARY]

GO

ALTER TABLE [dbo].[tabTest] WITH NOCHECK ADD

CONSTRAINT [PK_tabTest] PRIMARY KEY CLUSTERED

(

[ID]

) ON [PRIMARY]

GO

ALTER TABLE [dbo].[tabTest] ADD

CONSTRAINT [DF_tabTest_unqValue] DEFAULT (newid()) FOR[unqValue]

GO

CREATE INDEX [IX_tabTest_unqValue] ON[dbo].[tabTest]([unqValue]) ON [PRIMARY]

GO

declare @i int

declare @v int

set @i=0

while @i<10000

begin

set @v=rand()*1000

insert into tabTest ([intValue]) values (@v)

set @i=@i+1

end

然後我們執行兩個查詢並查看執行計劃,如圖:(在查詢分析器的查詢菜單中可以打開查詢計劃,同時圖上第一個查詢的GUID是我從數據庫中找的,大家做實驗的時候可以根據自己數據庫中的值來定):

 從圖中可以看出,在第一個查詢中,SQL Server使用了IX_tabTest_unqValue索引,根據箭頭方向,計算機先在索引範圍內找,找到後,使用Bookmark Lookup將索引節點映射到數據節點上,最後給出SELECT結果。在第二個查詢中,系統直接遍歷表給出結果,不過它使用了聚簇索引,爲什麼呢?不要忘了,聚簇索引的頁節點就是數據節點!這樣使用聚簇索引會更快一些(不受數據刪除、更新留下的存儲空洞的影響,直接遍歷數據是要跳過這些空洞的)。

下面,我們在SQL Server中將ID字段的聚簇索引更改爲非聚簇索引,然後再執行select * from tabTest,這回我們看到的執行計劃變成了:

SQLServer沒有使用任何索引,而是直接執行了Table Scan,因爲只有這樣,檢索效率纔是最高的。

三、聚簇索引與非聚簇索引的本質區別

現在可以討論聚簇索引與非聚簇索引的本質區別了。正如本文最前面的兩個圖所示,聚簇索引的葉節點就是數據節點,而非聚簇索引的頁節點仍然是索引檢點,並保留一個鏈接指向對應數據塊。

還是通過一道數學題來看看它們的區別吧:假設有一8000條記錄的表,表中每條記錄在磁盤上佔用1000字節,如果在一個10字節長的字段上建立非聚簇索引主鍵,需要二叉樹節點16000個(這16000個節點中有8000個葉節點,每個頁節點都指向一個數據記錄),這樣數據將佔用8000條×1000字節 /8K字節=1000個頁面;索引將佔用16000個節點×10字節/8K字節=20個頁面,共計1020個頁面。

同樣一張表,如果我們在對應字段上建立聚簇索引主鍵,由於聚簇索引的頁節點就是數據節點,所以索引節點僅有8000個,佔用10個頁面,數據仍然佔有1000個頁面。

下面我們看看在執行插入操作時,非聚簇索引的主鍵爲什麼比聚簇索引主鍵要快。主鍵約束要求主鍵不能出現重複,那麼SQL Server是怎麼知道不出現重複的呢?唯一的方法就是檢索。對於非聚簇索引,只需要檢索20個頁面中的16000個節點就知道是否有重複,因爲所有主鍵鍵值在這16000個索引節點中都包含了。但對於聚簇索引,索引節點僅僅包含了8000箇中間節點,至於會不會出現重複必須檢索另外8000個頁數據節點才知道,那麼相當於檢索10+1000=1010個頁面才知道是否有重複。所以聚簇索引主鍵的插入速度要比非聚簇索引主鍵的插入速度慢很多。

讓我們再來看看數據檢索的效率,如果對上述兩表進行檢索,在使用索引的情況下(有些時候SQL Server執行計劃會選擇不使用索引,不過我們這裏姑且假設一定使用索引),對於聚簇索引檢索,我們可能會訪問10個索引頁面外加1000個數據頁面得到結果(實際情況要比這個好),而對於非聚簇索引,系統會從20個頁面中找到符合條件的節點,再映射到1000個數據頁面上(這也是最糟糕的情況),比較一下,一個訪問了1010個頁面而另一個訪問了1020個頁面,可見檢索效率差異並不是很大。所以不管非聚簇索引也好還是聚簇索引也好,都適合排序,聚簇索引僅僅比非聚簇索引快一點。

結語

關於聚簇索引與非聚簇索引效率問題的實驗就不做了,感興趣的話可以自己使用查詢分析器對查詢計劃進行分析。SQL Server是一個很複雜的系統,尤其是索引以及查詢優化技術,Oracle就更復雜了。瞭解索引以及查詢背後的事情不是什麼壞事,它可以幫助我們更爲深刻的瞭解我們的系統。

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

非聚簇對於更新肯定是有優勢的而它在檢索的性能損失也不會太大所以能不用聚簇當然是最好的了但是如果使用\order by的話聚簇的優勢也應該是很明顯的

 

索引有兩種類型:聚簇索引和非聚簇索引。

在聚簇索引中,索引樹的葉級頁包含實際的數據:記錄的索引順序與物理順序相同。

在非聚簇索引中,葉級頁指向表中的記錄:記錄的物理順序與邏輯順序沒有必然的聯繫。

聚簇索引非常象目錄表,目錄表的順序與實際的頁碼順序是一致的。非聚簇索引則更象書的標準索引表,索引表中的順序通常與實際的頁碼順序是不一致的。一本書也許有多個索引。例如,它也許同時有主題索引和作者索引。同樣,一個表可以有多個非聚簇索引。

通常情況下,你使用的是聚簇索引,但是你應該對兩種類型索引的優缺點都有所理解。

每個表只能有一個聚簇索引,因爲一個表中的記錄只能以一種物理順序存放。通常你要對一個表按照標識字段建立聚簇索引。但是,你也可以對其它類型的字段建立聚簇索引,如字符型,數值型和日期時間型字段。

從建立了聚簇索引的表中取出數據要比建立了非聚簇索引的錶快。當你需要取出一定範圍內的數據時,用聚簇索引也比用非聚簇索引好。例如,假設你用一個表來記錄訪問者在你網點上的活動。如果你想取出在一定時間段內的登錄信息,你應該對這個表的DATETIME型字段建立聚簇索引。

對聚簇索引的主要限制是每個表只能建立一個聚簇索引。但是,一個表可以有不止一個非聚簇索引。實際上,對每個表你最多可以建立249個非聚簇索引。你也可以對一個表同時建立聚簇索引和非聚簇索引。

假如你不僅想根據日期,而且想根據用戶名從你的網點活動日誌中取數據。在這種情況下,同時建立一個聚簇索引和非聚簇索引是有效的。你可以對日期時間字段建立聚簇索引,對用戶名字段建立非聚簇索引。如果你發現你需要更多的索引方式,你可以增加更多的非聚簇索引。

非聚簇索引需要大量的硬盤空間和內存。另外,雖然非聚簇索引可以提高從表中取數據的速度,它也會降低向表中插入和更新數據的速度。每當你改變了一個建立了非聚簇索引的表中的數據時,必須同時更新索引。因此你對一個表建立非聚簇索引時要慎重考慮。如果你預計一個表需要頻繁地更新數據,那麼不要對它建立太多非聚簇索引。另外,如果硬盤和內存空間有限,也應該限制使用非聚簇索引的數量。

索引屬性

這兩種類型的索引都有兩個重要屬性:

你可以用兩者中任一種類型同時對多個字段建立索引(複合索引);

兩種類型的索引都可以指定爲唯一索引。

你可以對多個字段建立一個複合索引,甚至是複合的聚簇索引。假如有一個表記錄了你的網點訪問者的姓和名字。如果你希望根據完整姓名從表中取數據,你需要建立一個同時對姓字段和名字字段進行的索引。這和分別對兩個字段建立單獨的索引是不同的。當你希望同時對不止一個字段進行查詢時,你應該建立一個對多個字段的索引。如果你希望對各個字段進行分別查詢,你應該對各字段建立獨立的索引。

兩種類型的索引都可以被指定爲唯一索引。如果對一個字段建立了唯一索引,你將不能向這個字段輸入重複的值。一個標識字段會自動成爲唯一值字段,但你也可以對其它類型的字段建立唯一索引。假設你用一個表來保存你的網點的用戶密碼,你當然不希望兩個用戶有相同的密碼。通過強制一個字段成爲唯一值字段,你可以防止這種情況的發生。 

http://hi.baidu.com/guobeilei/blog/item/51f55afbda311e116c22eb0e.html

 

聚集索引基於數據行的鍵值在表內排序和存儲這些數據行。每個表只能有一個聚集索引,因爲數據行本身只能按一個順序存儲。有關聚集索引體系結構的詳細信息,請參閱聚集索引結構。

每個表幾乎都對列定義聚集索引來實現下列功能:

可用於經常使用的查詢。

提供高度唯一性。

注意:

創建 PRIMARY KEY 約束時,將在列上自動創建唯一索引。默認情況下,此索引是聚集索引,但是在創建約束時,可以指定創建非聚集索引。

 

可用於範圍查詢。

如果未使用 UNIQUE 屬性創建聚集索引,數據庫引擎將向表自動添加一個 4 字節的uniqueifier 列。必要時,數據庫引擎將向行自動添加一個 uniqueifier 值以使每個鍵唯一。此列和列值供內部使用,用戶不能查看或訪問。

查詢注意事項

在創建聚集索引之前,應先了解數據是如何被訪問的。考慮對具有以下特點的查詢使用聚集索引:

使用運算符(如 BETWEEN、>、>=、< 和 <=)返回一系列值。

使用聚集索引找到包含第一個值的行後,便可以確保包含後續索引值的行物理相鄰。例如,如果某個查詢在一系列銷售訂單號間檢索記錄,SalesOrderNumber 列的聚集索引可快速定位包含起始銷售訂單號的行,然後檢索表中所有連續的行,直到檢索到最後的銷售訂單號。

返回大型結果集。

使用 JOIN 子句;一般情況下,使用該子句的是外鍵列。

使用 ORDER BY 或 GROUP BY子句。

在 ORDER BY 或 GROUP BY子句中指定的列的索引,可以使數據庫引擎不必對數據進行排序,因爲這些行已經排序。這樣可以提高查詢性能。

列注意事項

一般情況下,定義聚集索引鍵時使用的列越少越好。考慮具有下列一個或多個屬性的列:

唯一或包含許多不重複的值

例如,僱員 ID 唯一地標識僱員。EmployeeID列的聚集索引或 PRIMARY KEY 約束將改善基於僱員 ID 號搜索僱員信息的查詢的性能。另外,可對 LastName、FirstName、MiddleName 列創建聚集索引,因爲經常以這種方式分組和查詢僱員記錄,而且這些列的組合還可提供高區分度。

按順序被訪問

例如,產品 ID 唯一地標識AdventureWorks 數據庫的 Production.Product 表中的產品。在其中指定順序搜索的查詢(如

WHERE ProductID BETWEEN 980 and 999

)將從 ProductID 的聚集索引受益。這是因爲行將按該鍵列的排序順序存儲。

由於保證了列在表中是唯一的,所以定義爲 IDENTITY。

經常用於對錶中檢索到的數據進行排序。

按該列對錶進行聚集(即物理排序)是一個好方法,它可以在每次查詢該列時節省排序操作的成本。

聚集索引不適用於具有下列屬性的列:

頻繁更改的列

這將導致整行移動,因爲數據庫引擎必須按物理順序保留行中的數據值。這一點要特別注意,因爲在大容量事務處理系統中數據通常是可變的。

寬鍵

寬鍵是若干列或若干大型列的組合。所有非聚集索引將聚集索引中的鍵值用作查找鍵。爲同一表定義的任何非聚集索引都將增大許多,這是因爲非聚集索引項包含聚集鍵,同時也包含爲此非聚集索引定義的鍵列。

索引選項

創建聚集索引時,可指定若干索引選項。因爲聚集索引通常都很大,所以應特別注意下列選項:

SORT_IN_TEMPDB

DROP_EXISTING

FILLFACTOR

ONLINE

 


發佈了45 篇原創文章 · 獲贊 2 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章