第4章丨IRIS Global —— 对象使用多维存储

写在前面

为了与大家保持一个愉快的沟通,以及便于描述方便,本文做了一些术语简写,如下:

  • global:全局变量,无特殊说明,指的就是 std-global
  • std-globals : 标准global
  • pro-globals: 进程global
  • ref-global: 引用global(映射global)

本章介绍 InterSystems IRIS® 对象SQL 引擎如何利用多维存储(全局变量)来存储持久性对象、关系表和索引。

虽然 InterSystems IRIS 对象SQL 引擎会自动提供和管理数据存储结构,但了解其工作原理的详细信息会很有用。

对象使用的存储结构和数据的关系视图是相同的。为简单起见,本文档仅从对象角度介绍存储。

1. 数据

每个持久类都使用 %Storage.Persistent存储,可以使用一个或多个多维存储(全局)节点将自身的实例存储在InterSystems IRIS 数据库中。

每个持久性类都有一个存储定义,用于定义其属性在全局节点中的存储方式。此存储定义(称为“默认结构”)由类编译器自动管理。(如果您愿意,可以修改此存储定义,甚至可以提供其替代版本。)

1.1 默认结构

用于存储持久性对象的默认结构非常简单:

  • 数据存储在一个全局中,其名称以完整的类名(包括包名)开头。追加“D”以形成数据全局的名称,而附加“I”用于索引全局。

  • 每个实例的数据都存储在数据全局的单个节点中,所有非瞬态(non-transient )属性都放在$List结构中。

  • 数据全局中的每个节点都按对象 ID 值下标。默认情况下,对象 ID 值是通过调用存储在数据全局的根(没有下标)的计数器节点上的 $Increment 函数提供的整数。

例如,假设我们定义了一个简单的持久类 MyApp.Person,它具有两个文本属性:

Class MyApp.Person Extends %Persistent
{
Property Name As %String;
Property Age As %Integer;
}

如果我们创建并保存此类的两个实例,则生成的全局将类似于:

 ^MyApp.PersonD = 2  // counter node
 ^MyApp.PersonD(1) = $LB("",530,"Abraham")
 ^MyApp.PersonD(2) = $LB("",680,"Philip")

请注意,存储在每个节点中的$List结构的第一部分是空的;这是为类名保留的。如果我们定义此 Person 类的任何子类,则此第一个位置将包含子类名称。%OpenId 方法(由%Persistent类提供) 使用此信息在相同范围内存储多个对象时以多态方式打开正确类型的对象。此位置在类存储定义中显示为名为“%%CLASSNAME”的属性。

1.2 IDKEY

IDKEY 机制允许您显式定义用作对象 ID 的值。为此,只需向类中添加一个 IDKEY 索引定义,并指定将提供 ID 值的一个或多个属性。请注意,保存对象后,其对象 ID 值将无法更改。这意味着,在保存使用 IDKEY 机制的对象后,无法再修改对象 ID 所基于的任何属性。


1)、IDKEY 基于单个属性
例如,我们可以修改上一示例中使用的 Person 类以使用 IDKEY 索引:

Class MyApp.Person Extends %Persistent
{
Index IDKEY On Name [ Idkey ];

Property Name As %String;
Property Age As %Integer;
}

如果我们创建并保存 Person 类的两个实例,则生成的全局现在类似于:

 ^MyApp.PersonD("Abraham") = $LB("",530,"Abraham")
 ^MyApp.PersonD("Philip") = $LB("",680,"Philip")

请注意,不再定义任何计数器节点。另请注意,通过将对象 ID 基于 Name 属性,我们暗示 Name 的值对于每个对象必须是唯一的。


2)、IDKEY 基于多个属性
如果 IDKEY 索引基于多个属性,则主数据节点具有多个下标。例如:

Class MyApp.Person Extends %Persistent
{
Index IDKEY On (Name,Age) [ Idkey ];

Property Name As %String;
Property Age As %Integer;
}

在这种情况下,生成的全局现在将类似于:

 ^MyApp.PersonD("Abraham",530) = $LB("",530,"Abraham")
 ^MyApp.PersonD("Philip",680) = $LB("",680,"Philip")

注意:在 IDKEY 索引使用的任何属性的值中,不得有一对连续的竖线 (||),除非该属性是对持久类实例的有效引用。此限制是由 InterSystems SQL 机制的工作方式施加的。在 IDKey 属性中使用||可能会导致不可预知的行为。

1.3 子类

默认情况下,由持久性对象的子类引入的任何字段都存储在其他节点中。子类的名称用作附加下标值。

例如,假设我们定义了一个具有两个文本属性的简单持久性 MyApp.Person 类:

Class MyApp.Person Extends %Persistent
{
Property Name As %String;

Property Age As %Integer;
}

现在,我们定义一个持久子类 MyApp.Student,它引入了两个额外的文本属性:

Class MyApp.Student Extends Person
{
Property Major As %String;

Property GPA As %Double;
}

如果我们创建并保存此 MyApp.Student 类的两个实例,则生成的全局将类似于:

^MyApp.PersonD = 2  // counter node
^MyApp.PersonD(1) = $LB("Student",19,"Jack")
^MyApp.PersonD(1,"Student") = $LB(3.2,"Physics")

^MyApp.PersonD(2) = $LB("Student",20,"Jill")
^MyApp.PersonD(2,"Student") = $LB(3.8,"Chemistry")

Person 类继承的属性存储在主节点中,而由 Student 类引入的属性存储在其他子节点中。此结构可确保学生数据可以互换用作人员数据。例如,列出所有 Person 对象名称的 SQL 查询可以正确选取 Person 和 Student 数据。此结构还使类编译器在将属性添加到超类或子类时更容易保持数据兼容性。

请注意,主节点的第一部分包含字符串“Student” — 这标识包含 Student 数据的节点。

1.4 父子关系

在父子关系中,子对象的实例存储为它们所属的父对象的子节点。此结构可确保子实例数据与父数据一起进行物理聚类。

例如,下面是两个相关类(发票)的定义:

/// An Invoice class
Class MyApp.Invoice Extends %Persistent
{
Property CustomerName As %String;

/// an Invoice has CHILDREN that are LineItems
Relationship Items As LineItem  [inverse = TheInvoice, cardinality = CHILDREN];
}
/// A LineItem class
Class MyApp.LineItem Extends %Persistent
{
Property Product As %String;
Property Quantity As %Integer;

/// a LineItem has a PARENT that is an Invoice
Relationship TheInvoice As Invoice [inverse = Items, cardinality = PARENT];
}

如果我们存储发票对象的多个实例,每个实例都有关联的 LineItem 对象,则生成的全局将类似于:

^MyApp.InvoiceD = 2  // invoice counter node
^MyApp.InvoiceD(1) = $LB("","Wiley Coyote")
^MyApp.InvoiceD(1,"Items",1) = $LB("","Rocket Roller Skates",2)
^MyApp.InvoiceD(1,"Items",2) = $LB("","Acme Magnet",1)

^MyApp.InvoiceD(2) = $LB("","Road Runner")
^MyApp.InvoiceD(2,"Items",1) = $LB("","Birdseed",30)

1.5 嵌入对象

通过以下方式存储嵌入对象:首先将它们转换为序列化状态(默认情况下是包含对象属性的$List结构),然后以与任何其他属性相同的方式存储此串行状态。

例如,假设我们定义了一个具有两个文本属性的简单串行(可嵌入)类:

Class MyApp.MyAddress Extends %SerialObject
{
Property City As %String;
Property State As %String;
}

现在,我们修改前面的示例以添加嵌入式家庭地址属性:

Class MyApp.MyClass Extends %Persistent
{
Property Name As %String;
Property Age As %Integer;
Property Home As MyAddress;
}

如果我们创建并保存此类的两个实例,则生成的全局等效于:

 ^MyApp.MyClassD = 2  // counter node
 ^MyApp.MyClassD(1) = $LB(530,"Abraham",$LB("UR","Mesopotamia"))
 ^MyApp.MyClassD(2) = $LB(680,"Philip",$LB("Bethsaida","Israel"))

1.6 流

全局流存储在全局变量中,方法是将其数据拆分为一系列块,每个块小于 32K 字节,然后将这些块写入一系列顺序节点。文件流存储在外部文件中。

2. 索引

持久类可以定义一个或多个索引;其他数据结构用于使操作(如排序或条件搜索)更有效。

  • InterSystems SQL在执行查询时会使用此类索引
  • InterSystems IRIS Object 和 SQL 在执行插入、更新和删除操作时自动维护索引内的正确值

2.1 标准索引的存储结构

标准索引将一个或多个属性值的有序集与包含这些属性的对象的对象 ID 值相关联。

例如,假设我们定义了一个简单的持久 MyApp.Person 类,该类具有两个文本属性和一个 Name 属性上的索引:

Class MyApp.Person Extends %Persistent
{
Index NameIdx On Name;

Property Name As %String;
Property Age As %Integer;
}

如果我们创建并保存此 Person 类的多个实例,则生成的数据和索引全局变量类似于:

 // data global
 ^MyApp.PersonD = 3  // counter node
 ^MyApp.PersonD(1) = $LB("",34,"Jones")
 ^MyApp.PersonD(2) = $LB("",22,"Smith")
 ^MyApp.PersonD(3) = $LB("",45,"Jones")


 // index global
 ^MyApp.PersonI("NameIdx"," JONES",1) = ""
 ^MyApp.PersonI("NameIdx"," JONES",3) = ""
 ^MyApp.PersonI("NameIdx"," SMITH",2) = ""

请注意有关全局索引的以下事项:

  1. 默认情况下,它被放置在一个全局变量中,其名称是类名,并附加了一个“I”(表示索引)。

  2. 默认情况下,第一个下标是索引名称;这允许将多个索引存储在同一个全局中而不会发生冲突。

  3. 第二个下标包含已整理的数据值。在这种情况下,将使用默认的 SQLUPPER 排序规则函数对数据进行排序。这会将所有字符转换为大写(以在不考虑大小写的情况下进行排序)并在前面附加一个空格字符(以强制所有数据作为字符串进行排序规则)。

  4. 第三个下标包含包含索引数据值的对象的对象 ID 值。

  5. 节点本身是空的;所有需要的数据都保存在下标中。请注意,如果索引定义指定数据应与索引一起存储,则该数据将放置在索引全局变量的节点中。

2.2 位图索引

位图索引与标准索引类似,不同之处在于它使用一系列位字符串来存储与索引值对应的对象 ID 值集。

2.2.1 位图索引的逻辑运算

位字符串是一个字符串,其中包含一组采用特殊压缩格式的位(0 和 1 值)。InterSystems IRIS 包括一组函数,可有效地创建和使用位字符串。下表列出了这些内容:

  • $Bit:在位字符串内设置或获取一点。
  • $BitCount:计算位串内的位数。
  • $BitFind :在位字符串中查找位的下一个匹配项。
  • $BitLogic:对两个或多个位字符串执行逻辑(AND、OR)运算。

在位图索引中,位字符串中的序号位置对应于索引表中的行(对象 ID 号)。对于给定值,位图索引维护一个位字符串,该位字符串对于存在给定值的每一行包含 1,对于不存在给定值的每一行包含 0。请注意,位图索引仅适用于使用具有系统分配的数字对象 ID 值的默认存储结构的对象。

除了这些索引之外,系统还会维护一个称为“范围索引”的附加索引,该索引对存在的每一行包含 1,对不存在的行(如已删除的行)包含 0。这用于某些操作,如否定。

2.2.2 位图索引的存储结构

位图索引将一个或多个属性值的有序集合与包含与属性值对应的对象 ID 值的一个或多个位字符串相关联。

例如,假设我们定义了一个简单的持久 MyApp.Person类,该类具有两个文本属性,并且其 Age 属性上有一个位图索引:

Class MyApp.Person Extends %Persistent
{
Index AgeIdx On Age [Type = bitmap];

Property Name As %String;
Property Age As %Integer;
}

如果我们创建并保存此 Person 类的多个实例,则生成的数据和索引全局变量类似于:

 // data global
 ^MyApp.PersonD = 3  // counter node
 ^MyApp.PersonD(1) = $LB("",34,"Jones")
 ^MyApp.PersonD(2) = $LB("",34,"Smith")
 ^MyApp.PersonD(3) = $LB("",45,"Jones")

 // index global
 ^MyApp.PersonI("AgeIdx",34,1) = 110...
 ^MyApp.PersonI("AgeIdx",45,1) = 001...

 // extent index global
 ^MyApp.PersonI("$Person",1) = 111...
 ^MyApp.PersonI("$Person",2) = 111...

请注意有关全局索引的以下事项:

  • 默认情况下,它被放置在一个全局变量中,其名称是类名,并附加了一个“I”(表示索引)。

  • 默认情况下,第一个下标是索引名称;这允许将多个索引存储在同一个全局中而不会发生冲突。

  • 第二个下标包含已整理的数据值。在这种情况下,不应用排序规则函数,因为这是数字数据的索引。

  • 第三个下标包含一个块号;为了提高效率,位图索引被划分为一系列位字符串,每个位字符串包含表中约 64000 行的信息。这些位字符串中的每一个都称为块。

  • 节点包含位字符串。

  • 另请注意:由于此表具有位图索引,因此会自动维护范围索引。此扩展数据块索引存储在索引全局索引中,并使用类名(前面附加一个“$”字符)作为其第一个下标。

2.2.3 直接访问位图索引

以下示例使用类扩展数据块索引来计算存储对象实例(行)的总数。请注意,它使用$Order来循环访问扩展数据块索引的块(每个块包含大约 64000 行的信息):

/// Return the number of objects for this class.<BR>
/// Equivalent to SELECT COUNT(*) FROM Person
ClassMethod Count() As %Integer
{
    New total,chunk,data
    Set total = 0
    
    Set chunk = $Order(^MyApp.PersonI("$Person",""),1,data)
    While (chunk '= "") {
        Set total = total + $bitcount(data,1)
        Set chunk = $Order(^MyApp.PersonI("$Person",chunk),1,data)
    }

    Quit total
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值