搜索​​​​

清除过滤器
文章
姚 鑫 · 七月 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 ``` 针对于字典如何初始化呢,目前是有异常,望指教
文章
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 的使用经验或任何其他系统架构话题。
文章
姚 鑫 · 四月 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`。
文章
姚 鑫 · 四月 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()`是构建索引的最慢方法,所以只有在没有其他选项的情况下才应该使用它。
文章
Hao Ma · 五月 17, 2023

IRIS, Caché监控指导 - 指标监控(1)

Caché, IRIS在系统产生了最严重的问题时会产生错误信息并通知客户,但这并不足够。一是客户需要更多更灵活的通知消息,二是客户通常会有第3方的监控系统,因此得到Cache, IRIS的监控指标是必须的。 在所有的指标中,用户最关心的是以下几类: - 硬件资源的使用,CPU, 内存, IO性能 - 数据库使用的硬盘的占用 - Cache, IRIS Journal的硬盘占有 - Mirror的状态 - License的使用情况 - Caché的性能指标 除此之外,第3方监控系统还需要获得Caché的一些系统信息,比如版本,instance名字等等。 ## 指标的获得 有以下几个获得指标的方法 ### 1. 系统仪表板及其Web服务 Caché的系统仪表板显示的数据包括:系统性能;系统运行状态 (运行时间,上一次备份,数据库,Journal状况等; 事务和进程情况;软件许可使用情况;任务,ECP等,还有就是错误和警告的数量。 系统仪表板包含4个子面板:Global和Routine统计数据;ECP统计数据;磁盘和缓冲器统计数据;系统资源统计数据。(IRIS和Caché稍有不同,缺少了“磁盘和缓冲器统计数据”) 。它们分别对应主仪表板中的相应模块,给出了更详细的数据。 ![image](/sites/default/files/inline/images/image-20230517111905809.png) Monitoring Web Service默认随系统的启动而开启,远程用户或者第3方系统可以访问该服务得到所以的系统监控指标,也就是系统仪表板上的所有数据。用户还可以使用该服务订阅Caché的警告消息。访问地址是:http://localhost:57772/csp/sys/SYS.WSMon.Service.cls, 或者IRIS : http://localhost:52773/csp/sys/SYS.WSMon.Service.cls *Monitoring Web Service的WSDL* ![image](/sites/default/files/inline/images/image-20230517131516506.png) 调用服务方法的结果示例: GetDashboard() ```xml 204604.00 50183591134 2396232998 264876546 4040613709 60130 585372 77743.51 OK 0 OK 0 OK OK 10d 4h 38m Normal Normal Normal 18304233 Normal Normal 39 1 0 0 256 HealthShare 2018.1.1, Enterprise:256, Concurrent User, Platform Independent, Multi, DeepSee, DSV Reporting, NLP 2 4 1 2 ``` ### 2. REST API 这是IRIS的新特性,参考文档在[这里](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCM_rest)。 下面是我的一个测试环境的指标列表: ``` iris_cpu_usage 0 iris_csp_activity{id="127.0.0.1:52773"} 44 iris_csp_actual_connections{id="127.0.0.1:52773"} 7 iris_csp_gateway_latency{id="127.0.0.1:52773"} .772 iris_csp_in_use_connections{id="127.0.0.1:52773"} 1 iris_csp_private_connections{id="127.0.0.1:52773"} 0 iris_csp_sessions 1 iris_cache_efficiency 57.260 iris_db_expansion_size_mb{id="DEMO"} 0 iris_db_expansion_size_mb{id="ENSLIB"} 0 iris_db_expansion_size_mb{id="HCC"} 0 iris_db_expansion_size_mb{id="HSCUSTOM"} 0 iris_db_expansion_size_mb{id="HSLIB"} 0 iris_db_expansion_size_mb{id="HSSYS"} 0 iris_db_expansion_size_mb{id="IRISAUDIT"} 0 iris_db_expansion_size_mb{id="IRISLOCALDATA"} 0 iris_db_expansion_size_mb{id="IRISSYS"} 0 iris_db_expansion_size_mb{id="IRISTEMP"} 0 iris_db_expansion_size_mb{id="MOCKSYS"} 0 iris_db_expansion_size_mb{id="OEESP"} 0 iris_db_expansion_size_mb{id="SMART"} 0 iris_db_expansion_size_mb{id="USER"} 0 iris_db_free_space{id="DEMO"} 10 iris_db_free_space{id="ENSLIB"} 17 iris_db_free_space{id="HCC"} 11 iris_db_free_space{id="HSCUSTOM"} 9.5 iris_db_free_space{id="HSLIB"} 131 iris_db_free_space{id="HSSYS"} 9 iris_db_free_space{id="IRISAUDIT"} 8.7 iris_db_free_space{id="IRISLOCALDATA"} 19 iris_db_free_space{id="IRISSYS"} 9.3 iris_db_free_space{id="IRISTEMP"} 161 iris_db_free_space{id="MOCKSYS"} 8.7 iris_db_free_space{id="OEESP"} 6.9 iris_db_free_space{id="SMART"} 16 iris_db_free_space{id="USER"} 4.9 iris_db_latency{id="DEMO"} 0.566 iris_db_latency{id="ENSLIB"} 0.143 iris_db_latency{id="HCC"} 0.191 iris_db_latency{id="HSCUSTOM"} 0.163 iris_db_latency{id="HSLIB"} 0.153 iris_db_latency{id="HSSYS"} 0.143 iris_db_latency{id="IRISAUDIT"} 0.133 iris_db_latency{id="IRISSYS"} 0.200 iris_db_latency{id="IRISTEMP"} 0.145 iris_db_latency{id="MOCKSYS"} 0.157 iris_db_latency{id="OEESP"} 0.565 iris_db_latency{id="SMART"} 1.113 iris_db_latency{id="USER"} 0.236 iris_db_max_size_mb{id="DEMO"} 0 iris_db_max_size_mb{id="ENSLIB"} 0 iris_db_max_size_mb{id="HCC"} 0 iris_db_max_size_mb{id="HSCUSTOM"} 0 iris_db_max_size_mb{id="HSLIB"} 0 iris_db_max_size_mb{id="HSSYS"} 0 iris_db_max_size_mb{id="IRISAUDIT"} 0 iris_db_max_size_mb{id="IRISLOCALDATA"} 0 iris_db_max_size_mb{id="IRISSYS"} 0 iris_db_max_size_mb{id="IRISTEMP"} 0 iris_db_max_size_mb{id="MOCKSYS"} 0 iris_db_max_size_mb{id="OEESP"} 0 iris_db_max_size_mb{id="SMART"} 0 iris_db_max_size_mb{id="USER"} 0 iris_db_size_mb{id="HCC",dir="/usr/irissys/mgr/HCC/"} 365 iris_db_size_mb{id="DEMO",dir="/external/demo/"} 229 iris_db_size_mb{id="USER",dir="/usr/irissys/mgr/user/"} 11 iris_db_size_mb{id="HSLIB",dir="/usr/irissys/mgr/hslib/"} 1219 iris_db_size_mb{id="HSSYS",dir="/usr/irissys/mgr/hssys/"} 21 iris_db_size_mb{id="OEESP",dir="/external/oeesp/"} 102 iris_db_size_mb{id="SMART",dir="/external/smart/"} 162 iris_db_size_mb{id="ENSLIB",dir="/usr/irissys/mgr/enslib/"} 209 iris_db_size_mb{id="IRISSYS",dir="/usr/irissys/mgr/"} 127 iris_db_size_mb{id="MOCKSYS",dir="/usr/irissys/mgr/MOCKSYS/"} 11 iris_db_size_mb{id="HSCUSTOM",dir="/usr/irissys/mgr/HSCUSTOM/"} 21 iris_db_size_mb{id="IRISTEMP",dir="/usr/irissys/mgr/iristemp/"} 162 iris_db_size_mb{id="IRISAUDIT",dir="/usr/irissys/mgr/irisaudit/"} 11 iris_db_size_mb{id="IRISLOCALDATA",dir="/usr/irissys/mgr/irislocaldata/"} 21 iris_directory_space{id="HCC",dir="/usr/irissys/mgr/HCC/"} 17142 iris_directory_space{id="DEMO",dir="/external/demo/"} 34188 iris_directory_space{id="USER",dir="/usr/irissys/mgr/user/"} 17142 iris_directory_space{id="HSLIB",dir="/usr/irissys/mgr/hslib/"} 17142 iris_directory_space{id="HSSYS",dir="/usr/irissys/mgr/hssys/"} 17142 iris_directory_space{id="OEESP",dir="/external/oeesp/"} 34188 iris_directory_space{id="SMART",dir="/external/smart/"} 34188 iris_directory_space{id="ENSLIB",dir="/usr/irissys/mgr/enslib/"} 17142 iris_directory_space{id="IRISSYS",dir="/usr/irissys/mgr/"} 17142 iris_directory_space{id="MOCKSYS",dir="/usr/irissys/mgr/MOCKSYS/"} 17142 iris_directory_space{id="HSCUSTOM",dir="/usr/irissys/mgr/HSCUSTOM/"} 17142 iris_directory_space{id="IRISTEMP",dir="/usr/irissys/mgr/iristemp/"} 17142 iris_directory_space{id="IRISAUDIT",dir="/usr/irissys/mgr/irisaudit/"} 17142 iris_disk_percent_full{id="HCC",dir="/usr/irissys/mgr/HCC/"} 83.61 iris_disk_percent_full{id="DEMO",dir="/external/demo/"} 92.83 iris_disk_percent_full{id="USER",dir="/usr/irissys/mgr/user/"} 83.61 iris_disk_percent_full{id="HSLIB",dir="/usr/irissys/mgr/hslib/"} 83.61 iris_disk_percent_full{id="HSSYS",dir="/usr/irissys/mgr/hssys/"} 83.61 iris_disk_percent_full{id="OEESP",dir="/external/oeesp/"} 92.83 iris_disk_percent_full{id="SMART",dir="/external/smart/"} 92.83 iris_disk_percent_full{id="ENSLIB",dir="/usr/irissys/mgr/enslib/"} 83.61 iris_disk_percent_full{id="IRISSYS",dir="/usr/irissys/mgr/"} 83.61 iris_disk_percent_full{id="MOCKSYS",dir="/usr/irissys/mgr/MOCKSYS/"} 83.61 iris_disk_percent_full{id="HSCUSTOM",dir="/usr/irissys/mgr/HSCUSTOM/"} 83.61 iris_disk_percent_full{id="IRISTEMP",dir="/usr/irissys/mgr/iristemp/"} 83.61 iris_disk_percent_full{id="IRISAUDIT",dir="/usr/irissys/mgr/irisaudit/"} 83.61 iris_ecp_conn 0 iris_ecp_conn_max 2 iris_ecp_connections 0 iris_ecp_latency 0 iris_ecps_conn 0 iris_ecps_conn_max 1 iris_glo_a_seize_per_sec 0 iris_glo_n_seize_per_sec 0 iris_glo_ref_per_sec 0 iris_glo_ref_rem_per_sec 0 iris_glo_seize_per_sec 0 iris_glo_update_per_sec 0 iris_glo_update_rem_per_sec 0 iris_jrn_block_per_sec 0 iris_jrn_entry_per_sec 0 iris_jrn_free_space{id="WIJ",dir="default"} 11802.25 iris_jrn_free_space{id="primary",dir="/usr/irissys/mgr/journal/"} 11802.25 iris_jrn_free_space{id="secondary",dir="/usr/irissys/mgr/journal/"} 11802.25 iris_jrn_size{id="WIJ"} 237 iris_jrn_size{id="primary"} 1 iris_jrn_size{id="secondary"} 0 iris_license_available 4 iris_license_consumed 1 iris_license_percent_used 20 iris_log_reads_per_sec 0 iris_obj_a_seize_per_sec 0 iris_obj_del_per_sec 0 iris_obj_hit_per_sec 0 iris_obj_load_per_sec 0 iris_obj_miss_per_sec 0 iris_obj_new_per_sec 0 iris_obj_seize_per_sec 0 iris_page_space_percent_used 30 iris_phys_mem_percent_used 61 iris_phys_reads_per_sec 0 iris_phys_writes_per_sec 0 iris_process_count 30 iris_rtn_a_seize_per_sec 0 iris_rtn_call_local_per_sec 0 iris_rtn_call_miss_per_sec 0 iris_rtn_call_remote_per_sec 0 iris_rtn_load_per_sec 0 iris_rtn_load_rem_per_sec 0 iris_rtn_seize_per_sec 0 iris_sam_get_db_sensors_seconds .021465 iris_sam_get_interop_sensors_seconds .000165 iris_sam_get_jrn_sensors_seconds .002873 iris_sam_get_sql_sensors_seconds .000258 iris_sam_get_wqm_sensors_seconds .000125 iris_smh_available{id="Classes_Instantiated"} 47360 iris_smh_available{id="DB_Name_&_Directory"} 64326 iris_smh_available{id="Global_Mapping"} 60384 iris_smh_available{id="Lock_Table"} 192816 iris_smh_available{id="Routine_Buffer_In_Use_Table"} 61952 iris_smh_available{id="Security_System"} 0 iris_smh_available{id="Semaphores_objects"} 61440 iris_smh_available{id="TTY_Hash_Table"} 32760 iris_smh_percent_full{id="Classes_Instantiated"} 98 iris_smh_percent_full{id="DB_Name_&_Directory"} 2 iris_smh_percent_full{id="Global_Mapping"} 69 iris_smh_percent_full{id="Lock_Table"} 2 iris_smh_percent_full{id="Routine_Buffer_In_Use_Table"} 5 iris_smh_percent_full{id="Semaphores_objects"} 6 iris_smh_percent_full{id="TTY_Hash_Table"} 50 iris_smh_total 3801088 iris_smh_total_percent_full 6 iris_smh_used{id="Classes_Instantiated"} 1984256 iris_smh_used{id="DB_Name_&_Directory"} 1210 iris_smh_used{id="Global_Mapping"} 136224 iris_smh_used{id="Lock_Table"} 3792 iris_smh_used{id="Routine_Buffer_In_Use_Table"} 3584 iris_smh_used{id="Security_System"} 65536 iris_smh_used{id="Semaphores_objects"} 4096 iris_smh_used{id="TTY_Hash_Table"} 32776 iris_sql_queries_avg_runtime{id="%SYS"} .000057375 iris_sql_queries_avg_runtime{id="all"} .000057375 iris_sql_queries_avg_runtime_std_dev{id="%SYS"} .00000000001060414620168293732 iris_sql_queries_avg_runtime_std_dev{id="all"} .00000000001060414620168293732 iris_sql_queries_per_second{id="%SYS"} .1333333333333333333 iris_sql_queries_per_second{id="all"} .1333333333333333333 iris_system_alerts 3 iris_system_alerts_log 3 iris_system_alerts_new 1 iris_system_state 2 iris_trans_open_count 0 iris_trans_open_secs 0 iris_trans_open_secs_max 0 iris_wd_buffer_redirty 2 iris_wd_buffer_write 2 iris_wd_cycle_time 6 iris_wd_proc_in_global 0 iris_wd_size_write 16 iris_wd_sleep 9969 iris_wd_temp_queue 51 iris_wd_temp_write 0 iris_wdwij_time 3 iris_wd_write_time 2 iris_wij_writes_per_sec 0 iris_wqm_active_worker_jobs{id="SYS"} 0 iris_wqm_commands_per_sec{id="SYS"} 103 iris_wqm_globals_per_sec{id="SYS"} 4 iris_wqm_max_active_worker_jobs{id="SYS"} 0 iris_wqm_max_work_queue_depth{id="SYS"} 0 iris_wqm_waiting_worker_jobs{id="SYS"} 1 ``` ### 3. 通过SNMP 在Caceh' 和IRIS的安装文件夹的SNMP子目录下, 你可以找到.mib文件, 分别是ISC-cache.mib或者ISC-IRIS.mib。如果是ensemble或者health connect, 还会有isc-ensemble.mib。 通过SNMP GET, 您可以使用第3方工具的SNMP客户端获得Caché和IRIS的全部指标。 ### 4. 历史监视器(History Monitor) 如果用户没有专用的监控系统,那么使用历史监视器可以是一个很好的选择。 历史监视器把数据库的读写和系统使用情况的历史数据存在一个表里, 用户可以通过SQL访问。 它的统计的内容为: - CPU Usage - 数据库的大小和Journal的大小 - Global Reference and Updates - 物理读写(Physical reads and writes) - License使用率 这是一个轻量级的监控工具,几乎不对生产环境增加有意义的开销。它收集数据的表的尺寸也很小, 因此SQL查询的速度很快。 (即使收集小时级别的统计数据,每年的硬盘占用也只有130MB左右)。所有它经常被用于建立一个系统性能的基准线,便于日后的性能问题分析,以及容量规划 (capacity planning)。 **启动Caché History Monitor操作** ``` %SYS>do ^%SYSMONMGR 1) Start/Stop System Monitor 2) Set System Monitor Options 3) Configure System Monitor Classes 4) View System Monitor State 5) Manage Application Monitor 6) Manage Health Monitor 7) View System Data 8) Exit Option? 5 1) Set Sample Interval 2) Manage Monitor Classes 3) Change Default Notification Method 4) Manage Email Options 5) Manage Alerts 6) Exit Option? 2 1) Activate/Deactivate Monitor Class 2) List Monitor Classes 3) Register Monitor System Classes 4) Remove/Purge Monitor Class 5) Set Class Sample Interval 6) Debug Monitor Classes 7) Exit Option? 1 Class? %Monitor.System.HistoryMemory Activate class? Yes => yes 1) Activate/Deactivate Monitor Class 2) List Monitor Classes 3) Register Monitor System Classes 4) Remove/Purge Monitor Class 5) Set Class Sample Interval 6) Debug Monitor Classes 7) Exit Option? 1 Class? %Monitor.System.HistoryPerf Activate class? Yes => yes 1) Activate/Deactivate Monitor Class 2) List Monitor Classes 3) Register Monitor System Classes 4) Remove/Purge Monitor Class 5) Set Class Sample Interval 6) Debug Monitor Classes 7) Exit Option? 1 Class? %Monitor.System.HistorySys Activate class? Yes => yes 1) Activate/Deactivate Monitor Class 2) List Monitor Classes 3) Register Monitor System Classes 4) Remove/Purge Monitor Class 5) Set Class Sample Interval 6) Debug Monitor Classes 7) Exit Option? 1 Class? %Monitor.System.HistoryUser Activate class? Yes => yes 1) Activate/Deactivate Monitor Class 2) List Monitor Classes 3) Register Monitor System Classes 4) Remove/Purge Monitor Class 5) Set Class Sample Interval 6) Debug Monitor Classes 7) Exit Option? 2 Class Active SampleInterval ----- ------ -------------- %Monitor.System.HistoryMemory Y default %Monitor.System.HistoryPerf Y default %Monitor.System.HistorySys Y default %Monitor.System.HistoryUser Y default %Monitor.System.AuditCount N default %Monitor.System.AuditEvents N default %Monitor.System.Clients N default %Monitor.System.Diskspace N default %Monitor.System.Freespace N default %Monitor.System.Globals N default %Monitor.System.Journals N default %Monitor.System.License N default %Monitor.System.LockTable N default %Monitor.System.Processes N default %Monitor.System.Routines N default %Monitor.System.Servers N default %Monitor.System.SystemMetrics N default %Monitor.System.CSPGateway N default MyMetric.Freespace N default ``` 注意: 激活后需要重新启动System Monitor。统计数据默认保留60天, 如果希望保留更长的时间, 下面的命令设置为保留一年: `%SYS>do ##class(SYS.History.Hourly).SetPurge(365)` **读取History Monitor数据** 历史监视器把数据存在SQL表里, 表明和其中的字段请参考[在线文档: History Monitor](https://docs.intersystems.com/iris20231/csp/docbook/DocBook.UI.Page.cls?KEY=GCM_historymon) !!!注意: SYS_History.PerfData给出采样间隔内的测量值,而daily, hourly的值为计算出的每秒的平均值。 这里给出使用历史监视器数据获得数据的2个例子 1. 读取平均CPU占用 ```sql SELECT Substr(DS.Daily,1,5) as DateH, (100-DS.Sys_CPUIdle) as AvgCPUBusy FROM SYS_History.Daily_SYS DS WHERE element_key='Avg' ORDER BY DateH ``` 2. 读取每天的CPU占用;9am-12am的CPU占用,global reference and update ```sql SELECT Substr(DS.Daily,1,5) Day, (100-DS.Sys_CPUIdle) as Daily_Avg_CPU, Round(AVG(100-H1.Sys_CPUIdle),2) Morning_Avg_CPU , DP.Perf_GloRef, DP.Perf_GloUpdate FROM SYS_History.Daily_Perf DP, SYS_History.Daily_SYS DS, SYS_History.Hourly_Sys H1 WHERE DP.Daily=DS.Daily and DP.element_key='Avg' and DS.element_key='Avg' and H1.element_key='Avg'and substr(DS.Daily,1,5)=Substr(H1.Hourly,1,5) and Substr(H1.Hourly,8,12) in (32400,36000,39600) GROUP BY DS.daily ``` ### 其他 除了上面的4个方式,Caché和IRIS还提供了另外的采集指标的方法,比如说执行routine, 存储过程,查看其他SQL表等等, 后面的文章中会对不同类型的指标做更近一步的介绍。
文章
姚 鑫 · 六月 10, 2022

第五章 数据类型(四)

# 第五章 数据类型(四) # Strings `%Library.String` 数据类型支持的最大字符串长度为 `3,641,144` 个字符。通常,极长的字符串应分配为 `%Stream.GlobalCharacter` 数据类型之一。 因为 IRIS 支持 xDBC 协议 50 和更高版本,所以没有强制执行 ODBC 或 JDBC 字符串长度限制。如果 IRIS 实例和 ODBC 驱动程序支持不同的协议,则使用两个协议中较低的一个。实际使用的协议记录在 ODBC 日志中。 请注意,默认情况下 IRIS 建立系统范围的 ODBC `VARCHAR` 最大长度为 `4096`;此 ODBC 最大长度是可配置的。 # 列表结构 IRIS 支持列表结构数据类型 `%List`(数据类型类 `%Library.List`)。这是一种压缩的二进制格式,不会映射到 SQL 的相应本机数据类型。在其内部表示中,它对应于数据类型 `VARBINARY`,默认 `MAXLEN` 为 `32749`。 IRIS 支持列表结构数据类型 `%ListOfBinary`(数据类型类 `%Library.ListOfBinary`)对应于数据类型 `VARBINARY`,默认 `MAXLEN` 为 `4096`。 因此,动态 SQL 不能在 `WHERE` 子句比较中使用 `%List` 数据。也不能使用 `INSERT` 或 `UPDATE` 来设置 `%List` 类型的属性值。 动态 SQL 将列表结构化数据的数据类型返回为 `VARCHAR`。要确定查询中的字段是数据类型 `%List` 还是 `%ListOfBinary`,可以使用 `select-item columns metadata isList` 布尔标志。这些数据类型的 `CType`(客户端数据类型)整数代码是 `6`。 如果使用 ODBC 或 JDBC 客户端,则使用 `LogicalToOdbc` 转换将 `%List` 数据投影到 `VARCHAR` 字符串数据。列表被投影为一个字符串,其元素由逗号分隔。这种类型的数据可以用在 `WHERE` 子句以及 `INSERT` 和 `UPDATE` 语句中。请注意,默认情况下,IRIS 建立系统范围的 ODBC `VARCHAR` 最大长度为 `4096`;此 ODBC 最大长度是可配置的。 SQL 支持八种列表函数:`$LIST`、`$LISTBUILD`、`$LISTDATA`、`$LISTFIND`、`$LISTFROMSTRING`、`$LISTGET`、`$LISTLENGTH` 和 `$LISTTOSTRING`。 ObjectScript 支持三个额外的列表函数:`$LISTVALID` 用于确定表达式是否为列表,`$LISTSAME` 用于比较两个列表,以及 `$LISTNEXT` 用于从列表中顺序检索元素。 # 位数据类型 `BIT (%Library.Boolean)` 数据类型接受 `0`、`1` 和 `NULL` 作为有效值。 - 在逻辑和 ODBC 模式下,唯一接受的值是 `0`、`1` 和 `NULL`。 - 在显示模式下,`DisplayToLogical` 方法首先将非空输入值转换为 `0` 或 `1`,如下所示: - 非零数字或数字字符串 = 1,例如 `3`, `'0.1'`, `'-1'`, `'7dwarves'` - 非数字字符串 = 0。例如,`“true”`或`“false”`。 - 空字符串 = 0。例如`''`。 # 流数据类型 `Stream` 数据类型对应于 IRIS 类属性数据类型 `%Stream.GlobalCharacter`(用于 `CLOB`)和 `%Stream.GlobalBinary`(用于 `BLOB`)。这些数据类型类可以使用指定的 `LOCATION` 参数定义流字段,或者省略该参数并默认为系统定义的存储位置。 具有 `Stream` 数据类型的字段不能用作大多数 SQL 标量、聚合或一元函数的参数。尝试这样做会生成 `SQLCODE -37` 错误代码。 具有 `Stream` 数据类型的字段不能用作大多数 SQL 谓词条件的参数。尝试这样做会生成 `SQLCODE -313` 错误代码。 Stream 数据类型在索引中的使用以及在执行插入和更新时也受到限制。 # 串行数据类型 具有 `SERIAL (%Library.Counter)` 数据类型的字段可以采用用户指定的正整数值,或者 IRIS 可以为其分配一个连续的正整数值。 `%Library.Counter` 扩展了 `%Library.BigInt`。 `INSERT` 操作为 `SERIAL` 字段指定以下值之一: - 无值、0(零)或非数字值: IRIS 忽略指定值,而是将此字段的当前串行计数器值增加 1,并将结果整数插入该字段。 - 正整数值:IRIS 将用户指定的值插入到字段中,并将该字段的串行计数器值更改为此整数值。 因此,`SERIAL` 字段包含一系列增量整数值。这些值不一定是连续的或唯一的。例如,以下是 SERIAL 字段的有效值系列:`1、2、3、17、18、25、25、26、27`。连续整数要么是 IRIS 生成的,要么是用户提供的;非连续整数是用户提供的。如果希望 `SERIAL` 字段值是唯一的,则必须对该字段应用 `UNIQUE` 约束。 `UPDATE` 操作对自动分配的 `SERIAL` 计数器字段值没有影响。但是,使用 `INSERT OR UPDATE` 执行的更新会导致对 `SERIAL` 字段的后续插入操作跳过整数序列。 如果该字段当前没有值(`NULL`),或者它的值为 `0`,则 `UPDATE` 操作只能更改串行字段值。否则,将生成 `SQLCODE -105` 错误。 IRIS 对表中的 `SERIAL` 字段的数量没有限制。 # ROWVERSION 数据类型 `ROWVERSION` 数据类型定义了一个只读字段,该字段包含一个唯一的系统分配的正整数,从 1 开始。 IRIS 分配顺序整数作为每个插入、更新或 `%Save` 操作的一部分。这些值不是用户可修改的。 IRIS 在命名空间范围内维护一个单行版本计数器。命名空间中包含 `ROWVERSION` 字段的所有表共享相同的行版本计数器。因此,`ROWVERSION` 字段提供行级版本控制,允许确定对命名空间中一个或多个表中的行进行更改的顺序。 每个表只能指定一个 `ROWVERSION` 数据类型的字段。 `ROWVERSION` 字段不应包含在唯一键或主键中。 `ROWVERSION` 字段不能是 `IDKey` 索引的一部分。 # ROWVERSION 和 SERIAL 计数器 作为 `INSERT` 操作的一部分,`ROWVERSION` 和 `SERIAL` `(%Library.Counter)` 数据类型字段都从内部计数器接收顺序整数。但是这两个计数器有很大的不同,并且用于不同的目的: - `ROWVERSION` 计数器位于命名空间级别。 `SERIAL` 计数器位于表级别。这两个计数器完全相互独立,独立于 `RowID` 计数器。 - `ROWVERSION` 计数器通过插入、更新或 `%Save` 操作递增。 `SERIAL` 计数器仅由插入操作递增。使用 `INSERT OR UPDATE` 执行的更新可能会导致 `SERIAL` 计数器序列出现间隙。 - `ROWVERSION` 字段值不能由用户指定;该值始终由 `ROWVERSION` 计数器提供。如果没有为该字段指定值,则在插入期间从表的内部计数器提供一个 `SERIAL` 字段值。如果插入提供了一个 `SERIAL` 整数值,则插入该值而不是当前计数器值: - 如果插入提供的 `SERIAL` 字段值大于当前内部计数器值, IRIS 将该值插入该字段并将内部计数器重置为该值。 - 如果插入提供的 `SERIAL` 字段值小于当前计数器值, IRIS 不会重置内部计数器。 - 插入可以提供 `SERIAL` 字段值作为负整数或小数。 IRIS 将小数截断为其整数部分。如果提供的 `SERIAL` 字段值为 `0` 或 `NULL`, IRIS 将忽略用户提供的值并插入当前的内部计数器值。 - 不能更新现有的 `SERIAL` 字段值。 - `ROWVERSION` 字段值始终是唯一的。因为可以插入用户指定的 `SERIAL` 字段值,所以必须指定 `UNIQUE` 字段约束以保证唯一的 `SERIAL` 字段值。 - 无法重置 `ROWVERSION` 计数器。 `TRUNCATE TABLE` 重置 `SERIAL` 计数器;对所有行执行 `DELETE` 不会重置 `SERIAL` 计数器。 - 每个表只允许一个 `ROWVERSION` 字段。可以在一个表中指定多个 `SERIAL` 字段。 # ODBC / JDBC 公开的 DDL 数据类型 ODBC 公开了 DDL 数据类型的子集,并将其他数据类型映射到该数据类型的子集。这些映射是不可逆的。例如,语句 `CREATE TABLE mytable (f1 BINARY)` 创建一个 IRIS 类,该类作为 `mytable (f1 VARBINARY)` 投影到 ODBC。 IRIS 列表数据类型作为 `VARCHAR` 字符串投影到 ODBC。 ODBC 公开以下数据类型:`BIGINT`、`BIT`、`DATE`、`DOUBLE`、`GUID`、`INTEGER`、`LONGVARBINARY`、`LONGVARCHAR`、`NUMERIC`、`OREF`、`POSIXTIME`、`SMALLINT`、`TIME`、`TIMESTAMP`、`TINYINT`、`VARBINARY`、`VARCHAR`。请注意,默认情况下 IRIS 建立系统范围的 ODBC `VARCHAR` 最大长度为 `4096`;此 ODBC 最大长度是可配置的。 当这些 ODBC/JDBC 数据类型值之一映射到 SQL 时,会发生以下操作: 使用 `$DOUBLE` 强制转换 `DOUBLE` 数据。 `NUMERIC` 数据使用 `$DECIMAL` 进行转换。 `GUID` 数据类型对应于 `SQL UNIQUEIDENTIFIER` 数据类型。未能为 `GUID / UNIQUEIDENTIFIER` 字段指定有效值会生成 `#7212` 一般错误。要生成 `GUID` 值,请使用 `%SYSTEM.Util.CreateGUID()` 方法。 # 查询元数据返回数据类型 可以使用动态 SQL 返回有关查询的元数据,包括查询中指定列的数据类型。 以下动态 SQL 示例为 `Sample.Person` 和 `Sample.Employee` 中的每个列返回列名和 ODBC 数据类型的整数代码: ```java /// d ##class(PHA.TEST.SQLFunction).QueryMetadataReturnsDataType() ClassMethod QueryMetadataReturnsDataType() { s myquery = "SELECT * FROM Sample.Person" s tStatement = ##class(%SQL.Statement).%New() s tStatus = tStatement.%Prepare(myquery) s x = tStatement.%Metadata.columnCount while x > 0 { s column = tStatement.%Metadata.columns.GetAt(x) w !,x," ",column.colName," ",column.ODBCType s x = x-1 } w !,"end of columns" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQLFunction).QueryMetadataReturnsDataType() 16 Office_Zip 12 15 Office_Street 12 14 Office_State 12 13 Office_City 12 12 Home_Zip 12 11 Home_Street 12 10 Home_State 12 9 Home_City 12 8 Spouse 4 7 SSN 12 6 Name 12 5 FavoriteColors 12 4 DOB 9 3 Age 4 2 AddDateTime 11 1 ID 4 end of columns ``` ```java /// d ##class(PHA.TEST.SQLFunction).QueryMetadataReturnsDataType1() ClassMethod QueryMetadataReturnsDataType1() { s myquery = "SELECT * FROM Sample.Employee" s tStatement = ##class(%SQL.Statement).%New() s tStatus = tStatement.%Prepare(myquery) s x = tStatement.%Metadata.columnCount while x > 0 { s column = tStatement.%Metadata.columns.GetAt(x) w !,x," ",column.colName," ",column.ODBCType s x = x - 1 } w !,"end of columns" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQLFunction).QueryMetadataReturnsDataType1() 20 Office_Zip 12 19 Office_Street 12 18 Office_State 12 17 Office_City 12 16 Home_Zip 12 15 Home_Street 12 14 Home_State 12 13 Home_City 12 12 Title 12 11 Spouse 4 10 Salary 4 9 SSN 12 8 Picture -4 7 Notes -1 6 Name 12 5 FavoriteColors 12 4 DOB 9 3 Company 4 2 Age 4 1 ID 4 end of columns ``` 列出结构化数据(例如 `Sample.Person` 中的 `FavoriteColors` 列)返回数据类型 `12 (VARCHAR)`,因为 ODBC 将 ObjectScript `%List` 数据类型值表示为逗号分隔值的字符串。 `Steam` 数据(例如 `Sample.Employee` 中的 `Notes` 和 `Picture` 列)返回数据类型 -1 (`LONGVARCHAR`) 或 -4 (`LONGVARBINARY`)。 `ROWVERSION` 字段返回数据类型 `-5`,因为 `%Library.RowVersion` 是 `%Library.BigInt` 的子类。
文章
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,《测试驱动的设计》
文章
姚 鑫 · 六月 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 } ```
文章
姚 鑫 · 六月 15, 2021

第七章 控制命名空间分配的外观

# 第七章 控制命名空间分配的外观 # 控制命名空间分配的外观 除了控制命名空间分配外,还可以控制命名空间分配在XML输出中的显示方式。具体地说,可以控制以下内容: ## 显式名称空间分配与隐式名称空间分配 将元素和属性分配给命名空间时,XML中有两种等效的表示形式,由编写器实例的`SuppressXmlns`属性控制。 为一个名为`Person`的对象生成XML输出,该对象被分配给名称空间`“http://www.person.org”`(通过前面讨论的`namespace`类参数)。 使用缺省输出(`SuppressXmlns`等于0)的示例如下: ```java Uberoth,Amanda Q. 1952-01-13 ``` 另一种可能的形式完全相同,如下所示。 这是使用`SuppressXmlns`等于1生成的,它确保显式分配给名称空间的每个元素都显示为该名称空间的前缀。 ```java Uberoth,Amanda Q. 1952-01-13 ``` 请注意,此属性仅影响命名空间分配的显示方式;它不控制如何分配任何命名空间。如果不使用命名空间,则此参数无效。 ## 为命名空间指定自定义前缀 当为对象生成XML输出时,系统会根据需要生成命名空间前缀。第一个名称空间前缀是`s01`,下一个是`s02`,依此类推。可以指定不同的前缀。为此,请在启用XML的对象本身的类定义中设置`XMLPREFIX`参数。此参数有两个效果: - 它确保在XML输出中声明指定的前缀。也就是说,即使没有必要这样做,它也会被声明。 - 它使用该前缀,而不是在其他情况下会看到的自动生成的前缀。 # 控制空字符串(`""`)的导出方式 为对象启用XML时,需要指定将空值和空字符串投影到XML的方式 其中一个选项是在支持xml的类中将`XMLIGNORENULL`设置为`“RUNTIME”`(不区分大小写)。 在这种情况下,当使用%XML.Write的`RuntimeIgnoreNull`属性的值来确定如何处理任何等于`""`的属性,如下所示: - **如果编写器的`RuntimeIgnoreNull`属性为0(默认值),则`XMLNIL`参数控制如何导出该属性。`XMLNIL`是一个类参数和一个属性参数;属性参数优先。** - 如果`XMLNIL`为0(默认值),则不投影特性。也就是说,它不包含在XML文档中。 - 如果`XMLNIL`为1,并且该属性用作元素,则该属性将按如下方式导出: ``` ``` - 如果`XMLNIL`为1并且特性用作属性,则不会输出特性。 - 如果编写器的`RuntimeIgnoreNull`属性为1,则该属性将导出为空元素或空属性(其导出方式与值`$char(0)`相同,后者始终导出为空元素或空导出)。 除非`XMLIGNORENULL`在启用xml的类中是`“RUNTIME”`,否则编写器的`RuntimeIgnoreNull`属性是无效的。 ## 示例:`RuntimeIgnoreNull`为0(默认值) ```java Class EmptyStrings.Export Extends (%Persistent, %XML.Adaptor) { Parameter XMLNAME="Test"; Parameter XMLIGNORENULL = "RUNTIME"; ///把这个作为一个元素 ///XMLNIL is 0, the default Property Property1 As %String; ///把这个作为一个元素 ///XMLNIL is 0, the default Property Property2 As %String(XMLPROJECTION = "ATTRIBUTE"); ///将其作为XMLNIL=1的元素 Property Property3 As %String(XMLNIL = 1); ///将其作为XMLNIL=1的属性进行项目 Property Property4 As %String(XMLNIL=1,XMLPROJECTION="ATTRIBUTE"); } ``` 如果创建了这个类的一个新实例(并且没有设置任何属性的值),然后使用 `%XML.Writer`输出,如下所示: ```java ``` ## 例如:`RuntimeIgnoreNull`为1 ```java ``` **在本例中,因为`RuntimeIgnoreNull`为1,所以没有使用`XMLNIL`参数。 相反,`""`被导出为空属性或空元素。** # 导出类型信息 默认情况下,XML编写器不写入类型信息。有两个选项可用于在输出中包括类型信息: - 编写器的`OutputTypeAttribute`属性。如果此属性为1,则编写器包括其写入的对象内所有元素的XML类型信息(但不包括对象本身)。例如: ```java Petersburg,Greta U. 1949-05-15 ``` 请注意,相应的命名空间将添加到XML文档的根。 - `Object()`和`RootObject()`方法的`className`参数。此参数用于指定对象的预期`ObjectScript`类型(类名)。 如果参数与实际类型相同,则编写器不包括对象的类型信息。 如果参数与实际类型不同,编写器将包括对象的实际XML类型(默认为类名)。例如,假设`Test2.PersonWithAddress`实例编写输出,并将`className`参数指定为`MyPackage.MyClass`。因为`MyPackage.MyClass`与实际的类名不同,所以编写器生成以下输出: ```java Avery,Robert H. Ukiah 82281 ``` # 生成SOAP编码的XML 对于`%XML.Writer`,`Format`属性控制输出的整体格式。这是以下选项之一: - `“literal”`,即默认值,在本书的大多数例子中都使用了它。 - `“encoded”`,按照SOAP 1.1标准中的描述进行编码。 - `“encoded12”`,按照SOAP 1.2标准中的描述进行编码。 ## 创建内联引用 在编码格式中,任何对象值属性都被作为引用包含,被引用的对象被导出为单独的元素。 要将这些属性内联导出,而不是作为单独的元素,请将`ReferencesInline`属性设置为1。 如果格式是`“literal”`,`ReferencesInline`属性没有效果。 # 导出后控制unswizling 当导出一个支持xml的持久对象时,系统会像往常一样自动将所有需要的信息混合到内存中;该信息包括对象值属性。导出对象后,InterSystems IRIS将消除任何对象列表,但(默认情况下)不会消除单个对象引用。 对于大对象,这可能导致``错误。 在这种情况下,要使任何单个对象引用不被混合,请在支持xml的类中设置`XMLUNSWIZZLE`参数,如下所示: ```java Parameter XMLUNSWIZZLE = 1; ``` 该参数默认值为0。 # 控制元素的关闭 只包含属性的元素可以用以下任一方式表示: ```java ``` `Object()`方法始终使用第一个语法导出元素。如果需要使用此处显示的第二种语法关闭元素,请手动编写对象,如本章前面的“手动构造元素”中所述。 咨询一下,我也是初学,有没有类似于oracle的exp远程备份数据库的方法?
文章
姚 鑫 · 七月 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` 比较运算符将字符串与指定的字符类型模式匹配。
文章
姚 鑫 · 六月 17, 2023

第六十章 镜像中断程序 - 使用主 ISCAgent 的日志数据进行 DR 提升和手动故障转移

# 第六十章 镜像中断程序 - 使用主 `ISCAgent` 的日志数据进行 `DR` 提升和手动故障转移 ## 使用主 `ISCAgent` 的日志数据进行 `DR` 提升和手动故障转移 如果 `IRIS A` 的主机系统正在运行,但 `IRIS` 实例没有且无法重新启动,您可以使用以下过程在通过升级后使用来自 `IRIS A` 的最新日志数据更新升级的 `IRIS C IRIS A` 的 `ISCAgent`。 1. 推广 `IRIS C`,选择 `IRIS A` 作为故障转移伙伴。 `IRIS C` 被提升为故障转移成员,从 `IRIS A` 的代理获取最新的日志数据,并成为主要成员。 2. 重新启动 `IRIS A` 上的 `IRIS` 实例,它作为备份重新加入镜像。 3. 在 `IRIS A` 重新加入镜像并变为活动状态后,可以使用使用升级的 DR 异步临时替换故障转移成员中描述的过程,将所有成员返回到它们以前的角色,首先是正常关闭 `IRIS C` ,然后在 `IRIS B` 的配置参数文件的 `[MirrorMember]` 部分中设置 `ValidatedMember=0`(请参阅配置参数文件参考中的 `[MirrorMember]`),将 `IRIS B` 重新启动为 `DR` 异步,将 `IRIS B` 提升为备份,并以 `DR` 异步方式重新启动 `IRIS C`。 注意:如果 `IRIS A` 的主机系统已关闭,但 `IRIS B` 的主机系统已启动,尽管其 `IRIS` 实例未运行,请按照手动故障转移到活动备份中所述在 `IRIS B` 上运行 `^MIRROR` 例程以确定 是否`IRIS B` 在发生故障时是一个活动备份。如果是这样,使用前面的过程,但在升级期间选择 `IRIS B` 作为故障转移伙伴,允许 `IRIS C` 从 `IRIS B` 的 `ISCAgent` 获取最新的日志数据。 ## 使用来自日志文件的日志数据进行 DR 提升和手动故障转移 如果 `IRIS A` 和 `IRIS B` 的主机系统都已关闭,但可以访问 `IRIS A` 的日志文件,或者 `IRIS B` 的日志文件和消息日志可用,您可以使用最新的日志数据更新 `IRIS C`从升级前的初级开始,使用以下过程。 1. 使用 `IRIS A` 或 `IRIS B` 的最新日志文件更新 `IRIS C`,如下所示: - 如果 `IRIS A` 的日志文件可用,则将最新的镜像日志文件从 `IRIS A` 复制到 `IRIS C`,从 `IRIS C` 上的最新日志文件开始,并包括来自 `IRIS A` 的任何后续文件。例如,如果 `MIRROR -MIRRORA-20180220.001` 是 `IRIS C` 上的最新文件,复制 `MIRROR-MIRRORA-20180220.001` 和 `IRIS A` 上的任何更新文件。 - 如果 `IRIS A` 的日志文件不可用但 `IRIS B` 的日志文件和消息日志可用: 1. 确认`IRIS B`很可能已被捕获,如下所示: a. 确认当`A`及其代理不可用时,`B`同时断开与 A的连接。可以通过在`Messages.log`文件中搜索类似于以下内容的消息来检查 `IRIS B`断开连接的时间: ```java MirrorClient: Primary AckDaemon failed to answer status request ``` b. 通过在其 `messages.log` 文件中搜索类似于以下内容的消息,确认 IRIS B 在断开连接时是活动备份: ```java Failed to contact agent on former primary, can't take over ``` 注意:`messages.log` 文件中的如下消息表明 `IRIS B` 在断开连接时未处于活动状态: ```java nonactive Backup is down ``` 当无法确认它是否已被追上时强制提升的 `DR` 异步成为主数据库可能会导致它成为主数据库而没有镜像生成的所有日志数据。因此,一些全局更新操作可能会丢失,而其他镜像成员可能需要从备份中重建。 2. 如果可以确认 `IRIS B` 处于活动状态,请将最新的镜像日志文件从 `IRIS B` 复制到 `IRIS C`,从 `IRIS C` 上的最新日志文件开始,然后包括来自 `IRIS B` 的所有后续文件。例如,如果 `MIRROR-MIRRORA-20180220.001` 是 `InterSystems IRIS C` 上的最新文件,请从 `IRIS C` 复制 `MIRROR-MIRRORA-20180220.001` 和任何更新的文件。检查文件的权限和所有权,并在必要时更改它们以匹配现有日志文件。 2. 在不选择故障转移合作伙伴的情况下将 `IRIS C` 提升为故障转移成员。 `IRIS C` 成为主要的。 3. 当 `IRIS A` 和 `IRIS B` 的问题得到修复时,尽早并在重新启动 `IRIS` 之前,在每个成员上的 `IRIS` 实例的配置参数文件的 `[MirrorMember]` 部分中设置 `ValidatedMember = 0`(参见 `[ MirrorMember]` 在配置参数文件参考)。说明指出,此更改是必需的。完成此操作后,在每个成员上重新启动 `IRIS`,从 `IRIS A`(最近成为主成员的成员)开始。 1. 如果成员在 `IRIS` 重新启动时作为备份或 `DR` 异步加入镜像,则不需要进一步的步骤。任何在故障成员上但不在当前主成员上的日志数据都已被丢弃。 2. 如果在 `IRIS` 实例重新启动时成员无法加入镜像,如重建镜像成员中描述的引用不一致数据的消息日志消息所示,则成员上的最新数据库更改晚于存在于上的最新日志数据 `IRIS C` 成为主要时。要解决此问题,请按照该部分中的描述重建成员。 4. 在大多数情况下,`DR` 异步系统不是主要故障转移成员的合适永久主机。在 `IRIS A` 和 `IRIS B` 重新加入镜像后,使用使用升级的 `DR` 异步临时替换故障转移成员中描述的过程将所有成员返回到它们以前的角色。如果 `IRIS A` 或 `IRIS B` 作为备份重新启动,则在备份处于活动状态时从正常关闭 `IRIS C` 开始,以故障转移到备份;如果 `IRIS A` 或 `IRIS B` 都重新启动为 `DR` 异步,将其中一个提升为备份,然后在 `IRIS C` 上执行正常关闭。将另一个以前的故障转移成员提升为备份,然后将 `IRIS C` 作为 `DR` 异步重启。
文章
姚 鑫 · 八月 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` 区域设置 — 国家语言支持区域设置。 - 此会话的首选语言 - 管理门户已本地化并可显示的语言的下拉列表。可以通过从下拉列表中选择新的语言来更改显示语言。最初,浏览会话的首选语言是为浏览器指定的语言,如果不支持浏览器语言,则为英语;在特定浏览器中选择首选语言后,即使更改了浏览器语言,该浏览器中的管理门户也会使用该语言。
文章
Guangliang Zhang · 十月 21, 2022

基于cconsole.log的cache数据库的实时监控

cache数据库自身带有系统监控Portal界面,但需要运维人员定期主动查看才能获取监控信息。当系统故障发生时,容易出现由于没有及时获取故障信息而不能及时处理,从而导致造成的影响扩大。本文研究通过解析cache数据库控制台日志(cconsole.log)进行监控信息获取并主动推送微信或短信实现cache数据库主动实时监控。 cache数据库在运行时会将所有控制台消息包括一般消息、系统错误、某些操作系统错误和网络错误都会发送到控制台日志文件,通过操作员控制台工具从其他系统远程启动的作业的成功或失败等信息也会写入控制台日志,因此通过对控制台日志的解析即可获取所需要监控信息。具体步骤方法如下: 解析控制台日志 控制台日志默认存储在install-dir\mgr路径下。 根据cache版本不同,使用的读取方法也不同。对于cache2016版本以上,系统提供了EnsLib.SQL.Snapshot类,可以直接获取日志的行和列信息,非常方便。对于cache2010及以下版本则无此方法,需要使用%File文件读取方法。 随着系统运行时间增加,控制台日志也会不断增大,造成每次检索时间加大,且不容易找出最近的监控信息,作出有效监控。较好的方式是每次解析时均从上次的节点继续进行,以便获取新的有效的监控信息。可以使用指定global来记录每次执行的最后一行ID,下次执行时从ID+1行开始解析来达到目的。对于控制台日志,EnsLib.SQL.Snapshot方法里提供了方法以获取当前行,对于cache2010及以下版本,则需要记录最后一次处理的内容,再次处理的时先找到本条内容的位置,从此位置继续处理。代码示例如下。 cache2016及以上版本代码示例: ClassMethod GetAlertInfo() As %String{ //cconsole.log存储路径 set FilePath="D:\InterSystems\HealthShare\mgr\cconsole.log" //使用EnsLib.SQL.Snapshot读取日志 set snap=##class(EnsLib.SQL.Snapshot).%New() set snap.MaxRowsToGet=-1 do snap.ImportFile(FilePath,," ") do Consolelogshow do snap.Clean() quit $$$OK //处理日志内容Consolelogshow set colsCount=snap.ColCount //获取上一次处理的最后一行行号 set snap.%CurrentRow=$g(^AlertCurrentRow("Consolelog","CurrentRow")) k ^AlertCurrentRow("Consolelog","PID") while snap.Next() { set alertLevel=snap.GetData(3) set pid=snap.GetData(2) set pid=$p(pid,"(",2) set pid=$p(pid,")",1) continue:pid="" //0级别信息不处理 continue:((alertLevel=0)) //进程号已存在不处理,即只获取每次每个进程的第一条告警/错误信息 continue:($d(^AlertCurrentRow("Consolelog","PID",pid))) //记录已处理的进程号 set ^AlertCurrentRow("Consolelog","PID",pid)=1 //定义并组装监控消息内容 set alertInfo="" i ((alertLevel=1)||(alertLevel=2)||(alertLevel=3)){ f i=4:1:colsCount{ set alertInfo=alertInfo_" "_snap.GetData(i) } set alertInfo=snap.GetData(1)_":"_alertLevel_":"_alertInfo continue:((alertLevel=1)&&(alertInfo'["Warning")) //发送监控信息至信息推送平台,如微信或短信等 set rt=..sendAlertInfo(alertInfo) } //记录当前处理的行 set ^AlertCurrentRow("Consolelog","CurrentRow")=snap.%CurrentRow } quit $$$OK} cache2010及以下版本代码示例: ClassMethod GetAlertInfo() As %String{ //使用%File读取日志 set file=##class(%File).%New("/cachesys/mgr/cconsole.log") set Status= file.Open("R") k ^AlertCurrentRow("Consolelog","PID") //获取上次处理的信息内容 set LastPosition=$g(^AlertCurrentRow("Consolelog","LastPosition")) s:LastPosition="" LastPosition=0 set LastPosition=LastPosition+1 set int=0 while 'file.AtEnd{ i int=0{ //跳转到上次处理的最后位置 do file.MoveTo(LastPosition) } set file.LineTerminator=$c(10) set text= file.ReadLine(,.sc) set int=int+1 //解析日志 set pid=$p(text," ",2) set pid=$p(pid,"(",2) set pid=$p(pid,")",1) set alertLevel=$p(text," ",3) s:text'="" textLast=text //定义并组装监控消息内容 i ((alertLevel=1)&&(alertLevel["Warning"))||(alertLevel=2)||(alertLevel=3){ set length=$length(text," ") set alert="",alertInfo="" f i=4:1:length{ set alert=$p(text," ",i) i i=4{ set alertInfo=alert }else{ set alertInfo=alertInfo_" "_alert } } set alertInfo=$p(text," ",1)_": "_alertLevel_": "_pid_":"_alertInfo set alertInfo=$zcvt(alertInfo,"O","UTF8") i '$d(^AlertCurrentRow("Consolelog","PID",pid)){ //记录已处理的进程号 set ^AlertCurrentRow("Consolelog","PID",pid)=1 //发送监控信息至信息推送平台,如微信或短信等 set rt=..sendAlertInfo(alertInfo) } } } //记录最后处理的行内容 set ^AlertCurrentRow("Consolelog","LastPosition")=file.FindAt(1,textLast) do file.Close() quit $$$OK} 控制台日志在记录信息时,经常会记录一系列问题。记录模式为时间+进程ID+信息级别+详细信息。如图1所示。信息级别从0级到3级,问题严重程度依次增加。为0的一般为正常运行信息,也是占日志主要部分的内容。问题级别为1的为警告信息。问题级别为2的为错误信息。问题级别为3的为严重错误信息。因此信息级别为1以上均是监控需要关注的信息。需要将此部分信息及时发出。 图1 cconsole.log普通记录示例 有时候控制台日志也会有其它格式的信息出现,如图2所示。此类信息说明有比较严重的异常行为出现,需要重点关注,因为一般将其全部发出。 图2 cconsole.log特殊记录示例 监控信息推送 控制台日志会对一个进程的多个操作进行记录,如果将所有信息发出,则会造成信息量过大而找不到重点。一般将每个进程的第一个告警或错误信息发出即可。处理方式见上述示例代码。 将获取到所需要发送的监控信息调取微信(如图3)或短信(如图4)或其它信息推送终端的接口即可进行信息推送。 图3 微信告警示例 图4 短信告警示例 任务部署 将程序部署成定时任务,按照所需自定义监控频率即可实现cache数据库的主动实时监控。如图5所示。 图5 监控任务示例 确实升级后的Cache强了很多 这个极实用,推荐大伙收藏。 实用,收藏