简介
首先介紹列存储的概念: 传统的数据库存储是行存储。对于SQL Server来说,每个page是8K;往page里面塞数据,假设该表每条数据长度是500字节,那么这个page 先塞第一条数据,然后再塞第二条数据,大概能塞 8K/500=16条数据。注意这里每一条数据都是包括所有字段(column)的。如图所示,下面是若干个page,每个page塞满了一行一行的数据。行存储示意图。
接下来介绍列存储,是一个column一个column塞,而且在SQL Server里,是向row group或者segment塞。列存储示意图
如图,该表有5个字段(column),每个红色柱体是一个Segment;每5个segment组成一个row group。Segment只包含一个column的数据,而row group包含所有column的数据。
每个Segment最多包含1百万条该column的数据;从性能上说,1百万条能达到性能最优(数量越大,压缩比越大),数量越少,该segment的性能越差。
列存储对比行存储有什么好处: 行存储适合OLTP系统,就是多用户使用,很多update/insert/delete,SQL 语句处理的对象都是几条数据或者几百条数据(数据量不大);
列存储适合数据仓库,用户数很少,数据量巨大,数据变化少(除了ETL)。在SQL Server中,ColumnStore index能把大量的数据压缩到1/10,从而减少IO,CPU和memory的使用,从而带来性能的飞跃;ColumnStore 除了压缩,还使用了Batch mode,segment eliminate等技术,对性能有很大提升。
2. ColumnStore Index 适用的场景
和其他技术一样,不能适合所有场景;如果选用的场景不适合,反而会带来性能的急剧下降。
(1) 用星性/雪花模型建模的数据仓库
(2) 该表(或者分区)的记录数要大于1百万
(3) 大部分SQL 语句是报表类的语句,就是range scan 而不是 seek。
(4) 该表的数据很少进行update/delete,大量的insert是可以的。
(5) 该表不能有varchar(max), nvarchar(max), or varbinary(max) 数据类型
(6) 对于OLTP数据库,在某些特定的场景下也可以使用columnstore index :real-time operational analytics
3. Column Store的物理结构
ColumnStore Index 除了Row group 还包括 DeltaStore。假设该表有1105万条记录,每个rowgroup容纳100万条,那么总共有11个row group,还有5万记录放在Deltastore里面。
DeltaStore是用来存放不够数量(这里是100万)的数据,行存储,没有压缩;而rowgroup都是列存储,而且压缩了。
随着insert 数据增多,Deltastore的数量增加,如果数量增加到100万,该Deltastore 会停止接收数据,变成row group,也就是 列存储,压缩;如果还有数据insert,会生成新的Deltastore。
Columnstore Index 的组成部分除了 Row Group,Delta store,还有 Delted Bitmap.
Columnstore index 删除记录并不是物理删除,而是逻辑删除,在 Delted Bitmap加一个标记; Delted Bitmap会记录整个表被删除的记录;SQL Server对该表做query的时候,除了查询row group,Delta store(row store)还要查询 Delted Bitmap,把三者的结果Union才是最后的结果。
那么ColumnStore Index什么时候做物理删除? 对index 进行rebuild或者reorganize的时候。
对Columnstore index进行update,并不是物理update,而是delete该条记录然后insert一条新的记录。
ColumnStore Index 结构小结:
(1) 包括Row Group(compressed,列存储),Delta Store (也叫Delta Row group,行存储),Deleted Bitmap (存储被删除的记录的信息)。
(2) 从SQL Server2016开始,一个表创建了ColumnStore index后,还可以创建传统的行存储的索引----non clustered index(NCI)。
(3)ColumnStore index本身不排序的,所以查询某一条记录都需要 全表扫描(full table scan);不排序这个特性对于insert是利好,performance很好;对于delete/update不好,特别表比较大的时候。 因为delete/update某一条记录,需要先找到它,而查找的代价对于ColumnStore Index 比较大。
(4) 对于上面第三点,要快速的找到某一条或几条数据,可以在ColumnStore index基础上再创建传统的行存储的索引----non clustered index(NCI)。
(5) 在ColumnStore index基础上再创建传统的行存储的索引----non clustered index(NCI),好处不仅是performance,还能给这个表加上 唯一性约束、主键约束和外键约束等约束。当然,这些都只能在SQL2016或之后的版本才能实现。
4. 如何发现某些表适合创建ColumnStore index。
首先它比较适合于数据仓库,但数据仓库的每个表都能创建ColumnStore Index吗?另外OLTP环境可以使用CCI吗?
另外可以用 DMV sys.dm_db_index_operational_stats来查看某个表的过往操作:
(1) 如果50%以上的操作是range scan,而不是seek
(2) update/delete的操作少于10%
那么这个表很适合创建cluster columnstore index(CCI);当然还有一个前提,该表足够大,至少1百万条记录,越大越好。
如何估计某个表创建clustered columnstore index 之后的压缩率? 在SQL 2019中,可以使用sp_estimate_data_compression_savings 来预估压缩率。
该sp在数据库中执行以下操作:
•创建临时表 T
•把该表的数据进行采样,载入一部分数据到T
•查看T的大小
•对T 进行列存储压缩,查看压缩后的大小
5. 快速的装载数据到已经创建 CCI的表中
(1) 装载外部文件
bulk insert tableA FROM 'c:\temp\fileA.csv'
csv文件的数据并行的装载到多个Row Group和多个Delta Store 中;超过102400条记录的数据进入Row Group,列压缩;没有超过102400条记录的数据进入 Delta Store (Delta Row group)。
因为Row Group提供了高的压缩比,所以装载数据产生的日志也会少很多;
SQL Server会自动的使用并行操作,同时向多个Row Group装载数据。
(2) 从其他表装载数据
Insert into select * from
与“装载外部文件”很类似,超过100k条记录的数据进入Row Group,列压缩;没有超过100k条记录的数据进入 Delta Store (Delta Row group)。
不过SQL Server不会自动的使用并行操作,要使用tablock才能触发并行。
insert into ccitest with (TABLOCK) select * from dbo.FactResellerSalesXL (能并行)
(3) 使用SSIS 来装载数据
从SQL2016开始,有一个新的参数 AutoAdjustBufferSize,它能根据batchsize来自动调整 buffer size,对性能有极大提升。
6. CCI 的性能
CCI 除了压缩比大带来的CPU/memory/IO的减少,还使用了 Batch mode,segment eliminate和 并行来提升性能。
(1) Batch Mode
Batch mode 从字面上,就是批处理,就是一次处理几百条数据,而不是一条一条处理数据;
比较适合于大数据量的数据仓库。
Batch mode 是从SQL Server 2012开始和ColumnStore Index 一起使用的;在SQL 2019之前,Batch mode只能在列存储中使用,从SQL 2019开始,batch mode也能在row store使用。
对于 SQL 语句: selectProductKey,OrderQuantityfromFactResellerSalesXL_CCI where OrderQuantity<3
batch mode会在内存中 占用64K大小的内存,形成上图中的vector,先scan 该表把64k的数据放到vector中,然后用predictte来过滤(OrderQuantity<3)。符合过滤条件的,会在memory当中的bitmap打上标记;bitmap在上图的最左边。
每次处理64k大小的数据。
在SQL 2016之前,很多函数不支持 batch mode,包括sum,avg,min,rank等;还有distinct,left join,group by, order by等也不被支持。 总之一句话,要用Columnstore index ,要用batch mode,不要选SQL 2012/2014,要选SQL 2016/2017/2019
(2) Segment elimination and column elimination
Segment elimination 也叫 Rowgroup elimination
从上图可知,如果表SalesTable创建了ColumnStore Index,SQL Server会自动把不需要的字段(column),不需要的rowgroup过滤掉。
CCI如何过滤Row group/segment?请看下面的元数据:
SELECT segment_id, object_name(p.object_id), s.column_id, s.min_data_id, s.max_data_id FROM sys.column_store_segments s, sys.partitions p
where p.object_id = object_id('FactResellerSalesXL_CCI') and
p.hobt_id = s.hobt_id and column_id = 2
从上图可以看到,该表的CCI的第二个字段(column)有12个segment,每个segment都记录了最大值,和最小值,这样sql语句查询的时候,很容易过滤不需要的segment。
另外执行 下面的语句:
set statistics IO ON
set statistics TIME ON
SELECT Productkey, OrderQuantity as curqty,
Sum (OrderQuantity) OVER (ORDER BY ProductKey) AS TotalQuantity
FROM FactResellerSalesXL_CCI WHERE orderdatekey in ( 20060301,20060401)
正因为第二个字段(orderdatekey)的元数据存储了最大值/最小值,所以上面的SQL 直接skip了 7个segment,只在另外的5个segment中查找数据
(3) Aggregate Pushdown
从SQL2016 开始,对于Select SUM(sales) from 这样的语句,当表非常大的时候,Aggregate Pushdown 特性可以极大提高性能。
简单的来说,对于sum/avg/group by/max/count这样·的函数,表有数以亿计的记录,SQL Server 会自动的把处理大量数据的工作放在执行计划的第一步,也就是最接近存储的地方,这样传送给执行计划的下一步的数据会大大减少。
该特性自动进行,不用任何调整。
7. CCI 的维护。
和其他传统的row store index一样,CCI也会有碎片(fragment)。
有两类碎片:
1.对于CCI,只有一个Delta Store是正常的,如果有多个Delta Store(超过10个以上)那就是碎片化,需要进行维护
2. 对于列存储的Row Group,删除数据只是逻辑删除,没有物理删除;如果删除的数据占该RowGroup中超过10%,那就是碎片化,需要进行维护。
从SQL 2016开始,使用 Alter Index on
(1) self merge
当Row group中的逻辑删除记录数占到10%以上,就会使用self merge物理删除这些记录;
(2)merge
两个Row group的记录数加起来都不到1百万,那么merge操作会把这两个RG 合并成一个
8. Columnstore 和 In-Memory OLTP的结合
首先解释什么是 Real-time Operational Analytics。
之前介绍的传统的数据仓库,适合使用 CCI;对比传统的数据仓库,现在有一些混合型的应用场景,就是既有OLTP,也有数据仓库的查询。这种场景,就是直接在OLTP的数据库上跑一些报表的大SQL,好处如下:因为没有专门的数据仓库,节约了硬件;
没有经过ETL,OLTP的数据一般是最新的,而传统的数据仓库往往经过ETL,数据往往不是最新的。
因为没有ETL,Stage 数据库的硬件,ETL的维护、软件的成本都节省了。
缺点也很明显:不能像传统数据仓库,有多个数据源
没有经过ETL,数据的结构不能像传统数据库那样实现星形/雪花建模
同时在一个数据库上跑OLTP和报表SQL,互相影响性能
前面介绍了,Real-time Operational Analytics可以使用disk based table(磁盘表),也可以使用in memory table;如果使用前者,就可以使用noclustered columnstore index;如果使用in memory table,必须使用 clustered columnstore index。下面介绍后者
首先该表是 in-memory OLTP table,表上可以创建hash index 或range index,这些都是传统的row store(行存储),只是把这些都搬到内存当中;图中最下面是columnstore index(列存储),也是放着memory当中。
上图告诉我们,数据实现了冗余,in memory table存储了数据,而内存中的columnstore index也存储了数据,而且是数据、索引放在一起。每当有数据insert,先对in memory table的hot 部分(尾部)进行insert,当这一部分的记录数达到1百万,这些数据会转移到columnstore index 当中。
当SQL 语句是OLTP类型,in memory table 可以很好的处理,效率非常高,具体原理要参考in memory OLTP特性。
当SQL 语句是报表类语句,columnstore index可以高效处理。
从上图看,该场景需要比较多的memory。