聚集索引:
該索引中鍵值的邏輯順序決定了表中相應行的物理順序。
如果用 新華字典 作例子來一個例子的話。
[拼音]就可以看作是聚集索引
例如 吖、阿、啊 在字典的最前面。
左、作、坐 在字典的最後面。
拼音[邏輯順序]很接近,在字典中頁數的位置[物理順序]也很接近。
適用場合:
含有大量非重複值的列
使用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
測試的結果顯示:
在 返回大型結果集的查詢 下
使用非聚集索引,性能比沒有索引的性能好不了多少,可能還會更差。
使用聚集索引能夠提高一定的性能。