搜索​​​​

清除过滤器
文章
姚 鑫 · 九月 11, 2022

第二十九章 管理许可(二)

# 第二十九章 管理许可(二) # 激活许可证密钥 `IRIS` 使用许可证密钥来确保其注册站点的正常运行、定义可用容量并控制对 `IRIS` 功能的访问。 许可证密钥以许可证密钥文件的形式提供,通常命名为 `iris.key`。 安装 `IRIS` 后,使用以下程序激活许可证密钥。始终可以使用相同的过程为任何已安装的实例激活新的许可证密钥(即升级密钥)。可以激活放置在管理门户可访问的任何位置的许可证密钥;作为激活的一部分,许可证密钥将作为 `iris.key` 复制到实例的 `install-dir/mgr` 目录(如果尚未命名)。 注意:也可以在 `Windows` 安装期间选择许可证密钥。执行此操作时,许可证会自动激活,并且许可证密钥会作为 `iris.key` 复制到实例的 `install-dir/mgr` 目录中;不需要此处描述的激活过程。 本节还讨论了许可证故障排除和在所有许可证单元都在使用时从操作系统命令行升级许可证。 要激活许可证密钥,请使用以下过程: 1. 导航到许可证密钥页面(系统管理 > 许可 > 许可证密钥)。将显示有关当前活动许可证密钥的信息。如果尚未激活任何许可证,则会显示这一点,例如通过标记客户名称:缺少许可证或不可读。此页面包含一个打印按钮,可让轻松打印显示的信息。 2. 单击激活许可证密钥并浏览到要激活的许可证密钥文件。当选择一个文件时,会显示有关它的信息,以便激活它之前验证是否拥有正确的许可证密钥;例如,它提供了所需的容量,并具有正确的到期日期。如果密钥无效,则会在错误消息中指出。如果许可证当前处于活动状态,则并排显示有关当前和选定许可证的信息。如果需要在激活后重新启动实例以使许可证密钥生效,则会记录这一点并提供原因。此对话框包括一个打印按钮,可让轻松打印有关当前活动许可证和选择的新许可证密钥的信息。 3. 单击激活以激活新的许可证密钥;它作为 `iris.key` 复制到实例的 `install-dir/mgr` 目录,覆盖之前的许可证密钥(如果有)。如果需要,确认对话框会提醒重新启动实例,并在新许可证启用的功能少于当前许可证时向发出警告。 通过使用 `Config.Startup`的 `LicenseID` 属性,可以将实例配置为从许可证服务器请求许可证密钥。在实例启动时,如果不存在 `iris.key` 文件并且已定义 `LicenseID`,则实例会从许可证服务器请求并激活许可证密钥。 **注意:相同的 `LicenseID` 必须在许可证密钥文件中,以及在需要下载许可证的实例上定义** 一般情况下无需重启实例,但升级许可证密钥时存在限制。如果将许可证类型从 `Power Unit` 更改为任何其他类型,则不会自动激活新密钥;这应该是一个罕见的事件。 另一个限制是许可证升级从通用内存堆 (`gmheap`) 空间中消耗的内存量。如果 `gmheap` 空间不可用,则无法扩展许可表条目的数量。如果没有足够的 `gmheap` 空间可用于许可证升级,则会将一条消息写入消息日志。可以从“高级内存设置”页面(系统管理 > 配置 > 高级内存设置)增加 `gmheap` 设置的大小。 如果新的许可证密钥比现有密钥消耗至少 `1000` 个 `64 KB` 页的 `gmheap` 空间,则必须重新启动 `IRIS` 实例才能完全激活新的许可证密钥。这种情况很少遇到,因为每个页面至少代表 `227` 个许可证。 ## 更新许可证密钥 要更新许可证密钥,请替换 `KeyDirectory` 中的密钥文件并运行 `ReloadKeys^%SYS.LICENSE`。每个实例上的许可证监视器 (`^LMFMON`) 每 30 分钟检查一次,以查看配置的 `LicenseID` 是否有不同的密钥,如果有,则尝试执行升级。 **注意:虽然大多数升级在实时实例上成功,但某些情况可能需要重新启动实例。在这种情况下,许可证监视器会记录一个错误,并且直到第二天才尝试再次升级密钥(以避免记录重复的错误)。实例重启会在启动时加载新密钥。** ## 许可证故障排除 如果在输入许可证并重新启动 `IRIS` 后只有一位用户可以登录,请使用管理门户进行调查。当选择按进程时,许可证使用页面(系统操作 > 许可证使用)显示正在运行的进程数。还可以使用门户从许可证密钥页面(系统管理 > 许可 > 许可证密钥)显示许可证信息,如激活许可证密钥中所述。如果密钥无效,则 `CustomerName` 字段包含说明。 还可以在消息日志和系统监控日志中查看许可证错误消息,可以在 `Portal` 的消息日志页面(系统操作 > 系统日志 > 消息日志)和系统监控日志页面(系统操作 > 系统日志)中查看> 系统监控日志),分别。 `System Monitor` 将许可证到期警告和警报写入这些日志,而 `Health Monitor` 则写入许可证获取警报和警告。当超过许可限制时,许可模块会将警报写入消息日志。在 `Application Monitor` 中,可以配置基于许可证指标的警报以发送电子邮件通知或呼叫通知方法。 `$System.License.Help` 显示可用于解决许可证问题的方法列表: ```java Do $System.License.Help() ``` ### Administrator Terminal Session 有几个问题会阻止获得终端会话。当 `IRIS` 无法正常启动并进入单用户模式时,或者只是在没有可用许可证时,可能会发生这种情况。在这些情况下,可能需要创建管理员终端会话,该会话使用特殊许可证来解决问题。 ### Administrator Session on Windows 使用命令提示符导航到 `install-dir\bin`。然后,以管理员身份执行以下命令: ```java irisdb -s\mgr -B ``` 这将从 `IRIS` 安装 `bin` 目录 (`install-dir\bin`) 运行 `IRIS` 可执行文件,指示 `install-dir\mgr` 的路径名(使用 `-s` 参数),并禁止所有登录,除了一个紧急登录(使用 `- B` 参数)。 例如,在默认目录中有一个名为 `MyIRIS` 的实例,该命令如下所示: ```java c:\InterSystems\MyIRIS\bin>irisdb -sc:\InterSystems\MyIRIS\mgr -B ``` ### Administrator Session on UNIX®, Linux, and macOS 使用命令提示符导航到 `install-dir/bin` 目录。然后,执行以下命令: ```java iris terminal -B ``` 例如,在默认目录中安装了一个名为 `MyIRIS` 的实例,该命令如下所示: ```java User:/InterSystems/MyIRIS/bin$ iris terminal MyIRIS -B ``` ## 从操作系统命令行升级许可证 `%SYSTEM.License.Upgrade()` 方法激活已复制到 `installdir\mgr` 目录的新许可证密钥。如果所有许可证单元都被用户使用,导致无法打开终端窗口,可以从命令行运行此方法以激活更大容量的新许可证密钥,如下所示: ```java iris terminal -U %SYS '##Class(%SYSTEM.License).Upgrade()' ```
文章
Jingwei Wang · 三月 28, 2023

在 IRIS 实例之间复制/同步系统配置和用户帐户,以简化用户和配置管理。

IRIS 配置和用户帐户包含需要跟踪的各种数据元素,许多人难以在 IRIS 实例之间复制或同步这些系统配置和用户帐户。那么如何简化这个过程呢? 在软件工程中,CI/CD 或 CICD 是持续集成 (CI) 和(更常见的)持续交付或(较少见的)持续部署 (CD) 的组合实践集。 CI/CD 能消除我们所有的挣扎吗? 我在一个开发和部署 IRIS 集群的团队工作。我们在 Red Hat OpenShift 容器平台上的容器中运行 IRIS。 如果您当前没有使用 Kubernetes,请不要停止阅读。即使您没有使用 Kubernetes 或在容器中运行 IRIS,您也可能会遇到与我和我的团队面临的挑战类似的挑战。 我们决定将代码与配置分开,并将它们放在不同的 GitHub 存储库中。每次在代码库中进行提交时,都会触发管道运行。结果,从代码库中的文件构建了一个新image。 我们通过将 YAML 文件和其他配置工件添加到部署 GitHub 存储库,将配置定义为以 GitOps 方式使用的代码。 GitOps 是一个软件开发框架,它使组织能够持续交付软件应用程序,同时使用 Git 作为单一事实来源有效地管理 IT 基础设施(以及更多)。 GitOps 的好处之一是能够轻松回滚。您所需要做的就是恢复到 Git 中的先前状态。 DevOps 是软件开发和 IT 行业的一种方法论。作为一套实践和工具使用,DevOps 将软件开发(Dev) 和IT 运营(Ops) 的工作集成并自动化,作为改进和缩短系统开发生命周期的一种手段。 [1] 我在维基百科上读到持续交付是“当团队在短周期内以高速和频率生产软件时,以便可以随时发布可靠的软件,并在决定部署时采用简单且可重复的部署过程。” 同时,维基百科将持续部署定义为“当新软件功能完全自动推出时”。 我们已决定将 YAML 文件存储在部署 GitHub 存储库中。 iris-cpf(上面的第 19 行)指的是一个 ConfigMap,其中包含用于 CPF Merge 的文件。 有多种可用的 CD 管道工具可以将配置作为代码推送部署,而不必手动应用文件。 例如,我的团队使用Argo CD 。它是一个 GitOps 工具,作为 Kubernetes 扩展部署在集群中。它很特别,因为它在集群中具有可见性。它的用户界面在浏览器中显示应用程序状态,因为 Argo CD 是 Kubernetes 扩展。 与仅启用基于推送的部署的外部 CD 工具不同,Argo CD 可以从 Git 存储库中拉取更新的(类似)代码并将其直接部署到 Kubernetes 资源。 像 Argo CD 这样的拉动部署工具将我们的 Kubernetes 集群的实际状态与我们的部署 repo 中描述的期望状态进行比较。 Argo CD 监视我们的部署 repo 和我们的 Kubernetes 集群。我们的部署repo 是唯一的真实来源。如果 GitHub 存储库中发生某些更改,Argo CD 将更新集群以匹配存储库中定义的所需状态。 Argo CD 代理同步 GitHub 存储库和 Kubernetes 集群。如果我们手动应用一个更改,当 Argo CD 将已部署的应用程序同步到 Git 中定义的所需状态时,它将被删除。 为了将不同的配置部署到不同的集群,我们使用Kustomize 。我们在部署仓库中定义了一个基本配置。我们还在部署 GitHub 存储库中定义了覆盖,以便为开发、SQA、阶段和生产等各种环境配置不同的系统默认设置和不同的images。 在第 417 行中,我们确定基于环境的系统默认设置存储在 SDS_ENV.xml 中。 那些没有使用 Kubernetes 的情况呢?我创建了许多可在 Open Exchange 中使用的应用程序。我学习了如何使用 Installer 类在 GitHub 存储库中定义 IRIS 配置,然后构建image并运行容器。 然而,如果在部署应用程序后需要对配置或用户帐户进行一些修改,情况会怎样呢?当涉及到持久卷时,事情就变得复杂了。发生这种情况是因为我们不想丢失存在于持久卷上的数据。 存储在持久卷上的所有这些东西是什么? IRIS 配置保存在数据目录的 mgr 目录中。 CPF Merge 功能应该使我们能够修改 iris.cpf 中的任何配置设置。 我的团队已将大量代码添加到 %ZSTART routines中,这些routines在 IRIS 计算或数据实例启动时执行。我们担心的一个问题是所谓的零大小 CPF 错误。我们经常遇到 IRIS 实例因大小为零的 CPF 文件而崩溃的情况。不幸的是,我们尚未发现该问题的根本原因。我们怀疑 CPF 合并操作以及在 %ZSTART routine中添加和删除的大量Routine、Global和包映射会导致零大小的 CPF 错误。 我们编写了代码来删除所有系统默认设置,并从作为卷安装在 IRIS 容器中的 Kubernetes ConfigMap 中导入它们。事实上,我们有两组系统默认设置:一组是在所有环境中导入的基本设置,另一组是因环境而异的环境特定设置。 我们决定从 XML 文件中导入用户。有时,当我们直接从 %ZSTART routine 执行这段代码时,我们会遇到问题。我们根据 InterSystems 的建议将此代码移至计划任务中。显然,我们发现了一个在某些情况下可能会破坏全局安全性的错误。无论如何,出于某种我现在不记得的原因,当用户帐户导入由计划从 %ZSTART routine 按需运行的任务执行时,此问题不再是问题。这一定是时间问题。计划任务晚于 %ZSTART routine运行。 我们创建了一个自定义密码验证routine来强制执行密码规则。 当我们需要一个新的 Web 应用程序时,我们应该怎么做?除了 CPF Merge,CSP Merge 怎么样? 我相信 Web 应用程序存储在 %SYS IRIS.dat 文件中。我考虑尝试在可以使用 ConfigMap 挂载的文件中定义 Web 应用程序。我们可以将代码添加到 %ZSTART 例程或添加另一个计划任务来查找文件并创建 IRIS 中尚不存在的任何 Web 应用程序。 使用 InterSystems Kubernetes Operator 部署的 Webgateway 容器具有一个持久数据卷,其中包含 CSP.conf 和 CSP.ini。但是,我们还没有实现在添加新的 Web 应用程序时根据需要自动更新这些文件的方法。 Lorenzo Scalese 创建了可在 Open Exchange 中使用的 config-api 和 config-copy 应用程序。他建议在您的应用程序安装程序模块中使用 IRS-Config-API 库。 IRIS-Config-API 可以在一个环境将 IRIS 配置导出到 JSON 文档,并在另一个环境中从 JSON 文档导入 IRIS 配置。 Lorenzo 创建了 iris-config-copy 工具,用于从一个 InterSystems IRIS 实例导出配置并将其导入另一个实例。如果我们在源实例和目标实例上都安装 iris-config-copy,则目标实例使用 REST 从源实例获取配置。 我们需要在源实例上创建一个 Web 应用程序,以使目标实例能够从源实例中检索 IRIS 配置。 Iris-config-copy 可以导出本地实例或远程实例的 IRIS 配置。 有一些方法可以导入特定的配置文件。我们可以导入Security、包含 SQL 连接的globals、CPF 配置数据或任务。
文章
Qiao Peng · 一月 14, 2021

使用类投影安装 Caché 应用程序

![](/sites/default/files/inline/images/i2.png) 您好! 本文介绍另一种为基于 InterSystems Caché 的解决方案创建安装程序的简单方法。 主题将涵盖只需一项操作即可安装或从 Caché 中完全删除的应用程序。 如果您仍在编写需要执行多个步骤才能安装应用程序的安装说明,是时候将这个过程自动化了。 问题的提出 假设我们为 Caché 开发了一个小型实用程序,之后我们想要将其分发。 当然,最好不要让不必要的配置和安装细节打扰到安装它的用户。 此外,这些说明必须非常全面,而且要面向可能对 Caché 一无所知的用户。如果是 Web 实用程序,安装程序不仅会要求用户将其类导入 Caché,而且至少还要配置 Web 应用程序才能对其进行访问,这是相当大的工作量: ![](/sites/default/files/inline/images/p1.png) ![](/sites/default/files/inline/images/2016-05-05_221105.png) ![](/sites/default/files/inline/images/2016-05-05_221716.png) 当然,所有这些操作都可以通过编程方式执行。 您只需要了解如何实现。 但即使是这样,我们也需要让用户执行操作,例如在终端中执行一个命令。 通过单次导入操作进行安装 Caché 允许我们在类导入期间执行安装。 这意味着用户只需要使用任一方便的方法导入包含类包的 XML 文件: 将 XML 文件拖放到 Studio 区域。 通过管理门户:系统资源管理器 -> 类 -> 导入。 通过终端:do $system.OBJ.Load("C:\FileToImport.xml","ck")。 我们为安装应用程序而预先准备的代码将在类导入和编译后立即执行。 如果用户要卸载我们的应用程序(删除软件包),我们还可以清理应用程序在安装过程中创建的所有内容。 创建投影 为了扩展 Caché 编译器的行为,或者,在我们的示例中,为了在类的编译或反编译期间执行代码,我们需要在软件包中创建一个投影类。 它是一个扩展了 %Projection.AbstractProjection 的类,并重载了它的两个方法:CreateProjection(在编译过程中执行)和 RemoveProjection(在重新编译或删除类时触发)。 通常,将这个类命名为 Installer 是个好方法。 我们来看一个名为“MyPackage”的软件包的简单安装程序示例: Class MyPackage.Installer Extends %Projection.AbstractProjection [ CompileAfter = (Class1, Class2) ] {Projection Reference As Installer;/// This method is invoked when a class is compiled.ClassMethod CreateProjection(cls As %String, ByRef params) As %Status{    write !, "Installing..."}/// This method is invoked when a class is 'uncompiled'.ClassMethod RemoveProjection(cls As %String, ByRef params, recompile As %Boolean) As %Status{    write !, "Uninstalling..."}} 这里的行为可以描述为: 第一次导入和编译软件包时,只触发 CreateProjection 方法。 以后再编译 MyApp.Installer 时,或者导入“新”的安装程序类覆盖“旧”类时,将触发旧类的 RemoveProjection 方法,且 %recompile 参数等于 1,之后调用新类的 CreateProjection 方法。 删除软件包(同时删除 MyApp.Installer)时,将只调用 RemoveProjection 方法,参数 recompile = 0。 还需要注意以下几点: 类关键字 CompileAfter 应该包括应用程序的类名列表,在执行投影类的方法之前,需要对它们进行编译。 始终建议在此列表中填入应用程序中的所有类,因为如果安装过程中出错,我们不需要执行投影类的代码; 两个方法都接受 cls 参数 - 它是顶级类名,在我们的示例中为 MyApp.Installer。 这个理念来自于创建投影类的本义 - 通过从派生自 %Projection.AbstractProjection 的类再派生,可以单独为我们的应用程序的任何类制作“安装程序”。 只有在这种情况下才会体现出意义,但对于我们的任务来说是多余的; CreateProjection 和 RemoveProjection 方法都接受第二个参数 params - 它是一个关联数组,以“参数名称”-“值”对的形式处理有关当前编译设置和当前类的参数值的信息。 通过执行 zwrite params 可以非常容易地探索该参数的内容; RemoveProjection 方法接受 recompile 参数,只有删除类时,该参数才等于 0,重新编译时不等于 0。 类 %Projection.AbstractProjection 还有其他方法,我们可以重新定义这些方法,但我们的任务并不需要这样做。 一个示例 让我们更深入地了解为我们的实用程序创建 Web 应用程序的任务,并创建一个简单示例。 假设我们的实用程序是一个 REST 应用程序,在浏览器中打开时,它只发送一个响应“I am installed!”。 要创建这样的应用程序,我们需要创建一个描述它的类: Class MyPackage.REST Extends %CSP.REST{XData UrlMap{    }ClassMethod Index() As %Status{    write "I am installed!"    return $$$OK}} 创建并编译该类后,我们需要将其注册为 Web 应用程序入口点。 我在本文的顶部图示了如何进行配置。 在执行所有这些步骤后,最好通过访问 http://localhost:57772/myWebApp/ 来检查我们的应用程序是否正常工作(注意以下几点:1. 末尾的斜线是必需的;2. 端口 57772 在您的系统中可能有所不同。 它将匹配您的管理门户端口)。 当然,所有这些步骤都可以通过 Web 应用程序创建方法 CreateProjection 以及删除方法 RemoveProjection 中的一些代码来自动执行。 在这种情况下,我们的投影类如下所示: Class MyPackage.Installer Extends %Projection.AbstractProjection [ CompileAfter = MyPackage.REST ]{Projection Reference As Installer;Parameter WebAppName As %String = "/myWebApp";Parameter DispatchClass As %String = "MyPackage.REST";ClassMethod CreateProjection(cls As %String, ByRef params) As %Status{    set currentNamespace = $Namespace    write !, "Changing namespace to %SYS..."    znspace "%SYS" // we need to change the namespace to %SYS, as Security.Applications class exists only there    write !, "Configuring WEB application..."    set cspProperties("AutheEnabled") = $$$AutheUnauthenticated // public application    set cspProperties("NameSpace") = currentNamespace // web-application for the namespace we import classes to    set cspProperties("Description") = "A test WEB application." // web-application description    set cspProperties("IsNameSpaceDefault") = $$$NO // this application is not the default application for the namespace    set cspProperties("DispatchClass") = ..#DispatchClass // the class we created before that handles the requests    return ##class(Security.Applications).Create(..#WebAppName, .cspProperties)}ClassMethod RemoveProjection(cls As %String, ByRef params, recompile As %Boolean) As %Status{    write !, "Changing namespace to %SYS..."    znspace "%SYS"    write !, "Deleting WEB application..."    return ##class(Security.Applications).Delete(..#WebAppName)}} 在此示例中,每次编译 MyPackage.Installer 类都将创建一个 Web 应用程序,每次“反编译”都将其删除。 最好在创建或删除应用程序之前检查该应用程序是否存在(例如,使用 ##class(Security.Applications).Exists(“Name”) ),但是为了使本示例简单起见,这个作业就留给阅读本文的读者来完成了。 在创建 MyPackage.REST 和 MyPackage.Installer 类之后,我们可以将这些类导出为一个 XML 文件,并将该文件分享给所有有需要的人。 导入此 XML 的用户将自动设置 Web 应用程序,然后可以开始在浏览器中使用。 结果 与 InterSystems 社区上介绍的使用 %Installer 类部署应用程序的方法不同,这种方法有以下优点: 使用“纯”Caché ObjectScript。 至于 %Installer,需要用特定标签来填充 xData 块,大量文档对此进行了介绍。 安装我们的应用程序的方法是在类编译后立即执行的,我们不需要手动执行; 如果类(包)被删除,将自动执行删除我们的应用程序的方法,这不能通过使用 %Installer 来实现。 我的项目中已经使用这种应用程序安装方法 - Caché WEB Terminal、Caché Class Explorer 和 Caché Visual Editor。 您可以在此处找到 Installer 类的示例。 顺便提一下,开发者社区还有一个帖子介绍了投影的功能,作者是 John Murray。 另外值得一提的是 Package Manager 项目,该项目旨在让 InterSystems 数据平台的第三方应用程序只需通过一个命令或一次点击即可安装,就像类似 npm 的包管理器一样。
文章
Jingwei Wang · 八月 30, 2023

使用 SQLAlchemy 将表传输到 IRIS 或从 IRIS 获取表

案例描述 假设您是一名 Python 开发人员或拥有一支训练有素的 Python 专业团队,但您分析 IRIS 中某些数据的期限很紧迫。当然,InterSystems 提供了许多用于各种分析和处理的工具。然而,在给定的场景中,最好使用旧的 Pandas 来完成工作,然后将 IRIS 留到下次使用。对于上述情况和许多其他情况,您可能需要从 IRIS 获取表来管理 InterSystems 产品之外的数据。但是,当您有任何格式(即 CSV、TXT 或 Pickle)的外部表时,您可能还需要以相反的方式执行操作,您需要在其上导入并使用 IRIS 工具。无论您是否必须处理上述问题,Innovatium让我明白,了解更多解决编码问题的方法总是能派上用场。好消息是,从 IRIS 引入表时,您不需要经历创建新表、传输所有行以及调整每种类型的繁琐过程。本文将向您展示如何通过几行代码快速将 IRIS 表转换为 Pandas 数据框架并向后转换。您可以在我的GitHub上查看代码,您可以在其中找到包含本教程每个步骤的 Jupiter Notebook。 从 IRIS 引入一张Table 当然,您应该首先导入该项目所需的库。 import pandas as pd import sqlalchemy as db 下一步将是在 Python 文件和 IRIS 实例之间创建连接。为此,我们将使用 SQLAlchemy 的函数 create_engine(),并以字符串作为参数。该字符串应包含有关操作方法、用户名和密码、实例的主机和端口以及目标命名空间的信息。有关使用 sqlalchemy-iris 的基本概念的更多信息,请查看我之前的一篇文章SQLAlchemy - 在 IRIS 数据库中使用 Python 和 SQL 的最简单方法。 engine = db.create_engine( "iris://_system:SYS@localhost:1972/SAMPLE" ) connection = engine.connect() 然后,我们可以声明将保存数据帧的变量,并在此连接上调用 Pandas 的 read_sql_table() 函数,将表名指定为带有Schema的字符串。您还可以在另一个参数中声明Schema,事实上,这是更好的选择,因为名称字符串上有一个点在某些情况下可能会导致错误。 df = pd.read_sql_table( "NameAge" , connection, schema= "PD" ) 最好仔细检查我们正在使用的表是否存在于我们想要使用的Schema中,当然,首先还要检查是否存在我们需要的Schema。在本文的最后一部分中,您将了解如何执行此操作以及更多提示。从现在开始,如果您有办法使用 Pandas,您可以执行任何您想要的更改和分析,因为您知道该怎么做。探索以下示例以了解其工作原理。 向 IRIS 发送表 在开始之前,让我们更改数据框中的某些内容作为示例。我们可以调整列的值以满足我们的需求(例如,添加行和列等)。经过一番尝试后,我将名称改为小写,并根据现有数据添加了一个新人和一列。您可以查看下图来查看结果。 现在我们可以用一行代码将其发送回 IRIS。我们需要的只是指定引擎和表名。 df.to_sql( "NameAge" , con=engine, schema= "PD" , if_exists= "replace" ) 再次,我们需要将Schema与表名分开放在参数中,以避免一些错误和不良行为。除此之外,if_exists 参数指定如果给定Schema中已经存在同名表时要执行的操作。可能的值为:replace、fail(默认)和append。当然,replace 选项会删除表并使用 SQL 命令创建一个新表,而append 会将数据添加到现有表中。请记住,此方法不会检查重复值,因此使用此属性时要小心。最后,失败值会引发以下错误: 请记住,如果您指定的表名不存在,该函数将创建它。 现在,您可以查询 IRIS 以查看新增内容,或者转至管理门户查看专用于 SQL 的部分。请记住,如果您使用替换值,您应该考虑该类的源代码,因为该方法完全重写了它。这意味着如果您实现了任何方法,则应该将它们保留在超类中。 有关 sqlalchemy-iris 的更多提示 如果您有任何问题无法通过其他社区或论坛中共享的与您的应用程序代码相关的信息来解决,您可能会在本节中找到所需的帮助。在这里您将找到有关如何查找有关engine和dialect的详细信息的提示列表。 方言特有的特征 SQL Alchemy 使用根据您的engine自动选择的dialect。当您使用函数 create_engine() 连接到 IRIS 数据库时,选择的dialect是Dmitry Maslennikov 的 sqlalchemy-iris 。您可以使用engine的dialect属性访问和编辑其功能。 engine = db.create_engine( "iris://_system:SYS@localhost:1972/SAMPLE" ) engine.dialect 通过 VSCode 的 IntelliCode 扩展,您可以从此属性中搜索每个选项,或者在CaretDev 的 GitHub上检查源代码。 检查engine中的可用Schema 该dialect中值得强调的一个特殊函数是 get_schema_names() 函数。注意!如果您想避免代码和迭代中出现错误,以下信息可能对您至关重要。 connection = engine.connect() engine.dialect.get_schema_names(connection) 检查Schema中的可用表 我们来看看类似的情况。您可能还需要了解Schema中的可用表。在这种情况下,您可以使用检查。在引擎上运行函数inspect()并将其保存在变量中。您将使用相同的变量来访问另一个函数 get_table_names()。它将返回一个列表,其中包含指定Schema中的表名称或默认的“SQLUser”。 inspection = db.inspect(engine) inspection.get_table_names(schema= "Sample" ) 此外,如果您想在数据上使用更多 SQL Alchemy 功能,您可以声明一个基础并使其元数据反映引擎中的Schema。 b = db.orm.declarative_base() b.metadata.reflect(engine, schema= "Sample" ) 如果您需要更多信息来解决此问题,请查看SQL Alchemy 文档和sqlalchemy-iris GitHub 存储库。或者,您也可以给我留言或发表评论,我们将一起尝试揭开这个秘密。 最后的考虑因素 本文中的实现方法强调使用 IRIS 实例作为云提供商,并使得可以在不同的基础上进行分析。它可以轻松地同时监控所有这些设备的任何质量问题并比较它们的性能和使用情况。如果您将这些知识与另一篇关于用 Django 制作的门户的文章中描述的开发结合起来,您可以根据需要快速构建一个强大的管理器,用于任意数量的特性和实例。此实现也是将数据从 IRIS 外部移动到构建良好的类的有效方法。由于您可能熟悉 Pandas 中用于处理多种不同语言的其他一些函数,即 CSV、JSON、HTML、Excel 和 Pickle,因此您可以轻松地将 read_sql_table 更改为 read_csv、read_json 或任何其他选项。是的,我应该警告您,某些类型与 InterSystems 的集成不是内置功能,因此可能不是很容易。然而,SQL Alchemy 和 Pandas 的结合在从 IRIS 导出数据时总是会派上用场。因此,在本文中,我们了解到 IRIS 拥有您所需的所有工具,可帮助您进行开发并轻松与系统的现有设备或您的专业知识小工具集成。
文章
Lilian Huang · 8 hr 前

使用 FHIRInsight 洞察验血结果的含义:将 FHIR 转化为清晰的见解

![image](/sites/default/files/inline/images/a-flat-vector-illustration-depicting-a-c_satayx4ztio8srvdwrm2uq_hyccprmjrveozm4na0128q.jpeg) 您知道当您拿到验血结果时一切看起来都像天书的那种感觉吗? 这就是 **FHIRInsight** 要解决的问题。 它最初的理念是,医疗数据不应该令人恐惧或困惑 – 它应该是我们所有人都能使用的东西。 验血是健康检查中十分常见的检查,但说实话,大多数人都很难理解它们,有时甚至对不擅长实验室工作的医务人员来说也是如此。 FHIRInsight 希望整个过程能够变得更简单,信息更富有实用价值。 ![FHIRInsight logo](/sites/default/files/inline/images/fhirinsight.png) ## 🤖我们为什么要构建 FHIRInsight 这一切都始于一个简单而有力的问题: > “为什么验血结果仍然很难读懂 — 有时甚至对医生来说也是如此?” > 如果您看过化验结果,您可能会看到一大堆数字、隐晦的缩写和“参考范围”,这些可能适用于您的年龄、性别或身体状况,也可能不适用。 毫无疑问,它是一种诊断工具,但如果没有背景信息,它就变成了一个猜谜游戏。 即使是经验丰富的医疗保健专业人员有时也需要交叉参考指导方针、研究论文或专家意见才能理解所有内容。 这正是 **FHIRInsight** 的用武之地。 我们不只是为患者而构建,也为一线医护人员而构建。 为轮流值班的医生,为捕捉生命体征细微变化的护士,为每一位试图在有限的时间和巨大的责任下做出正确决定的医护人员而构建。 我们的目标是让他们的工作简单一点,将密集的临床 FHIR 数据转化为**清晰、有用、以真正的医学科学为基础**的东西, 讲人类语言的东西。 FHIRInsight 不仅仅是解释化验结果。 它还: - **提供化验结果是轻度、中度还是重度的情境建议** - 根据临床症状**提出潜在病因和鉴别诊断** - **提出下一步行动建议** — 是后续检查、转诊还是紧急护理 - **利用 RAG(检索增强生成)**拉取**相关科学文章**,为分析提供支持 想象一下,一位年轻的医生正在查看患者的贫血检查结果。 他们不需要在 Google 上搜索每一个异常值或翻阅医学期刊,而是收到一份报告,上面不仅总结了问题,还引用了最近的研究或世界卫生组织的指导方针来支持这一推理。 这就是将 **AI** 与**针对精选研究的矢量搜索**相结合的力量。 那患者呢? 他们再也不用盯着满屏的数字,猜想“胆红素 2.3 mg/dL”是什么意思,或者他们是否应该担心了。 他们会得到简单、周全的解释。 感觉更像是一场对话,而不是一份临床报告。 一些他们能真正理解的东西 — 并与他们的医生进行讨论,让人感觉更有准备,不那么焦虑。 因为这就是 **FHIRInsight** 的真正意义**:将复杂的医疗数据转化为清晰的见解**,**帮助医疗保健专业人员和患者共同制定更好、更自信的决策**。 ## 🔍 表象之下 当然,所有这些表面上的简单,背后可能由一些默默运行的强大技术提供支持。 以下是 FHIRInsight 的构建基础: - **FHIR (Fast Healthcare Interoperability Resources)** — 这是健康数据的全球标准。 它是我们接收化验结果、患者病史、受众特征和诊疗等结构化信息的方式。 FHIR 是医疗系统使用的语言,我们将这种语言翻译成人们可以真正使用的东西。 - **RAG(检索增强生成)的矢量搜索**:FHIRInsight 通过在**使用 InterSystems IRIS 原生矢量搜索的矢量数据库**中建立**科学 PDF 论文和可信 URL** 索引,增强其诊断推理能力。 当化验结果看起来模棱两可或差别细微时,系统会检索相关内容来支持其建议,它不是从记忆库中进行检索,而是从**真实的、最新的研究**中进行检索。 - **医学推理提示工程**:我们对提示进行了微调,以指导 LLM 识别各种血液相关疾病。 无论是缺铁性贫血、凝血功能障碍、激素失衡还是自身免疫触发因素,提示都会引导 LLM 了解症状、检验室模式和可能病因的变化。 - **LiteLLM 集成**:自定义适配器通过统一的接口将请求路由到多个 LLM 提供程序(OpenAI、Anthropic、Ollama 等),从而轻松实现回退、流式传输和模型切换。 无论您是查看 30 个患者图表的医生,还是想要理解数字含义的患者,都可以在几秒钟内将原始的化验数据转化为**可解释、富有实用价值的医学见解**。 ## 🧩 创建 LiteLLM 适配器:使用一个接口管理所有模型 在后台,FHIRInsight 的 AI 赋能报告由 **LiteLLM** 驱动,后者是一个出色的抽象层,可以使我们通过一个 OpenAI 风格的界面调用 **100 多个 LLM**(OpenAI、Claude、Gemini、Ollama 等)。 但是要想将 LiteLLM 集成到 **InterSystems IRIS** 中,需要比隐藏在业务操作中的 Python 脚本更持久、更能重复使用的东西。 所以,我们创建了自己的 **LiteLLM 适配器**。 ### 认识 `LiteLLMAdapter` 此适配器类可以处理您希望从一个强大的 LLM 集成中获得的所有东西: - 接受 `prompt`、`model` 和 `temperature` 等参数 - 动态加载环境变量(例如,API 密钥) 为了将其嵌入我们的互操作性生产中,我们将其包装在一个专门的**业务操作**中: - 通过标准的 `LLMModel` 设置处理生产配置 - 与 FHIRAnalyzer 组件集成,以实时生成报告 - 作为未来任何需要访问 LLM 的组件的中心“AI 桥” 以下是简化的核心流程: ``` set response = ##class(dc.LLM.LiteLLMAdapter).CallLLM("Tell me about hemoglobin.", "openai/gpt-4o", 0.7) write response ``` ## 🧭 结语 当我们开始构建 FHIRInsight 时,我们的使命很简单**:让验血结果对每个人来说都更容易理解**。 不仅仅是患者,还有医生、护士、护理人员… 任何曾经盯着化验结果苦思冥想的人,“*好吧,这到底是什么意思*?” 我们都有过这样的经历。 通过融合 FHIR 的结构、InterSystems IRIS 的速度、LLM 的智能,以及通过矢量搜索实现真实医学研究的深度,我们创造了一个可以将令人困惑的数字转化成有意义的叙述的工具。 帮助人们对自己的健康做出更明智的决定,甚至可能及早发现一些被忽视的疾病。 但 FHIRInsight 不仅仅与数据相关。 它还与**我们查看数据时的感受**相关。 我们希望它给人一种清晰、支持和赋能的感觉。 我们希望这种体验… 有点像**“氛围编程”医疗保健** — 在智能的代码、优秀的设计和人类同理心方面达到最佳平衡点。 我们希望您能尝试它,打破它,质疑它,并帮助我们改进它。 告诉我们您接下来想看到什么。 更多条件? 更具可解释性? 更加个性化? 这只是一个开端 — 我们希望您能帮助塑造它的未来。
文章
Qiao Peng · 三月 17, 2024

生成式大语言模型和检索增强生成

近来生成式大语言模型掀起了革命性的AI浪潮。生成式大语言模型是什么原理?我们怎么在业务中利用它? 一. 大语言模型的工作原理 生成式大语言模型是生成式人工智能底层的机器学习模型,是一种用于自然语言处理的深度学习模型。 人工智能、机器学习与大语言模型的关系如下图: 1.1 为什么我们称之为大语言模型? 大语言模型的“大”体现在多个方面: 首先,模型尺寸巨大,尤其是它的参数数量。例如GPT3有1750亿的参数; 其次,大语言模型是在巨大的算力基础上,基于海量语料进行训练的。例如Meta的Llama 2 的训练数据达到了两万亿个词(token); 再次,大语言模型是为解决通用问题,而非特定问题构建的。 1.2 大语言模型是怎么训练的? 大语言模型是事先训练好的模型。 训练时,大语言模型基于各种语料 - 人类知识库(例如Wikipedia)、公共数据集、网络爬虫数据,让模型进行“填空”练习,并经过人工编辑和“校对” 训练出来的,需要成千上万的GPU建立集群进行训练。根据Meta的信息,其Llama 2 的训练数据达到了两万亿个token,上下文长度为4096,对话上也是使用100万人类标记的数据微调。 运行时,训练产生的大语言模型可以在小的多的硬件上运行。 1.3 大语言模型的机器学习算法 冰冻三尺,非一日之寒;滴水穿石,非一日之功。生成式大语言模型能够落地经历了相当漫长的技术积累与进步。 大语言模型使用的机器学习算法是优化过的神经网络(Neural Network)。 神经网络发明于上世纪40-50年代,本质上是一个曲线拟合算法,通过拟合多个、多层的Softplus(曲线)、ReLU(Rectified Linear Unit 折线)、Sigmoid(对数线),实现对任意曲线的拟合。 “神经网络”名字听起来很高大上,但并不是脑科学的产物。因为发明时,觉得算法中每个节点像神经元、每个连线像神经触突,因此称为神经网络。 它很早就应用于自动控制领域。后来发展出多种神经网络算法,例如用于图像识别的卷积神经网络(CNN)、很早就用于语言学习的递归神经网络(RNN)… 在大语言模型成熟前,自然语言处理进化出过众多的技术,例如词袋、词汇矢量化、基于递归神经网络的模型、超长短期记忆网络(LSTM)… 但都在能力和算力上有众多缺陷,无法用于有实用价值的内容生成领域。 虽然它们不能实现实用化的内容生成,但为内容生成式大语言模型落地打下了基础,也是我们了解大语言模型前必须了解的预备知识。 1.3.1 分词(Tokenization) 词汇是语言模型分析的最小语义单位,所以第一步要把语句拆分成词汇(token)。分词并不简单,例如中文语句的分词就无法通过空格区分。所以用于大语言模型的分词算法也是基于海量语料训练出来的。 而基于大语言模型的内容生成,就是基于当前的所有token,预测下一个token,从而产生完整的内容。 1.3.2 词汇和语句的矢量化 机器学习算法基本只能处理数字,无法处理文本、声音、图像等非数字内容。所以要处理语言,需要对语句进行矢量化的表达,将其转换为数字。 拿我们常玩的一个游戏做解释:一个人在头脑里想象一个事物,让另一个人猜。另一个人可以问任何问题,但第一个人只能回答是和否。例如问:是动物吗?答:是;问:是哺乳动物吗?答:不是。问:有脚吗?答:是。 这个游戏的过程就是用不同维度来验证和归类一个事物,最终可以让这个事物在不同的维度上得以表达,即这个事物在一个高维度矢量空间上可以得到一个定位(矢量),同时相近的概念在矢量空间互相接近。 大语言模型通过大规模语料训练用神经网络将每个词汇在一个高维度空间矢量化,得到表达矢量的数组,将词汇矢量化到如下示意的矢量空间中: 这里的矢量化出来的是密集矢量,即每个维度上都不是0,且维度数固定,从而用更少的字节中存在更多的信息,因此在计算上的利用成本更低。相较于稀疏矢量的例子,例如书籍的归类:科学、言情、教育、音乐… ,词汇和语句的矢量结果密集度高的多,因此是密集矢量。 而语句矢量化在词汇矢量化的基础上,要将词汇在句子中的顺序信息加入,从而将“小明追老虎”和“老虎追小明”这两个词汇完全相同但语义完全不同的句子在矢量化输出上能够加以区分。 1.3.3 基于大语言的矢量化模型 将词汇和语句矢量化,是迈向我们如今看到的生成式大语言模型的第二步。 不同的语言矢量化模型生成的密集矢量维度数是不一样的,越高的维度数的密集矢量需要越大的计算资源和越大的内存消耗。下面是一些常见的矢量化语言模型和它们的维度数: 模型 维度数量 BERT (Bidirectional Encoder Representations from Transformers) 768或1024 GPT (Generative Pre-trained Transformer) 768或1600 Word2Vec 300 USE (Universal Sentence Encoder) 512 MiniLM 384 1.3.4 矢量相似度查询 词汇和句子矢量化后,怎么找到相似的词汇和句子? 对两个矢量进行相似度查询,就是计算两个矢量间的“距离”。有很多算法,如下图中所示的这些常见算法。 在大规模、高维度矢量数据库中查找近义词,如果采用与矢量记录逐一计算相似度的方法,将需要巨大的计算量,其效率并不能满足实用的性能需求。 而实际需求并不需要精确的相似度,因此出现了近似近邻算法(Approximate Nearest Neighbors - ANN)解决效率问题。ANN有多种算法,例如Annoy (Approximate Nearest Neighbors Oh Yeah)、 HNSW (Hierarchical Navigable Small World)。 下图是Annoy算法的示意图: 在矢量数据集中随机找2个矢量,计算出一个矢量平面到2个矢量的距离相同,从而将矢量数据集分割成2个空间;然后再在每个空间里重复上面的过程,直到分割后的空间里矢量数量与目标相似度矢量数量一致(例如我们希望得到返回矢量数量为10个以内的相似度结果集,那么如果空间内的矢量数小于等于10,就停止上述过程);从而我们得到一个决策树,今后可以用这个决策树进行矢量相似度查询,显然会快很多。 因为Annoy是基于最初的随机选择的2个矢量开始决策树构建的,如果这2个矢量本身就是高度相似的,那这2个矢量永远不会被一个矢量相似度查询要求同时命中,从而带来显著的误差。怎么办?可以随机多选几组初始矢量,从而形成多棵决策树的决策森林,提高ANN的精度。 可见ANN是大规模矢量检索查询的核心。 1.3.5 生成式大语言模型 递归神经网络(recurrent neural network - RNN)很早就应用到自然语言处理领域,之后出现了RNN改进模型LSTM (Long short-term memory),它们按顺序处理输入语句的词汇,并行能力不足,而且越高阶的神经网络需要的算力越高,达不到实用化的性能需求。 在2017年Google一个小团队(Transformer八子)发表了一篇论文 - Attention Is All You Need, 阐述了一类特殊的神经网络 – 基于注意力(Attention)机制的Transformer。它的注意力机制根据输入数据的长度执行固定步骤的计算,并且对输入数据的词汇(token)是并行计算的,它奠定了实用的生成式大语言模型的基础。这个团队的成员后大多离开了Google,并创立或加入了目前市场上几个主要生成式大语言模型。 在Transformer并行处理能力和越来越强大的GPU并行算力加持下,生成式大语言模型终是水到渠成,可以说是大力出奇迹! 当然Transformer模型具备多个特殊能力支撑内容生成能力。下面这张图解释了Transformer模型的4个核心特性:词汇矢量化(Word Embedding)、词汇在语句中位置的矢量化叠加(Positional Encoding)、自我注意力(Self-Attention)和残值连接(Residual Connections)。可见它其实构建在前面出现的技术基础之上。 借助这个新神经网络模型思路,众多大厂发布了自己的生成式大语言模型,如下面列出的这些著名的大语言模型。它们的宣传中常常强调其百亿级、甚至千亿级的参数: 模型 厂商 参数 GPT OpenAI (Microsoft) 1750亿 Bard/Gemini Google 18亿,32.5亿 PaLM2 Google 3400亿 Llama 2 Meta 70亿,130亿,700亿 Claude 2/3 Anthropic (Amazon) 未披露 Stable Beluga Stability AI 70亿,130亿,700亿 Coral Cohere 未披露 Falcon Technology Innovation Institute 13亿,75亿,400亿,1800亿 MPT Mosaic 70亿,300亿 往往参数规模越大,其生成的内容越精确和越富有创造力。那么这些参数指什么?无论是什么样的大语言模型,它们底层都是神经网络,这些参数主要就是指神经网络中的权重和偏差。 二. 大语言模型应用中的问题和检索增强生成 从机制上,生成式大语言模型并不神秘。虽然它展现出了强大的理解能力甚至“创造力”,但它有以下几个问题: 它的知识来自于训练语料,并不知道所有知识。例如GPT-4 截止训练数据的时间是2022年1月份,对于后来的世界一无所知,更不可能知道您的机构中的未开放数据。 它是基于通用数据训练的,对于特定领域往往训练不足。 它的内容生成机制是使用神经网络逐词预测出回答中的下一个词从而构成完整的语句。因此它本质上不会拒绝回答任何问题,虽然人类限制它回答诸如如何制作病毒类的问题。结合它的“无知”和“创造力”,对不知道的问题,它也能一本正经地胡说八道,这就是生成式大语言模型的“幻觉”。 生成式大语言模型的“幻觉”在目前的应用中非常常见。例如我问了Bing Copilot一个关于“什么是InterSystems IRIS互联互通套件?”的问题,它不懂但没有拒绝回答,而且回答地相当“幻觉”: 如果想在我们自己的业务中直接应用生成式大语言模型,让它提供患者教育,或者回复患者的预约查询、亦或回答患者关于他/她自己的用药注意事项?显然不靠谱。 是不是可以用我们自己的数据进行训练?一来很多大模型都不是开放的,无法自己训练;二来相信大家都没有训练大语言模型的昂贵算力。 怎么解决这个问题? 大语言模型其实有三次“训练”机会: 预先训练就是大语言模型厂商通过海量语料进行的训练,我们干不了; 调优训练需要基于开放的大语言模型,算力成本也不低; 所以我们可以通过“提示”,让生成式大语言模型给我们想要的答案。 我又试了一次让Bing Copilot回答“什么是InterSystems IRIS互联互通套件?”,不过这次,我给了它提示,让它先读读关于InterSystems IRIS互联互通套件介绍的网页。这次它回答得相当到位: 也就是通过合适的提问,把本地数据提示给生成式大语言模型,从而让它可以准确回答而不会产生幻觉。 检索增强生成基于问题先在本地数据检索,将相关结果提示给生成式大语言模型,从而获得靠谱的回答,这就是检索增强生成(Retrieval Augmented Generation – RAG)。 这里的本地数据检索,是基于大语言的矢量相似度检索。所以,需要借助矢量数据库,对本地的数据矢量化保存、并提供基于问题的矢量相似度查询,从而基于问题给出最匹配的本地数据。 这里是完整的检索增强生成流程示意图,分为2个过程: 1. 基于本地数据建立矢量知识库的过程 预先建立知识库, 将本地文档切分成文本段 使用矢量化语言模型对数据矢量化 将矢量保存到矢量知识库 2. 借助本地矢量知识库和外部大语言模型回答问题的过程 使用矢量化模型将问题矢量化 在矢量数据库中检索与问题相关的矢量记录 将匹配的数据(知识)作为上下文组织到完整的问题与提示中,向大语言模型提问。例如提示模版是:请仅使用以下上下文回答问题 从大语言模型得到回答 由此可见,检索增强生成至少需要以下3个技术组件: 矢量数据库 – 用于本地数据的矢量化保存和矢量化查询 矢量化语言模型 – 用于将本地数据和问题矢量化 内容生成语言模型 – 用于基于问题和上下文生成自然语言回答 矢量化语言模型、内容生成语言模型都有很多选择,根据需要可以选择能部署到本地的模型、也可以选择厂商提供的云服务。 而矢量数据库是保存本地知识数据的矢量化版本的,市面上常见的是一些nonSQL的专用数据库,也就是说需要将本地数据迁移到矢量数据库,并专门学习其数据操作的API。 可以预见,生成式大语言模型的能力将迅速进化,但本地的知识和数据并不会以如此快的速度发生变化。因此RAG将本地的知识和数据通过矢量化与生成式大语言模型集成,借助其不断提升的强大能力又无需被任何一个模型绑架,将是一个合理的解决方案。 三. InterSystems IRIS的内容生成架构 InterSystems IRIS是应用在众多行业的通用数据平台,并在2024版本中加入了对矢量存储和查询的支持,无需将IRIS中已经保存的本地知识数据迁移到别的矢量数据库中,从而消除数据迁移时间差、额外部署矢量数据库的运维成本,同时降低敏感数据泄露风险、确保遵循特定行业中对数据迁移监管的要求。而InterSystems IRIS作为一个具有互操作能力的数据平台,可以轻松集成大语言模型,并建立和管理检索增强生成的pipeline,降低RAG的技术实现复杂度。 3.1 IRIS的矢量存储和矢量查询 IRIS提供矢量数据类型,它被完全集成在IRIS多模型的架构中,尤其使用SQL就可以完整使用矢量存储和查询。 例如要创建含有矢量类型字段vec的表: CREATE TABLE t (txt VARCHAR(1000), vec VECTOR(INT, 200)); 向矢量字段vec中插入数据: INSERT INTO t VALUES (‘…’, TO_VECTOR(‘1,2,3,…’, INT)); 这里的矢量数据是需要通过调用矢量化模型产生的。 基于矢量相似度查询最接近的10条记录: SELECT TOP 10 * FROM FROM ( SELECT t.*, VECTOR_DOT_PRODUCT(vec, TO_VECTOR(…)) AS similarity FROM t ) ORDER BY similarity DESC; 3.2 IRIS的矢量索引 IRIS进一步提供了更易使用的矢量索引:无需创建矢量字段,直接在现有数据表上就可以创建声明式的矢量索引,并自动调用集成的矢量化模型,从而使用SQL就可以免代码方式进行开发。 创建矢量索引 – 通过索引对title、author和article这3个字段组合进行矢量化: CREATE INDEX Vec ON MyNews(Title, Author, Article) AS VectorIndex(MODEL=‘BERT’); 执行矢量查询 – 查询与条件最近似的3条记录: SELECT TOP 3 * FROM MyNews WHERE Category = ‘NYT’ ORDER BY MyTable_VecSim(%ID, ‘climate change’); 3.3 基于IRIS构建完整的RAG方案 基于最新发布的InterSystems IRIS 2024.1,和部署到本地的矢量化模型(all-MiniLM-L12-v2)、内容生成模型(llama2),我构建了一个RAG原型: 这里IRIS实例即是保存本地数据的数据平台,也是本地数据的矢量化数据库,从而避免了数据的跨平台迁移。而全SQL的数据操作能力,让构建在自己数据上的检索增强生成方案能快速落地。 现在就把生成式大语言模型集成到您自己的业务中吧!注:本文中的部分图片来自StatQuest、medium、wikipedia和weaviate。
文章
姚 鑫 · 四月 14, 2021

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

# 第二章 定义和构建索引(一) # 定义索引 ### 使用带有索引的Unique、PrimaryKey和IdKey关键字 与典型的SQL一样,InterSystems IRIS支持惟一键和主键的概念。 InterSystems IRIS还能够定义`IdKey`,它是类实例(表中的行)的唯一记录ID。 这些特性是通过`Unique`、`PrimaryKey`和`IdKey`关键字实现的: - `Unique` -在索引的属性列表中列出的属性上定义一个唯一的约束。 也就是说,只有这个属性(字段)的唯一数据值可以被索引。 唯一性是根据属性的排序来确定的。 例如,**如果属性排序是精确的,则字母大小写不同的值是唯一的; 如果属性排序是`SQLUPPER`,则字母大小写不同的值不是唯一的。** 但是,请注意,对于未定义的属性,不会检查索引的惟一性。 根据SQL标准,未定义的属性总是被视为唯一的。 - `PrimaryKey` -在索引的属性列表中列出的属性上定义一个主键约束。 - `IdKey` -定义一个唯一的约束,并指定哪些属性用于定义实例(行)的唯一标识。 `IdKey`总是具有精确的排序规则,即使是数据类型为`string`时也是如此。 这些关键字的语法出现在下面的例子中: ```java Class MyApp.SampleTable Extends %Persistent [DdlAllowed] { Property Prop1 As %String; Property Prop2 As %String; Property Prop3 As %String; Index Prop1IDX on Prop1 [ Unique ]; Index Prop2IDX on Prop2 [ PrimaryKey ]; Index Prop3IDX on Prop3 [ IdKey ]; } ``` **注意:`IdKey`、`PrimaryKey`和`Unique`关键字只在标准索引下有效。 不能将它们与位图或位片索引一起使用。** 同时指定`IdKey`和`PrimaryKey`关键字也是有效的语法,例如: ``` Index IDPKIDX on Prop4 [ IdKey, PrimaryKey ]; ``` 这个语法指定`IDPKIDX`索引既是类(表)的`IdKey`,也是它的主键。 这些关键字的所有其他组合都是多余的。 对于使用这些关键字之一定义的任何索引,都有一个方法允许打开类的实例,其中与索引关联的属性有特定的值; ### 定义SQL搜索索引 可以在表类定义中定义SQL搜索索引,如下所示: ```java Class Sample.TextBooks Extends %Persistent [DdlAllowed] { Property BookName As %String; Property SampleText As %String(MAXLEN=5000); Index NameIDX On BookName [ IdKey ]; Index SQLSrchIDXB On (SampleText) As %iFind.Index.Basic; Index SQLSrchIDXS On (SampleText) As %iFind.Index.Semantic; Index SQLSrchIDXA On (SampleText) As %iFind.Index.Analytic; } ``` ### 用索引存储数据 **可以使用index Data关键字指定一个或多个数据值的副本存储在一个索引中:** ```java Class Sample.Person Extends %Persistent [DdlAllowed] { Property Name As %String; Property SSN As %String(MAXLEN=20); Index NameIDX On Name [Data = Name]; } ``` 在本例中,索引`NameIDX`的下标是各种`Name`值的排序(大写)值。名称的实际值的副本存储在索引中。当通过SQL更改`Sample.Person`表或通过对象更改对应的`Sample.Person`类或其实例时,将维护这些副本。 在经常执行选择性(从许多行中选择一些行)或有序搜索(从许多列中返回一些列)的情况下,在索引中维护数据副本会很有帮助。 例如,考虑以下针对`Sample.Person`表的查询: SQL引擎可以通过读取`NameIDX`而从不读取表的主数据来决定完全满足此请求。 ![image](/sites/default/files/inline/images/1_34.png) **注意:不能使用位图索引存储数据值。** ### 索引null 如果一个索引字段的数据为`NULL`(没有数据存在),相应的索引使用索引`NULL`标记来表示这个值。 **默认情况下,索引空标记值为`-1E14`。** 使用索引空标记可以使空值排序在所有非空值之前。 `%Library.BigInt`数据类型存储小于`-1E14`的小负数。默认情况下,`%BigInt`索引空标记值为`-1E14`,因此与现有`BigInt`索引兼容。如果索引的`%BigInt`数据值可能包括这些极小的负数,则可以使用`INDEXNULLMARKER`属性参数更改特定字段的索引`NULL`标记值,作为特性定义的一部分,如下例所示: ```java Property ExtremeNums As %Library.BigInt(INDEXNULLMARKER = "-1E19"); ``` 还可以在数据类型类定义中更改索引`NULL`标记的默认值。 **此参数属性在IRIS里有,Cache里没有。** ### 索引集合 为属性编制索引时,放在索引中的值是整个已整理属性值。对于集合,可以通过将(`Elements`)或(`Key`)附加到属性名称来定义与集合的元素和键值相对应的索引属性。(元素)和(键)允许指定从单个属性值生成多个值,并对每个子值进行索引。当属性是集合时,`Elements`令牌通过值引用集合的元素,`Key`令牌通过位置引用它们。当元素和键都出现在单个索引定义中时,索引键值包括键和关联的元素值。 例如,假设有一个基于`Sample.Person`类的`FavoriteColors`属性的索引。对此属性集合中的项进行索引的最简单形式是以下任一种: ```java INDEX fcIDX1 ON (FavoriteColors(ELEMENTS)); ``` 或 ```java INDEX fcIDX2 ON (FavoriteColors(KEYS)); ``` 其中,`FavoriteColor(Elements)`是指`FavoriteColors`属性的元素,因为它是一个集合。一般形式是`PropertyName`(元素)或`PropertyName`(键),其中该集合的内容是定义为某个数据类型的列表或数组的属性中包含的一组元素)。 若要索引文本属性,可以创建一个由`PropertyNameBuildValueArray()`方法生成的索引值数组(在下一节中介绍)。与集合本身一样,(Elements)和(Key)语法对索引值数组有效。 如果属性集合被投影为数组,则索引必须遵守以下限制才能被投影到集合表。索引必须包括(键)。索引不能引用集合本身和对象ID值以外的任何属性。如果投影索引还定义了要存储在索引中的数据,则存储的数据属性也必须限制为集合和ID。否则,不会投影索引。此限制适用于投影为数组的集合属性上的索引;不适用于投影为列表的集合上的索引。 与集合的元素或键值对应的索引还可以具有所有标准索引功能,例如将数据与索引一起存储、特定于索引的排序规则等。 InterSystems SQL可以通过指定`FOR SOME%ELEMENT`谓词来使用集合索引。 ### 使用(Elements)和(Key)索引数据类型属性 为了索引数据类型属性,还可以使用`BuildValueArray()`方法创建索引值数组。此方法将属性值解析为键和元素的数组;它通过生成从与其关联的属性的值派生的元素值集合来实现这一点。使用`BuildValueArray()`创建索引值数组时,其结构适合索引。 `BuildValueArray()`方法的名称为`PropertyNameBuildValueArray()`,其签名为: ```java ClassMethod propertynameBuildValueArray(value, ByRef valueArray As %Library.String) As %Status ``` - `BuildValueArray()`方法的名称以组合方法的典型方式派生于属性名。 - 第一个参数是属性值。 - 第二个参数是通过引用传递的数组。 这是一个包含键-元素对的数组,键下标的数组等于元素。 - 该方法返回一`%Status` 值。 这个例子: ```java /// DescriptiveWords是一个以逗号分隔的单词字符串 Property DescriptiveWords As %String; /// 基于描述词的索引 Index dwIDX On DescriptiveWords(ELEMENTS); /// 方法的作用是:演示如何在属性的子值上建立索引。 /// /// (如果DescriptiveWords被定义为一个集合,则不需要此方法。) ClassMethod DescriptiveWordsBuildValueArray( Words As %Library.String = "", ByRef wordArray As %Library.String) As %Status { If Words '= "" { For tPointer = 1:1:$Length(Words,",") { Set tWord = $Piece(Words,",",tPointer) If tWord '= "" { Set wordArray(tPointer) = tWord } } } Else { Set wordArray("TODO") = "Enter keywords for this person" } Quit $$$OK } ``` 在本例中,`dwIDX`索引基于`DescriptiveWords`属性。 `DescriptiveWordsBuildValueArray()`方法接受由`Words`参数指定的值,基于该值创建一个索引值数组,并将其存储在`wordArray`中。 InterSystems IRIS在内部使用`BuildValueArray()`实现; 不调用此方法。 注意:没有必要将任何元素/键值建立在属性值的基础上。 唯一的建议是,每次向该方法传递给定值时,都创建相同的元素和键数组。 为各种实例的描述性词所属性设置值和检查这些值的属性涉及活动(如以下): ```java SAMPLES>SET empsalesoref = ##class(MyApp.Salesperson).%OpenId(3) SAMPLES>SET empsalesoref.DescriptiveWords = "Creative" SAMPLES>WRITE empsalesoref.%Save() 1 SAMPLES>SET empsalesoref = ##class(MyApp.Salesperson).%OpenId(4) SAMPLES>SET empsalesoref.DescriptiveWords = "Logical,Tall" SAMPLES>WRITE empsalesoref.%Save() 1 ``` 这 `sample index`内容,例如: DescriptiveWords(ELEMENTS) | ID | Data ---|---|--- " CREATIVE" |3 |"" " ENTER KEYWORDS FOR THIS PERSON"| 1 |"" " ENTER KEYWORDS FOR THIS PERSON"| 2| "" " LOGICAL" |4| "" " TALL"| 4| "" 注意:此表显示抽象中的索引内容。磁盘上的实际存储形式可能会有所变化。 ### 将数组(元素)上的索引投影到子表 要在嵌入式对象中索引属性,需要在引用该嵌入式对象的持久化类中创建索引。 属性名必须指定表(`%Persistent`类)中的引用字段的名称和嵌入对象(`%SerialObject`)中的属性的名称,如下面的示例所示: ```java Class Sample.Person Extends (%Persistent) [ DdlAllowed ] { Property Name As %String(MAXLEN=50); Property Home As Sample.Address; Index StateInx On Home.State; } ``` 此处`Home`是`Sample.Person`中引用嵌入对象`Sample.Address`的属性,该对象包含`State`属性,如下例所示: ```java Class Sample.Address Extends (%SerialObject) { Property Street As %String; Property City As %String; Property State As %String; Property PostalCode As %String; } ``` **只有与持久类属性引用相关联的嵌入对象的实例中的数据值被索引。不能直接索引`%SerialObject`属性**。`%Library.SerialObject`(以及`%SerialObject`的所有未显式定义`SqlCategory`的子类)的`SqlCategory`为字符串。 还可以使用`SQL CREATE INDEX`语句在嵌入式对象属性上定义索引,如下例所示: ```sql CREATE INDEX StateIdx ON TABLE Sample.Person (Home_State) ``` ### 类中定义的索引注释 当在类定义中使用索引时,需要记住以下几点: - **索引定义仅从主(第一个)超类继承。** - **如果使用Studio添加(或删除)数据库中存储数据的类的索引定义,则必须使用“构建索引”中描述的过程之一来手动填充索引。** ## 使用DDL定义索引 如果你使用DDL语句来定义表,也可以使用以下DDL命令来创建和删除索引: - `CREATE INDEX` - `DROP INDEX` `DDL index`命令执行以下操作: 1. 它们更新在其上添加或删除索引的相应类和表定义。 重新编译修改后的类定义。 2. 它们根据需要在数据库中添加或删除索引数据:`CREATE index`命令使用当前存储在数据库中的数据填充索引。 类似地,`DROP INDEX`命令从数据库中删除索引数据(即实际索引)。
文章
姚 鑫 · 三月 30, 2021

第十四章 使用SQL Shell界面(一)

# 第十四章 使用SQL Shell界面(一) # 执行SQL的其他方式 可以使用`$SYSTEM.SQL.Execute()` 方法从Terminal命令行执行一行SQL代码,而无需调用SQL Shell。以下示例显示如何在终端提示下使用此方法: ```sql DHC-APP>SET result=$SYSTEM.SQL.Execute("SELECT TOP 5 name,dob,ssn FROM Sample.Person") DHC-APP>DO result.%Display() Name DOB SSN yaoxin 54536 111-11-1117 xiaoli 111-11-1111 姚鑫 63189 111-11-1112 姚鑫 63189 111-11-1113 姚鑫 50066 111-11-1114 5 Rows(s) Affected ``` 如果SQL语句包含错误,则`Execute()`方法成功完成;否则,该方法无效。 `%Display()`方法返回错误信息,例如: ```java USER>DO result.%Display() [SQLCODE: :] [%msg: < Field 'GAME' not found in the applicable tables^ SELECT TOP ? game ,>] 0 Rows Affected USER> ``` `Execute()`方法还提供了可选的`SelectMode`、`Dialect`和`ObjectSelectMode`参数。 InterSystems IRIS支持许多其他编写和执行SQL代码的方法 这些包括: - 嵌入式SQL:嵌入ObjectScript代码中的SQL代码。 - 动态SQL:使用`%SQL`。 从ObjectScript代码中执行SQL语句的语句类方法。 - 管理门户SQL接口:使用Execute Query接口从InterSystems IRIS管理门户执行动态SQL。 # 调用SQL Shell 可以使用`$SYSTEM.SQL.Shell()`方法在终端提示符中调用SQL Shell,如下所示: ```java DO $SYSTEM.SQL.Shell() ``` 或者,可以使用%SQL作为一个实例调用SQL Shell。 Shell类,如下所示: ```java DO ##class(%SQL.Shell).%Go("IRIS") ``` 或 ```java SET sqlsh=##class(%SQL.Shell).%New() DO sqlsh.%Go("IRIS") ``` 无论如何调用,SQL Shell都会返回SQL Shell提示符,如下所示: ```java [SQL]termprompt>> ``` 其中[SQL]是指在SQL Shell中,termprompt是配置的终端提示符,`>>`是指SQL命令行。 默认情况下,SQL Shell提示符如下所示:`[SQL]nsp>>`,其中`“nsp”`是当前命名空间的名称。 ```java DHC-APP>DO $SYSTEM.SQL.Shell() SQL Command Line Shell ---------------------------------------------------- The command prefix is currently set to: . Enter q to quit, ? for help. DHC-APP>> > 1>> 1>>SELECT TOP 5 name,dob,ssn FROM Sample.Person 2>> 2>>q DHC-APP>>q ``` 在这个提示符下,可以使用以下任一Shell模式: - 单行模式:在提示符下键入一行SQL代码。 结束SQL语句,按`“Enter”`。 默认情况下,这将准备并执行SQL代码(这称为立即执行模式)。 对于查询,结果集显示在终端屏幕上。 对于其他SQL语句,将在终端屏幕上显示SQLCODE和行数值。 - 多行模式:在提示符下按Enter。这使进入多行模式。可以键入多行SQL代码,每个新行提示均指示行号。 (空行不会增加行号。)要结束多行SQL语句,请键入GO并按Enter。默认情况下,这既准备并执行SQL代码。对于查询,结果集显示在终端屏幕上。对于其他SQL语句,SQLCODE和行计数值显示在终端屏幕上。 多行模式提供以下命令,可以在多行提示符下键入以下命令,然后按Enter:`L`或`LIST`列出到目前为止输入的所有SQL代码。 `C`或`CLEAR`删除到目前为止输入的所有SQL代码。 `C n`或`CLEAR n`(其中`n`是行号整数)以删除特定的SQL代码行。 `G`或`GO`准备和执行SQL代码,然后返回单行模式。 `Q`或`QUIT`删除到目前为止输入的所有SQL代码并返回单行模式。这些命令不区分大小写。发出命令不会增加下一个多行提示的行号。打`?`在多行提示符处列出了这些多行命令。 为了准备一条SQL语句,SQL Shell首先验证该语句,包括确认指定的表存在于当前名称空间中,并且指定的字段存在于表中。如果不是,它将显示适当的SQLCODE。 如果该语句有效,并且具有适当的特权,则SQL Shell会回显SQL语句,并为其分配一个序号。无论您是否更改名称空间和/或退出并重新进入SQL Shell,这些数字在终端会话期间都将按顺序分配。这些分配的语句编号允许重新调用以前的SQL语句,如下所述。 也可以使用`DO Shell^%apiSQL`.在终端提示下调用SQL Shell。 要列出所有可用的SQL Shell命令,请输入`?`。在SQL提示下。 要终止SQL Shell会话并返回到`Terminal`提示符,请在SQL提示符下输入`Q`或`QUIT`命令或`E`命令。 SQL Shell命令不区分大小写。 以下是使用默认参数设置的示例SQL Shell会话: ```java DHC-APP>DO $SYSTEM.SQL.Shell() SQL Command Line Shell ---------------------------------------------------- The command prefix is currently set to: . Enter q to quit, ? for help. DHC-APP>>SELECT TOP 5 Name,Home_State FROM Sample.Person ORDER BY Home_State 1. SELECT TOP 5 Name,Home_State FROM Sample.Person ORDER BY Home_State Name Home_State xiaoli 姚鑫 姚鑫 姚鑫 姚鑫 5 Rows(s) Affected statement prepare time(s)/globals/lines/disk: 0.0523s/45502/270281/2ms execute time(s)/globals/lines/disk: 0.0004s/225/2915/0ms --------------------------------------------------------------------------- DHC-APP>>q ``` 以下是使用默认参数设置的多行SQL Shell会话: ```java DHC-APP>DO $SYSTEM.SQL.Shell() SQL Command Line Shell ---------------------------------------------------- The command prefix is currently set to: . Enter q to quit, ? for help. DHC-APP>> > 1>>SELECT TOP 5 2>>Name,Home_State 3>>FROM Sample.Person 4>>ORDER BY Home_State 5>>GO 2. SELECT TOP 5 Name,Home_State FROM Sample.Person ORDER BY Home_State Name Home_State xiaoli 姚鑫 姚鑫 姚鑫 姚鑫 5 Rows(s) Affected statement prepare time(s)/globals/lines/disk: 0.0002s/18/1168/0ms execute time(s)/globals/lines/disk: 0.0003s/225/2886/0ms --------------------------------------------------------------------------- ``` ## GO命令 SQL Shell `GO`命令执行最新的SQL语句。在单行模式下,`GO`重新执行最近执行的SQL语句。在多行模式下,`GO`命令用于执行多行SQL语句并退出多行模式。单行模式下的后续`GO`将重新执行先前的多行SQL语句。 ## 输入参数 SQL Shell支持使用`“?”`输入参数的使用SQL语句中的字符。每次执行SQL语句时,系统都会提示指定这些输入参数的值。必须以与`“?”`相同的顺序指定这些值字符出现在SQL语句中:第一个提示为第一个`“?”`提供一个值,第二个提示为第二个`“?”`提供一个值,依此类推。 输入参数的数量没有限制。可以使用输入参数将值提供给`TOP`子句,`WHERE`子句,并将表达式提供给`SELECT`列表。不能使用输入参数将列名提供给`SELECT`列表。 可以将主机变量指定为输入参数值。在输入参数提示下,指定一个以冒号(:)开头的值。该值可以是公共变量,ObjectScript特殊变量,数字文字或表达式。然后,SQL Shell会提示“这是文字(Y / N)吗?”。在此提示下指定N(否)(或仅按Enter)意味着将输入值解析为主机变量。例如,`:myval`将被解析为局部变量myval的值; `:^ myval`将被解析为全局变量`^myval`的值; `:$HOROLOG`将被解析为`$HOROLOG`特殊变量的值; `:3`将被解析为数字3; `:10-3`将被解析为数字`7`。在此提示符下指定Y(是)表示将输入值(包括冒号)作为文字提供给输入参数。 ## 执行ObjectScript命令 在SQL Shell中,可能希望发出一个ObjectScript命令。例如,通过使用`SET $NAMESPACE`命令将InterSystems IRIS命名空间更改为包含要引用的SQL表或存储过程的命名空间。可以使用SQL Shell!命令或OBJ命令以发出由一个或多个ObjectScript命令组成的ObjectScript命令行。 (OBJ是OBJECTSCRIPT的缩写。)!,OBJ和OBJECTSCRIPT命令是同义词。以下示例显示了这些命令的用法: ```java %SYS>DO $SYSTEM.SQL.Shell() SQL Command Line Shell ---------------------------------------------- The command prefix is currently set to: . Enter q to quit, ? for help. [SQL]%SYS>>! SET oldns=$NAMESPACE SET $NAMESPACE="USER" WRITE "changed the namespace" changed the namespace [SQL]USER>>OBJ SET $NAMESPACE=oldns WRITE "reverted to old namespace" reverted to old namespace [SQL]%SYS>> ``` OBJ命令之后的其余命令行被视为ObjectScript代码。 !之间不需要空格。和ObjectScript命令行。可以在SQL Shell单行模式或SQL Shell多行模式下指定OBJ命令。以下示例在USER名称空间中定义的表上执行SELECT查询: ```java %SYS>DO $SYSTEM.SQL.Shell() SQL Command Line Shell ---------------------------------------------- The command prefix is currently set to: . Enter q to quit, ? for help. [SQL]%SYS>> > 1>>OBJ SET $NAMESPACE="USER" 1>>SELECT TOP 5 Name,Home_State 2>>FROM Sample.Person 3>>GO /* SQL query results */ [SQL]USER>> ``` 请注意,OBJ语句不会增加SQL行数。 在SQL Shell多行模式下,在返回行时将执行OBJ命令,但是直到指定`GO`才发出SQL语句。因此,以下示例在功能上与先前的示例相同: ```java %SYS>DO $SYSTEM.SQL.Shell() SQL Command Line Shell ---------------------------------------------- The command prefix is currently set to: . Enter q to quit, ? for help. [SQL]%SYS>> > 1>>SELECT TOP 5 Name,Home_State 2>>FROM Sample.Person 3>>OBJ SET $NAMESPACE="USER" WRITE "changed namespace" changed namespace 3>>GO /* SQL query results */ [SQL]USER>> ``` 以下示例使用OBJ命令定义主机变量: ```java USER>DO $SYSTEM.SQL.Shell() SQL Command Line Shell ---------------------------------------------- The command prefix is currently set to: . Enter q to quit, ? for help. [SQL]USER>> > 1>>SELECT TOP :n Name,Home_State 2>>FROM Sample.Person 3>>OBJ SET n=5 3>>GO ``` ## 浏览命名空间 SQL Shell支持BROWSE命令,该命令显示在当前名称空间中定义或可从其访问的架构,表和视图。该显示包括多个级别的提示。要返回上一个提示级别,请在提示时按`Return`键。名称区分大小写。 1. 在SQL Shell提示符下键入BROWSE,以列出当前名称空间中的架构。 2. 在“架构:”提示下,按名称或编号选择一个架构。这将列出架构中的表和视图。 3. 在“表/视图:”提示下,按名称或编号选择一个表(T)或视图(V)。这将显示表信息,然后显示选项列表。 4. 在“选项:”提示下,按编号选择一个选项。可以使用此选项列出为表定义的字段或映射。 指定选项1(按名称表示的字段)或选项2(按数字表示的字段)以显示“Field:”提示。指定选项3(地图)以显示“Map:”提示。 5. 在`“Field:”`提示下,按数字或名称选择一个字段,或指定*以列出所有字段。这列出了详细的字段信息。 在`“Map:”`提示下,按数字或名称选择地图,或指定`*`列出所有Map。这列出了详细的Map信息。 ## CALL 命令 可以使用SQL Shell发出SQL `CALL`语句来调用SQL存储过程,如以下示例所示: ```java DHC-APP>>CALL Sample.PersonSets('G','NY') 3. CALL Sample.PersonSets('G','NY') Dumping result #1 Name DOB Spouse Gallant,Thelma Q. 45767 94 Gibbs,Mark S. 37331 13 Goldman,Will H. 59069 10 Gomez,Mo Q. 55626 55 Gore,Alfred M. 42991 13 Gore,Fred X. 32391 6 Grabscheid,Jocelyn B. 59676 79 7 Rows(s) Affected Dumping result #2 Name Age Home_City Home_State Chadbourne,Danielle G. 34 Pueblo NY Eastman,Clint G. 4 Miami NY Pape,Linda M. 71 Vail NY Peterson,Janice N. 49 Islip NY Schaefer,Jocelyn V. 93 Zanesville NY 5 Rows(s) Affected statement prepare time(s)/globals/lines/disk: 0.0043s/2153/15795/0ms execute time(s)/globals/lines/disk: 0.0015s/315/7829/0ms --------------------------------------------------------------------------- ``` 如果指定的存储过程在当前名称空间中不存在,则SQL Shell会发出`SQLCODE -428`错误。 如果指定的输入参数多于存储过程中定义的参数,则SQL Shell会发出`SQLCODE -370`错误。可以使用文字(`“字符串”`),主机变量(`:var`)和输入参数(`?`)的任意组合为存储过程指定参数值。 可以在`CALL`语句中使用主机变量,如以下示例所示: ```java [SQL]USER>>OBJ SET a="G",b="NY" [SQL]USER>>CALL Sample.PersonSets(:a,:b) ``` - 可以在`CALL`语句中使用输入参数(`“?”`字符),如以下示例所示: ```java [SQL]USER>>CALL Sample.PersonSets(?,?) ``` 当执行`CALL`语句时,SQL Shell会提示为每个输入参数提供一个值。 ## 执行SQL脚本文件 SQL Shell `RUN`命令执行一个SQL脚本文件。脚本文件的类型由`DIALECT`设置确定。 `DIALECT`的默认值为IRIS(InterSystems SQL)。
文章
姚 鑫 · 九月 30, 2021

第三十一章 SQL命令 DROP DATABASE

# 第三十一章 SQL命令 DROP DATABASE 删除数据库(命名空间)。 # 大纲 ```sql DROP DATABASE dbname [RETAIN_FILES] ``` ## 参数 - `dbname` - 要删除的数据库(命名空间)的名称。 - `RETAIN_FILES` - 可选-如果指定,则不会删除物理数据库文件(`IRIS.DAT`文件)。默认情况下,删除`.dat`文件以及命名空间和其他数据库实体。 # 描述 `DROP DATABASE`命令删除命名空间及其关联的数据库。 指定的`dbname`是包含相应数据库文件的命名空间和目录的名称。指定`dbname`作为标识符。命名空间名称不区分大小写。如果指定的`DBNAME`命名空间不存在, IRIS将发出`SQLCODE-340`错误。 `DROP DATABASE`命令是一个特权操作。 在使用`DROP DATABASE`之前,必须以`%Admin_Manage`资源的用户身份登录。 用户还必须拥有用于例程和全局数据库定义的资源的`READ`权限。 如果不这样做,将导致`SQLCODE -99`错误(权限冲突)。 使用`$SYSTEM.Security.Login()`方法为用户分配适当的权限: ```java DO $SYSTEM.Security.Login("_SYSTEM","SYS") &sql( ) ``` 必须具有`%Service_Login:Use`权限才能调用`$SYSTEM.Security.Login`方法。 不管权限如何,`DROP DATABASE`都不能用于删除系统命名空间。尝试这样做会导致`SQLCODE-342`错误。 `DROP DATABASE`不能用于删除当前正在使用或连接到的命名空间。尝试这样做会导致`SQLCODE-344`错误。 还可以使用管理门户删除命名空间。依次选择System Administration、Configuration、System Configuration、Namespaces以列出现有的`Namespace`。单击要删除的命名空间的删除按钮。 ## RETAIN_FILES 如果指定此选项,则保留物理文件结构;删除数据库及其关联的命名空间。执行此操作后,后续尝试使用`DBNAME`将导致以下结果: - `DROP DATABASE`不带`RETAIN_FILES`无法删除此物理文件结构。相反,它会导致`SQLCODE-340`错误(未找到数据库)。 - `DROP DATABASE WITH RETAIN_FILES`还会导致`SQLCODE-340`错误(找不到数据库)。 - `CREATE DATABASE`无法创建同名的新数据库。相反,它会导致`SQLCODE-341`错误(无法为数据库创建数据库文件)。 - 尝试使用此命名空间会导致``错误。 ## 服务器初始化和断开代码 服务器初始化代码和服务器断开代码可以通过`$SYSTEM.SQL.Util.SetOption("ServerInitCode",value)`和`$SYSTEM.SQL.Util.SetOption("ServerDisconnectCode",value)`方法分配给命名空间。 可以使用相应的`$SYSTEM.SQL.Util.GetOption()`方法选项来确定当前值。 使用`DROP DATABASE`或其他接口删除命名空间,将删除这些`Server Init Code`和`Server Disconnect Code`值。 因此,删除并重新创建名称空间需要重新指定这些值。 # 示例 ```sql CREATE DATABASE DocTestDB ON DIRECTORY 'c:\InterSystems\IRIS142\mgr\DocTestDB' ``` ```sql DROP DATABASE DocTestDB RETAIN_FILES ```
文章
Michael Lei · 四月 13, 2022

用Globals 作为图数据库来存储和抽取图结构数据

这篇文章是对我的 iris-globals-graphDB 应用的介绍。在这篇文章中,我将演示如何在Python Flask Web 框架和PYVIS交互式网络可视化库的帮助下,将图形数据保存和抽取到InterSystems Globals中。 建议 阅读相关文档 使用 Globals 原生 SDK 介绍 PYVIS 互动式网络可视化库 第一步 : 通过使用Python 原生SDK建立与IRIS Globals的链接 #create and establish connection if not self.iris_connection: self.iris_connection = irisnative.createConnection("localhost", 1972, "USER", "superuser", "SYS") # Create an iris object self.iris_native = irisnative.createIris(self.iris_connection) return self.iris_native 第二步 : 使用 iris_native.set( ) 功能把数据保存到Globals 里 #import nodes data from csv file isdefined = self.iris_native.isDefined("^g1nodes") if isdefined == 0: with open("/opt/irisapp/misc/g1nodes.csv", newline='') as csvfile: reader = csv.DictReader(csvfile) for row in reader: self.iris_native.set(row["name"], "^g1nodes", row["id"]) #import edges data from csv file isdefined = self.iris_native.isDefined("^g1edges") if isdefined == 0: with open("/opt/irisapp/misc/g1edges.csv", newline='') as csvfile: reader = csv.DictReader(csvfile) counter = 0 for row in reader: counter = counter + 1 #Save data to globals self.iris_native.set(row["source"]+'-'+row["target"], "^g1edges", counter) 第三步: 使用iris_native.get() 功能把节点和边缘数据从Globals传递给PYVIS #Get nodes data for basic graph def get_g1nodes(self): iris = self.get_iris_native() leverl1_subscript_iter = iris.iterator("^g1nodes") result = [] # Iterate over all nodes forwards for level1_subscript, level1_value in leverl1_subscript_iter: #Get data from globals val = iris.get("^g1nodes",level1_subscript) element = {"id": level1_subscript, "label": val, "shape":"circle"} result.append(element) return result #Get edges data for basic graph def get_g1edges(self): iris = self.get_iris_native() leverl1_subscript_iter = iris.iterator("^g1edges") result = [] # Iterate over all nodes forwards for level1_subscript, level1_value in leverl1_subscript_iter: #Get data from globals val = iris.get("^g1edges",level1_subscript) element = {"from": int(val.rpartition('-')[0]), "to": int(val.rpartition('-')[2])} result.append(element) return result Step4: Use PYVIS Javascript to generate graph data <script type="text/javascript"> // initialize global variables. var edges; var nodes; var network; var container; var options, data; // This method is responsible for drawing the graph, returns the drawn network function drawGraph() { var container = document.getElementById('mynetwork'); let node = JSON.parse('{{ nodes | tojson }}'); let edge = JSON.parse('{{ edges | tojson }}'); // parsing and collecting nodes and edges from the python nodes = new vis.DataSet(node); edges = new vis.DataSet(edge); // adding nodes and edges to the graph data = {nodes: nodes, edges: edges}; var options = { "configure": { "enabled": true, "filter": [ "physics","nodes" ] }, "nodes": { "color": { "border": "rgba(233,180,56,1)", "background": "rgba(252,175,41,1)", "highlight": { "border": "rgba(38,137,233,1)", "background": "rgba(40,138,255,1)" }, "hover": { "border": "rgba(42,127,233,1)", "background": "rgba(42,126,255,1)" } }, "font": { "color": "rgba(255,255,255,1)" } }, "edges": { "color": { "inherit": true }, "smooth": { "enabled": false, "type": "continuous" } }, "interaction": { "dragNodes": true, "hideEdgesOnDrag": false, "hideNodesOnDrag": false, "navigationButtons": true, "hover": true }, "physics": { "barnesHut": { "avoidOverlap": 0, "centralGravity": 0.3, "damping": 0.09, "gravitationalConstant": -80000, "springConstant": 0.001, "springLength": 250 }, "enabled": true, "stabilization": { "enabled": true, "fit": true, "iterations": 1000, "onlyDynamicEdges": false, "updateInterval": 50 } } } // if this network requires displaying the configure window, // put it in its div options.configure["container"] = document.getElementById("config"); network = new vis.Network(container, data, options); return network; } drawGraph(); </script> 第五步: 从app.py 主文件调用上面的代码 #Mian route. (index) @app.route("/") def index(): #Establish connection and import data to globals irisglobal = IRISGLOBAL() irisglobal.import_g1_nodes_edges() irisglobal.import_g2_nodes_edges() #getting nodes data from globals nodes = irisglobal.get_g1nodes() #getting edges data from globals edges = irisglobal.get_g1edges() #To display graph with configuration pyvis = True return render_template('index.html', nodes = nodes,edges=edges,pyvis=pyvis) 下面是关于此项目的 介绍视频: 欢迎大家来我们的 Bilibili主页观看更多视频! 谢谢!
文章
姚 鑫 · 二月 12, 2023

第七十四章 使用 irisstat 实用程序监控 IRIS - 查看 irisstat 输出

# 第七十四章 使用 irisstat 实用程序监控 IRIS - 查看 irisstat 输出 # 查看 `irisstat` 输出 可以立即查看 `irisstat` 数据(通过终端)或重定向到输出文件以供以后分析。查看数据的最常见方法是: 注意:当 `IRIS` 被强制关闭时,`irisstat` 会运行以捕获系统的当前状态。作为紧急关闭程序的一部分,输出被添加到消息日志中。 ## irisstat 文本文件 `irisstat` 报告可以重定向到文件而不是终端,如果想收集一组 `IRIS` 工具(诊断报告任务、`IRISHung` 脚本、`^SystemPerformance` 实用程序)未提供的一组 `irisstat` 选项,这可能很有用或者如果在运行这些工具时遇到问题。 ## 诊断报告任务 诊断报告任务会创建一个包含基本信息和高级信息的 HTML 日志文件,InterSystems 全球响应中心 (WRC) 可以使用该文件来解决系统问题。 注意:诊断报告任务不能在挂起的系统上运行;如果系统挂起,请参阅本附录中的 `IRISHung` 脚本。 ## IRISHung 脚本 `IRISHung` 脚本是一个操作系统工具,用于在 `IRIS` 实例挂起时收集系统数据。位于 `install-dir\bin` 目录中的脚本名称是特定于平台的,如下表中指定: Platform| Script name ---|--- `Microsoft Windows`| `IRISHung.cmd` `UNIX®/Linux` |`IRISHung.sh` `IRISHung` 脚本应以管理员权限运行。与诊断报告任务一样,`IRISHung` 脚本运行 `irisstat` 两次,间隔 `30` 秒,以防状态发生变化,并将报告与其他收集的数据一起打包到一个 `html` 文件中。从 `IRISHung` 获取的 `irisstat` 报告使用以下选项 ```java irisstat -e2 -f-1 -m-1 -n3 -j5 -g1 -L1 -u-1 -v1 -p-1 -c-1 -q1 -w2 -E-1 -N65535 ``` `IRISHung` 还运行仅使用 `-S2` 选项的第三个 `irisstat`,它将其写入一个单独的输出部分,称为“自诊断”。 `-S2` 选项导致可疑进程留下小型转储;因此,运行 `IRISHung` 可能会收集有关负责挂起的特定进程的信息,而简单地强制实例关闭不会收集此信息。 此外,`IRISHung` 生成的 `irisstat` 输出文件通常非常大,在这种情况下,它们被保存到单独的 `.txt` 文件中。请记住在收集输出时检查这些文件。 ## `^SystemPerformance Utility` `^SystemPerformance` 实用程序收集有关 `IRIS` 实例及其运行平台的详细性能数据。在 `IRIS` 内运行一段可配置的时间,在该时间间隔内收集样本,并在完成时生成报告。
文章
姚 鑫 · 九月 23, 2022

第四十一章 使用多个 IRIS 实例(一)

# 第四十一章 使用多个 IRIS 实例(一) 可以在单个主机系统上安装和运行多个 `IRIS®` 数据平台实例。每个实例都是一个独特的、独立的 `IRIS` 环境。 # 管理 IRIS 实例 有许多方法可以连接和管理 `IRIS` 实例,它可能是安装在给定系统上的几种方法之一。两种最常用的方法如下: - 安装在 `Windows` 系统上的每个 `IRIS` 实例在系统托盘中都有自己的启动器,除其他选项外,还可以: - 通过打开管理门户、 `Terminal`和 `Studio` 开发者客户端连接到实例。 - 启动、停止和重新启动实例。 - 打开用户和开发人员文档。 从启动器中,还可以管理多个远程 `IRIS` 实例,包括但不限于运行远程备份、编辑配置设置以及创建和编译远程对象和例程。 - `iris command` 在操作系统命令行上执行 iris 命令可让管理访问 `IRIS` 实例,其中包括其他选项,可以: - 使用 `Terminal`连接到实例。 - 启动、停止和重新启动实例。 - 显示有关该实例以及系统上安装的其他实例的信息。 要在远程服务器上使用 `iris` 命令,请使用 `Telnet` 或 `SSH` 客户端;要将它与容器化实例一起使用,请在容器内使用它,或者使用 `docker exec` 命令从容器外部运行它。 # 连接到 `IRIS` 实例 `Terminal` 是一个命令行,可以在 `IRIS` 实例的任何命名空间中使用。使用命令 `iris terminal instname` 打开正在运行的实例的终端,其中 `instname` 是在安装时为实例指定的名称。容器化实例通常被命名为 `IRIS`。 使用在安装期间提供的密码或创建的帐户使用预定义的用户帐户之一登录。显示的提示指示登录命名空间,例如: ```java # iris terminal IRISHealth Node: intersystems2588, Instance: IRIS27 Username: admin Password: ******** USER> ``` 要退出终端并关闭窗口,请输入命令 `halt`。 当使用`docker exec` 命令打开容器化实例的终端时(如在部署和探索 `IRIS` 中使用 终端进行交互中所述),将自动以 `irisowner` 身份登录,无需进行身份验证。 在 `Windows` 系统上,必须从其位置( `IRIS` 实例的 `install-dir\bin` 目录)执行命令,或在命令中包含完整路径,例如 `c:\InterSystems\IRIS27\bin\iris terminal IRISHealth` .可以执行给定实例的二进制文件以连接到该实例或另一个;无论哪种方式,实例名称都是必需的。
文章
姚 鑫 · 八月 29, 2024

第十一章 创建和使用策略 - 在运行时指定策略

# 第十一章 创建和使用策略 - 在运行时指定策略 # 在运行时指定策略 对于 `IRIS Web` 客户端,可以指定运行时要使用的策略;这将覆盖任何策略配置类。要在运行时指定策略,请设置 `Web` 客户端实例的 `PolicyConfiguration` 属性。该值必须具有以下形式: ```java Configuration class name:Configuration name ``` 其中,配置类名称是策略配置类的完整包和类名,如本主题前面所述,配置名称是该类中策略的 `` 元素的 `name` 属性的值 # 抑制不支持的策略的编译错误 默认情况下,当编译配置类时,如果配置包含 `IRIS` 不支持的任何策略表达式, `IRIS` 会发出错误。要避免此类错误,请在配置类中包含以下内容: ```java Parameter REPORTANYERROR=0; ``` 当从 `WSDL` 生成 `Web` 客户端或 `Web` 服务时,如果 `IRIS` 还生成配置类,则会将此参数设置包含在该类中。 只要有一个受支持的策略替代方案,就可以忽略不受支持的替代方案。 ## 编辑生成的策略 如果从 `WSDL` 生成配置类,并且 `WSDL` 位于 `IRIS` 此实例的外部,则必须编辑配置类以包含有关要使用的证书和 `SSL/TLS` 配置的信息。或者可以在运行时指定此信息。 下表给出了详细信息: If the Generated Policy Includes ...| Do the following ... ---|--- `` |对于附加到客户端的策略,请执行以下操作之一:按照添加扩展属性中的说明编辑此元素。按照指定客户端要使用的 `SSL/TLS` 配置中所述指定 `SSL/TLS` 配置的名称。对于附加到服务的策略,不需要进行任何更改。 `` |对于附加到客户端的策略,请执行以下操作之一:按照添加 `InterSystems` 扩展属性中所述编辑其中的 ``元素。检索凭证集并添加包含的证书,如在运行时添加证书中所述。无论如何,这必须是客户端拥有的凭证集。对于附加到服务的策略,无需进行任何更改。 `` |执行以下操作之一:按照添加 扩展属性中所述编辑其中的 ``元素。检索凭证集并添加包含的证书,如在运行时添加证书中所述。无论哪种情况,这都必须是服务拥有的凭证集。 ``| 可选择添加 `cfg:Lifetime` 属性,如添加 扩展属性中所述。默认生存期为 `5` 分钟。
文章
姚 鑫 · 一月 27

第九章 D - E 开头的术语

[toc] # 第九章 D - E 开头的术语 #### 显示格式 (display format) **对象(Objects)** 属性的显示格式是用于显示和输入数据的格式。 #### 分布式数据库 (distributed database) **系统** 存储在网络中多台计算机上的数据库。当在分布式数据库环境中使用`IRIS` 时,位于一台计算机上的 `ObjectScript` 例程可以在网络中其他计算机的文件保护限制内访问全局。 #### DMNNET **系统** 处理来自网络的传入全局请求的`IRIS` 进程。它是在系统状态(`%SS`)显示或系统操作实用工具中的进程面板上表示网络守护进程的名称。 #### 点语法 (dot syntax) **对象(Objects)** 点语法允许获取和设置属性值以及执行方法。它还允许从引用对象访问被引用和嵌入对象的属性和方法。 # 以 E 开头的术语. #### 可嵌入类 (embeddable class) **对象(Objects)** 从可嵌入(序列化)类派生的对象可以在内存中独立存在,但在存储到数据库时,只作为持久对象中的数据存储。参见` %SerialObject` 方法。 #### 内嵌 HTML (embedded HTML) **ObjectScript** 直接嵌入在 `ObjectScript` 方法或宏例程中的 `HTML`。内嵌 `HTML` 必须包含在 `&html` 语句中。 #### 内嵌对象 (embedded object) **对象(Objects)** 可嵌入类的实例。内嵌对象在内存中是独立的对象,但只能作为持久对象中的数据嵌入存储。 #### 内嵌 SQL (embedded SQL) **InterSystems SQL** 直接嵌入在 `ObjectScript` 方法或宏例程中的 `SQL`。内嵌 `SQL` 必须包含在 `&sql()` 语句中。 #### 空字符串 (empty string) **系统** 逻辑上没有任何字符的字符串,通常在文本中表示为 ""。有些资料也将其称为“空值字符串”。 不同语言对空字符串的表示方式不同,这决定了它是否以及如何在计算机内存中占用实际空间。例如,在 `ObjectScript` 中,`""` 不会占用内存空间,而在 `SQL` 中会。还要注意,虽然一个字符串中没有字符,它仍然是一个字符串,并且在不同的上下文中可能会与 `NULL`(未分配)值不同对待。 例如,在以下 `ObjectScript` 代码中: ```java New A New B Set A = "" Write A Write B 第一个 Write 语句成功执行(尽管视觉上不明显),而第二个会导致 错误。 ``` #### 封装 (encapsulation) **对象(Objects)** 封装通过呈现一个公开接口来隐藏类的内部细节,该接口概述了类允许的所有交互,而不展示执行这些操作所使用的任何细节。
文章
姚 鑫 · 十二月 31, 2023

第十一章 创建Callout Library - 使用 J 链接类型传递标准计数字符串

# 第十一章 创建Callout Library - 使用 J 链接类型传递标准计数字符串 # 使用 `J` 链接类型传递标准计数字符串 `iris-callin.h` 头文件定义了计数字符串结构 `IRIS_EXSTR`,表示标准 `IRIS` 字符串。此结构包含一个字符元素数组(`8` 位、`16` 位 `Unicode` 或 `32` 位 `wchar t`)和一个指定数组中元素数量的 `int` 值(最多字符串长度限制): ```java typedef struct { unsigned int len; /* length of string */ union { Callin_char_t *ch; /* text of the 8-bit string */ unsigned short *wch; /* text of the 16-bit string */ wchar_t *lch; /* text of the 32-bit string */ /* OR unsigned short *lch if 32-bit characters are not enabled */ } str; } IRIS_EXSTR, *IRIS_EXSTRP; ``` C Datatype| Input |In/Out |Notes ---|---|---|--- IRIS_EXSTR| 1j or j |1J or J| 8 位国家字符的标准字符串 IRIS_EXSTR |2j or n| 2J or N|16 位 Unicode 字符的标准字符串 IRIS_EXSTR| 4j |4J |32 位字符的标准字符串 wchar_t 字符 `IRIS_EXSTR` 数据结构由 `Callin API`(低级 `InterSystems` 函数调用库)中的函数进行操作。有关详细信息,请参阅使用 `Callin API` 中的“`Callin` 函数参考”。尽管名称相似,`Callin API` 和 `$ZF`标注接口是完全独立的产品)。 以下函数用于创建和销毁 `IRIS_EXSTR` 实例: - `IrisExStrNew[W][H`] — 为字符串分配请求的存储量,并使用长度和指向该结构的值字段的指针填充 `IRIS_EXSTR` 结构。 - `IrisExStrKill` — 释放与 `IRIS_EXSTR` 字符串关联的存储。 这是一个 `Callout` 库,它使用所有三种链接类型来返回数字字符串: ### 使用 `J` 连接传递字符串 以下三个函数均生成一个随机整数,将其转换为最多包含 `6` 位数字的数字字符串,并使用 `J` 链接返回字符串 。 ```java #define ZF_DLL // Required when creating a Callout library. #include #include #include #include int get_sample_L(IRIS_EXSTRP retval) { // 8-bit characters Callin_char_t numstr[6]; size_t len = 0; sprintf(numstr,"%d",(rand()%1000000)); len = strlen(numstr); IRISEXSTRKILL(retval); if (!IRISEXSTRNEW(retval,len)) {return ZF_FAILURE;} memcpy(retval->str.ch,numstr,len); // copy to retval->str.ch return ZF_SUCCESS; } int get_sample_LW(IRIS_EXSTRP retval) { // 16-bit characters unsigned short numstr[6]; size_t len = 0; swprintf(numstr,6,L"%d",(rand()%1000000)); len = wcslen(numstr); IRISEXSTRKILL(retval); if (!IRISEXSTRNEW(retval,len)) {return ZF_FAILURE;} memcpy(retval->str.wch,numstr,(len*sizeof(unsigned short))); // copy to retval->str.wch return ZF_SUCCESS; } int get_sample_LH(IRIS_EXSTRP retval) { // 32-bit characters wchar_t numstr[6]; size_t len = 0; swprintf(numstr,6,L"%d",(rand()%1000000)); len = wcslen(numstr); IRISEXSTRKILL(retval); if (!IRISEXSTRNEW(retval,len)) {return ZF_FAILURE;} memcpy(retval->str.lch,numstr,(len*sizeof(wchar_t))); // copy to retval->str.lch return ZF_SUCCESS; } ZFBEGIN ZFENTRY("GetSampleL","1J",get_sample_L) ZFENTRY("GetSampleLW","2J",get_sample_LW) ZFENTRY("GetSampleLH","4J",get_sample_LH) ZFEND ``` 注意:始终终止 `IRIS_EXSTRP` 输入参数 在前面的示例中,始终调用 `IRISEXSTRKILL(retval)` 以从内存中删除输入参数。即使参数不用于输出,也应该始终这样做。如果不这样做可能会导致内存泄漏。