搜索​​​​

清除过滤器
文章
Michael Lei · 三月 2, 2023

基于IRIS的Python实践与示例

在这里,您将找到一个在 IRIS 环境中使用 Python 的简单程序,以及另一个在 Python 环境中使用 ObjectScript 的简单程序。另外,我想分享一些我在学习实践时遇到的麻烦。 IRIS 环境中的 Python 比方说,您在 IRIS 环境中想要解决一个您认为使用 Python 更容易或更有效的问题。 您可以简单地更改环境:像创建任何其他方法一样创建您的方法,并在其名称和规范的末尾添加 [Language = python]: 您可以在该方法中使用任何类型的参数,并且要访问它们,您可以执行与在 COS 中完全相同的操作: 假设您有这个 %String 参数 Arg 和一个来自自定义类的参数 OtherArg。这个其他类可能具有标题和作者等属性。您希望像这样访问: 此方法提供如下输出: 而且,对于访问类方法,它几乎是一样的。假设我们在 Demo.Books.PD.Books 中有一个名为“CreateString”的方法,它将标题和作者连接成类似于“Title: <Title>; Author: <Author>”的内容。 将其添加到我们的 python 方法的末尾: 将提供以下输出: (要访问该方法,您可以使用 OtherArg.CreateString(),但我选择将 OtherArg 中的相同值传递给 CreateString 方法,以便输出看起来相似并且代码看起来更简单) Python 环境中的 ObjectScript 此外,在 Python 环境中,您也可能希望能用上ObjectScript 的代码或资源。 首先,您需要勾选此列表中的一些项目,以便能够以多种方式从 Python 环境访问您的 COS 文件(我不一定会在这里使用所有这些): 你有先决条件吗? 在这里查看 你可以使用 python 外部服务器吗? 在这里查看 你有你需要的驱动吗?在此处下载它们或在此处了解更多信息 如果您觉得有帮助,可以随时返回这些链接或检查我在首次使用 COS 创建 python 文件时遇到的错误。 那么让我们开始编程吧! 首先,我们必须将一些东西从 COS 调整到 Python。幸运的是,InterSystems 已经做到了,我们只需输入“import iris”即可访问所有内容! 在下一步中,我们创建到所需命名空间的连接,使用包含主机、端口和命名空间的路径 (host:port/namespace),并提供用户和密码: 请注意我们最后是如何创建一个 IRIS 对象的,因此我们可以使用此连接来访问该命名空间中我们想要的所有内容。 最后,就可以编写你想要写的内容: 您可以通过提供类名、方法名和参数来使用 irispy.classMethodValue() 访问方法,使用 .set() (用于属性)和许多其他可能性操作对象,同时按照您喜欢的方式处理 python 上的所有内容。 更多iris提供的功能和使用方法,查看Native SDK for Python简介 在这个例子中,我在第 16 行实例化了一个 Persistent 类,在接下来的几行中将它的属性 Title 和 Author 设置为指环王和 Tolkien。 在第 20 行,我从另一个类中调用了一个方法,该方法将对象保存到一个表中,如果有效则返回一个状态。最后,我在第 23 行打印状态。 混合环境 在 ObjectScript 环境中,您可能希望使用 Python 的已知库或您自己的带有函数和例程的自定义文件。 您可以将“导入”命令与 Numpy、SciPy 和任何您想要的东西一起使用(前提是您已正确安装它们: 在此处检查如何执行此操作) 而且,如果您想访问您的本地文件,有几种方法可以做到这一点,并且很容易找到相关教程,因为 Python 非常流行。 对我来说,最容易使用的是以下内容: 在这里,我从位于 C:/python 的文件 testesql.py 中导入了所有内容,并打印了 select() 函数的结果 彩蛋——我遇到的麻烦 SHELLS :使用 Shell 时,请记住 Windows PowerShell 是作为基于 UNIX 的系统工作的(因为它基于 .NET),而命令提示符将与官方文档中的 Windows 示例一起使用。对于一些更有经验的程序员来说,这听起来很基础,但如果您没有给予足够的重视,您可能会在这上面浪费很多时间,所以我发现写一些关于它的内容很重要。 用户和权限:在 Python 环境中使用 ObjectScript 进行编码的当前用户需要拥有命名空间资源的权限。请记住,如果您不选择任何用户,则当前为 UnknownUser,如果您不选择任何名称空间,则当前为 USER。因此,在最简单的访问中,您可能需要遵循:管理门户 > 系统管理 > 安全 > 用户 > 未知用户 > 角色并选择 %DB_USER 并保存。 我不知道发生了什么:要检查有关您遇到的错误的更多信息,您可能需要关注管理门户 > 系统资源管理器 > SQL 并键入“SELECT * FROM %SYS.Audit ORDER BY UTCTimeStamp Desc”以获取最近的审计。在那里你会找到错误的原因,比如 IRIS_ACCESSDENIED() 以及更多你甚至可能在 IRIS 环境之外得到的错误。 PYTHON COMPILING ERROR:您将希望避免方法名称,例如 try() 或已在 Python 中构建的函数。编译器不会理解从方法到函数的区别。 感谢您的阅读,请随时分享建议、评论、疑问或您正在开发的任何内容!
文章
Michael Lei · 六月 18, 2023

医疗行业的未来--数据与人的融合

在数字化时代,数据的重要性无可置疑。数据作为新型生产要素,不仅在宏观政策层面得到党和政府的大力推动,也是医院高质量发展的关键和改变医疗行业的驱动力。随着医疗信息化的迅猛发展,我们正迈向一个数据随处可及、人人可用易用的医疗信息化时代。这一时代将数据与人的需求相结合,致力于让数据能“主动”找到需要他们的医护人员和患者,每一个行业从业者,都应致力于为医护人员和患者提供简单易用的软件解决方案,减少工作量,提高效率,推动医疗行业的进步。 数据与人的融合是实现医疗行业数字化转型的核心。当然,医疗数据的收集、存储和管理对于提供高质量的医疗服务至关重要。然而,仅仅有大量的数据并不足够,我们需要将数据与人的需求紧密结合起来。这意味着我们应该让更多的数据关联起来,并且能服务于更多的人群,让患者能够随时随地访问他们的电子病历,让医生和科研人员也能及时有效地获取病人在医院围墙内外进行治疗和健康管理的数据,并且以直观易懂的方式呈现给医护人员和患者,使他们能够快速、准确地获取所需的信息。数据的融合还包括将不同来源的数据整合起来,为医护人员提供全面、完整的视图,同时基于医疗诊断的规则,不管是通过CDSS的形式,还是通过ChatBot(聊天机器人),帮助他们做出更好的决策。 实现数据和人的融合要按照人的需求投放数据。数字化转型的重要目标是为医护人员和患者提供所需的数据,以支持决策和治疗过程。这意味着我们应该了解用户的需求,将数据按照他们的角色、职责和关注点进行分类和投放。医生可能需要即时的患者数据、病历历史和最新的医学研究,而患者可能需要查看自己的健康记录、预约医生和接收个性化的健康建议。通过根据人的需求进行数据投放,新型软件可以提供个性化的服务和支持,形成千人千面,为每个用户提供有价值的信息。 简单易用是实现数字化转型成功的另一个关键。医护人员和患者使用的软件解决方案应该简单易用,不需要复杂的培训和技术知识。界面应该简单、直观、友好,操作流程简化和优化,以确保用户能够快速上手并高效地使用软件。简单易用的软件不仅能够减少用户的学习曲线和工作负担,还能提高用户满意度和工作效率。(比如Apple的医疗软件Apple Health,通过FHIR 技术,通过一个app能够连接数千家医院的病历数据,让患者可以通过一个app实现多家医院的互联网服务和数据整合) 无论是数字化转型还是高质量发展,软件为人服务始终是医疗信息化的核心宗旨。我们应该将软件看作是为人服务的工具,旨在帮助医护人员提供更好的医疗服务,提升患者的体验和健康结果。软件应该以用户体验为中心,并不断优化和改进,不断进行供给侧改革,以满足不断变化和不同人群的需求,而不是增加负担。 最后,数据会在安全可靠的前提下进行传递和流通。在互联网发展的早期时代,由于无法可依,野蛮生长,数据的滥用、隐私保护等存在很大问题。但随着《数据安全法》等法律法规的发布,相信未来的医疗行业数据一定会在更加安全、可靠、合规的前提下进行有序流动。 在未来的医疗信息化发展中,数据与人的关系将变得更加密不可分。通过数据的融合、按需投放、简单易用、安全可靠和以人为本的新一代软件,我们可以实现数据随处可及、人人可用易用的医疗信息化目标。这将为医护人员和患者提供更好的工作环境和医疗体验,推动整个医疗行业向前迈进。InterSystems公司作为创新性的数据平台解决方案供应商,我们始终致力于助力合作伙伴开发创新的解决方案,与合作伙伴一起共同实现这一愿景,改善医疗服务的质量和效率,提高患者体验的获得感的同时帮助医院降本增效,实现高质量发展。
文章
TZ Zhuang · 二月 3, 2023

PerfTools IO 测试套件

# 目的 这两个工具(RanRead 和 RanWrite)用于在数据库(或一对数据库)内生成随机读写事件,以测试每秒输入/输出的操作数 (IOPS)。它们可以一起使用或分开单独使用,以测试 IO 硬件容量、验证目标 IOPS 并确保系统拥有可接受的磁盘响应时间。从 IO 测试中收集的结果将因配置而异,具体取决于 IO 子系统。在运行这些测试之前,请确保相应的操作系统监控和存储级别监控已配置,这些捕获的 IO 性能指标可以为以后的分析提供帮助。我们推荐使用 IRIS 中捆绑的系统性能工具,例如^SystemPerformance。 请注意,这里使用的工具是对先前版本的更新。之前的版本可在[这里](https://community.intersystems.com/post/random-read-io-storage-performance-tool)找到。 # 安装 从 GitHub 下载 **PerfTools.RanRead.xml** 和 **PerfTools.RanWrite.xml** 工具 点击[这里](https://github.com/intersystems-community/perftools-io-test-suite)。 将工具导入 USER 命名空间。 USER> do $system.OBJ.Load("/tmp/PerfTools.RanRead.xml","ckf") USER> do $system.OBJ.Load("/tmp/PerfTools.RanWrite.xml","ckf") 运行帮助方法以查看所有入口点。所有命令都在 USER 中运行。 USER> do ##class(PerfTools.RanRead).Help() - do ##class(PerfTools.RanRead).Setup(Directory,DatabaseName,SizeGB,LogLevel) 创建具有相同名称的数据库和命名空间。日志级别必须在 0 到 3 的范围内,其中 0 是“无”,3 是“详细”。 - do ##class(PerfTools.RanRead).Run(Directory,Processes,Count,Mode) 运行随机读取 IO 测试。模式参数,1(默认)代表时间,以秒为单位 ,2是循环次数,用前面的 Count 参数控制。 - do ##class(PerfTools.RanRead).Stop() 终止所有后台作业。 - do ##class(PerfTools.RanRead).Reset() 删除先前运行的统计信息。在测试之间运行这个很重要,否则之前运行的统计数据将平均到当前运行的统计数据中。 - do ##class(PerfTools.RanRead).Purge(Directory) 删除同名的命名空间和数据库。 - do ##class(PerfTools.RanRead).Export(Directory) 将所有随机读取测试历史的摘要导出到逗号分隔的文本文件。 USER> do ##class(PerfTools.RanWrite).Help() - do ##class(PerfTools.RanWrite).Setup(Directory,DatabaseName) 创建具有相同名称的数据库和命名空间。 - do ##class(PerfTools.RanWrite).Run(Directory,NumProcs,RunTime,HangTime,HangVariationPct,Global name length,Global node depth,Global subnode length) 运行随机写入 IO 测试。除目录外的所有参数都有默认值。 - do ##class(PerfTools.RanWrite).Stop() 终止所有后台作业。 - do ##class(PerfTools.RanWrite).Reset() 删除先前运行的统计信息。 - do ##class(PerfTools.RanWrite).Purge(Directory) 删除同名的命名空间和数据库。 - do ##class(PerfTools.RanWrite).Export(Directory) 将所有随机写入测试历史的摘要导出到逗号分隔的文本文件。 # 配置 创建一个名为 RAN 的空(预扩展)数据库,其大小至少是要测试的物理主机内存的两倍。同时确保这个空数据库至少是存储控制器缓存大小的四倍。数据库需要大于物理内存以确保读取的数据不会缓存在文件系统缓存中。您可以手动创建或使用以下方法自动创建命名空间和数据库。 USER> do ##class(PerfTools.RanRead).Setup("/ISC/tests/TMP","RAN",200,1) Created directory /ISC/tests/TMP/ Creating 200GB database in /ISC/tests/TMP/ Database created in /ISC/tests/TMP/ 注意:RanRead 和 RanWrite 可以使用相同的数据库。如果需要一次测试多个磁盘或用于特定目的,也可以使用分开的数据库。 RanRead 代码允许指定数据库的大小,但 RanWrite 代码不允许,因此最好使用 RanRead Setup 命令来创建所需的任何预先确定大小的数据库,即使要创建 RanWrite 做测试的数据库也可以。 # 方法论 从少量进程和 30-60 秒运行时间开始测试。然后增加进程数,例如从 10 个作业开始,然后增加 10、20、40 等。继续运行单个测试,直到响应时间始终超过 10 毫秒或计算出的 IOPS 不再以线性方式增加。 该工具使用 ObjectScript VIEW 命令读取内存中的数据库块,因此如果您没有获得预期的结果,则可能所有数据库块都已在内存中。 作为指南,全闪存阵列通常可以接受以下 8KB 和 64KB 数据库随机读取(非缓存)的响应时间: * 平均 do ##class(PerfTools.RanRead).Export("/ISC/tests/TMP/ ") Exporting summary of all random read statistics to /usr/iris/db/zranread/PerfToolsRanRead_20221023-1408.txt Done. # 分析 建议使用内置的 [SystemPerformance 工具](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCM_systemperf)来获取被分析的系统的真实情况。 SystemPerformance 的命令需要在 %SYS 命名空间中运行。要切换到那个命名空间,请使用 ZN 命令: USER> ZN "%SYS" 要查找系统瓶颈的详细信息,或者如果需要系统如何以目标 IOPS 运行的更多详细信息,则应创建具有高频率数据采集的 SystemPerformance 配置文件: %SYS> set rc=$$addprofile^SystemPerformance("5minhighdef","A 5-minute run sampling every second",1,300) 然后运行该配置文件(从 %SYS)并立即切换回 USER 并使用“job”而不是“do”来启动 RanRead 和/或 RanWrite: %SYS> set runid=$$run^SystemPerformance("5minhighdef") %SYS> ZN “USER” USER> job ##class(PerfTools.RanRead).Run("/ISC/tests/TMP",5,60) USER> job ##class(PerfTools.RanWrite).Run("/ISC/tests/TMP",1,60,.001) 然后可以等待 SystemPerformance 作业结束,并使用 [yaspe](https://github.com/murrayo/yaspe) 等工具分析生成的 html 文件。 # 清理 运行完测试后,需要删除历史记录: %SYS> do ##class(PerfTools.RanRead).Reset()
文章
姚 鑫 · 七月 12, 2022

第三章 嵌入式Python概述(三)

# 第三章 嵌入式Python概述(三) ## 使用 SQL `IRIS` 中的类被投影到 `SQL`,除了使用类方法或直接全局访问之外,还允许使用查询访问数据。 `iris` 模块为提供了两种从 `Python` 运行 `SQL` 语句的不同方式。 以下示例使用 `iris.sql.exec()` 运行 `SQL SELECT` 语句以查找类名称以“`%Net.LDAP`”开头的所有类定义,返回一个包含每个名称和超类的结果集每个班级。在这里,系统类 `%Dictionary.ClassDefinition` 将 `SQL` 投影为同名表。 ```java >>> rs = iris.sql.exec("SELECT Name, Super FROM %Dictionary.ClassDefinition WHERE Name %STARTSWITH '%Net.LDAP'") ``` 以下示例使用 `iris.sql.prepare()` 准备 `SQL` 查询对象,然后执行查询,将`“%Net.LDAP”`作为参数传入: ```java >>> stmt = iris.sql.prepare("SELECT Name, Super FROM %Dictionary.ClassDefinition WHERE Name %STARTSWITH ?") >>> rs = stmt.execute("%Net.LDAP") ``` 无论哪种情况,都可以按如下方式遍历结果集,并且输出相同: ```java >>> for idx, row in enumerate(rs): ... print(f"[{idx}]: {row}") ... [0]: ['%Net.LDAP.Client.EditEntry', '%RegisteredObject'] [1]: ['%Net.LDAP.Client.Entries', '%RegisteredObject,%Collection.AbstractIterator'] [2]: ['%Net.LDAP.Client.Entry', '%RegisteredObject,%Collection.AbstractIterator'] [3]: ['%Net.LDAP.Client.PropList', '%RegisteredObject'] [4]: ['%Net.LDAP.Client.Search.Scope', '%Integer'] [5]: ['%Net.LDAP.Client.Session', '%RegisteredObject'] [6]: ['%Net.LDAP.Client.StringList', '%RegisteredObject'] [7]: ['%Net.LDAP.Client.ValueList', '%RegisteredObject,%Collection.AbstractIterator'] ``` ## 使用Globals 在 `IRIS` 数据库中,所有数据都存储在全局变量中。全局数组是持久的(意味着它们存储在磁盘上)、多维的(意味着它们可以有任意数量的下标)和稀疏的(意味着下标不必是连续的)。当您在表中存储类的对象或行时,这些数据实际上存储在全局变量中,尽管您通常通过方法或 `SQL` 访问它们并且从不直接接触全局变量。 有时将持久数据存储在全局变量中会很有用,而无需设置类或 `SQL` 表。在 `IRIS` 中,全局变量看起来很像任何其他变量,但它在名称前用插入符号 (`^`) 表示。以下示例将工作日的名称存储在当前命名空间的全局 `^Workdays` 中。 ```java >>> myGref = iris.gref('^Workdays') >>> myGref[None] = 5 >>> myGref[1] = 'Monday' >>> myGref[2] = 'Tuesday' >>> myGref[3] = 'Wednesday' >>> myGref[4] = 'Thursday' >>> myGref[5] = 'Friday' >>> print(myGref[3]) Wednesday ``` 第一行代码 `mmyGref = iris.gref('^Workdays') ` 获取一个全局引用(或 `gref`),指向一个名为 `^Workdays` 的全局引用,它可能已经存在也可能不存在。 第二行 `myGref[None] = 5` 将工作日数存储在 `^Workdays` 中,不带下标。 第三行 `myGref[1] = 'Monday'` 将字符串 `Monday` 存储在位置 `^Workdays(1)` 中。接下来的四行将剩余的工作日存储在位置 `^Workdays(2)` 到 `^Workdays(5)` 中。 最后一行 `print(myGref[3])` 显示了如何在给定 `gref` 的情况下访问存储在全局中的值。 # 一起使用 ObjectScript 和 Python `IRIS` 让 `ObjectScript` 和 `Python` 程序员的混合团队轻松协作。例如,类中的一些方法可以用 `ObjectScript` 编写,而另一些可以用 `Python` 编写。程序员可以选择用他们最熟悉的语言编写,或者更适合手头任务的语言。 ## 创建混合 InterSystems IRIS 类 下面的类有一个用 `Python` 编写的 `Print()` 方法和一个用 `ObjectScript` 编写的 `Write()` 方法,但它们在功能上是等效的,并且可以从 `Python` 或 `ObjectScript` 调用这两种方法。 ```java Class Sample.Company Extends (%Persistent, %Populate, %XML.Adaptor) { /// The company's name. Property Name As %String(MAXLEN = 80, POPSPEC = "Company()") [ Required ]; /// The company's mission statement. Property Mission As %String(MAXLEN = 200, POPSPEC = "Mission()"); /// The unique Tax ID number for the company. Property TaxID As %String [ Required ]; /// The last reported revenue for the company. Property Revenue As %Integer; /// The Employee objects associated with this Company. Relationship Employees As Employee [ Cardinality = many, Inverse = Company ]; Method Print() [ Language = python ] { print ('\nName: ' + self.Name + ' TaxID: ' + self.TaxID) } Method Write() [ Language = objectscript ] { write !, "Name: ", ..Name, " TaxID: ", ..TaxID } } ``` 此 `Python` 代码示例展示了如何使用 `%Id=2` 打开 `Company` 对象并调用 `Print()` 和 `Write()` 方法。 ```java >>> company = iris.cls("Sample.Company")._OpenId(2) >>> company.Print() Name: IntraData Group Ltd. TaxID: G468 >>> company.Write() Name: IntraData Group Ltd. TaxID: G468 ``` 此 `ObjectScript` 代码示例展示了如何打开相同的 `Company` 对象并调用这两种方法。 ```java SAMPLES>set company = ##class(Sample.Company).%OpenId(2) SAMPLES>do company.Print() Name: IntraData Group Ltd. TaxID: G468 SAMPLES>do company.Write() Name: IntraData Group Ltd. TaxID: G468 ``` ## 在 Python 和 ObjectScript 之间传递数据 虽然 `Python` 和 `ObjectScript` 在许多方面都兼容,但它们有许多自己的数据类型和结构,有时在将数据从一种语言传递到另一种语言时需要进行一些数据转换。之前看到了一个示例,即从 `ObjectScript` 向 `Python` 传递命名参数的示例。 `%SYS.Python` 类的 `Builtins()` 方法为提供了一种方便的方式来访问 `Python` 的内置函数,它可以帮助创建 `Python` 方法所期望的类型的对象。 以下 `ObjectScript` 示例创建两个 `Python` 数组 `newport` 和 `cleveland`,每个数组都包含一个城市的纬度和经度: ```java USER>set builtins = ##class(%SYS.Python).Builtins() USER>set newport = builtins.list() USER>do newport.append(41.49008) USER>do newport.append(-71.312796) USER>set cleveland = builtins.list() USER>do cleveland.append(41.499498) USER>do cleveland.append(-81.695391) USER>zwrite newport newport=11@%SYS.Python ; [41.49008, -71.312796] ; USER>zwrite cleveland cleveland=11@%SYS.Python ; [41.499498, -81.695391] ; ``` 下面的代码使用在前面的示例中看到的 `geopy` 包来计算纽波特,罗德岛和克利夫兰,俄亥俄州之间的距离。它使用 `geopy.distance.distance()` 方法创建一条路线,将数组作为参数传递,然后打印路线的英里属性。 ```java USER>set distance = $system.Python.Import("geopy.distance") USER>set route = distance.distance(newport, cleveland) USER>write route.miles 538.3904453677205311 ``` 注意: `geopy.distance.distance()` 方法实际上期望参数是 `Python` 元组数据类型,但数组也可以。 ## 运行 Python 命令 当开发或测试某些东西时,有时运行一行 `Python` 代码以查看它的作用或是否有效可能会很有用。在这种情况下,可以使用 `%SYS.Python.Run()` 方法,如下例所示: ```java USER>set rslt = ##class(%SYS.Python).Run("print('hello world')") hello world ``` 针对于字典如何初始化呢,目前是有异常,望指教
文章
姚 鑫 · 四月 28, 2021

第八章 解释SQL查询计划(一)

# 第八章 解释SQL查询计划(一) # SQL语句 这个SQL语句列表为每个表提供了SQL查询和其他操作的记录,包括插入、更新和删除。 这些SQL语句链接到一个查询计划,该链接提供冻结该查询计划的选项。 系统为每个SQL DML操作创建一条SQL语句。 这提供了一个按表、视图或过程名称列出的SQL操作列表。 如果更改表定义,可以使用此SQL Statements列表来确定每个SQL操作的查询计划是否会受到此DDL更改的影响,以及/或是否需要修改某个SQL操作。 然后,可以: - 确定每个SQL操作使用哪个查询计划。 可以决定使用反映对表定义所做更改的修改后的查询计划。 或者可以冻结当前查询计划,保留在更改表定义之前生成的查询计划。 - 根据对表定义所做的更改,确定是否对对该表执行SQL操作的例程进行代码更改。 注意:SQL语句是一个SQL例程列表,它们可能会受到表定义更改的影响。 它不应该用作表定义或表数据更改的历史记录。 # 创建SQL语句操作 下面的SQL操作会创建相应的SQL语句: 数据管理(DML)操作包括对表的查询、插入、更新和删除操作。 每个数据管理(DML)操作(动态SQL和嵌入式SQL)在执行时都会创建一个SQL语句。 - 动态SQL `SELECT`命令在准备查询时创建SQL语句。 此外,在管理门户缓存查询列表中创建了一个条目。 - 嵌入式SQL基于指针的`SELECT`命令在`OPEN`命令调用声明的查询时创建SQL语句。管理门户缓存查询列表中不会创建单独的条目。 如果查询引用多个表,则在名称空间的SQL语句中创建一条SQL语句,该语句列出表/视图/过程名列中的所有被引用表,并且对于每个单独的被引用表,该表的SQL语句列表都包含该查询的条目。 SQL语句是在第一次准备查询时创建的。如果多个客户端发出相同的查询,则只记录第一次准备。例如,如果JDBC发出一个查询,然后ODBC发出一个相同的查询,那么SQL语句索引将只有关于第一个JDBC客户端的信息,而不是关于ODBC客户端的信息。 大多数SQL语句都有关联的查询计划。 创建该查询计划时,将解冻该查询计划; 可以随后将该查询计划指定为冻结计划。 带有查询计划的SQL语句包括涉及`SELECT`操作的DML命令。 下面的“计划状态”部分列出了没有查询计划的SQL语句。 注意:SQL语句只列出SQL操作的最新版本。 除非冻结SQL语句,否则InterSystems IRIS®数据平台将用下一个版本替换它。 因此,在例程中重写和调用SQL代码将导致旧的SQL代码从SQL语句中消失。 ## 其他SQL语句操作 下面的SQL命令执行更复杂的SQL语句操作: - `CREATE TRIGGER`: 在定义触发器的表中,无论是在定义触发器还是在提取触发器时,都不会创建SQL语句。 但是,如果触发器对另一个表执行DML操作,那么定义触发器将在被触发器代码修改过的表中创建一个SQL语句。 `Location`指定在其中定义触发器的表。 在定义触发器时定义SQL语句; 删除触发器将删除SQL语句。 触发触发器不会创建SQL语句。 - `CREATE VIEW` 不创建SQL语句,因为没有编译任何内容。 它也不会更改源表的SQL语句的Plan Timestamp。 然而,为视图编译DML命令会为该视图创建一个SQL语句。 # List SQL语句 本节介绍使用Management Portal界面列出SQL语句的详细信息。 也可以使用`^rINDEXSQL`全局返回SQL语句的索引列表。 注意,这个SQL语句List可能包含过时的(不再有效的)List 从Management Portal SQL界面可以列出如下SQL语句: - SQL语句选项卡:此选项卡列出名称空间中的所有SQL语句,先按模式排序,然后按每个模式中的表名/视图名排序。此列表仅包括当前用户拥有权限的那些表/视图。如果SQL语句引用多个表,则表/视图/过程名列将按字母顺序列出所有被引用的表。 - 通过单击列标题,可以按表/视图/过程名、计划状态、位置、SQL语句文本或列表中的任何其他列对SQL语句列表进行排序。这些可排序列使能够快速查找,例如,所有冻结计划(计划状态)、所有缓存查询(位置)或最慢的查询(平均时间)。 - 可以使用此选项卡提供的`Filter`选项将列出的SQL语句缩小到指定的子集。 指定的筛选器字符串筛选SQL语句列表中的所有数据,最有用的是模式或模式。 表名、例程位置或SQL语句文本中找到的子字符串。 过滤字符串不区分大小写,但必须紧跟语句文本标点空格`(name , age, not name,age)`。 如果查询引用了多个表,如果它选择了表/视图/过程名称列中的任何引用表,则`Filter`包括SQL语句。 过滤选项是用户自定义的。 - 最大行选项默认为`1,000`。 最大值为`10,000`。 最小值为`10`。 要列出超过`10,000`条SQL语句,请使用`INFORMATION_SCHEMA.STATEMENTS`。 页面大小和最大行选项是用户自定义的。 - Catalog Details选项卡:选择一个表并显示其Catalog详细信息。 此选项卡提供了一个表的SQL语句按钮,用于显示与该表关联的SQL语句。 注意,如果一个SQL语句引用了多个表,那么它将在表的SQL语句列表中列出每个被引用的表,但只有当前选择的表在表名列中列出。 通过单击列标题,可以根据列表的任何列对表的SQL语句列表进行排序。 可以使用`SQLTableStatements()`目录查询或`INFORMATION_SCHEMA`。 语句,列出根据各种条件选择的SQL语句,如下面的查询SQL语句中所述。 ## 列表列 SQL语句选项卡列出名称空间中的所有SQL语句。目录详细信息选项卡表的SQL语句按钮列出了所选表的SQL语句。这两个列表都包含以下列标题: - `#`:列表行的顺序编号。这些数字与特定的SQL语句没有关联。 - 表/视图/过程名:限定的SQL表(或视图或过程)名:`schema.name`。如果SQL语句查询引用了多个表或视图,则所有这些表或视图都会在此处列出。 - 计划状态:请参阅下面的计划状态。 - 新计划:见“冻结计划”一章中不同的新计划。 - 自然查询:请参阅下面的语句详细信息部分。 - 计数:请参阅下面的性能统计数据。 - 平均计数:请参阅下面的性能统计数据。 - 总时间:请参阅下面的性能统计数据。 - 平均时间:请参阅下面的性能统计数据。 - 标准开发人员:请参阅下面的性能统计数据。 - Location(S):编译查询的位置,例程名称(对于嵌入式SQL)或缓存查询名称(对于动态SQL)。如果包名为`%sqlcq`,则SQL语句为缓存查询。 - SQL语句文本:规范化格式的SQL语句文本(截断为`128`个字符),可能与以下SQL语句文本中指定的命令文本不同。 ## 计划状态 计划状态列出以下内容之一: - 解冻Unfrozen:未冻结,可冻结。 - 解冻/平行Unfrozen/Parallel::未冻结,不能冻结。 - 冻结/显式Frozen/Explicit:由用户动作冻结,可以解冻。 - Frozen/Upgrade:被InterSystems IRIS版本升级冻结,可以解冻。 - blank:没有关联的查询计划: - `INSERT... VALUES()` 命令创建的SQL语句没有关联的查询计划,因此无法解冻或冻结(计划状态列为空)。尽管此SQL命令不会生成查询计划,但它在SQL语句中的列表仍然很有用,因为它允许快速定位针对该表的所有SQL操作。例如,如果向表中添加一列,则可能需要找出该表的所有SQL插入的位置,以便可以更新这些命令以包括此新列。 - 基于游标的`UPDATE`或`DELETE`命令没有关联的查询计划,因此不能解冻或冻结(“计划状态”列为空)。对已声明的游标执行`OPEN`命令会生成一条带有关联查询计划的SQL语句。使用该游标的嵌入式SQL语句(`FETCH cursor, UPDATE...WHERE CURRENT OF cursor, DELETE...WHERE CURRENT OF cursor, and CLOSE cursor`)不生成单独的SQL语句。即使基于游标的`UPDATE`或`DELETE`不会产生查询计划,但SQL语句中列出的查询计划仍然很有用,因为它允许快速定位针对该表的所有SQL操作。 ## SQL语句文本 SQL语句文本通常不同于SQL命令,因为SQL语句生成规范化了字母和空格。 其他差异如下: 如果从Management Portal接口或SQL Shell接口发出查询,所得到的SQL语句与在`SELECT`语句前面加上`DECLARE QRS CURSOR FOR`(其中“QRS”可以是各种生成的游标名称)的查询不同。 这允许语句文本与Dynamic SQL缓存的查询相匹配。 如果SQL命令指定了一个非限定的表或视图名,那么生成的SQL语句将使用模式搜索路径(如果提供了DML)或默认模式名来提供模式。 SQL语句文本在`1024`个字符之后被截断。 要查看完整的SQL语句文本,请显示SQL语句详细信息。 一个SQL命令可能会产生多个SQL语句。 例如,如果一个查询引用一个视图,SQL Statements将显示两个语句文本,一个列在视图名称下,另一个列在基础表名称下。 冻结任意一条语句都会导致两个语句的Plan State为Frozen。 当通过xDBC准备SQL语句时,如果需要这些选项来生成语句索引散列,则SQL语句生成会向语句文本添加SQL Comment Options (`# Options`)。 如下面的例子所示: ```sql DECLARE C CURSOR FOR SELECT * INTO :%col(1) , :%col(2) , :%col(3) , :%col(4) , :%col(5) FROM SAMPLE . COMPANY /*#OPTIONS {"xDBCIsoLevel":0} */ ``` # 陈旧的SQL语句 删除与SQL语句关联的例程或类时,不会自动删除SQL语句列表。这种类型的SQL语句列表称为陈旧。由于访问此历史信息以及与SQL语句相关联的性能统计信息通常很有用,因此这些过时的条目将保留在管理门户SQL语句列表中。 可以使用`Clean Stale`(清除陈旧)按钮删除这些陈旧条目。清除陈旧删除关联例程或类(表)不再存在或不再包含SQL语句查询的所有非冻结SQL语句。清除陈旧不会删除冻结的SQL语句。 可以使用`$SYSTEM.SQL.Statement.Clean()`方法执行相同的清除陈旧操作。 如果删除与SQL语句关联的表(持久化类),则会修改表/视图/过程名称列,如下例所示:`SAMPLE.MYTESTTABLE - Deleted??;` ;已删除表的名称将转换为全部大写字母,并标记为“`DELETED??`”。或者,如果SQL语句引用了多个表:`SAMPLE.MYTESTTABLE - Deleted?? Sample.Person`. - 对于动态SQL查询,删除表时`Location`列为空,因为与该表关联的所有缓存查询都已自动清除。`CLEAN STALE`删除SQL语句。 - 对于嵌入式SQL查询,`Location`列包含用于执行查询的例程的名称。当更改例程使其不再执行原始查询时,位置列为空。`CLEAN STALE`删除SQL语句。删除查询使用的表时,该表被标记`“Deleted??”`;Clean Stale不会删除SQL语句。 注:系统任务在所有名称空间中每小时自动运行一次,以清除任何可能过时或具有过时例程引用的SQL语句的索引。执行此操作是为了维护系统性能。此内部清理不会反映在管理门户SQL语句列表中。可以使用管理门户监视此每小时一次的清理或强制其立即执行。要查看此任务上次完成和下次调度的时间,请依次选择系统操作、任务管理器、任务调度,然后查看清理SQL语句索引任务。可以单击任务名称查看任务详细信息。在Task Details(任务详细信息)显示中,可以使用Run(运行)按钮强制立即执行任务。请注意,这些操作不会更改SQL语句清单;必须使用Clean Stale来更新SQL语句清单。 # 数据管理(DML)SQL语句 创建SQL语句的数据管理语言(DML)命令包括:`INSERT`、`UPDATE`、`INSERT`或`UPDATE`、`DELETE`、`TRUNCATE TABLE`、`SELECT`和`OPEN CURSOR`(用于声明的基于游标的`SELECT`)。可以使用动态SQL或嵌入式SQL来调用DML命令。可以为表或视图调用DML命令,InterSystems IRIS将创建相应的SQL语句。 注意:系统在准备动态SQL或打开嵌入式SQL游标时(而不是在执行DML命令时)创建SQL语句。SQL语句时间戳记录此SQL代码调用的时间,而不是查询执行的时间(或是否)。因此,SQL语句可能表示从未实际执行的表数据更改。 准备动态SQL DML命令将创建相应的SQL语句。与此SQL语句关联的位置是缓存查询。动态SQL是在从管理门户SQL界面、SQL Shell界面执行SQL或从`.txt`文件导入时准备的。清除未冻结的缓存查询会将相应的SQL语句标记为清除陈旧删除。清除冻结的缓存查询会删除相应SQL语句的位置值。解冻SQL语句会将其标记为Clean Stale删除。 执行非游标嵌入式SQL数据管理语言(DML)命令将创建相应的SQL语句。每个嵌入式SQL DML命令都会创建相应的SQL语句。如果一个例程包含多个嵌入式SQL命令,则每个嵌入式SQL命令都会创建一个单独的SQL语句。(某些嵌入式SQL命令会创建多条SQL语句。)。SQL语句清单的Location列指定包含嵌入式SQL的例程。通过这种方式,SQL语句维护每个嵌入式SQL DML命令的记录。 打开基于游标的嵌入式SQL数据管理语言(DML)例程将创建带有查询计划的SQL语句。 关联的嵌入式SQL语句(`FETCH`游标、`CLOSE`游标)不会生成单独的SQL语句。 在FETCH游标之后,一个关联的`UPDATE table WHERE CURRENT OF cursor 或DELETE FROM table WHERE CURRENT OF cursor`会生成一个单独的SQL语句,但不会生成单独的`Query Plan`。 插入文字值的`INSERT`命令将创建一个“计划状态”列为空的SQL语句。 由于该命令不会创建查询计划,因此无法冻结SQL语句。 ## select命令 调用查询将创建相应的SQL语句。 它可以是一个简单的`SELECT`操作,也可以是一个基于指针的`SELECT/FETCH`操作。 可以对表或视图发出查询。 - 包含`JOIN`的查询为每个表创建相同的SQL语句。 Location是清单中存储的每个表的相同查询。 如SQL语句详细信息例程和关系部分所述,该语句使用以下关系列出所有表。 - 包含选择项子查询的查询为每个表创建相同的SQL语句。 `Location`是清单中存储的每个表的相同查询。 如SQL语句详细信息例程和关系部分所述,该语句使用以下关系列出所有表。 - 引用外部(链接)表的查询不能被冻结。 - 一个包含`FROM`子句`%PARALLEL`关键字的查询可以创建多个SQL语句。 你可以通过调用来显示这些生成的SQL语句: ![image](E3927D1DDE6B49FFA01E0F96682173B7) 这将显示包含原始查询的语句哈希的`Statement`列和包含生成的查询版本的语句哈希的`ParentHash`列。 `%PARALLEL`查询的SQL语句的计划状态为“未冻结/并行”,不能被冻结。 - 不包含FROM子句(因此不引用任何表)的查询仍然创建SQL语句。 例如:`SELECT $LENGTH('this string')`创建一个SQL语句,表列值`%TSQL_sys.snf`。
文章
姚 鑫 · 四月 19, 2021

第三章 优化表(二)

# 第三章 优化表(二) # 调整表计算值 调优表操作根据表中的代表性数据计算和设置表统计信息: - `ExtentSize`,它可能是表中的实际行数(行数),也可能不是。 - 表中每个属性(字段)的选择性。 可以选择性地阻止单个属性的选择性计算。 - 属性的离群选择性,其中一个值比其他值出现得更普遍。 有效的查询可以利用离群值优化。 - 标识某些属性特征的每个属性的注释。 - 每个属性的平均字段大小。 - 表的SQL `Map Name`、`BlockCount`和`Source of BlockCount`。 ## 区段大小和行计数 从管理门户运行Tune Table工具时,`ExtentSize`是表中当前行的实际计数。默认情况下,`GatherTableStats()`方法还将实际行数用作`ExtentSize`。当表包含大量行时,最好对较少的行执行分析。可以使用SQL tune table命令并指定`%SAMPLE_PERCENT`来仅对总行的一定百分比执行分析。在针对包含大量行的表运行时,可以使用此选项来提高性能。此`%SAMPLE_PERCENT`值应该足够大,以便对代表性数据进行采样。如果`ExtentSize`。 如果`TuneTable`返回异常值选择性,则正常选择性仍然是整个行集内每个非异常值数据值的百分比。例如,如果在`1000`个随机选择的值中检测到`11`个不同的值,其中一个是异常值,则选择性为`1/11(9.09%)`:平均每个条目出现的几率为十一分之一。如果异常值选择性是`80%`,常规选择性是`1%`,那么除了异常值之外,还可以找到大约`20((1-0.80)/0.01)`个额外的非异常值。 如果优化表初始采样仅返回单个值,但附加采样返回多个不同的值,则这些采样结果会修改正常选择性。例如,990个值的初始随机采样仅检测一个值,但后续采样检测其他不同值的10个单个实例。在这种情况下,初始离群值会影响选择性值,该值现在被设置为`1/1000(0.1%)`,因为10个非离群值中的每一个在1000个记录中只出现一次。 异常值选择性的最常见示例是允许`NULL`的属性。如果某个特性具有`NULL`的记录数大大超过该特性具有任何特定数据值的记录数,则`NULL`为异常值。以下是`FavoriteColors`字段的选择性和异常值选择性: ```java SELECTIVITY of FIELD FavoriteColors CURRENT = 1.8966% CALCULATED = 1.4405% CURRENT OUTLIER = 45.0000%, VALUE = CALCULATED OUTLIER = 39.5000%, VALUE = ``` 如果一个字段只包含一个不同的值(所有行都具有相同的值),则该字段的选择性为`100%`。选择性为`100%`的值不被视为异常值。调谐表通过采样数据来建立选择性和异常值选择值。为了确定这一点,优选表首先测试少量或几条记录,如果这些记录都具有相同的字段值,它将测试多达`100,000`条随机选择的记录,以支持非索引字段的所有值都相同的假设。只有在字段已编制索引,字段是索引的第一个字段,并且字段和索引具有相同的排序规则类型的情况下,优化表才能完全确定该字段的所有值是否相同。 - 如果已知未编制索引的字段具有在测试`100,000`条随机选择的记录中可能检测不到的其他值,则应手动设置选择性和离群值选择性。 - 如果已知非索引字段没有其他值,则可以手动指定`100%`的选择性,删除任何异常值选择性,并设置`CALCSELECTIVITY=0`以防止优选表尝试计算选择性或将此值指定为异常值。 要修改这些选择性、异常值选择性和异常值计算值,请从调谐表显示中选择单个字段。这会在显示屏右侧的详细信息区域中显示该字段的这些值。可以将选择性、异常值选择性和/或异常值修改为更适合预期完整数据集的值。 - 可以将选择性指定为带有百分号(`%`)的行的百分比,也可以指定为整数行(没有百分号)。如果指定为整数行数,InterSystems IRIS将使用区大小来计算选择性百分比。 - 可以为以前没有异常值的字段指定异常值选择性和异常值。将异常值选择性指定为带百分号(`%`)的百分比。如果仅指定异常值选择性,则Tune Table假定异常值为``。如果仅指定异常值,则除非还指定异常值选择性,否则调谐表不会保存此值。 ## CALCSELECTIVITY参数与不计算选择性 在某些情况下,可能不希望优化表工具计算属性的选择性。要防止计算选择性,请将属性的`CALCSELECTIVITY`参数的值指定为`0`(默认值为`1`)。在Studio中,可以在“新建属性向导”的“属性参数”页上设置`CALCSELECTIVITY`,也可以在检查器中的属性参数列表中设置`CALCSELECTIVITY`(可能需要收缩并重新展开属性参数列表才能显示它)。 应该指定`CALCSELECTIVITY=0`的一种情况是,如果该字段未编制索引,则已知该字段在所有行中只包含一个值(`选择性=100%`)。 ## 离群值的优化 默认情况下,查询优化器假定查询不会选择离群值。 例如,查询通常选择特定的字段值并从数据库返回少量记录,而不是返回大量记录,其中该字段值是离群值。 查询优化器总是使用选择性来构造查询计划,除非执行一些要求考虑离群选择性的操作。 根据选择离群值,可以执行以下几个操作来调整查询优化: - 如果异常值是``,则在查询`WHERE`子句中为该字段指定一个`is null`或`is NOT null`条件。 这将导致查询优化器在构造查询时使用离群值选择性。 - 如果离群值是一个数据值,查询优化器会假定选择的字段值不是离群值。 例如,总部位于马萨诸塞州的公司的员工记录可能有`Office_State`字段离群值`MA` (`Massachusetts`)。 优化器假设查询不会选择`' MA '`,因为这将返回数据库中的大多数记录。 但是,如果正在编写一个查询来选择离群值,可以通过将离群值封装在双括号中来通知优化器。 在该字段上查询时,指定一个`WHERE`子句,如下所示`:WHERE Office_State=(('MA'))`。 这种技术抑制了文字替换,并迫使查询优化器在构建查询计划时使用离群值选择性。 对于动态SQL查询,以及在使用ODBC/JDBC提供的InterSystems IRIS之外编写的查询,这种语法是必需的。 对于类查询、嵌入式SQL查询或通过视图访问的查询,则不需要这样做。 - 根据参数值SQL设置配置系统范围的优化查询。 该选项为离群值设置了运行时计划选择(RTPC)优化和作为离群值(BQO)优化的偏差查询的适当组合。 注意,更改此配置设置将清除所有名称空间中的所有缓存查询。 使用管理门户,选择System Administration、Configuration、SQL和Object Settings、SQL来查看和更改此选项。 可用的选择有: - 假设查询参数值不是字段离群值(`BQO=OFF`, `RTPC=OFF`,初始默认值) - 假设查询参数值经常匹配字段离群值(`BQO=ON`, `RTPC=OFF`) - 在运行时优化实际查询参数值(`BQO=OFF`, `RTPC=ON`) 要确定当前设置,调用`$SYSTEM.SQL.CurrentSettings()`。 - 覆盖查询的系统范围配置设置。 通过指定`%NORUNTIME restrict`关键字,可以覆盖单个查询的`RTPC`。 如果查询`SELECT Name,HaveContactInfo FROM t1 WHERE HaveContactInfo=?` 将导致`RTPC`处理,查询`SELECT %NORUNTIME Name,HaveContactInfo FROM t1 WHERE HaveContactInfo=?` 将覆盖RTPC,从而产生一个标准的查询计划。 通过指定注释选项`/*#OPTIONS {"BiasAsOutlier":1} */`,可以覆盖偏见查询作为单个查询的离群值。 ## “备注”列 管理门户优化表信息选项为每个字段显示一个备注列。此字段中的值是系统定义的,不可修改。它们包括以下内容: - `RowID`字段:一个表有一个`RowID`,由系统定义。它的名称通常是ID,但可以有不同的系统分配的名称。由于其所有值(根据定义)都是唯一的,因此其选择性始终为1。如果类定义包括`SqlRowIdPrivate`,则`Notes`列值为`RowID`字段、`Hidden`字段。 - 隐藏字段:隐藏字段定义为私有,`SELECT*`不显示。默认情况下,`CREATE TABLE`将`RowID`字段定义为隐藏;可以指定`%PUBLICROWID`关键字以使`RowID`不隐藏和公开。默认情况下,由持久化类定义定义的表将`RowID`定义为非隐藏;可以指定`SqlRowIdPrivate`将`RowID`定义为隐藏和私有。容器字段定义为隐藏。 - 流字段:表示使用流数据类型定义的字段,可以是字符流(`CLOB`),也可以是二进制流(`BLOB`)。流文件没有平均字段大小。 - 父引用字段:引用父表的字段。 注释列中未标识标识字段、`ROWVERSION`字段、序列字段或`UNIQUEIDENTIFIER(GUID)`字段。 ## 平均字段大小 运行调谐表根据当前表格数据集计算所有非流字段的平均字段大小(以字符为单位)。这与`AVG($length(Field))`相同(除非另有说明),四舍五入到小数点后两位。可以更改各个字段的平均字段大小,以反映字段数据的预期平均大小。 - NULL:因为`$LENGTH`函数将`NULL`字段视为长度为0,所以将长度为0的`NULL`字段取平均值。这可能会导致平均字段大小小于一个字符。 - 空列:如果列不包含数据(所有行都没有字段值),则平均字段大小值为1,而不是0。对于不包含数据的列,`AVG($length(Field))`为0。 - `ExtentSize=0`:将`ExtentSize`设置为0时,所有字段的平均字段大小将重置为0。 - 逻辑字段值:平均字段大小始终根据字段的逻辑(内部)值计算。 - 列表字段:InterSystems IRIS列表字段根据其逻辑(内部)编码值计算。此编码长度大于列表中元素的总长度。 - 容器字段:集合的容器字段大于其集合对象的总长度。例如,在`Sample.Person`中,`Home`容器字段的`Average Field` Size大于`Home_Street`、`Home_City`、`Home_State`和`Home_Zip`平均字段大小的总和。 - 流字段:流字段没有平均字段大小。 如果特性/字段的特性参数`CALCSELECTIVITY`设置为0,则调谐表不会计算该特性/字段的平均字段大小。 可以通过从调谐表显示中选择单个字段来修改平均字段大小计算值。这将在显示屏右侧的详细信息区域中显示该字段的值。可以将“平均字段大小”修改为更适合预期的完整数据集的值。由于设置此值时优化表不执行验证,因此应确保该字段不是流字段,并且指定的值不大于最大字段大小(`MaxLen`)。 平均字段大小还显示在管理门户目录详细信息选项卡字段选项表中。必须已为字段选项表运行了调整表,才能显示平均字段大小值。 ## map BlockCount选项卡 调优表Map `BlockCount`选项卡显示SQL映射名称、`BlockCount`(作为正整数)和`BlockCount`的来源。 块计数的来源可以在类定义中定义、由类编译器估计或由TuneTable度量。 将类编译器估计的调优表更改运行到TuneTable测量; 它不影响在类定义中定义的值。 通过从调优表显示中选择单个SQL映射名称,可以修改`BlockCount`计算值。 这将在显示器右侧的详细信息区域中显示该地图名称的块计数。 可以将块计数修改为一个更适合预期的完整数据集的值。 因为在设置该值时,Tune Table不执行验证,所以应该确保块计数是一个有效值。 修改`BlockCount`会将`BlockCount`的来源更改为类定义中定义的。 # 导出和重新导入调优表统计信息 可以从一个表或一组表导出调优表统计信息,然后将这些调优表统计信息导入一个表或一组表。 以下是可能希望执行此导出/导入的三种情况。 (为简单起见,这些描述了从单个表导出/导入统计数据; 在实际使用中,通常会从多个相互关联的表中导出/导入统计数据): - 为生产系统建模:生产表完全填充了实际数据,并使用`Tune table`进行优化。 在测试环境中,创建的表具有相同的表定义,但数据少得多。 通过从生产表导出调优表统计信息并将它们导入测试表,可以在测试表上对生产表优化建模。 - 要复制生产系统:生产表完全填充了实际数据,并使用tune Table进行了优化。将创建具有相同表定义的第二个生产表。(例如,生产环境及其备份环境,或者多个相同的表定义,每个表包含不同医院的患者记录。)。通过从第一个表导出调优表统计信息并将其导入第二个表,您可以为第二个表提供与第一个表相同的优化,而无需第二次运行调优表或等待第二个表填充有代表性的数据。 - 要恢复到以前的统计信息集:可以通过运行tune Table或显式设置统计信息来创建表的优化统计信息。通过导出这些统计信息,可以在尝试其他统计信息设置时保留它们。一旦确定了最佳统计信息集,就可以将它们重新导入到表中。 可以使用`$SYSTEM.SQL.Stats.Table.Export()`方法将调优表统计信息导出到`XML`文件。此方法可以导出名称空间中一个、多个或所有表的优化表统计信息,如以下示例所示: ```java DO $SYSTEM.SQL.Stats.Table.Export("C:\AllStats.xml") /* 导出当前命名空间中所有架构/表的TuneTable统计信息 */ ``` ```java DO $SYSTEM.SQL.Stats.Table.Export("C:\SampleStats.xml","Sample") /* 导出Sample模式中所有表的可调统计信息 */ ``` ```java DO $SYSTEM.SQL.Stats.Table.Export("C:\SamplePStats.xml","Sample","P*") /* 导出Sample模式中所有以字母“P”开头的表的可调统计信息 */ ``` ```java DO $SYSTEM.SQL.Stats.Table.Export("C:\SamplePersonStats.xml","Sample","Person") /* 导出Sample的可调统计信息Person */ ``` 可以使用`$SYSTEM.SQL.Stats.Table.Import()`方法重新导入使用`$SYSTEM.SQL.Stats.Table.Import()`方法导出的调优表统计信息。 `$SYSTEM.SQL.Stats.Table.Import()`有一个`KeepClassUpToDate boolean`选项。 如果为真(并且`update`为真),`$SYSTEM.SQL.Stats.Table.Import()`将用新的`EXTENTSIZE`和选择性值更新类定义,但类定义将保持最新。 但是,在许多情况下,最好在调优了类表之后重新编译类,这样类定义中的查询就可以重新编译,SQL查询优化器就可以使用更新后的数据统计信息。 默认值为`FALSE(0)`。请注意,如果该类已部署,则类定义不会更新。 `$SYSTEM.SQL.Stats.Table.Import()`有一个`ClearCurrentStats boolean`选项。 如果为`TRUE`, `$SYSTEM.SQL.Stats.Table.Import()`将在导入统计信息之前从现有表中清除所有先前的区段大小、选择性、块计数和其他调优表统计信息。 如果您想要完全清除导入文件中没有指定的那些表状态,而不是让它们在表的`persistent`类中定义,则可以使用此方法。 默认值是`FALSE(0)`。 如果`$SYSTEM.SQL.Stats.Table.Import()`没有找到相应的表,它将跳过该表并继续导入文件中指定的下一个表。 如果找到了一个表,但是没有找到一些字段,那么这些字段将被跳过。 无法继承类存储定义中映射的`BlockCount`。`BlockCount`只能出现在映射起源的类的存储定义中。如果映射源自超类,则`$SYSTEM.SQL.Stats.Table.Import()`仅设置投影表的`BlockCount`元数据,而不设置类存储`BlockCount`元数据。
文章
Michael Lei · 六月 23, 2021

使用 Ansible 自动化部署 Caché 应用程序 - 第 1

部分 Ansible 帮助我解决了快速部署 Caché 和应用程序组件以进行数据平台基准测试的问题。 您可以使用相同的工具和方法来建立您的测试实验室、培训系统、开发或其他环境。 如果在客户站点部署应用程序,可以将大量部署自动化,并确保系统、Caché 和应用程序的配置符合您的应用程序最佳做法标准。 ## 概述 作为一名技术架构师,我们的团队职责之一是在不同供应商的硬件和操作系统上对 InterSystems 数据平台进行基准测试。 通常,基础架构是预发布版,在必须归还或移交给其他人之前,我们的时间有限,因此,快速准确地设置基准测试,让我们有尽可能多的时间来做真正的基准测试工作,这一点至关重要。  多年来,我们通过 shell 脚本小程序来自动执行许多基准测试安装任务,并从速查表和检查清单中剪切和粘贴,但此类操作非常密集,而且容易出错,特别是有许多服务器并且在不同的操作系统之间切换时 - 在 SLES 11、Red Hat 6、Red Hat 7、AIX 等操作系统上安装或使用服务的差异可能很微小,让人厌烦。 在研究了几个可用于自动化配置和管理系统的软件选项之后,我选择了 Ansible 来执行预置数据平台、应用程序和基准测试组件的任务。 需要注意的是,我并没有规定 Ansible 是部署和配置的 **THE** 解决方案。 在选择 Ansible 之前,我研究了其他工具(如 Puppet 和 Chef)的功能和操作。 如果您的组织已经在使用其他工具,您可以使用它们 — 我在 Ansible 中使用的方法和命令等等应该可以转换到其他软件中,我希望这些帖子可以帮助您,不管您使用的是什么工具。 这是本系列的第一个帖子,将介绍在部署 InterSystems 数据平台应用程序时如何使用 Ansible。 本帖介绍如何通过安装 Caché 打下基础,下一帖将扩展解决方案以包括应用程序安装,包括使用 %installer 类。 本帖涵盖: * Ansible 的概述和安装 * Ansible 便于管理和扩展的布局。 * 同时在一个或多个服务器上安装 Caché。 ## 什么是 Ansible? 通过 Ansible 可以在配置一个或多个服务器的同时将复杂的任务自动化,并可以非常简单地添加新服务器。 任务会设计成幂等(您可以在同一台服务器上多次运行相同的脚本,得到的服务器配置将是相同的)。 我选择 Ansible 执行预置任务的一个关键原因是它对系统的要求最低(Python 2.7,Linux 服务器上自带 ),而且它是一个自包含解决方案 — Ansible 代码只安装在控制服务器上,并使用推送架构,通过 OpenSSH 在目标服务器上运行命令和脚本。 所预置的服务器上不需要任何代理。 作为对比,Chef 和 Puppet 采用拉取架构,软件在客户端服务器(Web、数据库等)上加载,并且客户端不断轮询主服务器以查找更新。 Ansible 的推送架构也适合按照您的计划需求逐步实施服务器。 Ansible 是开源的,由社区维护。 Ansible, Inc 从 2015 年开始为 Red Hat 所拥有。 Ansible, Inc 有一个高级的生命周期产品 (Ansible Tower),并提供收费的支持和培训,不过本帖中的所有内容均使用开源命令行版本。 还有一个活跃的社区 (Ansible Galaxy),您可以从中下载许多预制的解决方案来完成大量任务,如安装 Web 服务器、ftp、kerbros,不胜枚举。 例如,对于完整的基准测试部署项目,我包括了一个下载的 Apache 模块,并自定义成在 RHEL、SLES 或 Solaris(以及其他平台)上安装和配置 Apache 2.x。 Ansible 的下载和安装说明可以在 Ansible 网站和 Github 上找到。 如果您有问题或希望做出贡献,可以访问活跃社区。 https://www.ansible.com/get-startedhttp://docs.ansible.com ## 安装 Ansible 本帖中的示例已经在运行 Red Hat 7.0 和 7.2 的虚拟机上进行了测试 - 我也在我的安装了 Centos 7 的笔记本电脑上使用 virtual box 和 vagrant 对 Ansible 控制器服务器进行了初始测试。 Caché 不需要安装在控制器上,所以您的操作系统选择要多于 Caché 支持的平台列表。 为了简单起见,我使用了当前适用于 Red Hat 的 rpm 版本的 Ansible (Ansible 1.9.4),更高的版本可以从 GitHub 获取。 在示例中,我安装的是 cache-2015.2.2.805.0-lnxrhx64,但相同的常规过程也适用于 HealthShare 或 Ensemble 发行版。 您将在后面看到,我们使用特定文件名、目录路径等变量来参数化安装选项。 在第一个帖子中,我将任务削减为基本的 Caché 安装,因此大多数任务是独立于平台的。 当 Ansible playbook 启动时,首要任务之一是获取目标机器的清单 — 操作系统、接口卡、内存详细信息、CPU 数量、磁盘布局等,当运行命令以从目标上运行的实际命令(例如,Red Hat 上的 service start httpd 与 SLES 上的 /etc/init.d/apache2 restart)中提取 Ansible 脚本命令时,将使用目标操作系统的这些信息。 我假定您已读过说明,并且已按照您的平台说明在控制机上安装了 Ansible。 Ansible 必须使用 Linux 系统作为控制器,但目标系统可以是 Linux 或 Windows。 有关 Windows 目标的更多信息,请参见 Ansible 文档。 ### 控制器系统安装示例:在 RHEL/CentOS 7 64 位上安装 Ansible 在 Red Hat 或 CentOS 上,必须先安装 epel-release (Extra Packages for Enterprise Linux) RPM,其中包含 Ansible。 epel 项目面向主要 Linux 发行版设计,提供了许多有用的开源软件包(网络、系统管理、监视等)。 [root@localhost tmp]# wget http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-5.noarch.rpm : : [root@localhost tmp]# rpm -ivh epel-release-7-5.noarch.rpm : : [root@localhost tmp]# yum --enablerepo=epel info ansible Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager Installed Packages Name : ansible Arch : noarch Version : 1.9.4 Release : 1.el7 Size : 7.0 M Repo : installed From repo : epel Summary : SSH-based configuration management, deployment, and task execution system URL : http://ansible.com License : GPLv3+ Description : : Ansible is a radically simple model-driven configuration management, : multi-node deployment, and remote task execution system. Ansible works : over SSH and does not require any software or daemons to be installed : on remote nodes. Extension modules can be written in any language and   : are transferred to managed machines automatically. [root@localhost tmp]# [root@localhost tmp]# sudo yum install ansible : : [root@localhost tmp]# ansible --version ansible 1.9.4     configured module search path = None **很好... 准备开始!** ## Ansible 方面 关于不同的 Ansible 组件(清单、Playbook、模块、角色等)的用法,应查看 Ansible 文档。 为了简化管理以及避免使用大而复杂的脚本文件,使用了预定义的目录结构和搜索路径。 在本帖中,我将使用一个采用 Ansible 建议的文件结构,当您考虑构建更大的安装示例时,可以将本结构用作模型。 所使用的 Ansible 模块带有注释和自我说明,可以在 Github 上获取。 下载文件并通读以了解工作流程。 我们的示例的基本目录包含以下文件; * ansible.cfg:对 Ansible 默认值的更改。 * 清单:定义和描述工作环境。 例如服务器名称/IP。 * <各种>.yml 文件:这些文件描述了将为特定服务器角色运行的任务集。 ### 术语 为了使后面的讨论更清晰,下面是一些 Ansible 术语的快速解释。 模块是用于创建对系统执行的自动化操作的构建块。 每个模块都为特定任务构建,可以使用参数更改该任务。 例如复制文件、创建用户、运行命令、启动服务等。 目前,默认的 Ansible 安装已包含 400 多个模块,另外还有更多来自社区的模块,您也可以创建您自己的模块。 模块组合在一起生成 play 和 playbook,作为执行自动化工作流程的一种方式。 一个 Play 可以有多个任务,一个 Playbook 可以有多个 Play。 角色允许您组合 Playbook。 角色可以被视为根据目标服务器使用情况分组在一起的服务器组件配置。 在本帖的示例中,角色构建配置层以构建服务器。 在我的基准测试设置中,我有以下角色来构建服务器: * hs\_server\_common:配置操作系统,安装 Apache,安装 Caché。 *  webserver:复制 Web 文件(csp、html、js 等),针对应用程序配置 Apache。 * generator:复制文件,创建和配置 webstress 生成器数据库、命名空间、全局映射等。 * dbserver:复制文件,配置数据库服务器系统设置、应用程序数据库、命名空间、全局映射等。 可以组合这些角色来构建不同的服务器类型: * hs\_server\_common + webserver + generator = webstress 生成器服务器。 * hs\_server\_common + webserver = 应用程序 Web 服务器。 * hs\_server\_common + dbsevrer = 数据库服务器。 角色的组成以及每个角色中包含的配置将非常特定于要部署的应用程序。 本帖中的示例将使用最小的任务集,并假定操作系统已预先配置,但是,使用 Ansible 和 Galaxy 上的模块可以实现更复杂和全功能的系统配置。 ## 关于安装 Caché 的说明 {.MsoNormal} 我写了几个例子来介绍一些有趣和有用的特性,下面是一些精华部分。 注意:这些示例可以用作 InterSystems 数据平台(Caché、HealthShare 和 Ensemble)的安装指南。 我写的示例是安装 HealthShare,但 HealthShare 和 Caché 示例中的操作是相同的。 **./testserver/roles/hs\_server\_common/tasks/main.yml** 这是配置操作系统、安装 Apache、安装 Caché 等常见任务的主线脚本。 本帖中对其进行了删减,使其只包含用于 Red Hat 的文件,以及只安装和配置 Caché。 您可以看到,Ansible 在启动后已经在 _ansible_*_ 变量(包括 _ansible\_os\_family_)中保存了操作系统信息,我们可以在脚本中使用这些变量做出决策。 **./testserver/roles/hs\_server\_common/tasks/configure-healthshare2015.yml** 这是用于安装 Caché 的主脚本。 浏览该脚本,会看到针对目标执行的任务的逻辑工作流程,包括: * 创建操作系统用户和组。 * 从控制器上的清单文件夹复制安装文件。 * 解压缩安装文件。 * 使用静默安装方式安装 Caché(请参见下面的注释)。 * 复制 Caché 密钥文件 * 设置默认 Caché 实例 * 重启 Apache * 重启 Caché Caché 的静默安装有几个选项,包括: * 使用 parameters.is 文件。 模板 .isc 文件由以前的安装创建,可以按原样使用,也可以修改。 * 使用 cinstall_silent 以及环境中设置的键值对。 * 使用 %installer 类。 在此示例中,我选择了使用 install\_silent,但是我还包括了一个注释掉的使用参数文件的备用方法,以说明如何在 Ansible 中使用模板文件(请参见 /roles/hs\_server\_common/templates/parameters\_hs20152_rh64.isc)。 在以后的帖子中,我将说明如何使用 %installer 类安装 Caché 以及设置数据库和命名空间。 有关安装选项的详细信息,请参见 Caché 在线文档,社区中也有一个非常好的帖子介绍了 %installer 类的使用。 当您想要安装并配置 Caché,以将 CSPGateway 与除了旧版本 Caché 内部的 Apache 版本以外的 Web 服务器一起使用时,参数文件很有用。 自 Caché 2016.1 起,%installer 提供此功能。 **./testserver/roles/hs\_server\_common/tasks/setup_RedHat.yml** 包含此示例是为了说明如何使用系统特定变量 (ansible_*) 和设置操作系统变量。 **./testserver/roles/hs\_server\_common/vars/*** 变量文件包含键:值对形式的变量,如您所见,这是一种在不同环境和情况下重复使用相同脚本的方法。 ## 运行 Caché 安装 对于此示例,我假定系统可用,并按如下方式进行设置。 1. 控制器已安装 Ansible,并且以下目录填充了来自 Github 的文件和结构。 * **./testserver/***:包含清单、.yml 文件等等的目录树。 包括... * **./testserver/Distribution_Files/Cache**:(包含 Caché 分发包和 cache.key 的清单)。 2. 目标机器已安装 Red Hat 和 Apache。 您需要编辑以下文件来为您的测试环境自定义安装。 1. **inventory_test** 您需要编辑测试服务器名称或 IP 地址。 2. .**/testserver/roles/hs\_server\_common/vars/healthshare2015.yml** 您必须编辑路径以适合您的测试环境。 查看目标服务器的以下路径: * **common\_install\_base_path**:清单文件将复制到该位置后解压,并运行 Caché 安装。 * **ISC\_PACKAGE\_INSTALLDIR**:Caché 安装目录 如果目标服务器上不存在这些目录路径,将会创建。 注:自动化部署的特性之一是并行构建多个服务器。 如果清单文件中有多个服务器,在各个目标服务器上将并发运行每个步骤,直至该步骤完成,然后再在组中的每个服务器上开始下一个步骤。 如果任何服务器上有步骤失败,脚本将停止。 您将看到一条错误消息,帮助您更正问题。 更正错误后,只需从头重新运行 — 这是脚本的一个关键特性 — 脚本设计成幂等。 幂等的意思是,在某个步骤运行某个模块(例如复制文件)时,如果文件已经存在,则该步骤不会重新运行,脚本只是继续下一个步骤。 copy 之类的模块有参数可以设置为强制复制,但这不是默认设置。 仔细检查脚本会发现,在某些情况下使用了“creates”参数,例如:   - name: unattended install of hs using cinstall_silent shell: > ISC_PACKAGE_INSTANCENAME="{{ ISC_PACKAGE_INSTANCENAME }}" ISC_PACKAGE_INSTALLDIR="{{ ISC_PACKAGE_INSTALLDIR }}" ISC_PACKAGE_UNICODE="{{ ISC_PACKAGE_UNICODE }}" ISC_PACKAGE_INITIAL_SECURITY="{{ ISC_PACKAGE_INITIAL_SECURITY }}" ISC_PACKAGE_MGRUSER="{{ ISC_PACKAGE_MGRUSER }}" ISC_PACKAGE_MGRGROUP="{{ ISC_PACKAGE_MGRGROUP }}" ISC_PACKAGE_USER_PASSWORD="{{ ISC_PACKAGE_USER_PASSWORD }}" ISC_PACKAGE_CACHEUSER="{{ ISC_PACKAGE_CACHEUSER }}" ISC_PACKAGE_CACHEGROUP="{{ ISC_PACKAGE_CACHEGROUP }}" ./cinstall_silent chdir="{{ common_install_base_path }}/{{ hs_install_unpack_path }}" args: creates: "{{ ISC_PACKAGE_INSTALLDIR }}/cinstall.log" 上面一节使用 creates 参数告诉 Ansible 模块(在本例中是 shell 模块),此操作创建 cinstall.log 文件。 如果模块发现该文件(Caché 已经安装),则此步骤将不会运行。 好了,全部设置完毕后,我们可以运行安装了。 $ ansible-playbook dbserver.yml PLAY [dbservers] ************************************************************** GATHERING FACTS *************************************************************** ok: [db1] TASK: [hs_server_common | include_vars healthshare2015.yml] ******************* ok: [db1] TASK: [hs_server_common | include_vars os-RedHat.yml] ************************* ok: [db1] etc etc etc TASK: [hs_server_common | Create default cache group] ************************* changed: [db1] TASK: [hs_server_common | Create default cache manager group] ***************** changed: [db1] TASK: [hs_server_common | Create default cache user] ************************** changed: [db1] TASK: [hs_server_common | Create default cache system users] ****************** changed: [db1] TASK: [hs_server_common | Create full hs install temp directory] ************** changed: [db1] TASK: [hs_server_common | Check tar file (gunzipped already) does not exist] *** ok: [db1] TASK: [hs_server_common | Copy healthshare install file] ********************** changed: [db1] TASK: [hs_server_common | un zip hs folder] *********************************** changed: [db1] TASK: [hs_server_common | un tar hs install] ********************************** changed: [db1] TASK: [hs_server_common | Create hs install directory] ************************ changed: [db1] TASK: [hs_server_common | touch ztrak.conf.] ********************************** changed: [db1] TASK: [hs_server_common | Process parameters file] **************************** changed: [db1] TASK: [hs_server_common | unattended install of hs using cinstall_silent] ***** changed: [db1] TASK: [hs_server_common | copy hs key] **************************************** changed: [db1] TASK: [hs_server_common | Set default hs instance] **************************** changed: [db1] TASK: [hs_server_common | restart apache to initialize CSP.ini file] ********** changed: [db1] NOTIFIED: [hs_server_common | restart healthshare] **************************** changed: [db1] PLAY RECAP ******************************************************************** db1 : ok=32 changed=21 unreachable=0 failed=0 如果我们查看目标服务器 — 数据库服务器 Caché 现在已经启动并运行。 $ ccontrol list Configuration 'H2015' (default) directory: /test/hs2015 versionid: 2015.2.1.705.0 conf file: cache.cpf (SuperServer port = 1972, WebServer = 57772) status: running, since Wed Feb 17 15:59:11 2016 state: ok ## 总结 在后续的帖子中,我将构建包含其他任务的脚本,如编辑配置文件和使用 %installer 类配置应用程序。 如果您对此感兴趣并开始创建您自己的部署,请随时与我联系,提出问题或建议。 我经常在全球峰会上发表关于虚拟化和性能的演讲 - 因此,如果您参加今年的全球峰会,请介绍一下您自己,我非常乐意和您聊一聊 Ansible 的使用经验或任何其他系统架构话题。
文章
姚 鑫 · 四月 16, 2021

第二章 定义和构建索引(四)

# 第二章 定义和构建索引(四) # 位片索引 当数字数据字段用于某些数值运算时,位片索引用于该字段。位片索引将每个数值数据值表示为二进制位串。位片索引不是使用布尔标志来索引数值数据值(如在位图索引中那样),而是以二进制值表示每个值,并为二进制值中的每个数字创建一个位图,以记录哪些行的该二进制数字具有1。这是一种高度专门化的索引类型,可以显著提高以下操作的性能: - `SUM`、`COUNT`或`AVG` Aggregate计算。(位片索引不用于`COUNT(*)`计算。)。位片索引不用于其他聚合函数。 - 指定的字段 `TOP n ... ORDER BY field` - 在范围条件运算中指定的字段,`WHERE field > n` 或 `WHERE field BETWEEN lownum AND highnum`、 SQL优化器确定是否应该使用定义的位片索引。通常,优化器仅在处理大量(数千)行时才使用位片索引。 可以为字符串数据字段创建位片索引,但位片索引将这些数据值表示为规范数字。换句话说,任何非数字字符串(如`“abc”`)都将被索引为0。这种类型的位片索引可用于快速计数具有字符串字段值的记录,而不计算那些为空的记录。 在下面的例子中,`Salary`是位片索引的候选项: ```sql SELECT AVG(Salary) FROM SalesPerson ``` 位片索引可用于使用`WHERE`子句的查询中的聚合计算。如果`WHERE`子句包含大量记录,则这是最有效的。在下面的示例中,SQL优化器可能会使用`Salary`上的位片索引(如果已定义);如果定义了位片索引,它还会使用`REGION`上的位图索引,使用定义的位图或为`REGION`生成位图临时文件: ```sql SELECT AVG(Salary) FROM SalesPerson WHERE Region=2 ``` 但是,当索引无法满足`WHERE`条件时,不使用位片索引,而必须通过读取包含要聚合的字段的表来执行。以下示例将不使用`Salary`的位片索引: ```sql SELECT AVG(Salary) FROM SalesPerson WHERE Name LIKE '%Mc%' ``` 可以为任何包含数值的字段定义位片索引。InterSystems SQL使用`Scale`参数将小数转换为位字符串,如ObjectScript `$factor`函数中所述。可以为数据类型字符串的字段定义位片索引;在这种情况下,出于位片索引的目的,非数字字符串数据值被视为`0`。 可以为系统分配的行ID为正整数值的表中的字段定义位片索引,也可以为使用`%BID`属性定义以支持位图(和位片)索引的表中的字段定义位片索引。 位片索引只能为单个字段名定义,不能为多个字段的连接定义。 不能指定`WITH DATA`子句。 下面的例子比较了位片索引和位图索引。 如果你为1、5和22行创建一个位图索引,它会为这些值创建一个索引: ```java ^gloI("bitmap",1,1)= "100" ^gloI("bitmap",5,1)= "010" ^gloI("bitmap",22,1)="001" ``` 如果为第1、2和3行的值1、5和22创建位切片索引,则会首先将这些值转换为位值: ```java 1 = 00001 5 = 00101 22 = 10110 ``` 然后,它为这些位创建索引: ```java ^gloI("bitslice",1,1)="110" ^gloI("bitslice",2,1)="001" ^gloI("bitslice",3,1)="011" ^gloI("bitslice",4,1)="000" ^gloI("bitslice",5,1)="001" ``` 在本例中,位图索引中的值22需要设置1个全局节点;位片索引中的值22需要设置3个全局节点。 请注意,插入或更新需要在所有`n`个位片中设置一个位,而不是设置单个位串。这些附加的全局设置操作可能会影响涉及填充位片索引的插入和更新操作的性能。使用`INSERT`、`UPDATE`或`DELETE`操作填充和维护位片索引比填充位图索引或常规索引慢。维护多个位片索引和/或在频繁更新的字段上维护位片索引可能具有显著的性能成本。 在易失性表(执行许多插入、更新和删除操作)中,位片索引的存储效率可能会逐渐降低。`%SYS.Maint.Bitmap`实用程序方法同时压缩位图索引和位片索引,从而提高了还原效率。 # 重建索引 可以按如下方式构建/重新构建索引: - 使用`BUILD INDEX` SQL命令构建指定索引,或构建为表、架构或当前命名空间定义的所有索引。 - 使用管理门户重建指定类(表)的所有索引。 - 使用`%BuildIndices()`(或`%BuildIndicesAsync()`)方法,如本节所述。 当前数据库访问确定应如何重建现有索引: - 非活动系统(在索引构建或重建期间没有其他进程访问数据) - `READONLY`活动系统(能够在索引构建或重建期间查询数据的其他进程) - 读写活动系统(能够在索引构建或重建期间修改数据和查询数据的其他进程) 构建索引的首选方法是使用`%BuildIndices()`方法或`%BuildIndicesAsync()`方法。 - `%Library.Persistent.%BuildIndices()`:`%BuildIndices()`作为后台进程执行,但调用方必须等待`%BuildIndices()`完成才能接收回控制。 - `%Library.Persistent.%BuildIndicesAsync()`:`%BuildIndicesAsync()`将`%BuildIndices()`作为后台进程启动,调用方立即收到控制权。`%BuildIndicesAsync()`的第一个参数是`eueToken`输出参数。其余参数与`%BuildIndices()`相同。 `%BuildIndicesAsync()`返回`%Status`值:`Success`表示`%BuildIndices()`辅助作业已成功排队;失败表示该辅助作业未成功排队。 `%BuildIndicesAsync()`向`eueToken`输出参数返回一个值,该值指示`%BuildIndices()`完成状态。要获取完成状态,请通过引用将`eueToken`值传递`给%BuildIndicesAsyncResponse()`方法。还可以指定等待布尔值。如果`wait=1`,则`%BuildIndicesAsyncResponse()`将等待,直到由`eueToken`标识的`%BuildIndices()` JOB 完成。如果`wait=0`,`%BuildIndicesAsyncResponse()`将尽快返回状态值。如果返回时`%BuildIndicesAsyncResponse() ``eueToken`不为空,则`%BuildIndices()` job尚未完成。在这种情况下,可以使用`eueToken`再次调用`%BuildIndicesAsyncResponse()`。当`%BuildIndicesAsyncResponse()``eueToken`最终为`NULL`时,返回的`%BuildIndicesAsyncResponse()``%Status`值是`%BuildIndicesAsync()`调用的job的完成状态。 ## 在非活动系统上构建索引 系统自动生成方法(由`%Persistent`类提供),这些方法构建或清除为类(表)定义的每个索引。可以通过以下两种方式之一使用这些方法: - 通过管理门户进行交互。 - 以编程方式,作为方法调用。 构建索引执行以下操作: 1. 删除索引的当前内容。 2. 扫描(读取每一行)主表,并为表中的每一行添加索引项。如果可能,使用特殊的`$SortBegin`和`$SortEnd`函数来确保高效地构建大型索引。在构建标准索引时,除了在内存中缓存数据之外,使用`$SortBegin`/`$SortEnd`还可以使用`IRISTEMP`数据库中的空间。因此,在构建非常大的标准索引时,InterSystems IRIS可能需要`IRISTEMP`中大致等于最终索引大小的空间。 注:构建索引的方法仅为使用InterSystems IRIS默认存储结构的类(表)提供。映射到遗留存储结构的类不支持索引构建,因为它假定遗留应用程序管理索引的创建。 ### 使用管理门户构建索引 可以通过执行以下操作来构建表的现有索引(重建索引): 1. 从管理门户中选择系统资源管理器,然后选择SQL。使用页面顶部的切换选项选择一个命名空间;这将显示可用命名空间的列表。选择命名空间后,选择屏幕左侧的`Schema`下拉列表。这将显示当前名称空间中的模式列表,其中带有布尔标志,指示是否有任何表或视图与每个模式相关联。 2. 从此列表中选择一个架构;该架构将显示在架构框中。它的正上方是一个下拉列表,允许选择属于该模式的表、系统表、视图、过程或所有这些。选择“表”或“全部”,然后打开“表”文件夹以列出此架构中的表。如果没有表,则打开文件夹将显示空白页。(如果未选择“表”或“全部”,则打开“表”文件夹将列出整个命名空间的表。) 3. 选择其中一个列出的表。这将显示表的目录详细信息。 - 要重建所有索引:单击操作下拉列表,然后选择重建表的索引。 - 要重建单个索引:单击索引按钮以显示现有索引。每个列出的索引都有重建索引的选项。 **注意:当其他用户正在访问表的数据时,不要重建索引。要在活动系统上重建索引,请参阅在活动系统上构建索引。** ### 以编程方式构建索引 为非活动表构建索引的首选方法是使用随表的`Persistent`类提供的`%BuildIndices()`(或`%BuildIndicesAsync()`)方法。 **若要以编程方式生成一个或多个索引,请使用`%Library.Persistent.%BuildIndices()`方法。** 生成所有索引:调用`%BuildIndices()`,不带参数生成为给定类(表)定义的所有索引(为其提供值): ```java SET sc = ##class(MyApp.SalesPerson).%BuildIndices() IF sc=1 { WRITE !,"成功构建索引" } ELSE { WRITE !,"索引构建失败",! DO $System.Status.DisplayError(sc) QUIT } ``` 生成指定索引:调用`%BuildIndices()`,并将`$LIST`索引名作为第一个参数,为给定类(表)生成指定的已定义索引(为其提供值): ```java SET sc = ##class(MyApp.SalesPerson).%BuildIndices($ListBuild("NameIDX","SSNKey")) IF sc=1 { WRITE !,"成功构建索引" } ELSE { WRITE !,"索引构建失败",! DO $System.Status.DisplayError(sc) QUIT } ``` 生成除以下项之外的所有索引:调用`%BuildIndices()`,并将索引名称的`$LIST`作为第七个参数来构建(为其提供值)给定类(表)的所有已定义索引(指定索引除外): ```java SET sc = ##class(MyApp.SalesPerson).%BuildIndices("",,,,,,$ListBuild("NameIDX","SSNKey")) IF sc=1 { WRITE !,"成功构建索引" } ELSE { WRITE !,"索引构建失败",! DO $System.Status.DisplayError(sc) QUIT } ``` `%BuildIndices()`方法执行以下操作: 1. 对要重建的任何(非位图)索引调用`$SortBegin`函数(这将启动对这些索引的高性能排序操作)。 2. 循环遍历类(表)的主要数据,收集索引使用的值,并将这些值添加到索引(通过适当的排序转换)。 3. 调用`$SortEnd`函数来完成索引排序过程。 如果索引已经有值,则必须使用两个参数调用`%BuildIndices()`,其中第二个参数的值为1。 为此参数指定1将导致该方法在重新生成值之前清除这些值。 例如: ```java SET sc = ##class(MyApp.SalesPerson).%BuildIndices(,1) IF sc=1 { WRITE !,"成功构建索引" } ELSE { WRITE !,"索引构建失败",! DO $System.Status.DisplayError(sc) QUIT } ``` 清除并重建所有的索引。 你也可以清除并重建索引的子集,例如: ```java SET sc = ##class(MyApp.SalesPerson).%BuildIndices($ListBuild("NameIDX","SSNKey"),1) IF sc=1 { WRITE !,"成功构建索引" } ELSE { WRITE !,"索引构建失败",! DO $System.Status.DisplayError(sc) QUIT } ``` 注意:当表的数据被其他用户访问时,不要重建索引。 若要在活动系统上重建索引,请参见在活动系统上构建索引。 ## 在活动系统上构建索引 在活动系统上构建(或重建)索引时,有两个问题: - 除非正在构建的索引对`SELECT` `Query`隐藏,否则活动`Query`可能返回不正确的结果。这是在构建索引之前使用`SetMapSelecability()`方法处理的。 - 索引构建期间对数据的活动更新不会反映在索引条目中。这是通过在生成索引时使生成操作锁定单个行来处理的。 **注意:如果应用程序在单个事务内对数据执行大量更新,则可能会出现锁表争用问题。** ### 在Readonly主动系统上构建索引 如果表当前仅用于查询操作(`READONLY`),则可以在不中断查询操作的情况下构建新索引或重建现有索引。这是通过在重建索引时使索引对查询优化器不可用来实现的。 如果要为其构建一个或多个索引的所有类当前都是`READONLY`,请使用“在读写活动系统上构建索引”中描述的相同系列操作,但有以下区别:使用`%BuildIndices()`时,设置`pLockFlag=3`(共享区锁定)。 ### 在读写活动系统上构建索引 如果持久化类(表)当前正在使用并且可用于读写访问(查询和数据修改),则可以在不中断这些操作的情况下构建新索引或重建现有索引。如果要为其重建一个或多个索引的类当前可读写访问,则构建索引的首选方法是使用与表的持久类一起提供的`%BuildIndices()`(或`%BuildIndicesAsync()`)方法。 **注意:以下信息适用于动态SQL查询,而不适用于嵌入式SQL。嵌入式SQL在编译时(而不是在运行时)检查`MapSelecability`设置。因此,关闭索引的`MapSelecability`对已经编译的嵌入式SQL查询没有任何影响。因此,嵌入式SQL查询仍可能尝试使用禁用的索引,并将给出不正确的结果。** 在并发读写访问期间,需要执行以下一系列操作来构建一个或多个索引: 1. 望构建的索引对查询不可用(读取访问权限)。这是使用`SetMapSelecability()`完成的。这使得查询优化器无法使用该索引。在重建现有索引和创建新索引时都应执行此操作。例如: ```java SET status=$SYSTEM.SQL.Util.SetMapSelectability("Sample.MyStudents","StudentNameIDX",0) ``` - 第一个参数是`Schema.Table`名称,它是`SqlTableName`,而不是持久类名称。例如,默认模式是`SQLUser`,而不是`User`。该值区分大小写。 - 第二个参数是SQL索引映射名称。这通常是索引的名称,指的是磁盘上存储索引的名称。对于新索引,这是在创建索引时将使用的名称。该值不区分大小写。 - 第三个参数是`MapSelecability`标志,其中0将索引映射定义为不可选择(`OFF`),1将索引映射定义为可选择(`ON`)。指定0。 可以通过调用`GetMapSelecability()`方法来确定索引是否不可选。如果已将索引显式标记为不可选,则此方法返回0。在所有其他情况下,它返回1;它不执行表或索引是否存在的验证检查。请注意,`Schema.Table`名称是`SqlTableName`,并且区分大小写。 `SetMapSelecability()`和`GetMapSelecability()`仅适用于当前命名空间中的索引映射。如果该表映射到多个命名空间,并且需要在每个命名空间中构建索引,则应该在每个命名空间中调用`SetMapSelecability()`。 2. 在索引构建期间建立并发操作: - 对于新索引:在类中创建索引定义(或在类的`%Storage.SQL`中创建新的SQL Index Map规范)。编译类。此时,索引存在于表定义中;这意味着对象保存、SQL `INSERT`操作和SQL `UPDATE`操作都记录在索引中。但是,由于在步骤1中调用了`SetMapSelecability()`,因此不会为任何数据检索选择此索引映射。`SetMapSelecability()`阻止查询使用区索引,但是数据映射将被投影到SQL以使用索引全局和数据全局。对于新索引,这是合适的,因为索引尚未填充。在对表运行查询之前,需要填充区索引。 - 对于现有索引:清除任何引用该表的缓存查询。索引构建执行的第一个操作是终止索引。因此,在重新生成索引时,不能依赖任何经过优化以使用该索引的代码。 3. 使用`pLockFlag=2`(行级锁定)的持久化类(表)的`%BuildIndices()`方法构建一个或多个索引。`PLockFlag=2`标志在重建过程中在单个行上建立独占写锁,以便并发数据修改操作与构建索引操作相协调。 默认情况下,`%BuildIndices()`构建为持久类定义的所有索引;可以使用`pIgnoreIndexList`从重建中排除索引。 默认情况下,`%BuildIndices()`为所有`ID`构建索引项。但是,可以使用`pStartID`和`pEndID`来定义`ID`范围。`%BuildIndices()`将仅为该范围内(含)的ID构建索引项。例如,如果使用带有`%NOINDEX`限制的`INSERT`将一系列新记录添加到表中,则可以稍后使用具有ID范围的`%BuildIndices()`为这些新记录构建索引项。还可以使用`pStartID`和`pEndID`在节中构建极大的索引。 `%BuildIndices()`返回`%Status`值。如果`%BuildIndices()`因检索数据时出现问题而失败,系统将生成一个`SQLCODE`错误和一条消息(`%msg`),其中包含遇到错误的`%ROWID`。 4. 构建完索引后,启用映射以供查询优化器选择。将第三个参数`MapSelecability`标志设置为1,如下例所示: ```java SET status=$SYSTEM.SQL.Util.SetMapSelectability("Sample.MyStudents","StudentNameIDX",1) ``` 5. 再次清除引用该表的所有缓存查询。这将消除在此程序中创建的缓存查询,这些查询无法使用索引,因此不如使用索引的相同查询最佳。 这就完成了这个过程。索引已完全填充,查询优化器能够考虑该索引。 注意:`%BuildIndices()`只能用于重建`ID`值为正整数的表的索引。如果父表具有正整数`ID`值,还可以使用`%BuildIndices()`重建子表中的索引。对于其他表,请使用`%ValidateIndices()`方法,如验证索引中所述。因为`%ValidateIndices()`是构建索引的最慢方法,所以只有在没有其他选项的情况下才应该使用它。
文章
Louis Lu · 五月 30, 2021

如何保存、查询 List 类型数据

本文主要总结了在InterSystems IRIS 中如何保存、查询List类型数据 假设我们设计的对象中包含姓名,同时每个姓名下可以包含多个电话。我们可以使用下面方法进行处理。 1. 传统方式 我们可以把每一个姓名和电话放在不同列中。 Class Test.Person Extends %Persistent { Property Name As %String; Property Phone As %String; } 我们使用SQL语句插入数据: insert into Test.Person values ('a','111-111-1111'); insert into Test.Person values ('b','222-111-1111'); insert into Test.Person values ('a','111-222-1111'); insert into Test.Person values ('c','333-111-1111'); insert into Test.Person values ('b','222-222-1111'); 数据在表中是这样的: Name Phone a 111-111-1111 b 222-111-1111 a 111-222-1111 c 333-111-1111 b 222-222-1111 这种情况下,我们可以使用下面的sql语句将结果返回: SELECT distinct %exact(Name) Name, LIST(phone %foreach(Name)) Phonestr FROM test.person Name Phonestr a 111-111-1111,111-222-1111 b 222-111-1111,222-222-1111 c 333-111-1111 我们可以为电话号码创建索引,以提高搜索速度,如下: Index IdxP On Phone; 使用这种方式保存list数据比较简单,当是当list数据非常多时,这种方法会使表格臃肿。 2. 保存在一个字符串字段中,使用分隔符区分 这里我们将所有电话号码保存在一个字符串字段中,每个号码之间用逗号区分 Class Test.Person2 Extends %Persistent { Property Name As %String; Property PhoneStr As %String; } 填充数据后,类似于这样 Name PhoneStr a 111-111-1111,111-222-1111 b 222-111-1111,222-222-1111 c 333-111-1111 d 333-111-1111,222-222-1111 这种情况下我们可以用下面方法实现对每个电话的索引 Index idxP On PhoneStr(ELEMENTS); ClassMethod PhoneStrBuildValueArray(value, ByRef array) As %Status { if value="" { s array(0)=value }else{ s list=$lfs(value,","),ptr=0 while $listnext(list,ptr,item){ s array(ptr)=item } } q $$$OK } 这里用到了一个函数 ClassMethod propertynameBuildValueArray(value, ByRef valueArray) As %Status 其中: value – 需要拆分的内容; valueArray – 返回array类型的值,其中包含拆分的内容,格式为 array(key1)=value1, array(key2)=value2... 这时候我们的数据是这样: USER>zw ^Test.Person2D ^Test.Person2D=4 ^Test.Person2D(1)=$lb("","a","111-111-1111,111-222-1111") ^Test.Person2D(2)=$lb("","b","222-111-1111,222-222-1111") ^Test.Person2D(3)=$lb("","c","333-111-1111") ^Test.Person2D(4)=$lb("","d","333-111-1111,222-222-1111") 索引是这样: USER>zw ^Test.Person2I ^Test.Person2I("idxP"," 111-111-1111",1)="" ^Test.Person2I("idxP"," 111-222-1111",1)="" ^Test.Person2I("idxP"," 222-111-1111",2)="" ^Test.Person2I("idxP"," 222-222-1111",2)="" ^Test.Person2I("idxP"," 222-222-1111",4)="" ^Test.Person2I("idxP"," 333-111-1111",3)="" ^Test.Person2I("idxP"," 333-111-1111",4)="" 这种情况下我们可以通过下面的SQL 语句查找 包含电话号码 333-111-1111 的姓名 select Name from test.person2 where phonestr ['333-111-1111' select Name from test.person2 where phonestr like '%333-111-1111%' 但是当你检查查询计划的时候,却发现它并没有使用任何索引 我们只能通过类似于下面SQL语句的写法才能使用该索引 select Name from test.person2 where for some %element(Phonestr) (%value = '333-111-1111') 类似的还有下面的写法 (%Value %STARTSWITH 'а') (%Value [ 'a' and %Value [ 'b') (%Value in ('c','d')) (%Value is null) 3. 使用 %List 类型 Class Test.Person3 Extends %Persistent { Property Name As %String; Property PhoneList As %List; Index idxP On PhoneList(ELEMENTS); ClassMethod PhoneListBuildValueArray(value, ByRef array) As %Status { if value="" { s array(0)=value }else{ s ptr=0 while $listnext(value,ptr,item){ s array(ptr)=item } } q $$$OK } } 插入数据 insert into Test.Person3 (Name,PhoneList) select 'a', $LISTBUILD('111-111-1111','111-222-1111') insert into Test.Person3 (Name,PhoneList) select 'b', $LISTBUILD('222-111-1111','222-222-1111') insert into Test.Person3 (Name,PhoneList) select 'c', $LISTBUILD('333-111-1111') insert into Test.Person3 (Name,PhoneList) select 'd', $LISTBUILD('333-111-1111','222-222-1111') 数据和索引保存为 USER>zw ^Test.Person3D ^Test.Person3D=4 ^Test.Person3D(1)=$lb("","a",$lb("111-111-1111","111-222-1111")) ^Test.Person3D(2)=$lb("","b",$lb("222-111-1111","222-222-1111")) ^Test.Person3D(3)=$lb("","c",$lb("333-111-1111")) ^Test.Person3D(4)=$lb("","d",$lb("333-111-1111","222-222-1111")) USER>zw ^Test.Person3I ^Test.Person3I("idxP","111-111-1111",1)="" ^Test.Person3I("idxP","111-222-1111",1)="" ^Test.Person3I("idxP","222-111-1111",2)="" ^Test.Person3I("idxP","222-222-1111",2)="" ^Test.Person3I("idxP","222-222-1111",4)="" ^Test.Person3I("idxP","333-111-1111",3)="" ^Test.Person3I("idxP","333-111-1111",4)="" 同样可以使用下面的SQL语句查找包含电话333-111-1111的姓名 select Name from test.person2 where for some %element(phonelist) (%value = '333-111-1111') 4 使用 List Of、Array Of 保存 不需要定义propertynameBuildValueArray函数 Class Test.Person4 Extends %Persistent { Property Name As %String; Property PhoneList As list Of %String; Index idxP On PhoneList(ELEMENTS); } 使用同样的方式插入数据 insert into Test.Person4 (Name,PhoneList) select 'a', $LISTBUILD('111-111-1111','111-222-1111') insert into Test.Person4 (Name,PhoneList) select 'b', $LISTBUILD('222-111-1111','222-222-1111') insert into Test.Person4 (Name,PhoneList) select 'c', $LISTBUILD('333-111-1111') insert into Test.Person4 (Name,PhoneList) select 'd', $LISTBUILD('333-111-1111','222-222-1111') 数据和索引保存为 USER>zw ^Test.Person4D ^Test.Person4D=4 ^Test.Person4D(1)=$lb("","a",$lb("111-111-1111","111-222-1111")) ^Test.Person4D(2)=$lb("","b",$lb("222-111-1111","222-222-1111")) ^Test.Person4D(3)=$lb("","c",$lb("333-111-1111")) ^Test.Person4D(4)=$lb("","d",$lb("333-111-1111","222-222-1111")) USER>zw ^Test.Person4I ^Test.Person4I("idxP"," 111-111-1111",1)="" ^Test.Person4I("idxP"," 111-222-1111",1)="" ^Test.Person4I("idxP"," 222-111-1111",2)="" ^Test.Person4I("idxP"," 222-222-1111",2)="" ^Test.Person4I("idxP"," 222-222-1111",4)="" ^Test.Person4I("idxP"," 333-111-1111",3)="" ^Test.Person4I("idxP"," 333-111-1111",4)="" 使用同样的SQL查询可以得到结果 select Name from test.person4 where for some %element(Phonelist) (%value = '333-111-1111') 引申话题:针对日期字段的索引 日期格式通常是yyyy-mm-dd,我们经常要求按照某年或者某月查询数据,我们可以使用propertynameBuildValueArray函数设定保存的索引方式实现这个目的 Class Test.Person5 Extends %Persistent { Property Name As %String; Property DOB As %Date; Index idxD On (DOB(KEYS), DOB(ELEMENTS)); ClassMethod DOBBuildValueArray(value, ByRef array) As %Status { if value="" { s array(0)=value }else{ s d=$zd(value,3) s array("yy")=+$p(d,"-",1) s array("mm")=+$p(d,"-",2) s array("dd")=+$p(d,"-",3) } q $$$OK } } 插入数据 insert into Test.Person5 (Name,DOB) select 'a', {d '2000-01-01'} union all select 'b', {d '2000-01-02'} union all select 'c', {d '2000-02-01'} union all select 'd', {d '2001-01-02'} union all select 'e', {d '2001-01-01'} union all select 'f', {d '2001-02-01'} 查看数据以及索引保存的内容 USER>zw ^Test.Person5D ^Test.Person5D=6 ^Test.Person5D(1)=$lb("","a",58074) ^Test.Person5D(2)=$lb("","b",58075) ^Test.Person5D(3)=$lb("","c",58105) ^Test.Person5D(4)=$lb("","d",58441) ^Test.Person5D(5)=$lb("","e",58440) ^Test.Person5D(6)=$lb("","f",58471) USER>zw ^Test.Person5I ^Test.Person5I("idxD","dd",1,1)="" ^Test.Person5I("idxD","dd",1,3)="" ^Test.Person5I("idxD","dd",1,5)="" ^Test.Person5I("idxD","dd",1,6)="" ^Test.Person5I("idxD","dd",2,2)="" ^Test.Person5I("idxD","dd",2,4)="" ^Test.Person5I("idxD","mm",1,1)="" ^Test.Person5I("idxD","mm",1,2)="" ^Test.Person5I("idxD","mm",1,4)="" ^Test.Person5I("idxD","mm",1,5)="" ^Test.Person5I("idxD","mm",2,3)="" ^Test.Person5I("idxD","mm",2,6)="" ^Test.Person5I("idxD","yy",2000,1)="" ^Test.Person5I("idxD","yy",2000,2)="" ^Test.Person5I("idxD","yy",2000,3)="" ^Test.Person5I("idxD","yy",2001,4)="" ^Test.Person5I("idxD","yy",2001,5)="" ^Test.Person5I("idxD","yy",2001,6)="" 执行下面 SQL 可以显示所有2月出生的信息 select * from Test.Person5 where for some %element(DOB) (%key='mm' and %value = 2) 这篇文章源自 这里,作者 Vitaliy Serdtsev
文章
姚 鑫 · 五月 15, 2021

第一章 单元测试概述

# 第一章 单元测试概述 本教程的第一部分概述了单元测试。完成本教程的这一部分后,将能够: - 定义单元测试并区分单元测试和集成测试 - 列出单元测试的几个好处 - 描述InterSystems IRIS `%UnitTest`包和`xUnit`测试框架之间的相似性。 - 列出软件开发中测试优先方法经常声称的几个好处。 # 什么是单元测试? 单元测试是对单个代码模块的正确性的测试,例如,方法或类的测试。通常,开发人员在开发代码时为其代码创建单元测试。典型的单元测试是一种执行方法的方法,该方法测试并验证该方法是否为给定的一组输入生成了正确的输出。 单元测试不同于集成测试。集成测试验证了一组代码模块交互的正确性。单元测试仅单独验证代码模块的正确性。一组代码模块的集成测试可能会失败,即使每个模块都通过了单元测试。 # 为什么要进行单元测试? 单元测试提供了许多好处,包括: - 提供代码模块是否正确的验证。这是单元测试的主要原因。 - 提供自动回归测试。更改代码模块后,应重新运行单元测试,以确保代码模块仍然正确。也就是说,应该使用单元测试来确保更改没有破坏代码模块。理想情况下,所有代码模块的单元测试都应该在更改任何一个模块之后运行。 - 提供文档。通常,代码模块的单元测试与代码模块一起交付。检查单元测试提供了大量有关代码模块如何工作的信息。 # XUnit测试框架 单元测试框架是为开发和执行单元测试提供支持的类包。它们可以很容易地扩展以支持更具体或专门化类型的单元测试。 XUnit系列测试框架基于原始的`Sunit`框架(用于单元测试`SmallTalk`代码),包括以下框架: - `JUnit-Java`代码的单元测试框架。 - `NUnit-C#`、`VB.NET`和其他`.NET`语言代码的单元测试框架。 - `CppUnit-C++`代码的单元测试框架。 - `PyUnit-Python`代码的单元测试框架。 # %UnitTest和xUnit框架的结构 `%UnitTest`包和`xUnit`框架共享相同的基本结构。熟悉任何`Unit`框架的开发人员都可以毫不费力地学习使用`%UnitTest`包。`%UnitTest`和`xUnit`框架都围绕以下基本测试结构组织: - 测试装置-为一个测试或一组测试做准备和清理工作的代码。准备测试可能包括创建数据库连接,或使用测试数据初始化数据库。清理可能包括关闭数据库连接或恢复数据库状态。 - 测试用例-测试的最小单元。验证特定的一组输入是否会产生给定模块的特定输出。 - 测试套件-设计为一起执行的测试和测试套件的集合。 - Test Runner-用于执行测试并显示其结果的实用程序。 # 测试自动化 `%UnitTest`包和`xUnit`框架都支持测试自动化。当单元测试完成执行时,它会报告测试是通过还是失败。不需要解释测试结果。这是非常重要的。可以为每个代码更改执行大量单元测试。如果必须不断地阅读和解释结果,这个过程很快就会变得非常乏味和容易出错。 许多`xUnit`框架提供了汇总测试结果的图形用户界面(GUI)。`%UnitTest`会生成一个显示测试结果的网页。它以绿色显示有关通过的测试的信息,以红色显示有关失败的测试的信息。开发人员可以一目了然地判断是否有任何测试失败。 这是由`%UnitTest`单元测试生成的测试报告。用户可以通过单击页面上的超链接深入查看提供有关测试的更多详细信息的页面。 ![image](6F95C9D03A024D2CB690E5213CF37A4B) # 测试优先方法论 敏捷软件方法论,例如测试驱动开发(TDD)和极限编程,特别强调单元测试。事实上,这些方法使用单元测试来驱动开发过程。他们提倡“测试优先”的软件开发方法。在这种方法中,开发人员在编写代码模块的一行代码之前设计并编写代码模块的单元测试。然后,开发人员创建代码模块,目标是通过单元测试。 `Test First`方法的倡导者声称该方法具有以下好处: - 它迫使开发人员在开发任何模块之前很久就决定代码模块的正确输入和输出。 - 它集中了开发人员在创建代码模块时的注意力。开发人员关注的是在创建模块时通过单元测试的具体目标。 - 它可以防止单元测试成为事后的想法。如果首先创建单元测试,则在项目结束之前不能忽略单元测试。 - 它确保了代码的高度测试覆盖率。 注意:测试优先开发的支持者通常主张在代码模块之前执行单元测试,而不仅仅是创建单元测试。当然,在这一点上测试应该会失败。他们甚至可能不会编译。 # Red – Green – Refactor `XUnit`和`%UnitTest`测试报告GUI报告以绿色表示通过测试,以红色表示未通过测试。下面是使用测试优先开发方法的开发节奏: 1. 红色 - 编写一个不起作用的小测试,也许一开始不会编译。 2. 绿色 - 让测试快速运行,在测试过程中犯下所有必要的错误。 3. 重构 - 消除仅在使测试正常工作时产生的所有重复。 > Kent Beck,《测试驱动的设计》
文章
姚 鑫 · 八月 9, 2021

方法关键字SoapRequestMessage,SoapTypeNameSpace,SqlName,SqlProc

# 第八十一章 方法关键字 - SoapRequestMessage 当多个`web方法`具有相同的`SoapAction`时使用此方法。 在默认场景中,该关键字指定请求消息的`SOAP`正文中的顶级元素的名称。 仅适用于定义为`web服务`或`web客户端`的类。 # 用法 要在请求消息的`SOAP`体中指定顶级元素的名称,请使用以下语法: ```java Method name(formal_spec) As returnclass [ WebMethod, SoapAction = "MyAct", SoapRequestMessage="MyReqMessage" ] { //implementation } ``` 其中`soaprequestmessage`是有效的XML标识符。 # 详解 注意:此关键字仅对包装的文档/文字`document/literal`消息有效。 对于包装的文档/文字消息,该关键字指定请求消息的`SOAP`主体中的顶部元素的名称。(默认情况下,包装文档/文字消息。 如果对同一`web服务`中的多个`web方法`使用相同的`SoapAction`值,请指定此关键字。否则,一般不需要这个关键字。 # 与WSDL的关系 `SoapRequestMessage`关键字影响`web服务`的`WSDL`的``部分。例如,考虑以下web方法: ```java Method Add(a as %Numeric,b as %Numeric) As %Numeric [ SoapAction = MyAct,SoapRequestMessage=MyReqMessage, WebMethod ] { Quit a + b } ``` 对于这个web服务,WSDL包含以下内容: ```xml ``` 这些元素在``部分中相应地定义。 默认情况下,如果方法没有指定`SoapRequestMessage`关键字,``部分将改为如下所示: ```xml ``` 如果使用`SOAP`向导从`WSDL` IRIS `web服务`或客户端, IRIS将此关键字设置为适合该WSDL的。 # 对Message的影响 对于前面显示的`web方法`,`web服务`需要以下形式的请求消息: ```xml 12 ``` 相反,如果该方法没有指定`SoapRequestMessage`关键字,则该消息将如下所示: ```xml 12 ``` # 第八十二章 方法关键字 - SoapTypeNameSpace 为此`web方法`使用的类型指定`XML`命名空间。仅适用于定义为`web服务`或`web客户端`的类。 # 用法 若要重写类型的默认`XML`命名空间(当该方法用作web方法时),请使用以下语法: ```java Method name(formal_spec) As returnclass [ SoapTypeNameSpace = "soapnamespace", SoapBindingStyle = document, WebMethod ] { //implementation } ``` 其中`soapnamespace`是命名空间`URI`。请注意,如果`URI`包含冒号(`:`),则该字符串必须加引号。也就是说,可以使用以下内容: ```java Method MyMethod() [ SoapTypeNameSpace = "http://www.mynamespace.org", SoapBindingStyle = document, WebMethod ] ``` 或以下内容: ```java Method MyMethod() [ SoapTypeNameSpace = othervalue, SoapBindingStyle = document, WebMethod ] ``` 但不包括以下内容: ```java Method MyMethod() [ SoapTypeNameSpace = http://www.mynamespace.org, SoapBindingStyle = document, WebMethod ] ``` 重要提示:对于手动创建的`web服务`,该关键字的默认值通常是合适的。当使用SOAP向导从`WSDL`生成`web客户端`或服务时,InterSystems IRIS会将该关键字设置为适合该`WSDL`;如果修改该值,`web客户端`或服务可能不再工作。 # 详解 此关键字指定此`web方法`使用的类型的XML命名空间。 注意:只有当方法使用文档样式绑定时,此关键字才有作用。也就是说,方法(或包含它的类)必须用等于`document`的`SoapBindingStyle`标记。(对于使用`rpc-style`绑定的方法,指定这个关键字是没有意义的。) # 默认 如果省略此关键字,则此方法的类型位于由`web服务`或`客户端`类的`TYPENAMESPACE`参数指定的命名空间中。如果未指定`TYPENAMESPACE`,则类型将位于由`web服务`或客户端的`are`参数指定的命名空间中。 # 与WSDL的关系 `SoapTypeNameSpace`关键字影响`WSDL`的以下部分: ``元素中的命名空间声明。指定的命名空间(例如,`http://www.customtypes.org`)将添加到这里。例如: ```xml ... xmlns:ns2="http://www.customtypes.org" xmlns:s0="http://www.wbns.org" xmlns:s1="http://webservicetypesns.org" ... targetNamespace="http://www.wbns.org" ``` 在本例中,`http://www.customtypes.org`命名空间被分配给前缀`ns2`。 请注意,`WSDL`还像往常一样声明了以下名称空间: - `Web服务`的命名空间(`http://www.wsns.org`),在本例中,它被分配给前缀`s0`,也用作`Web服务`的目标命名空间。 - 网络服务的类型命名空间`http://www.webservicetypesns.org`),在本例中它被分配给`前缀s1`。 如果在`web服务`类中没有指定类型命名空间,则该命名空间不包含在`WSDL`中。 - ``元素,它包含一个``元素,该元素的`targetNamespace`属性等于为`SoapTypeNameSpace`指定的命名空间: ```xml ... ... ``` 相反,如果没有指定`SoapTypeNameSpace`,那么`WSDL`的这一部分将如下所示。请注意,``元素的`targetNamespace`是`web服务`类型的命名空间: ```xml ... ... ``` (此外,如果在`web服务类`中没有指定类型命名空间,则`targetNamespace`将改为`web服务`的命名空间。) # 对消息的影响 `SOAP`消息可能如下所示(为了可读性,添加了换行符和空格): ```xml 3 ``` 请注意,``元素位于`“http://www.customtypes.org”`命名空间中。 相反,如果没有指定`SoapTypeNameSpace`关键字,则消息可以如下所示: ```xml 3 ``` # 第八十三章 方法关键字 - SqlName 覆盖投影`SQL`存储过程的默认名称。 仅当此方法被投影为`SQL`存储过程时应用。 # 用法 要覆盖方法投射为`SQL`存储过程时使用的默认名称,请使用以下语法: ```java ClassMethod name(formal_spec) As returnclass [ SqlProc, SqlName = sqlname ] { //implementation } ``` 其中`sqlname`是`SQL`标识符。 # 详解 如果将此方法投影为`SQL`存储过程,则使用此名称作为存储过程的名称。 # 默认 如果忽略这个关键字, IRIS确定`SQL`名称如下: ```java CLASSNAME_METHODNAME ``` 默认使用大写字母。 但是,在调用存储过程时可以使用任何情况,因为SQL是不区分大小写的。 因此,在下面的示例中,默认的`SQL name`值是`TEST1_PROC1`。 这个默认值是在`SELECT`语句中指定的: ```java Class User.Test1 Extends %Persistent { ClassMethod Proc1(BO,SUM) As %INTEGER [ SqlProc ] { ///definition not shown } Query Q1(KD As %String,P1 As %String,P2 As %String) As %SqlQuery { SELECT SUM(SQLUser.TEST1_PROC1(1,2)) AS Sumd FROM SQLUser.Test1 } } ``` # 第八十四章 方法关键字 - SqlProc 指定是否可以作为`SQL`存储过程调用该方法。 只有类方法(而不是实例方法)可以作为SQL存储过程调用。 # 用法 要指定该方法可以作为`SQL`存储过程调用,请使用以下语法: ```java ClassMethod name(formal_spec) As returnclass [ SqlProc ] { //implementation } ``` 否则,忽略该关键字或将`Not`放在该关键字之前。 # 详解 该关键字指定可以作为`SQL`存储过程调用该方法。 只有类方法(而不是实例方法)可以作为`SQL`存储过程调用。 存储过程由子类继承。 # 默认 如果忽略此关键字,则该方法作为`SQL`存储过程不可用。
文章
姚 鑫 · 八月 15, 2022

第二章 使用管理门户(二)

# 第二章 使用管理门户(二) # 管理门户概述 本节介绍管理门户页面的一些常见布局元素。 注意:在管理门户中的任何位置,将光标移到菜单项上都会显示该项目的描述。 ## 管理门户主页 管理门户主页的标题是 `Welcome, `。在标题旁边,功能区包含以下选项: - 两个视图按钮,可让指定如何在菜单列中显示链接。 - 搜索栏,位于功能区的右侧。当指定一个词并按 Enter 键时,将显示包含该词的所有页面的列表;然后,可以单击要显示的目标页面,而无需浏览子菜单。 以下部分描述了主页的区域: ### 管理门户菜单栏 位于主页左边缘的菜单栏是导航门户的主要方法。 ### 管理门户欢迎窗格 欢迎窗格位于主页的中心,包括经常访问的页面的快捷方式。它包含以下字段: - 收藏夹`Favorites` — 列出选择为收藏夹的管理门户页面(请参阅操作窗格);可以单击每个页面标题直接转到该页面。 - 最近`Recent` — 列出自上次启动 IRIS 以来最近显示的页面。 - `Did you know?` — 显示提示。 - 链接 `Links` - 指向可能想要访问的页面的链接。 ### 管理门户消息窗格 位于主页右侧边缘的消息窗格显示一般系统信息并提供指向系统仪表板的链接。 如果实例是镜像成员,则消息窗格还显示它所属的镜像、其状态和成员类型以及指向镜像监视器的链接。 ## 管理门户标题 页眉位于管理门户中每个页面的顶部,可用于快速导航门户。 标题包含以下链接: - 主页`Home` — 显示管理门户主页。 - 关于`About` — 显示系统概览信息。 - 帮助`Help` — 显示正在查看的页面/主题的在线文档(帮助)。 - 联系方式`Contact` — 显示全球响应中心 (WRC) 的联系方式页面。 - 注销`Logout` — 注销您并带您进入管理门户的登录页面。 - 菜单`Menu` — 根据用户担任的角色显示常见任务列表。 标头还包含有用的信息,例如: - 服务器`Server` — 运行 IRIS 的服务器的名称。 - 命名空间`Namespace` — 当前使用的命名空间的名称。要在不同的命名空间中工作,请单击切换并选择所需的命名空间。 - 用户`User` — 登录到管理门户的用户的名称。要更改用户的密码,请单击名称。 - 许可证`Licensed to` — 出现在许可证密钥信息中的客户名称。 - 实例`Instance` — 服务器上运行的 `IRIS` 实例的名称。 此外,可以显示系统模式标签(例如,测试系统); 管理门户标题的左侧显示正在使用的产品的名称。 ## 管理门户功能区 功能区位于标题正下方,并显示特定于每个页面的不同内容。例如,数据库页面(`System Explorer` > `Databases`)的功能区如下图所示: 功能区的典型内容是: - 正在显示的管理门户页面的标题。 - 当前页面的面包屑,直接列在页面标题上方。路径中列出的每个页面都是一个活动链接,可以使用它返回到先前显示的子菜单/列表。当在页面上进行未保存的更改时,会在面包屑中附加一个星号,例如系统 > 配置 >内存和启动 —(配置设置)*。在离开未保存的更改之前,系统始终会提示进行确认。 注意:页签不会列出路径中的每个页面,并且页签中的页面并不总是与导航菜单中的页面匹配。始终可以通过单击主页返回到管理门户主页并使用搜索工具导航到特定页面,本节稍后将对此进行介绍。 - 允许在页面上执行操作的几个按钮。例如,`Databases` 页面包含按钮 `Integrity Check` 和 `Integrity Log`。 - 刷新按钮,包含有关页面上次更新时间的信息。 ## 系统概述信息 单击管理门户标题上的关于时,将显示一个表格,其中包含以下信息: - 版本 — 此 `IRIS` 实例的特定构建信息,包括平台、构建号和构建日期。 - 配置 - 此实例正在使用的配置 (`.cpf`) 文件的名称和位置。 - 数据库缓存 (`MB`) — 为数据库分配的空间例程缓存 (`MB`) — 为例程分配的空间。 - 日志文件 - 当前日志文件的名称和位置。 - `SuperServer` 端口 — 运行 `IRIS` 服务器的端口号。 - `Web`服务器端口 — 运行私有 `IRIS Web` 服务器的端口号。 - 许可证服务器地址/端口 — `IRIS` 许可证服务器的 `IP` 地址和运行它的端口号。 - 许可给 — 出现在许可密钥信息中的客户名称。 - 集群支持 - 指示此实例是否是集群的一部分。 - 镜像 — 指示此实例是否是镜像的成员。 - `Time System Started` — 上次启动 `InterSystems IRIS` 实例的日期和时间。 - 加密密钥标识符 — 如果激活加密,则为加密密钥的 `GUID`(全局唯一 `ID`)。 - `NLS` 区域设置 — 国家语言支持区域设置。 - 此会话的首选语言 - 管理门户已本地化并可显示的语言的下拉列表。可以通过从下拉列表中选择新的语言来更改显示语言。最初,浏览会话的首选语言是为浏览器指定的语言,如果不支持浏览器语言,则为英语;在特定浏览器中选择首选语言后,即使更改了浏览器语言,该浏览器中的管理门户也会使用该语言。
文章
姚 鑫 · 七月 21, 2021

关键字参数定义,映射定义,属性定义,查询定义

# 第五章 参数定义 描述参数定义的结构。 # 介绍 参数定义定义了一个给定类的所有对象都可用的常数值。创建类定义时(或在编译前的任何时候),可以设置其类参数的值。默认情况下,每个参数的值都是空字符串,但是可以在参数定义中指定一个非空值。在编译时,为类的所有实例建立参数值。除了极少数例外,该值不能在运行时更改。 # 详解 参数定义具有以下结构: ```java /// description Parameter name As parameter_type [ keyword_list ] = value ; ``` - `description`描述(可选)旨在显示在“类参考”中。默认情况下,描述为空白。 - `name`(必需)是参数的名称。这必须是有效的类成员名称,并且不能与任何其他类成员名称冲突。 - `parameter_type`(可选)指定参数的用户界面类型,由Studio用于在检查器内为参数提供输入验证。 **这不是类名;参见下一节。在大多数情况下,编译器会忽略这个关键字。** 如果省略参数类型,也要省略单词`As` - `value`(可选)指定参数的值。如果省略值,也要省略等号= - `keyword_list`(可选)是以逗号分隔的关键字列表,用于进一步定义参数。 如果省略此列表,也要省略方括号。 # 参数的允许类型 参数类型`parameter_type` 选项可以是下列值之一: - `BOOLEAN` — true(1)或false(0)值。 - `CLASSNAME` — 有效的类名。 - `COSCODE` — ObjectScript代码。 - `COSEXPRESSION` — 有效的ObjectScript表达式。 **如果参数是`COSEXPRESSION`类型,则在运行时计算该表达式。** 与形参`Type`关键字的大多数其他值不同,这个值影响编译器。 - `COSIDENTIFIER` — 有效的ObjectScript标识符。 - `INTEGER` — 整数值。 - `SQL` — SQL语句 - `SQLIDENTIFIER` — 有效的SQL标识符。 - `STRING` —字符串值。 - `TEXT` — 多行文本值。 - `CONFIGVALUE` -可以在类定义之外修改的参数。 与形参`Type`关键字的大多数其他值不同,这个值影响编译器。 如果参数的类型是`CONFIGVALUE`,那么可以通过`$SYSTEM.OBJ.UpdateConfigParam()`修改参数。 例如,下面的代码更改了参数`MYPARM`(在类`MyApp`中)的值。 `MyClass`的新值为`42`: ```java set sc=$system.OBJ.UpdateConfigParam("MyApp.MyClass","MYPARM",42) ``` **注意,`$SYSTEM.OBJ.UpdateConfigParam()`影响任何新进程所使用的生成的类描述符,但不影响类定义。 如果重新编译类,InterSystems IRIS将重新生成类描述符,该描述符现在将使用包含在类定义中的这个参数的值(从而覆盖通过`$SYSTEM.OBJ.UpdateConfigParam()`所做的更改)。** 也可以省略`parameter_type`,在这种情况下`Inspector`将允许参数的任何值。 ``` /// web服务的名称。 Parameter SERVICENAME = "SOAPDemo" ; ``` # 第六章 映射定义 描述投影定义的结构。 # 介绍 投影定义指示类编译器在编译或删除类定义时执行指定的操作。 投影定义投影类的名称(来自`%Projection.AbstractProjection`)实现方法称为类的编译完成后,当一个类定义中删除(因为它被删除或者因为类即将重新编译)。 # 详情 投影定义有以下结构: ```java /// description Projection name As projection_class (parameter_list) ; ``` - `description`(可选)用于在类引用中显示(但请注意投影目前没有显示在类引用中)。 说明默认为空。 - `Name`(必需)是投影的名称。 这必须是一个有效的类成员名,并且不能与任何其他类成员名冲突。 - `projection_class` (required)是投影类的名称,它是`%Projection.AbstractProjection`的子类。 - `parameter_list`(可选)是一个以逗号分隔的参数及其值列表。 如果指定,这些应该是`projection_class`使用的参数。 如果省略了这个列表,也省略了括号。 - `Keyword_list`(可选)是一个逗号分隔的关键字列表,这些关键字进一步定义了投影。 如果省略了这个列表,也可以省略方括号。 # 第七章 属性定义 描述属性定义的结构。注意,关系是一种属性。 # 介绍 属性包含与类实例相关的信息。可以向对象类添加属性定义。它们在其他类中没有意义。 # 详情 属性定义有以下结构: ```java /// description Property name As classname (parameter_list) [ keyword_list ] ; ``` 或者(对于列表属性): ```java /// description Property name As List Of classname (parameter_list) [ keyword_list ] ; ``` 或者(对于数组属性): ```java /// description Property name As Array Of classname (parameter_list) [ keyword_list ] ; ``` 或者(对于关系属性): ```java /// description Relationship name As classname [ keyword_list ] ; ``` - `description`(可选)用于在类引用中显示。说明默认为空。 - `name`(必需)是属性的名称。 这必须是一个有效的类成员名,并且不能与任何其他类成员名冲突。 - `classname`(可选)是该属性所基于的类的名称。 - `parameter_list`(可选)是参数及其值的逗号分隔列表。如果指定,这些应该是由类名使用的参数,或者是对所有属性都可用的参数。 如果省略此列表,也要省略括号。 - `keyword_list`(对于关系属性是必需的,但在其他方面是可选的)是一个逗号分隔的关键字列表,用于进一步定义属性。 如果省略此列表,也要省略方括号。 注意:分片类不支持属性关系。 ```java Property SSN As %String(PATTERN = "3N1""-""2N1""-""4N") [ Required ]; ``` # 第八章 查询定义 描述查询定义的结构。 # 介绍 类查询是作为类结构一部分的命名查询,可以通过动态SQL进行访问。 **可以在任何类中定义类查询;不需要将它们包含在持久类中。** # 详解 查询定义具有以下结构: ```java /// description Query name(formal_spec) As classname [ keyword_list ] { implementation } ``` - `description`描述(可选)旨在显示在“类别参考”中。默认情况下,描述为空白。 - `name`(必需)是查询的名称。这必须是有效的类成员名称,并且不能与任何其他类成员名称冲突。 - `formal_spec`(可选)指定传递给查询的参数列表。 具体来说,这是通过关联查询类的`Execute()`方法传递给查询的参数列表。 - `classname`(必需)指定此查询使用的查询类。 **对于基于SQL的查询,该值通常为`%SQLQuery`,对于自定义查询,该值通常为%Query。** **注意:分片类不支持自定义类查询。** - `keyword_list`(可选)是逗号分隔的关键字列表,用于进一步定义查询。 如果省略此列表,也要省略方括号。 - `implementation` 实现(可选)是定义查询的零行或多行代码。
文章
姚 鑫 · 六月 14, 2022

第九章 其他参考资料(二)

# 第九章 其他参考资料(二) # 特殊变量 (SQL) 系统提供的变量。 ```sql $HOROLOG $JOB $NAMESPACE $TLEVEL $USERNAME $ZHOROLOG $ZJOB $ZPI $ZTIMESTAMP $ZTIMEZONE $ZVERSION ``` SQL直接支持许多对象脚本特殊变量。这些变量包含系统提供的值。只要可以在SQL中指定文字值,就可以使用它们。 SQL特殊变量名不区分大小写。大多数可以使用缩写来指定。 Variable| Name| Abbreviation| Data Type Returned Use ---|---|---|--- $HOROLOG| $H| %String/VARCHAR |当前进程的本地日期和时间 $JOB| $J| %String/VARCHAR |当前进程的 job ID $NAMESPACE| none| %String/VARCHAR |当前命名空间名称 $TLEVEL| $TL| %Integer/INTEGER|| 当前事务嵌套级别 $USERNAME| none| %String/VARCHAR|当前进程的用户名 $ZHOROLOG| $ZH |%Numeric/NUMERIC(21,6)|自InterSystems IRIS启动后经过的秒数 $ZJOB| $ZJ| %Integer/INTEGER|当前进程的job状态 $ZPI| none| %Numeric/NUMERIC(21,18) |数值常量PI $ZTIMESTAMP |$ZTS| %String/VARCHAR |协调世界时间格式的当前日期和时间 $ZTIMEZONE| $ZTZ| %Integer/INTEGER| 当地时区与GMT的偏移量 $ZVERSION| $ZV| %String/VARCHAR| IRIS的当前版本 # 示例 ```sql SELECT TOP 5 Name,$H FROM Sample.Person ``` 以下示例仅在时区位于大陆内时才返回结果集: ```sql SELECT TOP 5 Name,Home_State FROM Sample.Person WHERE $ZTIMEZONE BETWEEN -480 AND 480 ``` # 字符串操作(SQL) 字符串操作函数和运算符。 SQL 支持多种类型的字符串操作: - 字符串可以通过长度、字符位置或子字符串值进行操作。 - 字符串可以通过指定的分隔符或分隔符字符串来操作。 - 字符串可以通过模式匹配和单词感知搜索来测试。 - 特殊编码的字符串(称为列表)包含嵌入的子字符串标识符,而不使用分隔符。各种 `$LIST` 函数对这些与标准字符串不兼容的编码字符串进行操作。唯一的例外是 `$LISTGET` 函数和 `$LIST` 的单参数和双参数形式,它们将编码字符串作为输入,但将单个元素值作为标准字符串输出。 SQL 支持字符串函数、字符串条件表达式和字符串运算符。 ObjectScript 字符串操作区分大小写。字符串中的字母可以转换为大写、小写或混合大小写。字符串排序规则可以区分大小写,也可以不区分大小写;默认情况下,SQL 字符串排序规则是不区分大小写的 `SQLUPPER`。 SQL 提供了许多字母大小写和排序规则函数和运算符。 当为数字参数指定字符串时,大多数 SQL 函数执行以下字符串到数字的转换: 非数字字符串转换为数字 0;将数字字符串转换为规范数字;并且混合数字字符串在第一个非数字字符处被截断,然后转换为规范数字。 # 字符串连接 以下函数将子字符串连接成字符串: - `CONCAT`:连接两个子字符串,返回一个字符串。 - `STRING`:连接两个或多个子字符串,返回单个字符串。 - `XMLAGG`:连接列的所有值,返回单个字符串。 - `LIST`:连接列的所有值,包括逗号分隔符,返回单个字符串。 - 连接运算符 (`||`) 也可用于连接两个字符串。 # 字符串长度 以下函数可用于确定字符串的长度: - `CHARACTER_LENGTH` 和 `CHAR_LENGTH`:返回字符串中的字符数,包括尾随空格。 `NULL` 返回 `NULL`。 - `LENGTH`:返回字符串中的字符数,不包括尾随空格。 `NULL` 返回 NULL。 - `$LENGTH`:返回字符串中的字符数,包括尾随空格。 `NULL` 返回为 0。 # Truncation and Trim 以下函数可用于截断或修剪字符串。截断限制字符串的长度,删除超出指定长度的所有字符。`Trim`从字符串中删除前导和/或尾随空格。 - `Truncation`: `CONVERT`, `%SQLSTRING`, and `%SQLUPPER`. - `Trimming`: `TRIM`, `LTRIM`, and `RTRIM`. # 子串搜索 以下函数在字符串中搜索子字符串并返回字符串位置: - `POSITION`:按子字符串值搜索,找到第一个匹配项,返回子字符串开始的位置。 - `CHARINDEX`:按子字符串值搜索,找到第一个匹配项,返回子字符串开始的位置。可以指定起点。 - `$FIND`:按子串值搜索,找到第一个匹配项,返回子串结束的位置。可以指定起点。 - `INSTR`:按子字符串值搜索,找到第一个匹配项,返回子字符串开始的位置。可以指定起点和子串出现。 以下函数在字符串中按位置或分隔符搜索子字符串并返回子字符串: - `$EXTRACT`:按字符串位置搜索,返回由开始位置或开始和结束位置指定的子字符串。从字符串的开头搜索。 - `SUBSTRING`:按字符串位置搜索,返回由开始位置或开始和长度指定的子字符串。从字符串的开头搜索。 - `SUBSTR`:按字符串位置搜索,返回由起始位置或起始和长度指定的子字符串。从字符串的开头或结尾搜索。 - `$PIECE`:按分隔符搜索,返回第一个分隔的子字符串。可以指定起点或默认为字符串的开头。 - `$LENGTH`:按分隔符搜索,返回分隔子串的数量。从字符串的开头搜索。 - `$LIST`:在特殊编码的列表字符串上按子字符串计数搜索。它通过子串计数定位子串并返回子串值。从字符串的开头搜索。 - 包含运算符 (`[`) 也可用于确定子字符串是否出现在字符串中。 - `%STARTSWITH` 比较运算符将指定的字符与字符串的开头进行匹配。 # 子串搜索和替换 以下函数在字符串中搜索子字符串并将其替换为另一个子字符串。 - `REPLACE`:按字符串值搜索,用新的子字符串替换子字符串。从字符串的开头搜索。 - `STUFF`:按字符串位置和长度搜索,用新的子字符串替换子字符串。从字符串的开头搜索。 # 字符类型和 Word-Aware 比较 `%PATTERN` 比较运算符将字符串与指定的字符类型模式匹配。
文章
姚 鑫 · 六月 24, 2021

第十七章 加密XML文档

# 第十七章 加密XML文档 本章介绍如何加密XML文档。 提示:发现在此命名空间中启用`SOAP`日志记录非常有用,这样就可以收到有关任何错误的更多信息。 # 关于加密的XML文档 加密的XML文档包括以下元素: - ``元素,其中包含由随机生成的对称密钥加密的加密数据。(使用对称密钥加密比使用公钥加密更有效。) - 至少有一个``元素。每个``元素携带用于加密数据的对称密钥的加密副本;它还包含一个带有公钥的`X.509`证书。拥有匹配私钥的接收方可以解密对称密钥,然后解密``元素。 - (可选)其他明文元素。 ```xml MIICnDCCAYQCAWUwDQYJKo... content omitted J2DjVgcB8vQx3UCy5uejMB ... content omitted LmoBK7+nDelTOsC3 ... content omitted ``` 要创建加密文档,请使用类`%XML.Security.EncryptedData`和`%XML.Security.EncryptedKey`。这些启用XML的类投影到适当名称空间中的有效``和``元素。 # 创建加密的XML文档 创建加密的XML文档的最简单方法如下: 1. 定义并使用可以直接投影到所需XML文档的通用容器类。 2. 创建包含要加密的XML的流。 3. 加密该流,并将其与相应的加密密钥一起写入容器类的相应属性。 4. 为容器类生成XML输出。 ## 加密的前提条件 在加密文档之前,必须创建包含要将加密文档发送到的实体的证书的 IRIS凭据集。在这种情况下,不需要(也不应该拥有)关联的私钥。 ## 容器类的要求 一个通用容器类必须包括以下内容: - 类型为`%XML.Security`的属性。 被投影为``元素的`EncryptedData`。 这个属性将携带加密的数据。 - 至少一个类型为`%XML.Security`的属性。被投影为``元素的`EncryptedKey`。 这些属性将携带相应的密钥信息。 示例如下: ```java Class XMLEncryption.Container Extends (%RegisteredObject, %XML.Adaptor) { Property Data As %XML.Security.EncryptedData(XMLNAME = "EncryptedData"); Property Key As %XML.Security.EncryptedKey(XMLNAME = "EncryptedKey"); Parameter NAMESPACE = "http://www.w3.org/2001/04/xmlenc#"; } ``` ## 生成加密的XML文档 要生成并编写加密文档,请执行以下操作: 1. 创建包含XML文档的流。 为此,通常使用`%XML.Writer`将启用XML的对象的输出写入流。 2. 创建`%SYS.X509Credentials`的至少一个实例,将访问要向其提供加密文档的实体的InterSystems IRIS凭据集。为此,请调用此类的`GetByAlias()`类方法。例如: ```java set credset=##class(%SYS.X509Credentials).GetByAlias("recipient") ``` 若要运行此方法,必须以该凭据集的`OwnerList`中包含的用户身份登录,否则`OwnerList`必须为空。 3. 至少创建`%XML.Security.EncryptedKey`实例。若要创建此类的实例,请使用此类的`CreateX509()`类方法。例如: ```java set enckey=##class(%XML.Security.EncryptedKey).Createx509(credset,encryptionOptions,referenceOption) ``` - `credset`是`%SYS`的实例。 `x509credentials`在刚刚创建的新窗口中打开。 - `encryptionOptions`是`$$$SOAPWSIncludeNone`(还有其他选项,但它们不适用于此场景)。 此宏在`%soap.inc`包含文件中定义。 - `referenceOption`指定了对加密元素的引用的性质。 这里使用的宏在`%soap.inc`包含文件中定义。 4. 在创建`%Library.ListOfObjects`实例,并使用其`Insert()`方法在刚创建插入`%XML.Security.EncryptedKey`实例。 5. 使用`%New()`方法创建`%XML.Security.EncryptedData`实例。例如: ```java set encdata=##class(%XML.Security.EncryptedData).%New() ``` 6. 使用`%XML.Security.EncryptedData的EncryptStream()`实例方法加密在步骤2中创建的流。例如: ```java set status=encdata.EncryptStream(stream,encryptedKeys) ``` - stream 流是在步骤1中创建的流。 - encryptedKeys是在步骤4中创建的密钥列表。 7. 创建并更新容器类的实例。 - 将键列表写入此类的相应属性。 - 将 `%XML.Security.EncryptedData`的实例写入此类的相应属性。 8. 使用`%XML.Writer`为容器类生成输出。 例如,前面显示的`CONTAINER`类还包括以下方法: ```java /// w ##class(XMLEncryption.Container).Demo("E:\temp\SecurityXml.txt") ClassMethod Demo(filename = "", obj = "") { #Include %soap if (obj = "") { s obj = ##class(MyApp.Person).%OpenId(1) } //从此启用XML的对象创建流 set writer = ##class(%XML.Writer).%New() set stream = ##class(%GlobalCharacterStream).%New() set status = writer.OutputToStream(stream) if $$$ISERR(status) {do $System.Status.DisplayError(status) quit } set status = writer.RootObject(obj) if $$$ISERR(status) {do $System.Status.DisplayError(status) quit } do stream.Rewind() set container = ..%New() ; 这就是我们要写出的对象 set cred = ##class(%SYS.X509Credentials).GetByAlias("servercred") set parts =$$$SOAPWSIncludeNone set ref = $$$KeyInfoX509Certificate set key = ##class(%XML.Security.EncryptedKey).CreateX509(cred, parts, ref) set container.Key = key ; 这个细节取决于类 //需要创建一个键列表(本例中仅为一个) set keys = ##class(%Collection.ListOfObj).%New() do keys.Insert(key) set encdata = ##class(%XML.Security.EncryptedData).%New() set status = encdata.EncryptStream(stream, keys) set container.Data = encdata ; 这个细节取决于类 // 为容器写输出 set writer = ##class(%XML.Writer).%New() set writer.Indent = 1 if (filename'="") { set status = writer.OutputToFile(filename) if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit} } set status = writer.RootObject(container) if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit} } ``` 此方法可以接受任何启用XML的类的`OREF`;如果没有提供,则使用默认值。 # 解密加密的XML文件 ## 解密的前提条件 在解密加密的`XML`文档之前,必须同时提供以下两项: - IRIS要使用的受信任证书。 - IRIS凭据集,其私钥与加密中使用的公钥匹配。 ## 解密文档 要解密加密的XML文档,请执行以下操作: 1. 创建`%XML.Reader`实例打开并使用它打开文档。 2. 获取`Document`属性,`%XML.Reader`实例。 其中包含作为DOM的XML文档。 3. 使用阅读器的`correlation()`方法将``元素或元素与类`%XML.Security.EncryptedKey`关联起来。 例如: ```java do reader.Correlate("EncryptedKey","%XML.Security.EncryptedKey") ``` 4. 遍历文档以读取``元素或多个元素。 为此,可以使用阅读器的`Next()`方法,该方法通过引用返回一个导入的对象(如果有的话)。 例如: ```java if 'reader.Next(.ikey,.status) { write !,"Unable to import key",! do $system.OBJ.DisplayError(status) quit } ``` 导入的对象`是%XML.Security.EncryptedKey`的实例。 5. 创建`%Library.ListOfObjects`的实例。 并使用它的`Insert()`方法插入`%XML.Security.EncryptedKey`的实例。 刚从文档中获得的。 6. 调用类`%XML.Security.EncryptedData`的`ValidateDocument()`方法 ```java set status=##class(%XML.Security.EncryptedData).ValidateDocument(.doc,keys) ``` 第一个参数(通过引用返回)是在第2步中检索到的DOM的修改版本。 第二个参数是上一步中的键列表。 7. 可以选择使用`%XML.Writer`为修改后的DOM生成输出。 例如,前面显示的`CONTAINER`类包含以下类方法: ```java ClassMethod DecryptDoc(filename As %String) { #Include %soap set reader = ##class(%XML.Reader).%New() set status = reader.OpenFile(filename) if $$$ISERR(status) {do $System.Status.DisplayError(status) quit } set doc = reader.Document //获取元素 do reader.Correlate("EncryptedKey","%XML.Security.EncryptedKey") if 'reader.Next(.ikey,.status) { write !,"无法导入密钥",! do $system.OBJ.DisplayError(status) quit } set keys = ##class(%Collection.ListOfObj).%New() do keys.Insert(ikey) // 以下步骤返回解密的文档 set status = ##class(%XML.Security.EncryptedData).ValidateDocument(.doc,keys) set writer = ##class(%XML.Writer).%New() set writer.Indent = 1 do writer.Document(doc) quit $$$OK } ```