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






4.73/5 (8投票s)
本文将探讨列存储索引——SSIS Denali CTP3 中的一项新功能。
SQL 11 (代号 Denali) 入门 - 第七部分 (CTP 3 中的列存储索引)
目录
- 引言
- 背景
- 什么是行存储和列存储
- 行存储方法的缺点
- 列存储相对于行存储方法的优点
- 什么是列存储索引?
- 创建列存储索引
- 对具有列存储索引的表进行数据操作
- 列存储索引的性能
- IGNORE_NONCLUSTERED_COLUMNSTORE_INDEX 的目的
- 列存储索引的当前限制
- 使用列存储索引的理想场景
- 为什么我们要使用列存储索引?
- 参考
- 结论
引言
微软在技术领域最热门、最棒的开发之一于 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 的其他文章:
- SQL 11 (代号 Denali) 入门 - 第一部分 (CTP 1 中的 SSMS 功能)
- SQL 11 (代号 Denali) 入门 - 第二部分 (CTP 1 中的 T-Sql 功能)
- SQL 11 (代号 Denali) 入门 - 第三部分 (CTP 1 中的 SSIS 功能)
- SQL 11 (代号 Denali) 入门 - 第四部分 (CTP 1 中的独立数据库)
- SQL 11 (代号 Denali) 入门 - 第五部分 (CTP 3 中的 SSIS 功能)
- SQL 11 (代号 Denali) 入门 - 第六部分 (CTP 3 中的 T-Sql 功能)
什么是行存储和列存储
在讨论列存储索引是什么之前,花一些时间了解什么是列存储以及它的作用是值得的。
我们知道,页是 SQL Server 中数据存储的基本单元。数据行是实际数据的存储单元。它们紧跟在页头之后,并按顺序放置在页上。这种以行方式在页上存储记录的方式称为行存储。
当来自单个列但来自多个行中的值连续存储时,称为列存储。
行存储方法的缺点
如果我们参考图 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 的“索引”文件夹,我们将发现索引已创建。
通过对象资源管理器创建列存储索引
步骤 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:展开表的树结构,然后右键单击“索引”图标 -> 新建索引 -> 非聚集列存储索引。
将打开“新建索引”窗口。
让我们输入索引名称,然后通过单击“添加”按钮添加列。单击“添加”按钮后,将打开“选择列”对话框,从中选择列。
完成后,单击“确定”。此时,我们将看到以下内容。
单击“确定”,索引将创建。
对具有列存储索引的表进行数据操作
对已具有列存储索引的表进行数据操作(任何 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 毫秒 |
因此,我们可以从中推断出,对于聚合数据,列存储比非列存储索引是更好的选择。
我们还看一下第一个查询(无列存储)的执行模式:
可以看到,在传统方法中,存储模式是行存储,数据一次处理一行。
现在,我们看一下第二个查询(带列存储)的执行模式:
新的批处理执行模式比以前的方法更有效,因为它以批处理而非行的形式处理数据,这对于数据量大、工作负载重的数据仓库处理至关重要。执行计划中的每个运算符都可以使用行执行模式,当列存储索引可用时,某些运算符也可以使用批处理模式。既有估计执行模式,也有实际执行模式,这些信息显示在查询执行计划上。另请注意,有一个新的物理运算符,即列存储索引扫描。
IGNORE_NONCLUSTERED_COLUMNSTORE_INDEX 的目的
如果我们想禁止使用列存储索引,我们可以使用 IGNORE_NONCLUSTERED_COLUMNSTORE_INDEX,如下所示:
SELECT BelongsTo ,Cnt = COUNT(*) FROM tbl_Players_WithColumnStore GROUP BY BelongsTo OPTION (IGNORE_NONCLUSTERED_COLUMNSTORE_INDEX);
从执行计划可以看出,物理运算符已更改为聚集索引扫描。
列存储索引的当前限制
在当前版本中,列存储索引存在一些限制,如下所示:
(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 语句中使用它。我们还对传统方法和新列存储方法进行了性能基准测试,并讨论了它为何比其他方法更快。
希望本文能帮助您理解这一新功能。
感谢阅读本文。