文章目录
- 基于`IRIS`列存储,我们能做什么
- 简介
- 使用场景
- 如何使用列存储
- 什么情况下使用列储存
- 统计数据数量`count`
- 计算字段平均值`avg`
- 计算字段和`sum`
- 列存储与行存储区别
- 总结
基于IRIS
列存储,我们能做什么
简介
列存储是一种数据存储方式,与传统的行存储数据库(如关系数据库)不同。在列存储中,数据被存储在列中,而不是行中。
这种存储方式的主要优点在于,它可以更有效地存储和查询结构化数据,特别是需要执行大量聚合查询的数据。列存储数据库通常具有高性能,可以快速处理大量数据,并且适用于分析型数据仓库,时序数据,设备数据等。
列存储数据库通常使用NoSQL
(Not Only SQL
)数据库技术,并且不需要严格的数据模式。因此,它们更加灵活,可以快速适应数据变化,并且适用于处理大量数据。
使用场景
- 提高查询性能:列存储将同一列的数据存储在一起,减少了需要读取的磁盘块数量,因此在查询时可以更快地检索需要的数据,特别是当查询需要检索一部分列时,列存储可以提供更好的查询性能。
- 提高压缩比率:列存储可以使用列级别的压缩算法,由于同一列的数据通常具有相似性质,因此可以使用更高效的压缩算法,从而减少存储空间。
- 支持大规模数据分析:列存储是大规模数据分析的关键技术之一,因为它可以对大量的数据进行快速的分析和计算。通过将同一列的数据存储在一起,可以提供更高效的数据分析和聚合功能。
如何使用列存储
定义列存储需要在持久类中添加参数 STORAGEDEFAULT = "columnar
并上类关键字Final
。
定义User.Person
表如下:
Class User.Person Extends %Persistent [ DdlAllowed, Final ]
{
Parameter STORAGEDEFAULT = "columnar";
Property Name As %String [ SqlColumnNumber = 2 ];
Property Number As %Integer [ SqlColumnNumber = 3 ];
Property Price As %Integer [ SqlColumnNumber = 4 ];
Property Amout As %Integer [ SqlColumnNumber = 5 ];
}
编译该类会产生如下所示的存储定义:
Storage Default
{
<Data name="_CDM_Amout">
<Attribute>Amout</Attribute>
<Structure>vector</Structure>
</Data>
<Data name="_CDM_Name">
<Attribute>Name</Attribute>
<Structure>vector</Structure>
</Data>
<Data name="_CDM_Number">
<Attribute>Number</Attribute>
<Structure>vector</Structure>
</Data>
<Data name="_CDM_Price">
<Attribute>Price</Attribute>
<Structure>vector</Structure>
</Data>
<DataLocation>^User.PersonD</DataLocation>
<IdLocation>^User.PersonD</IdLocation>
<IndexLocation>^User.PersonI</IndexLocation>
<StreamLocation>^User.PersonS</StreamLocation>
<Type>%Storage.Persistent</Type>
}
此存储定义看起来与为标准类定义创建的定义略有不同,后者使用传统的基于行的存储布局。虽然Global
名称相同,但每个使用列式存储的属性都有一个 <Data>
元素。 (CDM
代表列数据映射。) <Data>
标签的名称属性用作 ^User.PersonI
Global
中的下标,<Attribute>
元素包含属性名称,<Structure>
元素带有值向量表示向量用于以列形式表示属性的值。
向User.Person
表添加数据,代码如下:
ClassMethod Save(num)
{
ts
for i = 1 : 1 : num {
s obj = ##class(User.Person).%New()
s obj.Name = "yx" _ $random(100)
s obj.Number = $random(100)
s obj.Price = $random(100)
s obj.Amout = obj.Number * obj.Price
s sc = obj.%Save()
ret:($$$ISERR(sc)) $system.Status.GetErrorText(sc)
}
b
tc
q $$$OK
}
列存储持久类中每个属性的数据存储在列中,数据并不存储在行中。所以^User.GlobalD
中仅包含ID
,不包含数据。
USER>w ##class(M.ColumnStorage).Save(1)
1
USER>w ##class(M.ColumnStorage).Save(1)
1
USER>w ##class(M.ColumnStorage).Save(1)
1
USER>zw ^User.PersonD
^User.PersonD=3
^User.PersonD(1)=""
^User.PersonD(2)=""
^User.PersonD(3)=""
数据保存在^User.PersonI
索引中,以Json
格式保存数据。
type
- 字段类型。count
- 数据数量。vector
- 数据值。length
- 向量长度。
USER>zw ^User.PersonI
^User.PersonI("$Person",1)=$zwc(412,1,0)/*$bit(2..4)*/
^User.PersonI("_CDM_Amout",1)={"type":"integer", "count":3, "length":4, "vector":[,7533,42,3496]} ; <VECTOR>
^User.PersonI("_CDM_Name",1)={"type":"string", "count":3, "length":4, "vector":[,"yx80","yx49","yx55"]} ; <VECTOR>
^User.PersonI("_CDM_Number",1)={"type":"integer", "count":3, "length":4, "vector":[,81,6,76]} ; <VECTOR>
^User.PersonI("_CDM_Price",1)={"type":"integer", "count":3, "length":4, "vector":[,93,7,46]} ; <VECTOR>
这里我们注意索引第二个节点,代表当前索引存储数据的序号,当数据length
超过64,000
时,下标会自动加一。
注:count
计数低于length
,因为该列包含 NULL
值。
添加10W
条数据。
USER>w ##class(M.ColumnStorage).Save(100000)
1
什么情况下使用列储存
创建User.People
表字段与User.Person
表相同,但User.People
表为行存储,User.Person
表为列储存。
Class User.People Extends %Persistent
{
Property Name As %String [ SqlColumnNumber = 2 ];
Property Number As %Integer [ SqlColumnNumber = 3 ];
Property Price As %Integer [ SqlColumnNumber = 4 ];
Property Amout As %Integer [ SqlColumnNumber = 5 ];
}
分别给User.People
表与User.Person
表添加1000W
条数据。
如下示例计算的数据量均为1000W
条数据。
统计数据数量count
统计User.People
表,数据数量,如图执行时间为1.6561
秒。引用Global
数量11100322
个。
统计User.Person
表,数据数量,如图执行时间为0.0026
秒。引用Global
数量496
个。
如上可观察到列储存User.Person
聚合count
效率是行储存User.People
表 636
倍。
计算字段平均值avg
计算User.People
表,Amount
字段平均值,如图执行时间为2.3046
秒。引用Global
数量11101379
个。
计算User.Person
表,Amount
字段平均值,如图执行时间为0.0179
秒。引用Global
数量2447
个。
如上可观察到列储存User.Person
聚合avg
效率是行储存User.People
表 128
倍。
计算字段和sum
计算User.People
表,Amount
字段所有数据之和,如图执行时间为2.2902
秒。引用Global
数量88801366
个。
计算User.Person
表,Amount
字段数据之和,如图执行时间为0.0075
秒。引用Global
数量1922
个。
如上可观察到列储存User.Person
聚合sum
效率是行储存User.People
表 305
倍。
列存储与行存储区别
列存储 | 行存储 | |
---|---|---|
数据存储 | 主数据存储在每列一个Global 中。 64,000 个数据元素的序列存储在单独的Global 。 | 主要数据存储在一个Global 中。每行数据都存储在单独的Global 下标中。 |
插入数据速度 | 慢,(因为每个列存储表插入数据时,会打开多个Global 去赋值) | 快。(因为insert 时,仅需要打开一个Global ,添加$list 即可) |
使用场景 | 不频繁变动的数据,例如,台帐,日志,病人生命体征数据,就很适合。 | 频繁变动的数据。 |
数据里要求 | SQL 表数据量大于100w (数据量100w 时效率与行存储效率区别不大)。 | SQL 表数据量小于100 w,则无需考虑列式存储。 |
单列数据查询 | 列存储查询时,单列所有数据都在一个Global 中。查询1000w 条数据可能只需174 个Global 。 | 行存储查询时,需要将每一列的Global 保存到内存中,在根据Global 的$list 再取值。查询1000w 条数据。就需要查询1000W 个Global 。 |
总结
- 选择正确的存储方式可以将查询性能提高一个数量级。
以上是个人对基于列存储的一些理解,由于个人能力有限,欢迎大家提出意见,共同交流。