SQL Server 聚集索引/非聚集索引

前些天看看 SQL Server 的聚集索引與非聚集索引的資料,拿來做做實現,分享一下心得。



聚集索引:
  該索引中鍵值的邏輯順序決定了表中相應行的物理順序。

如果用 新華字典 作例子來一個例子的話。
  [拼音]就可以看作是聚集索引
    例如 吖、阿、啊 在字典的最前面。
    左、作、坐 在字典的最後面。
    拼音[邏輯順序]很接近,在字典中頁數的位置[物理順序]也很接近。

適用場合:
  含有大量非重複值的列
  使用BETWEEN,>,>=,<或<=返回一個範圍值的列
  被連續訪問的列
  返回大型結果集的查詢
  經常被使用連接或GROUP BY子句的查詢訪問的列



非聚集索引:
  非聚集索引與聚集索引一樣有 B 樹結構,但是有兩個重大差別:
  數據行不按非聚集索引鍵的順序排序和存儲。
  非聚集索引的葉層不包含數據頁。
  相反,葉節點包含索引行。每個索引行包含非聚集鍵值以及一個或多個行定位器,
  這些行定位器指向有該鍵值的數據行(如果索引不唯一,則可能是多行)。

如果用 新華字典 作例子來一個例子的話。
  [筆畫]就可以看作是非聚集索引
    例如 化 仇 仃 僅 仂 這幾個字,都是 單人旁,筆畫數相同的。
    筆畫[邏輯順序]很接近,在字典中頁數的位置[物理順序]則在不同的位置上。

適用場合:
  含有大量非重複值的列
  非連續訪問的列
  返回小型結果集的查詢



-- 由於 SQL SERVER 默認是在主鍵上建立聚集索引的。
-- 一個表,又只允許有一個 聚集索引。
-- 這裏創建主鍵的時候,要指定這個主鍵,使用非聚集索引
CREATE TABLE TestDoc(
  id             INT identity(1, 1),
  createDate      DATETIME,
  owner            VARCHAR(10),
  docInfo        TEXT,
  PRIMARY KEY NONCLUSTERED (id)
);
go

 

 

 

-- 首先插入 36500*5 = 182500 條數據.
DECLARE
  @i AS INT,
  @myDate AS DATETIME;
BEGIN
  SET @i = 0;
  SET @myDate = CONVERT(DATETIME, '1949.10.01', 102);
  WHILE @i < 36500
  BEGIN
    SET @i = @i + 1;
    INSERT INTO TestDoc
        VALUES ( DATEADD(dd, @i, @myDate), '張三', NULL);
    INSERT INTO TestDoc
        VALUES ( DATEADD(dd, @i, @myDate), '李四', NULL);
    INSERT INTO TestDoc
        VALUES ( DATEADD(dd, @i, @myDate), '王五', NULL);
    INSERT INTO TestDoc
        VALUES ( DATEADD(dd, @i, @myDate), '趙六', NULL);
    INSERT INTO TestDoc
        VALUES ( DATEADD(dd, @i, @myDate), 'Admin', NULL);
  END;
END
go


-- 複製自己一次 = 182500 * 2 = 365000
INSERT INTO TestDoc
  SELECT createDate, owner + '1', docInfo FROM TestDoc;
go

-- 再自己複製自己一次 = 365000 *2 = 730000
INSERT INTO TestDoc
  SELECT createDate, owner + '2', docInfo FROM TestDoc;
go

-- 再自己複製自己一次 = 730000 *2 = 1460000
INSERT INTO TestDoc
  SELECT createDate, owner + '3', docInfo FROM TestDoc;
go

-- 再自己複製自己一次 = 1460000 *2 = 2920000
INSERT INTO TestDoc
  SELECT createDate, owner + '4', docInfo FROM TestDoc;
go

 

 

-- 用於測試執行性能的存儲過程.
-- 該存儲過程,只在很大的數據量中,查詢很少量的數據. [1/36500]
CREATE PROCEDURE TestQuery1
AS
BEGIN
  SET NOCOUNT ON;

  IF EXISTS(SELECT * FROM sys.Tables WHERE name='#temp1')
  DROP TABLE #temp1;

  IF EXISTS(SELECT * FROM sys.Tables WHERE name='#temp2')
  DROP TABLE #temp2;


  -- 定義一個開始之間.
  DECLARE @startDate AS DATETIME;

  -- 首先只查詢一個字段的
  -- 設置 開始執行時間.
  SET @startDate = GETDATE();
  -- 執行查詢.
  SELECT
    * INTO #temp1
  FROM
    TestDoc
  WHERE
    createDate = CONVERT(DATETIME, '2008.01.01', 102);
  -- 輸出查詢經過的時間.
  PRINT DATEDIFF(MS, @startDate, GETDATE());


  -- 然後查詢多個字段的
  -- 設置 開始執行時間.
  SET @startDate = GETDATE();
  -- 執行查詢.
  SELECT
    * INTO #temp2
  FROM
    TestDoc
  WHERE
    createDate = CONVERT(DATETIME, '2008.01.01', 102)
    AND owner LIKE '張三%';
  -- 輸出查詢經過的時間.
  PRINT DATEDIFF(MS, @startDate, GETDATE());

END
go




-- 用於測試執行性能的存儲過程.
-- 該存儲過程,在很大的數據量中,查詢比較大量的數據. [1/100]
CREATE PROCEDURE TestQuery2
AS
BEGIN
  SET NOCOUNT ON;

  IF EXISTS(SELECT * FROM sys.Tables WHERE name='#temp1')
  DROP TABLE #temp1;

  IF EXISTS(SELECT * FROM sys.Tables WHERE name='#temp2')
  DROP TABLE #temp2;

  -- 定義一個開始之間.
  DECLARE @startDate AS DATETIME;

  -- 首先只查詢一個字段的
  -- 設置 開始執行時間.
  SET @startDate = GETDATE();
  -- 執行查詢.
  SELECT
    * INTO #temp1
  FROM
    TestDoc
  WHERE
    createDate >= CONVERT(DATETIME, '2008.01.01', 102)
    AND createDate < CONVERT(DATETIME, '2009.01.01', 102);
  -- 輸出查詢經過的時間.
  PRINT DATEDIFF(MS, @startDate, GETDATE());


  -- 然後查詢多個字段的
  -- 設置 開始執行時間.
  SET @startDate = GETDATE();
  -- 執行查詢.
  SELECT
    * INTO #temp2
  FROM
    TestDoc
  WHERE
    createDate >= CONVERT(DATETIME, '2008.01.01', 102)
    AND createDate < CONVERT(DATETIME, '2009.01.01', 102)
    AND owner LIKE '張三%';
  -- 輸出查詢經過的時間.
  PRINT DATEDIFF(MS, @startDate, GETDATE());

END
go



-- 用於測試執行性能的存儲過程.
-- 該存儲過程,在很大的數據量中,查詢很大量的數據. [1/10]
CREATE PROCEDURE TestQuery3
AS
BEGIN
  SET NOCOUNT ON;

  IF EXISTS(SELECT * FROM sys.Tables WHERE name='#temp1')
  DROP TABLE #temp1;

  IF EXISTS(SELECT * FROM sys.Tables WHERE name='#temp2')
  DROP TABLE #temp2;

  -- 定義一個開始之間.
  DECLARE @startDate AS DATETIME;

  -- 首先只查詢一個字段的
  -- 設置 開始執行時間.
  SET @startDate = GETDATE();
  -- 執行查詢.
  SELECT
    * INTO #temp1
  FROM
    TestDoc
  WHERE
    createDate >= CONVERT(DATETIME, '1999.01.01', 102)
    AND createDate < CONVERT(DATETIME, '2009.01.01', 102);
  -- 輸出查詢經過的時間.
  PRINT DATEDIFF(MS, @startDate, GETDATE());


  -- 然後查詢多個字段的
  -- 設置 開始執行時間.
  SET @startDate = GETDATE();
  -- 執行查詢.
  SELECT
    * INTO #temp2
  FROM
    TestDoc
  WHERE
    createDate >= CONVERT(DATETIME, '1999.01.01', 102)
    AND createDate < CONVERT(DATETIME, '2009.01.01', 102)
    AND owner LIKE '張三%';
  -- 輸出查詢經過的時間.
  PRINT DATEDIFF(MS, @startDate, GETDATE());

END
go

-- 在沒有索引的情況下:
EXECUTE TestQuery1;
GO
EXECUTE TestQuery2;
GO
EXECUTE TestQuery3;
GO

 


-- 創建非聚集索引
CREATE NONCLUSTERED INDEX idx_TestDoc ON TestDoc(createDate);
go

-- 使用非聚集索引的情況下
EXECUTE TestQuery1;
GO
EXECUTE TestQuery2;
GO
EXECUTE TestQuery3;
GO


-- 刪除前面創建的索引
DROP INDEX idx_TestDoc ON TestDoc;
go

-- 創建新的聚集索引
CREATE CLUSTERED INDEX idx_TestDoc ON TestDoc(createDate);
go

-- 使用聚集索引的情況下
EXECUTE TestQuery1;
GO
EXECUTE TestQuery2;
GO
EXECUTE TestQuery3;
GO

 

最後的測試結果,大概的對比情況如下: 單位 毫秒

數據百分比  無索引    非聚集    聚集
1/36500     393-470   0-16      0-33
1/100       413-500   416-486   43-56
1/10        740-916   730-940   273-293


測試的結果顯示:
在 返回大型結果集的查詢 下
使用非聚集索引,性能比沒有索引的性能好不了多少,可能還會更差。
使用聚集索引能夠提高一定的性能。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章