文章
· 九月 27, 2022 阅读大约需 6 分钟

Cache / IRIS 操作数据的3种基本方式

背景

Cache起源于没有SQL的1970时代,当时各种高级计算机语言才刚刚诞生,其中M语言较为独特,它的诞生就是为了在没有操作系统的机器上,进行数据存储。别忘了,Unix在1971年才发布。M语言别具一格地采用了Global多维数组,统一了复杂的内存操作和文件读写,使之成为了1970年代数据库的事实标准,特别是在医疗行业。而后Intersystems在1978年接过M语言的旗帜,在M语言上添加了SQL兼容层和ObjectScript层,前者顺应了时代的潮流,后者不仅为M语言提供了强大的OOP和各种便捷的语法糖,还让数据能以对象形式进行访问,让数据和代码更加紧密。

本文将简述多维数组、SQL、对象这3种数据操作方式,提供实例代码片段,并在运行效率、开发效率、管理效率、实用性方面讨论它们的优缺点。
为方便讨论,以学校与学生为例。对每种操作方法,都列举3种典型的用例,分别为,访问某特定ID的学生(即数据库ID索引)、访问某特定studentID的学生(即遍历唯一索引)、和访问某学校的所有人(即遍历非唯一索引)。

现假设学生表/对象定义如下:

Class Student Extends %Persistent
{
Property schoolId AS  %String;
Property studentId As %String;
Property name As %String;

Index IdxOnSchoolId ON schoolId ;
Index IdxOnStudentId ON studentId [Unique];

Storage Default
{
<Data name="StudentDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>schoolId</Value>
</Value>
<Value name="3">
<Value>studentId</Value>
</Value>
<Value name="4">
<Value>name</Value>
</Value>
</Data>
<DataLocation>^StudentD</DataLocation>
<DefaultData>StudentDefaultData</DefaultData>
<IdLocation>^StudentD</IdLocation>
<IndexLocation>^StudentI</IndexLocation>
<StreamLocation>^StudentS</StreamLocation>
<Type>%Library.CacheStorage</Type>
}
}

方法1 多维数组

  • 例1. 访问某特定ID的学生
s id = 1 // 已知id
s student = ^StudentD(id)
s name = $LIST(student, 4)
w name
  • 例2. 访问某特定studentID的学生
s studentId = 1 // 已知studentId
s id = $ORDER(^StudentI("IdxOnStudentId",studentId,""))
s student = ^StudentD(id)
s name = $LIST(student, 4)
w name
  • 例3. 访问某学校的所有人
s schoolId = 1 // 已知schoolId
s id=""
for {
  s id = $ORDER(^StudentI("IdxOnSchoolId",schoolId,id))
  q:id=""

  s student = ^StudentD(id)
  s name = $LIST(student, 4)
  w name
}

$ORDER 方法返回多维数组最末端下标的下一个值。用来遍历多维数组。

方法2 SQL

  • 例1. 访问某特定ID的学生
s id = 1 // 已知id
&sql(SELECT name INTO :name from Student where id=:id)
w name
  • 例2. 访问某特定studentID的学生
s studentId = 1 // 已知studentId
&sql(SELECT name INTO :name from Student where studentId=:studentId)
w name
  • 例3. 访问某学校的所有人
s schoolId = 1 // 已知schoolId
s query="SELECT name from Student where schoolId=?"
s statement=##class(%SQL.Statement).%New()
s sc=statement.%Prepare(query)
s rset=statement.%Execute(schoolId)
while (rset.%Next()) {
    s name = rset.%Get("name")
    w name,!
}


  • &sql()为嵌入式SQL语句,在INTO子句中赋值给变量,适合单行查询。
  • &sql()也可以返回游标Cursor,以实现多多行查询,但效率比SQL.Statement低,不推荐使用。
  • SQL.Statement类实现动态SQL语句,动态查询适合返回多行结果。

方法3 对象

  • 例1. 访问某特定ID的学生
s id = 1 // 已知id
s student = ##class(Student).%OpenId(id)
s name = student.name
w name
  • 例2. 访问某特定studentID的学生
s studentId = 1 // 已知studentId
s student = ##class(Student).IdxOnStudentIdOpen(studentId)
s name = student.name
w name
  • 例3. 访问某学校的所有人
s schoolId = 1 // 已知schoolId
s id=""
for {
  s id = $ORDER(^StudentI("IdxOnSchoolId",schoolId,id))
  q:id=""

  s student = ##class(Student).%OpenId(id)
  s name = student.name
  w name
}
  • %OpenId方法通过ID查找并返回对象。
  • IndexOpen方法通过唯一索引值查找并返回对象。注意,非唯一索引没有类似方法。

讨论

  • 多维数组
    • 运行效率: 高。
      • 可控程度高,只要有老练的程序员,有足够的加班时间,有足够的资金和时间,总能打磨出最好的效率。据说多维数组的效率是SQL的10倍。
      • 面向过程编程,能够实现SQL难以实现的逻辑控制。
      • 注意,事实上,未经优化的多维数组操作未必比SQL效率高。
    • 开发效率: 低。
      • 虽然对于简单的数据操作,利用多维数组也能快速实现。但是一旦数组结构、索引、下标达到一定数量级,直接对为数组操作是个噩梦。代码中将充斥数组名,索引名,下标等magic values。
      • 直接操作数组太过底层,数据校验、初始值、空置、锁管理、事务等都需要人工编码。
    • 管理效率:低。
      • 值和索引必须同时维护,稍有不慎,容易造成索引损坏。
      • 不同熟练度的程序员实现可能千差万别,对锁的使用、回调函数的调用等容易产生分歧,统一化难度大。
      • 一旦数据定义发生变化,或者数据分布发生变化,需要调整或者调优,都需要较大人力投入。
      • 数据提取、数据迁移、数据备份等日常操作,都须要程序员参与。
    • 实用性:高
      • 对临时数据、不需要考虑数据提取的数据,多维数组是很好的Key-Value数据库。
  • SQL
    • 运行效率: 中。
      • SQL解析和优化需要耗费额外时间。
      • 适合批量处理。
      • 不适合面向过程的逻辑。
      • 可控程度低,如果不使用Frozen Plan,实际执行策略变化大,造成系统不稳定的假象。
      • 但是经过调优后的SQL,可以实现较好的执行效率。
    • 开发效率: 高。
      • SQL提供隔离等级、事务、锁表等指令,简化了并发。
      • SQL是声明式语言,简洁明了,可读性高,使程序员更关注结果,而不是遍历各种索引的过程。
    • 管理效率:高。
      • SQL提供了数据定义、数据查询、数据更新等的统一化。
      • 数据提取、数据迁移、数据备份可以通过标准SQL客户端。
      • 自适应性高,对存储的变化,例如变更索引,变更数据分布等,都能自动适应。
      • Intersystems为SQL提供了额外的权限配置。
    • 实用性:高
  • 对象
    • 运行效率: 低。
      • 不支持对索引的遍历,只能通过主索引和唯一索引访问单一对象。
    • 开发效率: 中。
      • 使对列的访问转化成了对对象属性的访问,使对外键的访问转化成了对外键对象的访问,代码的语义性强,可读性高。
    • 管理效率:中。
      • 在锁管理、值校验等方面统一化程度比多维数组高。
      • 和多维数组一样,也无法提供标准客户端来访问数据。
    • 实用性: 中
      • 实际应用中,持久类除了单个对象内字段的校验逻辑,几乎不包含业务逻辑。一是因为持久类必须稳定,一旦编译,要尽量避免再次编译。二是因为实际项目中,业务逻辑在业务层中,数据是相互依存的,例如退费数据需要退费审核,而这样的逻辑,不可能在某个数据对象中存在,只能在数据层之上的业务层才合理。

Do's & Don'ts

  • 批量的读写操作多用SQL。
  • 写操作应尽量用SQL或者对象。
  • 多维数组应尽量只用于读操作。
  • 多维数组的读、写操作应封装在方法中。
讨论 (3)2
登录或注册以继续