• 第2章丨IRIS Global 使用多维存储


    写在前面

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

    • global:全局变量,无特殊说明,指的就是 std-global
    • std-globals : 标准global
    • pro-globals: 进程global
    • ref-global: 扩展映射global
    • nak-global:省略的global引用
    • nak-cursor :省略的global的游标

    1. 在全局变量中存储数据

    在全局节点中存储数据很简单:您可以像对待任何其他变量一样处理global(全局变量)。不同之处在于,global上的操作会自动写入数据库。

    1.1 创建global

    无需进行任何设置工作即可创建新的全局变量。简单地将数据设置为全局隐式地创建一个新的全局结构。您可以创建全局(或全局下标)并通过单个操作将数据放入其中,也可以创建全局(或下标)并通过将其设置为 null 字符串将其留空。在 ObjectScript 中,这些操作是使用 SET 命令完成的。

    下面的示例定义一个名为 Colorglobal(如果尚不存在),并将值“Red”与其关联。如果已存在名为 Color 的全局变量,则这些示例会对其进行修改以包含新信息。

    在 ObjectScript 中:

     SET ^Color = "Red"
    
    • 1

    1.2 在全局节点中存储数据

    要将值存储在全局下标节点中,只需像设置任何其他变量数组一样设置全局节点的值。如果指定的节点以前不存在,则创建该节点。如果它确实存在,则其内容将替换为新值。

    您可以通过表达式(称为全局引用)在全局变量中指定节点。全局引用由上箭头 (^)、全局名称和(如果需要)一个或多个下标值组成。下标(如果存在)括在括号“( )”内,并用逗号分隔。每个下标值本身就是一个表达式:文本值、变量、逻辑表达式,甚至是全局引用

    设置全局节点的值是一个原子操作:它保证成功,您不需要使用任何锁来确保并发性。

    以下是所有有效的全局引用:

    在 ObjectScript 中:

       SET ^Data = 2
       SET ^Data("Color")="Red"
       SET ^Data(1,1)=100        /* The 2nd-level subscript (1,1) is set
                                    to the value 100. No value is stored at
                                    the 1st-level subscript (^Data(1)). */   
       SET ^Data(^Data)=10       /* The value of global variable ^Data 
                                    is the name of the subscript. */
       SET ^Data(a,b)=50         /* The values of local variables a and b
                                    are the names of the subscripts. */
       SET ^Data(a+10)=50       
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    1.3 在全局节点中存储结构化数据

    每个全局节点可以包含最多 32K 个字符的单个字符串。

    数据通常通过以下方式之一存储在节点中:

    • 作为最多 32K 个字符(具体而言,32K 减去 1)的单个字符串。

    • 作为包含多段数据的字符分隔字符串。

    要使用字符分隔符在节点内存储一组字段,只需使用连接运算符 下划线 _ 将这些值连接在一起即可。下面的 ObjectScript 示例使用 # 字符作为分隔符:

       SET ^Data(id)=field(1)_"#"_field(2)_"#"_field(3)
    
    • 1

    检索数据时,您可以使用$PIECE函数将字段分开:

       SET data = $GET(^Data(id))
       FOR i=1:1:3 {
         SET field(i) = $PIECE(data,"#",i)
         }
       QUIT
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 作为包含多段数据的$LIST编码字符串。

    $LIST函数使用特殊的长度编码方案,该方案不需要您保留分隔符字符。(这是 InterSystems IRIS 对象和 SQL 使用的默认结构。

    要在节点中存储一组字段,请使用 $LISTBUILD 函数构造列表:

       SET ^Data(id)=$LISTBUILD(field(1),field(2),field(3))
    
    • 1

    检索数据时,您可以使用$LIST$LISTGET函数将字段分开:

       SET data = $GET(^Data(id))
       FOR i = 1:1:3 {
           SET field(i)=$LIST(data,i)
           }
       QUIT
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 作为较大数据集(如流或“BLOB”)的一部分。

    由于单个节点被限制为略低于32K的数据,因此通过将数据存储在一组连续节点中来实现更大的结构(例如流):

       SET ^Data("Stream1",1) = "First part of stream...."
       SET ^Data("Stream1",2) = "Second part of stream...."
       SET ^Data("Stream1",3) = "Third part of stream...."
    
    • 1
    • 2
    • 3

    提取流的代码(如 %GlobalCharacterStream ) 循环访问此类结构中的连续节点,将数据作为连续字符串提供。

    • 作为一个小小的字符串。

    如果要实现位图索引(位字符串中的位对应于表中的行的索引),则应将全局索引的节点值设置为位字符串。请注意,InterSystems IRIS使用压缩算法对位字符串进行编码。因此,位字符串只能使用InterSystems IRIS $BIT函数进行处理。有关位字符串的更多详细信息,请参阅位字符串函数概述。

    • 作为空节点。

    如果您感兴趣的数据由节点本身提供,则通常将实际下标设置为空字符串(“”)。例如,将名称与 ID 值相关联的索引通常如下所示:

      SET ^Data("APPLE",1) = ""
      SET ^Data("ORANGE",2) = ""
      SET ^Data("BANANA",3) = ""
    
    • 1
    • 2
    • 3

    2. 删除全局节点

    若要从数据库中删除全局节点、一组子节点或整个全局节点,请使用 ObjectScript KILLZKILL 命令。

    • KILL 命令删除特定全局引用中的所有节点(数据及其在数组中的相应条目),包括任何后代子节点。也就是说,将删除以指定下标开头的所有节点

    例如,底下ObjectScript 语句,删除整个 ^Data

      KILL ^Data
    
    • 1
    • ObjectScript ZKILL 命令删除指定的全局或全局下标节点。它不会删除后代子节点

    • 不能对全局变量global使用 NEW 命令。

    • 在使用kill命令删除一个大型全局变量后,该全局变量曾经占用的空间可能尚未完全释放,因为垃圾回收器守护进程将在后台将块标记为空闲。因此,在类在删除一个大型全局变量后,立即调用 SYS.Database 类中的 ReturnUnusedSpace 方法,返回的空间可能不会像预期的那样多,因为该全局所占用的块可能尚未被释放。

    3. 测试全局节点是否存在

    若要测试特定全局(或其后代)是否包含数据,请使用 $DATA 函数。

    $DATA 返回一个值,该值指示指定的global引用是否存在。可能的返回值为:

    • 0:全局变量未定义。
    • 1:全局变量存在并包含数据,但没有后代。请注意,空字符串 (“”) 限定为数据。
    • 10:全局变量具有后代(包含指向子节点的向下指针),但本身不包含数据。对此类变量的任何直接引用都将导致错误。例如,如果 $DATA(^y) 返回 10,则 SET x=^y 将产生错误。
    • 11:全局变量既包含数据又具有后代(包含指向子节点的向下指针)。

    4. 获取全局节点的值

    要获取存储在特定全局节点中的值,只需使用全局引用作为表达式:

       SET color = ^Data("Color")    ; assign to a local variable
       WRITE ^Data("Color")          ; use as a command argument
       SET x=$LENGTH(^Data("Color")) ; use as a function parameter
    
    • 1
    • 2
    • 3

    4.1 $GET功能

    您还可以使用 $GET 函数获取全局节点的值:

     SET mydata = $GET(^Data("Color"))
    
    • 1

    这将检索指定节点的值(如果存在),如果节点没有值,则返回空字符串 (“”)。如果节点没有值,则可以使用可选的第二个参数 $GET 返回指定的默认值。

    4.2 WRITE、ZWRITE 和 ZZDUMP 命令

    您可以使用各种 ObjectScript 显示命令显示全局或全局子节点的内容。

    • WRITE 命令以字符串形式返回指定全局或子节点的值。
    • ZWRITE 命令返回全局变量的名称及其值,以及其每个后代节点及其值。
    • ZZDUMP 命令以十六进制转储格式返回指定的全局或子节点的值。

    5. 遍历全局内的数据

    ObjectScript $ORDER函数允许您按顺序访问全局中的每个节点。

    5.1 使用$ORDER循环

    以下 ObjectScript 代码定义了一个简单的全局,然后循环访问其所有第一级下标:

     // clear ^Data in case it has data
     Kill ^Data
    
     // fill in ^Data with sample data
     For i = 1:1:100 {
         // Set each node to a random person's name
         Set ^Data(i) = ##class(%PopulateUtils).Name()
     }
    
     // loop over every node
     // Find first node
     Set key = $Order(^Data(""))
    
     While (key '= "") {
         // Write out contents
         Write "#", key, " ", ^Data(key),!
    
         // Find next node
         Set key = $Order(^Data(key))
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    5.2 $ORDER 参数

    ObjectScript $ORDER函数采用可选的第二个和第三个参数。

    • 第二个参数是一个方向标志,指示您希望在哪个方向遍历全局。默认值 1 指定正向遍历,而 –1 指定向后遍历

    • 第三个参数(如果存在)包含局部变量名称。如果$ORDER找到的节点包含数据,则找到的数据将写入此局部变量。当您在全局变量上进行循环并且对节点值和下标值感兴趣时,此操作的运行效率会更高

    	s TList(1)="Q"
    	s TList(2)="i"
    	s TList(3)="u"
    	s key = ""
    	for {
    		s key=$order(TList(key),1,value)
    		q:key=""
    		w key_": "_value,!
    		
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    5.3 for循环

    如果您知道给定的全局变量是使用连续的数字下标组织的,则可以使用简单的 For 循环来循环访问其值。例如:

     For i = 1:1:100 {
         Write ^Data(i),!
     }
    
    • 1
    • 2
    • 3

    通常,最好使用上面描述的$ORDER函数:它更有效,您不必担心数据中的间隙(例如已删除的节点)。

    5.4 $QUERY功能

    如果需要访问全局中的每个节点和子节点,在子节点上上下移动,请使用 ObjectScript $QUERY函数。(或者,您可以使用嵌套$ORDER循环)。

    $QUERY 函数采用全局引用,并返回一个字符串,其中包含全局中下一个节点的全局引用(如果没有后续节点,则返回“”)。若要使用$QUERY返回的值,必须使用 ObjectScript 间接寻址运算符 (@)。

    例如,假设您定义了以下全局变量:

     Set ^Data(1) = ""
     Set ^Data(1,1) = ""
     Set ^Data(1,2) = ""
     Set ^Data(2) = ""
     Set ^Data(2,1) = ""
     Set ^Data(2,2) = ""
     Set ^Data(5,1,2) = ""
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    以下调用$QUERY

     SET node = $QUERY(^Data(""))
    
    • 1

    node 设置为字符串 “^Data(1)”,即全局中第一个节点的地址。然后,要获取全局中的下一个节点,请再次调用$QUERY并使用节点上的间接寻址运算符

     SET node = $QUERY(@node)
    
    • 1

    此时,node 包含字符串“^Data(1,1)”

    下面的示例定义一组全局节点,然后使用$QUERY遍历它们,并按实际操作输出每个节点的地址:

    	 Kill ^Data // make sure ^Data is empty
    
    	 // place some data into ^Data
    	 Set ^Data(1) = "1"
    	 Set ^Data(1,1) = "11"
    	 Set ^Data(1,2) = "12"
    	 Set ^Data(2) = "2"
    	 Set ^Data(2,1) = "21"
    	 Set ^Data(2,2) = "22"
    	 Set ^Data(5,1,2) = "512"
    
    	 // now walk over ^Data
    	 // find first node
    	 Set node = $Query(^Data(""))
    	 While (node '= "") {
    	     Write node_": "_@node,!
    	     // get next node
    	     Set node = $Query(@node)
    	 }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    输出

    ^Data(1): 1
    ^Data(1,1): 11
    ^Data(1,2): 12
    ^Data(2): 2
    ^Data(2,1): 21
    ^Data(2,2): 22
    ^Data(5,1,2): 512
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    6. 复制 global

    若要将全局(全部或部分)的内容复制到另一个全局(或本地数组)中,请使用 ObjectScript MERGE 命令。

    下面的示例演示如何使用 MERGE 命令将 OldData 全局的所有内容复制到 NewData 全局中:

     Merge ^NewData = ^OldData
    
    • 1

    如果 MERGE 命令的源参数具有下标,则将复制该节点及其后代中的所有数据。如果目标参数具有下标,则使用目标地址作为顶级节点复制数据。例如,下面的代码:

     Merge ^NewData(1,2) = ^OldData(5,6,7)
    
    • 1

    ^OldData(5,6,7) 处和下方的所有数据复制到 ^NewData(1,2) 中。

     s ^OldData(5,6,7)="567"
     s ^OldData(5,6,7,1)="5671"
     s ^OldData(5,6,7,2)="5672"
     s ^OldData(5,6,7,3)="5673"
     
     Merge ^NewData(1,2) = ^OldData(5,6,7)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    最终复制的结果如下:

    ^NewData(1,2)=567
    ^NewData(1,2,1)=5671
    ^NewData(1,2,2)=5672
    ^NewData(1,2,3)=5673
    
    • 1
    • 2
    • 3
    • 4

    7. 在全局变量中维护共享计数器

    大规模事务处理应用程序的一个主要并发瓶颈可能是创建唯一标识符值。例如,考虑一个订单处理应用程序,其中必须为每个新发票指定一个唯一的标识号。传统的方法是维护某种计数器表。创建新发票的每个进程都会等待在此计数器上获取锁,增加其值并解锁它。这可能会导致对此单个记录的大量资源争用。

    为了解决这个问题,InterSystems IRIS提供了ObjectScript $INCREMENT函数。$INCREMENT以原子方式递增全局节点的值(如果该节点没有值,则将其设置为 1)。$INCREMENT的原子性质意味着不需要锁;该函数保证返回一个新的递增值,而不会受到任何其他进程的干扰。

    您可以按如下方式使用$INCREMENT。首先,您必须确定要在其中保存计数器的全局节点。接下来,每当需要新的计数器值时,只需调用$INCREMENT

     SET counter = $INCREMENT(^MyCounter)
    
    • 1

    InterSystems IRIS 对象和 SQL 使用的默认存储结构使用$INCREMENT来分配唯一的对象(行)标识符值。

    8. 在全局变量中对数据进行排序

    存储在全局变量中的数据将根据下标的值自动排序。例如,下面的 ObjectScript 代码定义了一组全局变量(以随机顺序),然后循环访问它们以演示全局节点是否按下标自动排序:

     // Erase any existing data
     Kill ^Data
     
     // Define a set of global nodes
     Set ^Data("Cambridge") = ""
     Set ^Data("New York") = ""
     Set ^Data("Boston") = ""
     Set ^Data("London") = ""
     Set ^Data("Athens") = ""
    
     // Now iterate and display (in order)
     Set key = $Order(^Data(""))
     While (key '= "") {
         Write key,!
         Set key = $Order(^Data(key)) // next subscript
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    输出

    Athens
    Boston
    Cambridge
    London
    New York
    
    • 1
    • 2
    • 3
    • 4
    • 5

    应用程序可以利用全局变量提供的自动排序来执行排序操作或维护对某些值的有序、交叉引用的索引。InterSystems SQL 和 ObjectScript 使用全局变量来自动执行此类任务。

    8.1 全局节点的排序规则

    全局节点的排序顺序(称为排序规则)在两个级别进行控制:

    • 在全局本身内和由使用全局的应用程序控制。
    • 在应用程序级别,您可以通过对用作下标的值执行数据转换来控制全局节点的排序方式(InterSystems SQL 和对象通过用户指定的排序规则函数执行此操作)。

    例如,如果您希望创建按字母顺序排序但忽略大小写的名称列表,则通常使用名称的大写版本作为下标:

     // Erase any existing data
     Kill ^Data
     
     // Define a set of global nodes for sorting
     For name = "Cobra","jackal","zebra","AARDVark" {
         // use UPPERCASE name as subscript
         Set ^Data($ZCONVERT(name,"U")) = name
     }
    
     // Now iterate and display (in order)
     Set key = $Order(^Data(""))
     While (key '= "") {
         Write ^Data(key),!  // write untransformed name
         Set key = $Order(^Data(key)) // next subscript
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    输出

    AARDVark
    Cobra
    jackal
    zebra
    
    • 1
    • 2
    • 3
    • 4

    本示例将每个名称转换为大写(使用$ZCONVERT函数),以便对下标进行排序而不考虑大小写。每个节点都包含未转换的值,以便可以显示原始值。

    8.2 数字和字符串值下标

    数值在字符串值之前进行整理;即值 1 先于值“a”。如果对给定的下标同时使用数字和字符串值,则需要注意这一事实。如果将全局值用于索引(即,根据值对数据进行排序),则最常见的做法是将值排序为数字(如工资)或字符串(如邮政编码)。

    对于按数字排序的节点,典型的解决方案是使用一元运算符:+ 将下标值强制转换为数值。例如,如果要构建一个按年龄对 id 值进行排序的索引,则可以强制年龄始终为数字:

     Set ^Data(+age,id) = ""
    
    • 1

    如果您希望将值排序为字符串(例如“0022”,“0342”,“1584”),则可以通过在前面加上空格(“”)字符来强制下标值始终为字符串。例如,如果要构建按邮政编码对 id 值进行排序的索引,则可以强制邮政编码始终为字符串:

     Set ^Data(" "_zipcode,id) = ""
    
    • 1

    这可确保始终将带有前导零的值(如“0022”)视为字符串。

    8.3 $SORTBEGIN$SORTEND 功能

    通常,您不必担心在InterSystems IRIS中对数据进行排序。无论您使用 SQL 还是直接全局访问,都会自动处理排序。

    但是,在某些情况下,可以更有效地进行排序。具体而言,在以下情况下:

    • (1) 您需要设置大量随机(即未排序)顺序的全局节点,
    • (2) 生成的全局的总大小接近 InterSystems IRIS 缓冲池的很大一部分,则性能可能会受到不利影响 — 因为许多 SET 操作涉及磁盘操作(因为数据不适合缓存)。这种情况通常出现在涉及创建索引全局变量(如批量数据加载、索引填充或在临时全局变量中对未编制索引的值进行排序)的情况下。

    为了有效地处理这些情况,ObjectScript 提供了$SORTBEGIN$SORTEND函数。

    • $SORTBEGIN函数为全局(或其一部分)启动一种特殊模式,其中全局数据集将写入特殊的暂存缓冲区并在内存(或临时磁盘存储)中排序。
    • 在操作结束时调用$SORTEND函数时,数据将按顺序写入实际的全局存储。整体操作效率要高得多,因为实际写入是按需要更少磁盘操作的顺序完成的。

    $SORTBEGIN功能非常易于使用;只需在开始排序操作之前使用要排序的全局的名称调用它,并在操作完成时调用$SORTEND

     // Erase any existing data
     Kill ^Data
    
     // Initiate sort mode for ^Data global
     Set ret = $SortBegin(^Data)
    
     // Write random data into ^Data
     For i = 1:1:10000 {
         Set ^Data($Random(1000000)) = ""
     }
    
     Set ret = $SortEnd(^Data)
    
     // ^Data is now set and sorted
    
     // Now iterate and display (in order)
     Set key = $Order(^Data(""))
     While (key '= "") {
         Write key,!
         Set key = $Order(^Data(key)) // next subscript
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • $SORTBEGIN函数是为全局创建的特殊情况而设计的,必须谨慎使用。具体来说,在$SORTBEGIN模式下,您不得从您正在写入的全局变量中读取;由于数据未写入,读取将不正确。

    • InterSystems SQL 自动使用这些函数来创建临时索引全局变量(例如,用于对未编制索引的字段进行排序)。

    9. 对 global 使用间接寻址

    通过间接寻址,ObjectScript 提供了一种在运行时创建全局引用的方法。这在程序编译时不知道全局结构或名称的应用程序中非常有用。

    间接寻址运算符 @: 支持间接寻址,该运算符取消引用包含表达式的字符串。根据 @ 运算符的使用方式,有几种类型的间接寻址。

    • 下面的代码提供了一个名称间接寻址的示例,其中 @ 运算符用于取消引用包含全局引用的字符串:
     // Erase any existing data
     Kill ^Data
    
     // Set var to an global reference expression
     Set var = "^Data(100)"
    
     // Now use indirection to set ^Data(100)
     Set @var = "This data was set indirectly."
    
     // Now display the value directly:
     Write "Value: ",^Data(100)
    
    // Value: This data was set indirectly.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 您还可以使用下标间接寻址在间接语句中混合表达式(变量或文本值):
     // Erase any existing data
     Kill ^Data
    
     // Set var to a subscript value
     Set glvn = "^Data"
    
     // Now use indirection to set ^Data(1) to ^Data(10)
     For i = 1:1:10 {
         Set @glvn@(i) = "This data was set indirectly."
     }
    
     // Now display the values directly:
     Set key = $Order(^Data(""))
     While (key '= "") {
         Write "Value ",key, ": ", ^Data(key),!
         Set key = $Order(^Data(key))
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    
    ^Data(1)="This data was set indirectly."
    ^Data(2)="This data was set indirectly."
    ^Data(3)="This data was set indirectly."
    ^Data(4)="This data was set indirectly."
    ^Data(5)="This data was set indirectly."
    ^Data(6)="This data was set indirectly."
    ^Data(7)="This data was set indirectly."
    ^Data(8)="This data was set indirectly."
    ^Data(9)="This data was set indirectly."
    ^Data(10)="This data was set indirectly."
    
    Value 1: This data was set indirectly.
    Value 2: This data was set indirectly.
    Value 3: This data was set indirectly.
    Value 4: This data was set indirectly.
    Value 5: This data was set indirectly.
    Value 6: This data was set indirectly.
    Value 7: This data was set indirectly.
    Value 8: This data was set indirectly.
    Value 9: This data was set indirectly.
    Value 10: This data was set indirectly.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 间接寻址是ObjectScript的基本特征;它不仅限于全局引用。间接寻址的效率低于直接访问,因此您应该明智地使用它。

    10. 管理事务

    InterSystems IRIS 提供了使用全局变量实现完整事务处理所需的基本操作。InterSystems IRIS 对象和 SQL 会自动使用这些功能。如果直接将事务数据写入全局变量,则可以使用这些操作。

    • 事务命令是TSTART,它定义事务的开始;
    • TCOMMIT,它提交当前事务;
    • TROLLBACK,它会中止当前事务并撤消自事务开始以来对全局变量所做的任何更改。

    例如,下面的 ObjectScript 代码定义事务的开始,设置多个全局节点,然后根据 ok 的值提交或回滚事务:

     TSTART
    
     Set ^Data(1) = "Apple"
     Set ^Data(2) = "Berry"
    
     If (ok) {
         TCOMMIT
     }
     Else {
         TROLLBACK
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    TSTARTInterSystems IRIS 日志文件中写入事务开始标记。这定义了事务的起始边界。如果在上面的示例中变量 ok 为 true(非零),则 TCOMMIT 命令将标记事务的成功结束,并将事务完成标记写入日志文件。如果 ok 为 false (0),则 TROLLBACK 命令将撤消自事务开始以来所做的每个设置或终止操作。在这种情况下,^Data(1) 和 ^Data(2) 将恢复到它们以前的值。

    请注意,在交易成功完成时不会写入任何数据。这是因为在事务期间对数据库的所有修改都是在事务过程中正常执行的。只有在回滚的情况下,数据库中的数据才会受到影响。这意味着此示例中的事务具有有限的隔离;也就是说,其他进程可以在提交事务之前看到修改后的全局值。这通常称为未提交的读取。这是好是坏取决于应用要求;在许多情况下,这是完全合理的行为。如果应用程序需要更高程度的隔离,则可以通过使用锁来实现

    11. 锁和事务

    若要创建隔离的事务(即,为了防止其他进程在提交事务之前看到修改后的数据),需要使用。在 ObjectScript 中,您可以通过 LOCK 命令直接获取和释放锁。锁按惯例工作;对于给定的数据结构(例如用于持久性对象),所有需要锁的代码都使用相同的逻辑锁引用(即,LOCK 命令使用相同的地址)。

    在事务中,锁具有特殊行为;在交易过程中获得的任何锁在交易结束之前都不会释放。要了解为什么会这样,请考虑典型事务执行的操作:

    1. 使用 TSTART 启动事务。
    2. 在要修改的节点(或多个节点)上获取一个或多个锁。这通常称为“写”锁。
    3. 修改一个或多个节点。
    4. 松开一把(或多把)锁。因为我们处于事务中,所以此时实际上并未释放这些锁。
    5. 使用 TCOMMIT 提交事务。此时,在上一步中释放的所有锁实际上都已释放。

    如果另一个进程想要查看此事务中涉及的节点,并且不希望看到未提交的修改,那么它只是在从节点读取数据之前测试锁定(称为“读取”锁定)。由于写锁定一直保持到事务结束,因此读取过程在事务完成(提交或回滚)之前看不到数据。

    大多数数据库管理系统使用类似的机制来提供事务隔离。InterSystems IRIS的独特之处在于它为开发人员提供了这种机制。这样就可以为新的应用程序类型创建自定义数据库结构,同时仍然支持事务。当然,您可以简单地使用InterSystems IRIS对象或SQL来管理您的数据,并让您的交易自动管理。

    11.1 对 TSTART 的嵌套调用

    InterSystems IRIS 维护一个特殊的系统变量 $TLEVEL,用于跟踪 TSTART 命令被调用的次数。

    • $TLEVEL以值 0 开头;对 TSTART的每次调用都会将 $TLEVEL 的值递增 1,而对 TCOMMIT 的每次调用都会将其值递减 1。如果对 TCOMMIT 的调用导致将 $TLEVEL 设置回 0,则事务结束(使用提交)。
    • TROLLBACK 命令的调用始终终止当前事务,并将$TLEVEL设置回 0,而不考虑$TLEVEL的值。

    此行为使应用程序能够将事务包装在本身包含事务的代码(如对象方法)周围。例如,由持久性对象提供的 %Save 方法始终将其操作作为事务执行。通过显式调用 TSTARTTCOMMIT,您可以创建一个包含多个对象保存操作的较大事务:

     TSTART
     Set sc = object1.%Save()
     If ($$$ISOK(sc)) {
         // first save worked, do the second
        Set sc = object2.%Save()
     }
    
     If ($$$ISERR(sc)) {
         // one of the saves failed, rollback
         TROLLBACK
     }
     Else {
         // everything is ok, commit
      TCOMMIT
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    11.2 管理并发性

    设置或检索单个全局节点的操作是原子的;它保证始终以一致的结果取得成功。对于多个节点上的操作或控制事务隔离,InterSystems IRIS 提供了获取和释放锁的功能。

    锁由 InterSystems IRIS 锁管理器管理。在 ObjectScript 中,您可以通过 LOCK 命令直接获取和释放锁。(InterSystems IRIS 对象和 SQL 可根据需要自动获取和释放锁)。

    12. 检查最新的全局引用

    最新的全局引用记录在 ObjectScript $ZREFERENCE特殊变量中。$ZREFERENCE包含最新的全局引用,包括下标和扩展的全局引用(如果已指定)。请注意,$ZREFERENCE既不指示全局引用是否成功,也不指示指定的全局是否存在。InterSystems IRIS 仅记录最近指定的全局引用。

    12.1 省略的Global引用

    在下标全局引用之后,InterSystems IRIS 将nak-cursor 设置为该全局名称和下标级别。然后,您可以使用nak-global对同一全局级别和下标级别进行后续引用,省略全局名称和更高级别下标。这简化了在相同(或更低)下标级别对同一全局的重复引用。

    在省略引用中指定较低的下标级别会将nak-cursor重置为该下标级别。因此,在使用nak-global时,您始终在由最新的全局引用建立的下标级别工作。

    nak-cursor指标值记录在特殊变量$ZREFERENCEnak-cursor初始化为空字符串。在未设置nak-cursor时尝试nak-global引用会导致错误。更改命名空间将重新初始化nak-cursor。您可以通过将$ZREFERENCE设置为空字符串 (“”) 来重新初始化nak-cursor指示器。

    在下面的示例中,在第一个引用中指定了下标的全局 ^Produce(“fruit”,1)InterSystems IRIS 将此全局名称和下标保存在nak-cursor指标中,以便后续的nak-global引用可以省略全局名称“Produce”和更高的下标级别“fruit”。当 ^(3,1) 引用进入较低的下标级别时,这个新的下标级别将成为后续nak-global全局引用的前缀。

       SET ^Produce("fruit",1)="Apples"  /* Full global reference  */
       SET ^(2)="Oranges"                /* Naked global references */
       SET ^(3)="Pears"                  /* assume subscript level 2 */
       SET ^(3,1)="Bartlett pears"       /* Go to subscript level 3  */
       SET ^(2)="Anjou pears"            /* Assume subscript level 3 */
       WRITE "latest global reference is: ",$ZREFERENCE,!
       ZWRITE ^Produce
       KILL ^Produce
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    输出

    latest global reference is: ^Produce("fruit",3,2)
    ^Produce("fruit",1)="Apples"
    ^Produce("fruit",2)="Oranges"
    ^Produce("fruit",3)="Pears"
    ^Produce("fruit",3,1)="Bartlett pears"
    ^Produce("fruit",3,2)="Anjou pears"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    除了少数例外,每个全局引用 都设置nak-cursor指标。特殊变量$ZREFERENCE包含最新的全局引用的完整全局名称和下标ZWRITE 命令还显示每个全局变量的完整全局名称和下标,无论它是否使用nak-global引用进行设置。

    应谨慎使用nak-global,因为 InterSystems IRIS 在并不总是明显的情况下设置nak-cursor,包括以下内容:

    • 完整的全局引用最初设置nak-cursor,随后的完整全局引用nak-global省略的全局引用更改nak-cursor,即使全局引用不成功。例如,尝试写入不存在的全局的值会设置nak-cursor

    • 引用下标全局的命令后置条件设置nak-cursor,而不管 InterSystems IRIS 如何评估后置条件。

    • 引用下标全局的可选函数参数可能会设置也可能不设置裸指标,具体取决于 InterSystems IRIS 是否评估所有参数。例如,$GET 的第二个参数始终设置nak-cursor,即使未使用它包含的默认值也是如此。InterSystems IRIS 以从左到右的顺序评估参数,因此最后一个参数可能会重置第一个参数设置的nak-cursor

    • 回滚事务的 TROLLBACK 命令不会在事务开始时将nak-cursor回滚到其值。

  • 相关阅读:
    some(),every()
    代理模型学习记录
    2023NOIP A层联测32
    关于电脑使用的实用技巧
    nnunetv2训练报错 ValueError: mmap length is greater than file size
    python 获取apk信息
    K8S上安装LongHorn(分布式块存储) --use
    CAD与GIS集成说明(在线CAD结合GIS,webCAD)
    Linux系统:OpenSSH7.4p升级到9.0p(服务器漏洞)
    Spring框架(五):SpringAop底层原理和注解配置
  • 原文地址:https://blog.csdn.net/DUQGQG/article/details/126119960