SQL Server索引誤區使用建議


常見的誤區:

1.數據庫不需要索引

2.主鍵總是聚集的

3.聯機索引操作不引起阻塞

4.復合索引下列的順序不重要

5.聚集索引以物理順序存儲

6.填充因子可以應用在索引的插入過程中

7.每個表應該有聚集索引

一:數據庫不需要索引

	 --生成堆表
			 SELECT * INTO MythOne FROM Sales.SalesOrderDetail
			 --統計查詢所用的I/O
			 SET STATISTICS IO ON
             SET NOCOUNT ON
             GO 
			 SELECT salesorderID,SalesOrderDetailID,CarrierTrackingNumber,OrderQty,productID,SpecialOfferID,
			 UnitPrice,UnitPriceDiscount,LineTotal  FROM MythOne   WHERE CarrierTrackingNumber='4911-403c-98'
			 SET STATISTICS IO OFF

 

在CarrierTrackingNumber創建一個索引,以提高查詢性能

 CREATE INDEX IX_CarrierTrackingNumber ON mythone(CarrierTrackingNumber)
		   GO
           SET STATISTICS IO ON 
		   SET NOCOUNT ON 
		   GO
           SELECT salesorderID,SalesOrderDetailID,CarrierTrackingNumber,OrderQty,productID,SpecialOfferID,
			 UnitPrice,UnitPriceDiscount,LineTotal  FROM MythOne   WHERE CarrierTrackingNumber='4911-403c-98'
			 GO
             SET STATISTICS IO OFF

 --表 'MythOne'。掃描計數 1,邏輯讀取 15 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。

二:主鍵總是聚集的

當你創建一個主鍵,默認就會在上面自動創建一個聚集索引,主鍵的目的是為了保持數據的唯一性、從邏輯上組織數據

	 --創建測試表
			 CREATE TABLE dbo.MythoTwo1(
			 RowID INT NOT NULL,
			 Column1 NVARCHAR(128),
			 Column2 NVARCHAR(128)
			 )
			 --創建具有聚集特性的主鍵
			 ALTER TABLE dbo.MythoTwo1
			 ADD CONSTRAINT PK_MythoTwo1 PRIMARY KEY (RowID)
			 GO
             CREATE TABLE dbo.MythTwo2(
			 RowID INT NOT NULL,
			 Column1 NVARCHAR(128),
			 Column2 NVARCHAR(128)
			 )
			 --先創建聚集索引
			 CREATE CLUSTERED  INDEX INDEX_CL_MythTwo2 ON dbo.MythTwo2(rowid)
			 --添加主鍵
			 ALTER TABLE dbo.MythTwo2
			 ADD CONSTRAINT PK_MythTwo2 PRIMARY KEY (RowID)
			 GO
             --檢查兩個表的索引類型
			 SELECT OBJECT_NAME(object_id) AS table_name, 
			 name, index_id,type,type_desc,is_unique,is_primary_key
			  FROM sys.indexes
			 WHERE OBJECT_ID IN (OBJECT_ID('dbo.MythoTwo1'),OBJECT_ID('dbo.MythoTwo2'))

 

MythoTwo1只有一個聚集索引,並且是唯一的(is_unique)和聚集的(is_primary_key);而MythoTwo2上有兩個索引,一個是單純的聚集索引,一個是唯一且為主鍵的非聚集索引

3.聯機索引操作不引起阻塞

SQL Server 會在表中受聯機索引操作影響的數據上加上意向共享鎖,並在最終更新原有索引時對這部分數據架構更新鎖或者共享鎖,然后阻塞其他事務的修改操作

4.復合索引下,列的順序不重要
一個索引通常不會使用到表中的所有列,並且列的順序也是有意義的,復合索引中最左的一列有統計信息,其他列不計算統計信息,這就說明索引的列是有順序,而且非常重要。
 --創建表
			 SELECT SalesOrderID,orderdate,DueDate,ShipDate
			 INTO dbo.MythFour
			 FROM Sales.SalesOrderHeader;

			 --添加一個聚集索引
			 ALTER TABLE dbo.mythFour
			 ADD CONSTRAINT PK_MythFour PRIMARY KEY CLUSTERED(SalesOrderID);
			 --添加由三列組成的非聚集索引
			 CREATE NONCLUSTERED INDEX IX_MythFour ON dbo.MythFour(orderdate,DueDate,ShipDate);

			 --執行查詢索引最左邊
			 SELECT orderdate FROM dbo.MythFour WHERE orderdate='2008-07-17 00:00:00.000'

			 --使用索引最右邊
			SELECT orderdate FROM dbo.MythFour WHERE  ShipDate='2008-07-17 00:00:00.000'
             

 

一個是索引掃描,一個是索引查找,因為前面已經說過,除了索引最左側的列有統計信息之外,其他列沒有,所以用其他列,SQLServer 無法預估行數,只能使用掃描來遍歷索引

5.聚集索引以物理順序存儲

                  --創建測試表
			 CREATE TABLE dbo.MythFive
			 (
			 rowid INT PRIMARY KEY CLUSTERED,
			 TestValue VARCHAR(20) NOT NULL
			 )

			INSERT INTO dbo.MythFive(rowid,TestValue)
			VALUES(1,'FirstRecordAdded');
			INSERT INTO dbo.MythFive(rowid,TestValue)
			VALUES(3,'SecondRecordAdded');
			INSERT INTO dbo.MythFive(rowid,TestValue)
			VALUES(2,'ThirdRecordAdded');

			--檢查索引情況
			DBCC IND ('AdventureWorks2014','dbo.MythFive',1)

 MythFive的索引情況:

檢查PageType=1即數據頁的情況

	DBCC TRACEON(3604);
			GO
            DBCC PAGE(AdventureWorks2014,1,140107,2)

 聚集索引並不是按物理順序存放數據

6.填充因子可以應用在索引的插入過程中

填充因子用於創建(不是指第一次初始化,而是使用create index with drop_existing這種方式,第一次初始化的時候會盡量填滿頁)、重建或者重組索引。

--創建測試表
			CREATE TABLE dbo.MythSix
			(
			RowID INT NOT NULL,
			Columnl VARCHAR(500)
			);
			--創建聚集索引,填充因子為50,即只填滿頁的一半就是分頁
			ALTER TABLE dbo.MythSix ADD CONSTRAINT
			PK_MythSix PRIMARY KEY CLUSTERED (RowID) WITH(FILLFACTOR=50);

			--制造測試數據
			WITH L1(z) AS (SELECT 0 UNION ALL SELECT 0)
			, L2(z) AS (SELECT 0 FROM L1 a CROSS JOIN L1 b)
			, L3(z) AS (SELECT 0 FROM L2 a CROSS JOIN L2 b)
			, L4(z) AS (SELECT 0 FROM L3 a CROSS JOIN L3 b)
			, L5(z) AS (SELECT 0 FROM L4 a CROSS JOIN L4 b)
			, L6(z) AS (SELECT TOP 1000 0 FROM L5 a CROSS JOIN L5 b)

			INSERT INTO dbo.MythSix
			       SELECT ROW_NUMBER() OVER(ORDER BY z) AS RowID,REPLICATE('X',500) FROM L6

		     --檢查索引的可用情況
			 SELECT object_id,index_id,avg_page_space_used_in_percent
			  FROM sys.dm_db_index_physical_stats(DB_ID(),OBJECT_ID('dbo.MythSix'),NULL,NULL,'DETAILED') WHERE index_level=0

 執行結果:

可用空間並不是50,可見填充因子在插入時並未起作用。實際上,插入數據時,SQL Server依舊會盡量填滿一頁在分配新頁。為了證明在重建、重組時填充因子會起作用。可以重建這個表

ALTER TABLE dbo.MythSix rebuild

 

這時候填充因子才生效,但這些配置並不是絕對的,也就是說不一定設了50就真的是正好50

 7.每個表應該有聚集索引

不使用聚集索引的理由:

1.碎片問題增加I/O操作

2.當因為數據修改而產生page split時,會引起聚集索引上多條記錄位置變更

3.過渡的key lookup會引起額外的I/O開銷,這通常是索引設計的問題

不使用堆的理由:

1.過多的forwarded records會導致額外的I/O,降低性能

2.沒有辦法通過移除forwarded records來維護堆表

3.非聚集索引往往可能要進行多余的排序操作

選擇是否用聚集索引時,可以考慮以下幾點:

1.表上是否有唯一、自增的鍵值。

2.表上是否有高度唯一的列

3是否經常有范圍查詢

堆表可以比聚集索引更好的性能表現:

1.高頻率的增刪操作

2.鍵值經常改變,特別是索引上的位置改變

3.插入大量數據到列中

4.主鍵值並不自增或者唯一

 

索引使用建議

1.保留主鍵創建中的聚集索引選項

 由於聚集索引合適建立在唯一、自增的列或者多列上而這些特性在主鍵中能夠得到滿足,所以SQL Server 創建主鍵時默認就是聚集索引。

2.平衡索引的個數

3.填充因子

(1)數據庫層面的填充因子用於控制索引創建、維護過程中索引頁保留空余空間的默認值。往往數據庫層面的填充因子不建議修改,如有必要,可以修改索引層面上的填充因子

(2)如果索引上存在嚴重的碎片問題,在索引層面上調整填充因子可以在一定程度上減少碎片問題

4.在外鍵列加索引

創建外鍵列后,強烈建議在外鍵列上加索引,這可以幫助父表和子表關聯時提高性能

 

關於索引的查詢建議

1.Like   把like 的這列數據進行索引化,把核心的數據或者常用數據當成一個列

  SET STATISTICS IO ON ;
			  SELECT AddressID,AddressLine1,AddressLine2,City,StateProvinceID,PostalCode FROM Person.Address
			  WHERE AddressLine1 LIKE '%Cynthia%'

 

表 'Address'。掃描計數 1,邏輯讀取 216 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。

創建一個全文索引:

 CREATE FULLTEXT CATALOG ftQueryStrategies AS DEFAULT;
			  CREATE FULLTEXT INDEX ON person.Address(AddressLine1)
			  KEY INDEX PK_Address_AddressID;
			  GO 
			  SET STATISTICS IO ON;
			   SELECT AddressID,AddressLine1,AddressLine2,City,StateProvinceID,PostalCode FROM Person.Address
			  WHERE CONTAINS (AddressLine1,'Cynthia')

 表 'Address'。掃描計數 0,邏輯讀取 12 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。

 

串聯:

在查詢過程中,很多時候需要把某些列串聯起來作為新列,特別是在where條件中,使用了FirstName+LastName=‘***’,會導致索引無效

 SET STATISTICS IO ON;
			  CREATE INDEX IX_PersonContact_FirstNameLastName ON Person.Person(FirstName,LastName)
			  GO
              SELECT BusinessEntityID,FirstName,LastName FROM Person.Person WHERE FirstName+' '+ LastName='Gustavo Achong'

 表 'Person'。掃描計數 1,邏輯讀取 99 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。  

有兩種解決方案:

1.使用計算列,把兩個列上的值預先存儲,然后在計算列上加上索引

2.改寫查詢

  --使用計算列
			  ALTER TABLE Person.Person ADD Name AS Firstname+''+ lastname
			  CREATE NONCLUSTERED INDEX IX_PersonContact_Name ON Person.Person(Name)
  --用新列查詢
			  SET STATISTICS IO ON;
			  SELECT BusinessEntityID,FirstName,LastName FROM Person.Person WHERE name='Gustavo Achong'

 表 'Worktable'。掃描計數 0,邏輯讀取 0 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。
表 'Person'。掃描計數 1,邏輯讀取 2 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。

  --第二種情況
			  DROP INDEX IX_PersonContact_Name ON Person.Person
			  SET STATISTICS IO ON ;
			  SELECT BusinessEntityID,FirstName,LastName FROM Person.Person WHERE FirstName='Gustavo' AND LastName='Achong'

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

 3.標量函數

使用函數,特別是where條件和on條件中使用標量函數,也會對索引帶來影響。標量函數會使列上的值在查詢過程中“變成”另一個值,索引上面的統計信息就會失效,進而導致優化器選擇掃描操作

  SELECT BusinessEntityID,FirstName,LastName FROM person.Person
			  WHERE FirstName='Gustavo' 
  SELECT BusinessEntityID,FirstName,LastName FROM Person.Person
			  WHERE UPPER(FirstName)='Gustavo'

 

4.數據類型轉換

 

  --4.數據類型轉換
			  --創建示例表
			  SELECT BusinessEntityID
			  ,CAST(FirstName  AS VARCHAR(50)) AS FirstName
			  ,CAST(MiddleName AS VARCHAR(50)) AS MiddleName
			  ,CAST(LastName AS VARCHAR(50)) AS  LastName 
			  INTO PersonPerson
			  FROM Person.Person;

			   CREATE CLUSTERED INDEX IX_PersonPerson_ContactID ON  PersonPerson(BusinessEntityID);
			   GO
               SET STATISTICS IO ON 

			   DECLARE @FirstName nvarchar(100) --注意類型
			   SET @FirstName ='Katherine';
			   --提高帶參數執行SQL語句的索引效率 OPTION(RECOMPILE)
			   SELECT  * FROM PersonPerson WHERE @FirstName=@FirstName  OPTION(RECOMPILE);

			   GO
               DECLARE @FirstName VARCHAR(100)--注意類型型,這里不存在類型轉換,均為varchar
			   SET @FirstName='Katherine';
			   SELECT * FROM PersonPerson
			   WHERE FirstName=@FirstName

 表 'PersonPerson'。掃描計數 1,邏輯讀取 89 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。

數據類型轉換和標量函數的影響類似,都會使得統計信息丟失,從而使索引無效,所以應該盡可能保持WHERE、ON條件兩邊的類型相等。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM