聚集索引和非聚集索引(转)

本文通过实例演示了SQLServer中聚集索引与非聚集索引的区别及应用场合,并通过大量数据测试了不同索引对查询性能的影响。

前些天看看 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


测试的结果显示:
在 返回大型结果集的查询 下
使用非聚集索引,性能比没有索引的性能好不了多少,可能还会更差。
使用聚集索引能够提高一定的性能。

转自http://hi.baidu.com/wangzhiqing999/blog/item/ea57b6932325457854fb9634.html

<think>好的,用户想了解聚集索引非聚集索引的区别。我需要先回忆一下数据库索引的基础知识,确保自己理解正确。首先,聚集索引应该表的物理存储顺序有关,而非聚集索引则是独立的结构。 接下来,我应该整理两者的定义。聚集索引决定了数据在磁盘上的排列方式,所以每个表只能有一个。而非聚集索引是单独的,可以有多个。需要确认这个点是否正确,比如SQL Server确实是这样,但其他数据库比如MySQL的InnoDB中,聚集索引是主键,如果没有主键则会选其他唯一索引,所以可能有所不同,但用户可能指的是通用情况,还是以常见数据库为例。 然后要比较存储结构。聚集索引的叶子节点直接存数据行,而非聚集索引的叶子节点存的是指向数据的指针或者聚集键。这里可能需要解释清楚指针聚集键的区别,比如如果表有聚集索引非聚集索引会使用聚集键作为指针,否则可能使用行ID。 性能方面,聚集索引在范围查询时效率高,因为数据物理连续,而非聚集索引可能需要回表,导致随机IO。但点查询的话,两者都可能很快,不过聚集索引直接找到数据,而非聚集索引需要二次查找。 维护成本方面,聚集索引的插入更新可能更耗时,因为要调整数据的位置,而非聚集索引只需要更新索引结构,不影响数据存储顺序。但如果有多个非聚集索引,更新数据时也需要维护这些索引,所以维护成本可能更高。 还需要提到每个表只能有一个聚集索引,而非聚集索引可以有多个。这也是重要的区别。 可能用户是数据库初学者,或者正在设计表结构,需要优化查询性能。他们可能想了解如何选择索引类型,或者为什么查询速度不同。所以例子会有帮助,比如用订单表,聚集索引在订单日期,这样按日期范围查询快;非聚集索引在客户ID,查询特定客户时需要回表查找。 需要确保语言简洁,结构清晰,分点说明。同时要避免专业术语过多,用容易理解的中文解释。可能还需要提醒使用场景,比如聚集索引适合经常需要范围查询的列,非聚集索引适合常用于查询条件的列,但更新频繁的表可能会有更多索引维护开销。 最后检查是否有错误,比如是否每个表真的只能有一个聚集索引,不同数据库是否有差异,但为了通用性,还是按常见情况说明。确保LaTeX格式正确,比如索引结构用行内公式表示,比如B+树结构。</think>聚集索引非聚集索引是数据库索引的两种核心类型,主要区别体现在存储结构、数据组织方式查询效率上。以下是逐步对比分析: --- ### 一、定义对比 1. **聚集索引(Clustered Index)** $$ \text{索引键值的顺序 = 数据行的物理存储顺序} $$ 每个表**只能有一个**聚集索引,因为它直接决定了数据的物理排列。 2. **非聚集索引(Non-Clustered Index)** $$ \text{索引键值的顺序 ≠ 数据行的物理存储顺序} $$ 索引数据分离存储,一个表可创建**多个非聚集索引**。 --- ### 二、存储结构差异 | 特性 | 聚集索引 | 非聚集索引 | |---------------------|-----------------------------------|-----------------------------------| | **叶子节点内容** | 直接存储数据行 | 存储指向数据行的指针(或聚集索引键) | | **额外存储空间** | 不需要额外空间(索引即数据) | 需要独立存储空间(索引与数据分离) | | **逻辑示意图** | ![聚集索引结构] | ![非聚集索引结构] | --- ### 三、查询性能对比 1. **范围查询(如`WHERE date BETWEEN ...`)** - 聚集索引:数据物理连续,顺序读取快($O(\log n) + \text{顺序扫描}$)。 - 非聚集索引:需多次跳,可能触发随机I/O($O(\log n) + \text{随机读取}$)。 2. **点查询(如`WHERE id = 5`)** - 两者效率接近(均通过B+树快速定位,$O(\log n)$),但非聚集索引需额外回表查数据。 --- ### 四、维护成本对比 1. **插入/更新数据** - 聚集索引:可能触发数据页分裂,影响性能。 - 非聚集索引:仅需更新索引结构,不影响数据物理顺序。 2. **删除数据** - 聚集索引:删除后可能导致页合并。 - 非聚集索引:只需标记索引条目为无效。 --- ### 五、适用场景 | 场景 | 推荐索引类型 | 原因 | |---------------------|-----------------------|------------------------------------| | 主键或频繁范围查询列 | 聚集索引 | 利用物理连续性加速范围查询 | | 高频筛选的非主键列 | 非聚集索引 | 快速定位数据,避免全表扫描 | | 频繁更新的列 | 谨慎创建非聚集索引 | 多个索引会增加写操作开销 | --- ### 六、示例说明 假设有一个订单表`Orders`: ```sql CREATE TABLE Orders ( OrderID INT PRIMARY KEY, -- 聚集索引(默认主键) CustomerID INT, OrderDate DATE, TotalAmount DECIMAL ); ``` - **场景1**:按`OrderDate`范围查询 ```sql SELECT * FROM Orders WHERE OrderDate BETWEEN '2023-01-01' AND '2023-12-31'; ``` 若在`OrderDate`上建聚集索引,数据按日期物理排序,查询更快。 - **场景2**:按`CustomerID`查找 ```sql SELECT * FROM Orders WHERE CustomerID = 1001; ``` 在`CustomerID`上建非聚集索引,通过索引快速定位,再回表获取完整数据。 --- ### 七、总结 | 维度 | 聚集索引 | 非聚集索引 | |-------------|----------------------------------|----------------------------------| | **物理存储** | 数据按索引排序存储 | 独立存储,数据无序 | | **数量限制** | 每表仅1个 | 每表可多个 | | **查询效率** | 范围查询快,避免回表 | 点查询快,但需回表 | | **适用列** | 主键、高频范围查询列 | 高频筛选的非主键列 |
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值