65.9K
CodeProject 正在变化。 阅读更多。
Home

SQL 11 (代号 Denali) 入门 - 第七部分 (CTP 3 中的列存储索引)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (8投票s)

2011年9月1日

CPOL

12分钟阅读

viewsIcon

22836

本文将探讨列存储索引——SSIS Denali CTP3 中的一项新功能。

SQL 11 (代号 Denali) 入门 - 第七部分 (CTP 3 中的列存储索引)

目录

  1. 引言
  2. 背景
  3. 什么是行存储和列存储
  4. 行存储方法的缺点
  5. 列存储相对于行存储方法的优点
  6. 什么是列存储索引?
  7. 创建列存储索引
  8. 对具有列存储索引的表进行数据操作
  9. 列存储索引的性能
  10. IGNORE_NONCLUSTERED_COLUMNSTORE_INDEX 的目的
  11. 列存储索引的当前限制
  12. 使用列存储索引的理想场景
  13. 为什么我们要使用列存储索引?
  14. 参考
  15. 结论

引言

微软在技术领域最热门、最棒的开发之一于 2010 年 11 月 8 日发布了 SQL Server 2011(代号 Denali)的社区技术预览版 1 (CTP 1)。不出所料,Denali 为 SQL 爱好者带来了许多新功能,无论是开发人员、管理员还是商业智能 (BI) 专业人士。 第一部分介绍了 SSMS 中的功能和增强。 第二部分侧重于 T-SQL 方面的新开发和增强功能。 第三部分探讨了 SSIS 方面的增强,而 第四部分讨论了容器数据库。

但这还不是 Denali 的全部。这是一个持续的开发,在 CTP1 取得巨大成功后,现在是 CTP 3,它于 2011 年 7 月 12 日发布。不出所料,Denali 正在向各个领域扩展,无论是 T-SQL 方面的增强、新的外观和感觉-Juneau、使用 Project Crescent 的新报表工具,还是通过列存储索引等增强数据仓库的处理。Denali 为 SQL Server 数据库带来了新的维度,本系列将探讨作为 Apollo 项目一部分的新列存储索引功能。

背景

在过去几年中,微软为开发人员带来了许多新技术。随着 SQL Server 2005(代号 **Yukon**)的出现,SQL Server 的术语发生了巨大变化,在随后的 SQL Server 2008(代号 **Katmai**)和 SQL 11(代号 **Denali**)等版本中,通过引入新功能、增强和改进,保持了同样的步伐。在本文中,我们将探讨 Denali CTP 3 的新列存储索引功能。后续文章将重点介绍其他领域的增强功能。

您可以通过以下方式阅读我关于 Denali 的其他文章:

  1. SQL 11 (代号 Denali) 入门 - 第一部分 (CTP 1 中的 SSMS 功能)
  2. SQL 11 (代号 Denali) 入门 - 第二部分 (CTP 1 中的 T-Sql 功能)
  3. SQL 11 (代号 Denali) 入门 - 第三部分 (CTP 1 中的 SSIS 功能)
  4. SQL 11 (代号 Denali) 入门 - 第四部分 (CTP 1 中的独立数据库)
  5. SQL 11 (代号 Denali) 入门 - 第五部分 (CTP 3 中的 SSIS 功能)
  6. SQL 11 (代号 Denali) 入门 - 第六部分 (CTP 3 中的 T-Sql 功能)

什么是行存储和列存储

在讨论列存储索引是什么之前,花一些时间了解什么是列存储以及它的作用是值得的。

我们知道,页是 SQL Server 中数据存储的基本单元。数据行是实际数据的存储单元。它们紧跟在页头之后,并按顺序放置在页上。这种以行方式在页上存储记录的方式称为行存储。

当来自单个列但来自多个行中的值连续存储时,称为列存储。

1.jpg

行存储方法的缺点

如果我们参考图 1,可以看出 SQL Server 总是读取查询中的所有列记录,即使并非所有列都需要。

列存储相对于行存储方法的优点

如图 1 所示,磁盘上的每个页存储的是单个列的值,而不是整行。因此,压缩算法更有效,因为它们可以处理同类值。例如,假设我们有一个包含 3 列的表,第一列为整数类型,第二列为 varchar,第三列为布尔字段。假设该表中有 100 条记录或行。如果我们看第一列,所有记录都是整数类型。因此,它们都是同类的,在这种情况下很容易应用压缩算法,并且压缩效果会很显著。这还表明我们不需要访问表中的所有列,而只需要访问列的子集,这反过来可以减少磁盘 I/O 并改善缓存,从而更有效地利用磁盘存储,例如索引维护。

什么是列存储索引?

列存储索引将列存储在数据页中,而不是像行存储体系结构中那样存储行。在普通索引中,行存储在磁盘页中,但使用列存储索引,列存储在独立的磁盘页集中,因此速度更快。查询优化器将列存储索引视为访问数据的源,就像在创建查询计划时考虑其他索引一样。它针对改进和快速的数据仓库查询处理进行了优化。由于在这种情况下不需要读取表的所有列,因此可以节省大量的磁盘 I/O,并且更多的数据可以放入内存中。

在列存储方法中,数据是按列分析的。因此,数据基数越低,即列中重复值越多,压缩率就越高。它使用 Vertipaq 压缩引擎技术(这也是 PowerPivot 中的压缩引擎)来存储列,而不是传统索引。在行存储方法中,来自每行的所有索引数据都放在单个页上,并且每列中的数据分布在索引的所有页上。在列存储索引中,每列的数据都集中在一起,因此每个数据页只包含来自单个列的数据,如图 1 所示。

创建列存储索引

创建列存储的一般语法如下:

CREATE NONCLUSTERED COLUMNSTORE INDEX  <ColumnStore_Index_Name> ON <Table_Name>(<Column1>,<Column2>,… <Column N>)

其中

ColumnStore_Index_Name => 列存储索引的名称,

Table_Name => 创建索引的表对象,

Column1 … Column N => 创建索引的列。

可以通过 T-SQL 命令或使用 Management Studio 中的对象资源管理器来创建列存储索引。

假设我们有一个表 tbl_ColumnStoreIndex,包含 4 列:Column1(int 类型,主键)、Column2(varchar(50) 类型)、Column3(DateTime 类型)、Column4(NUMERIC(16,2) 类型)。

我们将在该表的后三列上创建列存储索引。

T-SQL 方法

IF EXISTS (SELECT * FROM sys.objects WHERE name = N'tbl_ColumnStoreIndex' AND type = 'U')
    DROP TABLE tbl_ColumnStoreIndex
GO
SET ANSI_NULLS ON
GO
--Create the table
CREATE TABLE tbl_ColumnStoreIndex (
	Column1 INT IDENTITY,
	Column2 VARCHAR(50),
	Column3 DATETIME,	
	Column4 NUMERIC(16,2)
)
-- Creating clustered index
CREATE CLUSTERED INDEX IX_Column1 ON tbl_ColumnStoreIndex(Column1)
-- Creating COLUMNSTORE nonclustered index
CREATE NONCLUSTERED COLUMNSTORE INDEX  IX_CS_C2_C3_c4 
ON tbl_ColumnStoreIndex(Column2,Column3,Column4)

现在,如果我们展开对象资源管理器中的表节点并访问 tbl_ColumnStoreIndex 的“索引”文件夹,我们将发现索引已创建。

2.jpg

通过对象资源管理器创建列存储索引

步骤 1:首先使用表设计器或编写 T-SQL 脚本创建表。

IF EXISTS (SELECT * FROM sys.objects WHERE name = N'tbl_ColumnStoreIndex' AND type = 'U')
    DROP TABLE tbl_ColumnStoreIndex
GO
SET ANSI_NULLS ON
GO
--Create the table
CREATE TABLE tbl_ColumnStoreIndex (
	Column1 INT IDENTITY,
	Column2 VARCHAR(50),
	Column3 DATETIME,	
	Column4 NUMERIC(16,2)
)
-- Creating clustered index
CREATE CLUSTERED INDEX IX_Column1 ON tbl_ColumnStoreIndex(Column1)

步骤 2:展开表的树结构,然后右键单击“索引”图标 -> 新建索引 -> 非聚集列存储索引。

3.jpg

将打开“新建索引”窗口。

4_new.jpg

让我们输入索引名称,然后通过单击“添加”按钮添加列。单击“添加”按钮后,将打开“选择列”对话框,从中选择列。

5.jpg

完成后,单击“确定”。此时,我们将看到以下内容。

6_new.jpg

单击“确定”,索引将创建。

对具有列存储索引的表进行数据操作

对已具有列存储索引的表进行数据操作(任何 DML 操作)将采用略微不同的方法。

如果我们先创建列存储索引,然后尝试按如下方式填充数据:

IF EXISTS (SELECT * FROM sys.objects WHERE name = N'tbl_ColumnStoreIndex' AND type = 'U')
    DROP TABLE tbl_ColumnStoreIndex
GO
SET ANSI_NULLS ON
GO
--Create the table
CREATE TABLE tbl_ColumnStoreIndex (
	Column1 INT IDENTITY,
	Column2 VARCHAR(50),
	Column3 DATETIME,	
	Column4 NUMERIC(16,2)
)
-- Creating clustered index
CREATE CLUSTERED INDEX IX_Column1 ON tbl_ColumnStoreIndex(Column1)

-- Creating COLUMNSTORE nonclustered index
CREATE NONCLUSTERED COLUMNSTORE INDEX  IX_CS_C2_C3_c4 
ON tbl_ColumnStoreIndex(Column2,Column3,Column4)

--Insert record
Insert into tbl_ColumnStoreIndex Values('Col2 Val1',GETDATE(),10.23)

我们将收到如下错误:

消息 35330,级别 15,状态 1,行 16
INSERT 语句失败,因为无法在具有列存储索引的表中更新数据。请考虑在发出 INSERT 语句之前禁用列存储索引,然后在 INSERT 完成后重新生成列存储索引。

因此,消息表明,我们首先需要禁用列存储索引,然后执行插入操作,然后再次重新生成索引。因此,按照消息的指示,让我们再次尝试:

IF EXISTS (SELECT * FROM sys.objects WHERE name = N'tbl_ColumnStoreIndex' AND type = 'U')
    DROP TABLE tbl_ColumnStoreIndex
GO
SET ANSI_NULLS ON
GO
--Create the table
CREATE TABLE tbl_ColumnStoreIndex (
	Column1 INT IDENTITY,
	Column2 VARCHAR(50),
	Column3 DATETIME,	
	Column4 NUMERIC(16,2)
)
-- Creating clustered index
CREATE CLUSTERED INDEX IX_Column1 ON tbl_ColumnStoreIndex(Column1)

-- Creating COLUMNSTORE nonclustered index
CREATE NONCLUSTERED COLUMNSTORE INDEX  IX_CS_C2_C3_c4 
ON tbl_ColumnStoreIndex(Column2,Column3,Column4)

-- Disabling the columnstore index before issuing the INSERT statement
ALTER INDEX IX_CS_C2_C3_c4 ON tbl_ColumnStoreIndex DISABLE;

--Insert record
INSERT INTO tbl_ColumnStoreIndex VALUES('Col2 Val1',GETDATE(),10.23)
INSERT INTO tbl_ColumnStoreIndex VALUES('Col2 Val2',GETDATE(),10.24)

-- Rebuilding the columnstore index after INSERT is complete
ALTER INDEX IX_CS_C2_C3_c4 ON tbl_ColumnStoreIndex REBUILD;

这次成功了。

或者,我们可以先创建表,然后添加记录,然后按如下方式创建列存储索引:

IF EXISTS (SELECT * FROM sys.objects WHERE name = N'tbl_ColumnStoreIndex' AND type = 'U')
    DROP TABLE tbl_ColumnStoreIndex
GO
SET ANSI_NULLS ON
GO
--Create the table
CREATE TABLE tbl_ColumnStoreIndex (
	Column1 INT IDENTITY,
	Column2 VARCHAR(50),
	Column3 DATETIME,	
	Column4 NUMERIC(16,2)
)
--Insert record
INSERT INTO tbl_ColumnStoreIndex VALUES('Col2 Val1',GETDATE(),10.23)
INSERT INTO tbl_ColumnStoreIndex VALUES('Col2 Val2',GETDATE(),10.24)

--Crate the indexes
-- Creating clustered index
CREATE CLUSTERED INDEX IX_Column1 ON tbl_ColumnStoreIndex(Column1)

-- Creating COLUMNSTORE nonclustered index
CREATE NONCLUSTERED COLUMNSTORE INDEX  IX_CS_C2_C3_c4 
ON tbl_ColumnStoreIndex(Column2,Column3,Column4)

但这种方法仅在第一次有效。从下一次开始,我们需要插入、更新或删除列,必须遵循第一种方法。

因此,如果我们发出以下语句:

UPDATE tbl_ColumnStoreIndex
SET Column2 = 'Updated Value'
WHERE Column1  = 1

我们将收到以下错误消息:

消息 35330,级别 15,状态 1,行 3
UPDATE 语句失败,因为无法在具有列存储索引的表中更新数据。请考虑在发出 UPDATE 语句之前禁用列存储索引,然后在 UPDATE 完成后重新生成列存储索引。

为了解决错误,我们需要执行以下操作:

-- Disabling the columnstore index before issuing the INSERT statement
ALTER INDEX IX_CS_C2_C3_c4 ON tbl_ColumnStoreIndex DISABLE;

UPDATE tbl_ColumnStoreIndex
SET Column2 = 'Updated Value'
WHERE Column1  = 1

-- Rebuilding the columnstore index after INSERT is complete
ALTER INDEX IX_CS_C2_C3_c4 ON tbl_ColumnStoreIndex REBUILD;

SELECT * FROM tbl_ColumnStoreIndex

/* Result
Column1	Column2			Column3					Column4
1		Updated Value	2011-09-01 10:35:21.193	10.23
2		Col2 Val2		2011-09-01 10:35:21.213	10.24
*/

列存储索引的性能

为了衡量列存储索引的性能,让我们创建一个简单的测试环境。我们将创建两个相同的表,区别在于一个表具有非列存储索引,而另一个表具有列存储索引。

不带列存储索引的表脚本(N'tbl_Players_WithoutColumnStore)

-- Drop the table if it exists
IF EXISTS (SELECT * FROM sys.objects WHERE name = N'tbl_Players_WithoutColumnStore' AND type = 'U')
    DROP TABLE tbl_Players_WithoutColumnStore
GO
SET ANSI_NULLS ON
GO
--Create the table
CREATE TABLE tbl_Players_WithoutColumnStore (
	PlayerID INT IDENTITY,
	PlayerName VARCHAR(15),
	BelongsTo VARCHAR(15)
)

-- Creating clustered index
CREATE CLUSTERED INDEX IX_PlayerID ON tbl_Players_WithoutColumnStore(PlayerID)
-- Creating normal nonclustered index
CREATE NONCLUSTERED INDEX IX_BelongsTo ON tbl_Players_WithoutColumnStore(BelongsTo)

--Prepare the data
;With Cte As
(
	Select 
		Id=1
		,PlayerName='Player' + CAST( 1 As Varchar(50))
		,BelongsTo = Cast ('India' As Varchar(50))
	Union All
	Select 
		Id +1 
		,PlayerName= 'Player' + CAST( Id+1 As Varchar(50)) 
		,BelongsTo = Cast(Choose(((ID%5)+1),'India','Pakistan','Srilanka','Bangaladesh','Bhutan')As Varchar(50))
			
	From Cte 
	Where Id < 2000000
)
Insert into tbl_Players_WithoutColumnStore
Select PlayerName,BelongsTo from Cte
Option (Maxrecursion 0)

带列存储索引的表(N'tbl_Players_WithColumnStore)

-- Drop the table if it exists
IF EXISTS (SELECT * FROM sys.objects WHERE name = N'tbl_Players_WithColumnStore' AND type = 'U')
    DROP TABLE tbl_Players_WithColumnStore
GO
SET ANSI_NULLS ON
GO
--Create the table
CREATE TABLE tbl_Players_WithColumnStore (
	PlayerID INT IDENTITY,
	PlayerName VARCHAR(15),
	BelongsTo VARCHAR(15)
)
--Prepare the data
;With Cte As
(
	Select 
		Id=1
		,PlayerName='Player' + CAST( 1 As Varchar(50))
		,BelongsTo = Cast ('India' As Varchar(50))
	Union All
	Select 
		Id +1 
		,PlayerName= 'Player' + CAST( Id+1 As Varchar(50)) 
		,BelongsTo = Cast(Choose(((ID%5)+1),'India','Pakistan','Srilanka','Bangaladesh','Bhutan')As Varchar(50))
			
	From Cte 
	Where Id < 2000000
)
--Insert the record
Insert into tbl_Players_WithColumnStore
Select PlayerName,BelongsTo from Cte
Option (Maxrecursion 0)

-- Creating clustered index
CREATE CLUSTERED INDEX IX_PlayerID ON tbl_Players_WithColumnStore(PlayerID)
-- Creating column store nonclustered index
CREATE NONCLUSTERED COLUMNSTORE INDEX  IX_CS_BelongsTo ON tbl_Players_WithoutColumnStore(BelongsTo)

在每个表中,我们都插入了 2,000,000(两百万)条数据。

我们还可以通过查询 sys.column_store_index_stats 目录表来获取有关创建的列存储索引的信息:

SELECT 
TableName = object_name(object_id)
, * 
FROM sys.column_store_index_stats 
/* Result
TABLENAME					NAME		OBJECT_ID	INDEX_ID	TYPE_DESC	NUMBER_OF_SEGMENTS
TBL_PLAYERS_WITHCOLUMNSTORE	IX_CS_BELONGSTO	1410104064	2	NONCLUSTERED COLUMNSTORE	9
*/

因此,现在我们有了大量数据,可以开始性能测试了。

让我们运行以下查询:

查询 1:无列存储索引

DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE

USE Test;
GO       
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
GO

SELECT 
		BelongsTo
		,Cnt = COUNT(*)
FROM tbl_Players_WithoutColumnStore WITH (INDEX (IX_BelongsTo))
GROUP BY  BelongsTo

GO
SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;
GO
/*
Table 'tbl_Players_WithoutColumnStore'. Scan count 3, logical reads 5442, physical reads 1, read-ahead reads 5392, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 609 ms,  elapsed time = 2314 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
*/

查询 2:带列存储索引

DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE

USE Test;
GO       
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
GO


SELECT 
		BelongsTo
		,Cnt = COUNT(*)
FROM tbl_Players_WithColumnStore WITH (INDEX (IX_CS_BelongsTo))
GROUP BY  BelongsTo

GO
SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;
GO

/*
Table 'tbl_Players_WithColumnStore'. Scan count 2, logical reads 15, physical reads 0, read-ahead reads 8, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 45 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
*/

对于第一个查询,服务器执行时间如下:

CPU 时间 = 640 毫秒,经过时间 = 2321 毫秒。

对于第二个查询,服务器执行时间如下:

CPU 时间 = 0 毫秒,经过时间 = 44 毫秒。

我们主要关心 CPU 时间(执行查询所用时间)和经过时间(查询运行所用时间)。CPU 和经过时间的表格比较如下:

索引类型 CPU 时间 已用时间
列存储 0 毫秒 44 毫秒
非列存储 640 毫秒 2321 毫秒

因此,我们可以从中推断出,对于聚合数据,列存储比非列存储索引是更好的选择。

我们还看一下第一个查询(无列存储)的执行模式:

7_new.jpg

可以看到,在传统方法中,存储模式是行存储,数据一次处理一行。

现在,我们看一下第二个查询(带列存储)的执行模式:

8_new.jpg

新的批处理执行模式比以前的方法更有效,因为它以批处理而非行的形式处理数据,这对于数据量大、工作负载重的数据仓库处理至关重要。执行计划中的每个运算符都可以使用行执行模式,当列存储索引可用时,某些运算符也可以使用批处理模式。既有估计执行模式,也有实际执行模式,这些信息显示在查询执行计划上。另请注意,有一个新的物理运算符,即列存储索引扫描。

IGNORE_NONCLUSTERED_COLUMNSTORE_INDEX 的目的

如果我们想禁止使用列存储索引,我们可以使用 IGNORE_NONCLUSTERED_COLUMNSTORE_INDEX,如下所示:

SELECT 
		BelongsTo
		,Cnt = COUNT(*)
FROM tbl_Players_WithColumnStore 
GROUP BY  BelongsTo
OPTION (IGNORE_NONCLUSTERED_COLUMNSTORE_INDEX);

从执行计划可以看出,物理运算符已更改为聚集索引扫描。

9.jpg

列存储索引的当前限制

在当前版本中,列存储索引存在一些限制,如下所示:

(A)无法创建聚集列存储索引

IF EXISTS (SELECT * FROM sys.objects WHERE name = N'tbl_ColumnStoreIndex' AND type = 'U')
    DROP TABLE tbl_ColumnStoreIndex
GO
SET ANSI_NULLS ON
GO
--Create the table
CREATE TABLE tbl_ColumnStoreIndex (
	Column1 INT IDENTITY,
	Column2 VARCHAR(50),
	Column3 DATETIME,	
	Column4 NUMERIC(16,2)
)

-- Creating COLUMNSTORE nonclustered index
CREATE CLUSTERED COLUMNSTORE INDEX  IX_CS_C2_C3_c4 
ON tbl_ColumnStoreIndex(Column2,Column3,Column4)

消息 35335,级别 15,状态 1,行 11
CREATE INDEX 语句失败,因为在创建聚集列存储索引时不允许指定键列表。请在不指定键列表的情况下创建聚集列存储索引。

(B)无法创建非聚集列存储覆盖索引

IF EXISTS (SELECT * FROM sys.objects WHERE name = N'tbl_ColumnStoreIndex' AND type = 'U')
    DROP TABLE tbl_ColumnStoreIndex
GO
SET ANSI_NULLS ON
GO
--Create the table
CREATE TABLE tbl_ColumnStoreIndex (
	Column1 INT IDENTITY,
	Column2 VARCHAR(50),
	Column3 DATETIME,	
	Column4 NUMERIC(16,2)
)

-- Creating COLUMNSTORE nonclustered index
CREATE NONCLUSTERED COLUMNSTORE INDEX  IX_CS_C2
ON tbl_ColumnStoreIndex(Column2)
INCLUDE (Column1 ,Column3 ,Column4 )

消息 35311,级别 15,状态 1,行 10
CREATE INDEX 语句失败,因为列存储索引不能包含包含的列。请在不指定任何包含的列的情况下,在所需的列上创建列存储索引。

(C)不能在计算列上创建

IF EXISTS (SELECT * FROM sys.objects WHERE name = N'tbl_ColumnStoreIndex' AND type = 'U')
    DROP TABLE tbl_ColumnStoreIndex
GO
SET ANSI_NULLS ON
GO
--Create the table
CREATE TABLE tbl_ColumnStoreIndex (
	Column1 INT IDENTITY,
	Column2 VARCHAR(50),
	Column3 DATETIME,	
	Column4 INT
	,Column5  AS (Column1+Column4) -- It is a computed Column
)

-- Creating COLUMNSTORE nonclustered index
CREATE NONCLUSTERED COLUMNSTORE INDEX  IX_CS_C2
ON tbl_ColumnStoreIndex(Column5)

CREATE INDEX 语句失败,因为表“tbl_ColumnStoreIndex”上的列“Column5”是计算列,而不能在计算列上创建列存储索引。请考虑在不包含该列的列的子集上创建非聚集列存储索引。

(D)不能创建多个列存储索引

IF EXISTS (SELECT * FROM sys.objects WHERE name = N'tbl_ColumnStoreIndex' AND type = 'U')
    DROP TABLE tbl_ColumnStoreIndex
GO
SET ANSI_NULLS ON
GO
--Create the table
CREATE TABLE tbl_ColumnStoreIndex (
	Column1 INT IDENTITY,
	Column2 VARCHAR(50),
	Column3 DATETIME,	
	Column4 NUMERIC(16,2)

)

-- Creating COLUMNSTORE nonclustered index
CREATE NONCLUSTERED COLUMNSTORE INDEX  IX_CS_C2
ON tbl_ColumnStoreIndex(Column2)

CREATE NONCLUSTERED COLUMNSTORE INDEX  IX_CS_C3
ON tbl_ColumnStoreIndex(Column3)

消息 35339,级别 16,状态 1,行 14 不支持多个非聚集列存储索引。

使用列存储索引的理想场景

我们已经看到,一旦我们在表上添加了列存储索引,表本身就变为只读。如果我们需要插入新行或更新现有行,我们可以禁用索引,执行数据修改,然后重新生成列存储索引。由于此功能,它更适合包含静态数据的数据库表。

为什么我们要使用列存储索引?

(A)索引中的列数据经过压缩,占用的空间更少。因此,SQL Server 需要扫描的数据页更少。

(B)由于 SQL Server 需要执行的扫描次数更少,因此它能够将其保存在内存缓冲区中,并且查询性能会更快。

(C)由于需要处理的数据页更少,结果会更快,SQL Server 会尝试将列缓存到内存缓冲区中。

(D)SQL Server 有机会访问选定的列,而不是访问所有行。

参考

SQL Server 列存储索引常见问题解答

结论

在本文中,我们了解了 Apollo 项目提供的一个新功能,即列存储索引。我们了解了传统方法的缺点以及列存储如何在这种情况下提供帮助,如何在 DML 语句中使用它。我们还对传统方法和新列存储方法进行了性能基准测试,并讨论了它为何比其他方法更快。

希望本文能帮助您理解这一新功能。

感谢阅读本文。

© . All rights reserved.