清除过滤器
文章
Michael Lei · 七月 4, 2021
(ECP) Caché 出色的可用性和扩展特性之一是企业缓存协议 (ECP)。 在应用程序开发过程中,如对使用 ECP 的分布式处理加以考虑,可以横向扩展 Caché 应用程序的架构。 应用程序处理可以调整为非常高的速率,处理能力从单个应用程序服务器扩展到最多 255 个应用程序服务器,并且不需要任何应用程序更改。
在我参与的 TrakCare 部署中,ECP 已广泛使用多年。 十年前,主要供应商之一的一台“大型”x86 服务器可能总共只有八个核心。 对于大型部署来说,ECP 是横向扩展商业服务器处理能力的方式,不适合单台昂贵的大型企业服务器。 即使是高核心数的企业服务器也有限制,因此 ECP 也用于扩展这些服务器上的部署。
如今,大多数的新 TrakCare 部署或升级到当前硬件_不需要 ECP_ 即可扩展。 目前的双插槽 x86 生产服务器可以拥有数十个核心和巨大容量的内存。 我们看到,在最近的 Caché 版本中,TrakCare 以及许多其他 Caché 应用程序具有可预测的线性扩展能力,能够随着单台服务器中 CPU 核心数量和内存的增加而支持逐渐增多的用户和事务。 在现场,我看到大多数的新部署都是虚拟化的,即使如此,虚拟机也可以根据需要扩展到主机服务器的规模。 如果资源需求超过单个物理主机可以提供的资源,则使用 ECP 进行横向扩展。
- ___提示:___ _为了简化管理和部署规模,在部署 ECP 之前,先在单台服务器内扩展。_
在本帖中,我将展示一个示例架构以及 ECP 工作原理的基础知识,然后评论性能注意事项,重点是存储。
有关配置 ECP 和应用程序开发的具体信息,请参见在线的 [Caché 分布式数据管理指南](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GDDM),并且[社区上有一个 ECP 学习轨迹](https://community.intersystems.com/learning-track/enterprise-cache-protocol-ecp-videos)。
ECP 的其他关键特性之一是提高了应用程序可用性,有关详细信息,请参见 [Caché 高可用性指南](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GHA_ecp)中的 ECP 部分。
[本系列其他帖子的列表](https://cn.community.intersystems.com/post/intersystems-数据平台的容量规划和性能系列文章)
# ECP 架构基础知识
ECP 的架构和运行在概念上很简单,ECP 提供了在多个服务器系统之间有效共享数据、锁定和可执行代码的方法。 从应用程序服务器角度看,数据和代码远程存储在_数据服务器_上,但缓存在_应用程序服务器_的本地内存中,以提供对活动数据的有效访问,同时尽可能减少网络流量。
数据服务器管理对磁盘上持久性存储的数据库读写,而多个应用程序服务器是解决方案的主力,执行大多数应用程序处理。
## 多层架构
ECP 采用多层架构。 描述处理层和它们扮演的角色有多种不同的方式,以下是我在描述基于 Web 浏览器的 Caché 应用程序时发现很有用的方式,也是我的帖子的模型和术语。 我知道可能有不同的方法来细分层级,但现在先使用我的方法 :)
基于浏览器的应用程序(例如 Caché Server Pages (CSP))使用多层架构,其中表示、应用程序处理和数据管理功能在逻辑上是分开的。 具有不同角色的__逻辑__“服务器”填充各层。 逻辑服务器不必保留在单独的物理主机或虚拟服务器上,出于成本效益和可管理性的考虑,部分甚至全部逻辑服务器可能位于单个主机或操作系统实例上。 随着部署规模的扩展,服务器可以通过 ECP 划分到多个物理或虚拟主机上,从而可根据需要分散处理工作负载,而无需更改应用程序。
主机系统可以是物理的或虚拟化的,具体取决于容量和可用性要求。 以下层和逻辑服务器构成了一个部署:
- _表示层:_包括在基于浏览器的客户端和应用程序层之间充当网关的 Web 服务器。
- _应用程序层:_这是 ECP 应用程序服务器所在的位置。 如上文所述,这是一个逻辑模型,其中应用程序服务器不必与数据服务器分开,而且除了最大型的站点外,所有情况下通常都不需要分开。 该层还可能包括进行专门处理的其他服务器,如报告服务器。
- _数据层:_这是数据服务器所在的位置。 数据服务器执行事务处理,是存储在 Caché 数据库中的应用程序代码和数据存储库。 数据服务器负责读写持久性磁盘存储。
## 逻辑架构
下图是一个基于浏览器的应用程序在部署为三层架构时的逻辑视图:
尽管初看之下该架构可能很复杂,但构成它的组件仍然与安装在单台服务器上的 Caché 系统的组件相同,只是逻辑组件安装在多个物理或虚拟服务器上。 服务器之间的所有通信都通过 TCP/IP 进行。
### 逻辑视图中的 ECP 操作
上图从顶部开始,显示用户安全地连接到多个已进行负载平衡的 Web 服务器。 这些 Web 服务器在客户端和应用程序层(应用程序服务器)之间传递 CSP 网页请求,应用程序层进行所有处理,允许动态创建内容,并通过 Web 服务器将完成的页面返回给客户端。
在这个三层模型中,应用程序处理通过 ECP 分散到多个应用程序服务器上。 应用程序只将数据(您的应用程序数据库)视为应用程序服务器的本地数据。
当应用程序服务器发出数据请求时,它将尝试从本地缓存满足请求,如果不能满足,ECP 将向数据服务器请求必要的数据,数据服务器自己的缓存可能会满足请求,否则将从磁盘获取数据。 数据服务器对应用程序服务器的回复包括存储该数据的数据库块。 这些块将被使用,并且此时将缓存到应用程序服务器上。 ECP 自动负责管理整个网络中的缓存一致性,并将变化传播回数据服务器。 客户端会体验到快速响应,因为它们经常使用本地缓存的数据。
默认情况下,Web 服务器与首选的应用程序服务器通信,确保同一应用程序服务器满足相关数据的后续请求,因为这些数据可能已经在本地缓存中。
- ___提示:___ _如 [Caché 文档](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GDDM_develop#GDDM_develop_considerations)中详述,在循环或负载平衡方案中,应避免用户连接到应用程序服务器,因为这会影响应用程序服务器上缓存的优势。 理想情况下,相同的用户或用户组保持连接到同一应用程序服务器。_
该解决方案通过在表示层添加 Web 服务器和在应用程序层添加其他应用程序服务器来进行扩展,无需用户停机。 数据层通过增加数据服务器的 CPU 和内存来进行扩展。
## 物理架构
下图显示了与三层逻辑架构示例相同的三层部署中使用的物理主机示例:
请注意,在每一层部署物理或虚拟主机时均采用 n+1 或 n+2 模式,以确保在主机故障或计划维护时保持 100% 能力。 由于用户分布在多个 Web 和应用程序服务器上,单个服务器故障只会影响少量用户,他们会自动重新连接到其余服务器之一。
数据管理层具有高度可用性,例如,位于连接到一个或多个存储阵列的故障转移集群上(例如,虚拟化 HA、InterSystems 数据库镜像或传统的故障转移集群)。 如果硬件或服务出现故障,集群将在其中一个幸存节点上重启服务。 ECP 的一个附加好处是内置弹性,并且在数据库节点集群发生故障转移时能保持事务完整性,应用程序用户将观察到处理暂停,直到故障转移和自动恢复完成,随后用户将无缝继续,不会断开连接。
同样的架构也可以映射到虚拟化服务器,例如,VMware vSphere 可用于虚拟化应用程序服务器。
# ECP 容量规划
如上文所述,数据服务器管理对持久性磁盘的数据库读写,而多个应用程序服务器是解决方案的主力,执行大多数应用程序处理。 这是考虑系统资源容量规划时的一个关键概念,总结来说:
- ___数据服务器___(有时称为数据库服务器)通常执行很少的应用程序处理,因此_对 CPU 要求低_,但该服务器执行大部分存储 IO,因此可能有_非常高的存储 IOPS_,即数据库读写以及日志写入(稍后将详细介绍日志 IO)。
- ___应用程序服务器___执行大多数应用程序处理,因此_对 CPU 要求高_,但存储 IO 非常少。
通常,调整 ECP 服务器 CPU、内存和 IO 要求的规则与调整非常大的单服务器解决方案的规则相同,同时考虑 N+1 或 N+2 台服务器以确保高可用性。
## 基本 CPU 和存储规模调整:
假设 My_Application 需要最多 72 个 CPU 核心进行应用程序处理(记得还要考虑余量),并且预计在写入守护进程周期期间需要 20,000 次写入,以及 10,000 次随机数据库读取的持续峰值。
一个简单的虚拟或物理服务器规模调整方案为:
- 4 台 32 CPU 应用程序服务器(3 台服务器 + 1 台服务器用于确保 HA)。 低 IOPS 要求。
- 2 台 10 CPU 数据服务器(镜像或集群以确保 HA)。 [低延迟 IOPS 要求](https://cn.community.intersystems.com/post/数据平台和性能-第-6-部分-caché-存储-io-配置文件)为 20K 写入、10K 读取,加上 WIJ 和日志。
虽然数据服务器只执行非常少的处理,但考虑到系统和 Caché 进程,将其规模调整为 8-10 个 CPU。 应用程序服务器的规模可以根据每台物理主机的最佳性价比和/或可用性来进行调整。 横向扩展时会有一些效率损失,但通常可以在服务器块中增加处理能力,并预计吞吐量有近乎线性的增长。 限制更有可能首先在存储 IO 中出现。
- ___提示:____与确保 HA 一样,要考虑主机、机箱或机架故障的影响。 在 VMWare 上虚拟化应用程序和数据服务器时,确保应用 vSphere DRS 和相关性规则以分散处理负载并确保可用性。_
## 日志同步 IO 要求
ECP 部署的另一个容量规划注意事项是,由于日志同步,它们需要较高 IO,并且对存储响应时间的要求非常严格,以保持数据服务器上日志记录的可伸缩性 。 同步请求可以触发对日志中最后一个块的写入,以确保数据耐久性。
不过您的情况可能有所不同;在一个典型的以高事务处理速率运行的客户站点上,我经常看到非 ECP 配置上的日志写入 IOPS 为每秒十几次。 在繁忙的系统上使用 ECP 时,由于 ECP 强制日志同步,可以在日志磁盘上看到 100 到 1000 的写入 IOPS。
- ___提示:____如果在繁忙的系统上显示 mgstat 或查看 [pButtons](https://cn.community.intersystems.com/post/intersystems-数据平台和性能-–-第-1-篇) 中的 mgstat,您将看到 Jrnwrts(日志写入次数),您将在存储 IO 资源规划中对其加以考虑。 在 ECP 数据服务器上,还有未显示在 mgstat 中的对日志磁盘的日志同步写入,要了解这些信息,您需要查看日志磁盘的操作系统指标,例如使用 iostat 查看_。
### 什么是日志同步?
需要日志同步的原因:
- 确保在数据服务器发生故障时数据的耐久性和可恢复性。
- 它们也是确保应用程序服务器之间的缓存一致性的触发器。
在非 ECP 配置中,对 Caché 数据库的修改将写入日志缓冲区(128 x 64K 缓冲区),当日志缓冲区满时或每两秒由日志守护程序写入磁盘上的日志文件。 Caché 为整个缓冲区分配 64k,并且这些缓冲区总是被重复使用,而不是被销毁和重新创建,Caché 只是跟踪末尾偏移量。 在大多数情况下(除非一次进行大量更新),日志写入次数非常小。
ECP 系统中也有日志同步。 日志同步可以定义为将当前日志缓冲区的相关部分重新写入磁盘,以确保磁盘上的日志始终是最新的。 因此,日志同步会请求多次重新写入同一日志块的某个部分(大小在 2k 到 64k 之间)。
ECP 客户端上可以触发日志同步请求的事件为更新(SET 或 KILL)或 LOCK。 例如,对于每个 SET 或 KILL,都会将当前日志缓冲区写入(或重新写入)磁盘。 在非常繁忙的系统中,单次同步操作中的日志同步可能被捆绑或延迟为多个同步请求。
### 日志同步的容量规划
为确保持续的吞吐量,日志同步的平均写入响应时间必须:
- _
文章
Lele Yang · 六月 8, 2023
++ 更新:2018 年 8 月 1 日
使用内置于 Caché 数据库镜像的 InterSystems 虚拟 IP (VIP) 地址有一定的局限性。特别是,它只能在镜像成员驻留在同一网络子网时使用。当使用多个数据中心时,由于增加了网络复杂性( 此处有更详细的讨论),网络子网通常不会“延伸”到物理数据中心之外。出于类似的原因,当数据库托管在云端时,虚拟 IP 通常无法使用。
负载均衡器(物理或虚拟)等网络流量管理设备可用于实现相同级别的透明度,为客户端应用程序或设备提供单一地址。网络流量管理器自动将客户端重定向到当前镜像主服务器的真实 IP 地址。自动化旨在满足灾难后 HA 故障转移和 DR 升级的需求。
网络流量管理器的集成
当今市场上有许多支持网络流量重定向的选项。这些中的每一个都支持类似甚至多种方法来根据应用程序要求控制网络流量。为了简化这些方法,我们考虑了三个类别:数据库服务器调用 API、网络设备轮询或两者的组合。
下一节将概述这些方法中的每一个,并就如何将这些方法与 InterSystems 产品集成提供指导。在所有情况下,仲裁器都用于在镜像成员无法直接通信时提供安全的故障转移决策。可以在此处找到有关仲裁器的详细信息。
出于本文的目的,示例图将描述 3 个镜像成员:主机、备份和 DR 异步。但是,我们知道您的配置可能比这更多或更少。
选项 1:网络设备轮询(推荐)
在这种方法中,网络负载均衡设备使用其内置的轮询机制与两个镜像成员通信以确定主镜像成员。
使用 2017.1 中可用的 CSP 网关的mirror_status.cxw页面的轮询方法可以用作 ELB 健康监视器中对添加到 ELB 服务器池的每个镜像成员的轮询方法。只有主镜像会响应“SUCCESS”,从而将网络流量仅定向到活动的主镜像成员。
此方法不需要向 ^ZMIRROR 添加任何逻辑。请注意,大多数负载均衡网络设备对运行状态检查的频率都有限制。通常,最高频率不少于 5 秒,这通常可以接受以支持大多数正常运行时间服务级别协议。
对以下资源的 HTTP 请求将测试本地缓存配置的镜像成员状态。
/csp/bin/mirror_status.cxw
对于所有其他情况,这些镜像状态请求的路径应该使用与请求真实 CSP 页面所用的相同的层次机制解析到适当的缓存服务器和名称空间。
示例:测试 /csp/user/ 路径中应用程序配置服务的镜像状态:
/csp/user/mirror_status.cxw
注意:调用镜像状态检查不会消耗 CSP 许可证。
根据目标实例是否是活动主机,网关将返回以下 CSP 响应之一:
** 成功(是主镜像成员)
===============================
HTTP/1.1 200 OK
Content-Type: text/plain
Connection: close
Content-Length: 7
SUCCESS
** 失败(不是主镜像成员)
===============================
HTTP/1.1 503 Service Unavailable
Content-Type: text/plain
Connection: close
Content-Length: 6
FAILED
** 失败(Caché服务器不支持Mirror_Status.cxw请求)
===============================
HTTP/1.1 500 Internal Server Error
Content-Type: text/plain
Connection: close
Content-Length: 6
FAILED
考虑下图作为轮询的示例。
同步故障转移镜像成员之间自动发生故障转移:
下图演示了将 DR 异步镜像成员提升到负载均衡池中,这通常假设同一个负载均衡网络设备正在为所有镜像成员提供服务(地理分割方案将在本文后面介绍)。根据标准 DR 程序,灾难恢复成员的提升涉及人为决策,然后是数据库级别的简单管理操作。但是,一旦采取该操作,就不需要对网络设备执行任何管理操作:它会自动发现新的主要设备。
选项 2:数据库服务器调用 API
在这种方法中,使用了网络流量管理设备,它有一个用故障转移镜像成员和潜在的 DR 异步镜像成员定义的服务器池。
当镜像成员成为主镜像成员时,向网络设备发出 API 调用以调整优先级或权重,以立即指示网络设备将网络流量定向到新的主镜像成员。
相同的模型适用于在主镜像成员和备份镜像成员都不可用的情况下提升 DR 异步镜像成员。
此 API 在 ^ZMIRROR 代码中定义为过程调用的一部分: $$CheckBecomePrimaryOK^ZMIRROR()
在此过程调用中,插入可用于相应网络设备的任何 API 逻辑和方法,例如 REST API、命令行界面等。与虚拟 IP 一样,这是网络配置的突然更改,不涉及任何应用程序逻辑以通知连接到故障主镜像成员的现有客户端正在发生故障转移。根据故障的性质,这些连接可能由于应用程序超时或错误、新主实例强制旧主实例关闭或客户端使用的TCP 保持活动计时器过期造成的故障本身而关闭。
因此,用户可能必须重新连接并登录。您的应用程序的行为将决定此行为。
选项 3:地理分散部署
在具有多个数据中心和可能地理分散的部署(例如具有多个可用性区域和地理区域的云部署)的配置中,需要使用基于 DNS 的负载均衡和本地负载均衡在一个简单且易于支持的模型中考虑地理重定向实践。
通过这种组合模型,引入了与 DNS 服务配合使用的附加网络设备,如 Amazon Route 53、F5 Global Traffic Manager、Citrix NetScaler Global Server Load Balancing 或 Cisco Global Site Selector,在每个数据中心、可用性区域或云地理区域与网络负载均衡器相结合。
在此模型中,前面提到的轮询(推荐)或 API 方法在本地用于操作任何镜像成员(故障转移或 DR 异步)的位置。这用于向地理/全球网络设备报告它是否可以将流量定向到任一数据中心。同样在此配置中,本地网络流量管理设备将其自己的 VIP 提供给地理/全球网络设备。
在正常稳定状态下,活动主镜像成员向本地网络设备报告它是主镜像成员并提供“启动”状态。此“启动”状态被转发到地理/全球设备以调整和维护 DNS 记录,以将所有请求转发到此活动的主镜像成员。
在同一数据中心内的故障转移场景中(备份同步镜像成员成为主镜像成员),API 或轮询方法与本地负载均衡器一起使用,现在重定向到同一数据中心内的新主镜像成员。由于新的主镜像成员处于活动状态,因此本地负载均衡器仍以“启动”状态响应,因此未对地理/全局设备进行任何更改。
出于本示例的目的,API 方法在下图中用于本地集成到网络设备。
在使用 API 或轮询方法到不同数据中心(备用数据中心中的同步镜像或 DR 异步镜像成员)的故障转移场景中,新提升的主镜像成员开始向本地网络设备报告为主要成员。
在故障转移期间,曾经包含主镜像成员的数据中心现在不再从本地负载均衡器向地理/全球报告“Up”。地理/全球设备不会将流量定向到该本地设备。备用数据中心的本地设备将向地理/全球设备报告“Up”,并将调用 DNS 记录更新以现在定向到备用数据中心的本地负载均衡器提供的虚拟 IP。
选项 4:多层和地理分散的部署
为了使解决方案更进一步,引入了一个单独的 Web 服务器层,既可以作为私有 WAN 的内部,也可以通过 Internet 访问。此选项可能是大型企业应用程序的典型部署模型。
以下示例显示了使用多个网络设备安全隔离和支持 Web 和数据库层的示例配置。在此模型中,使用了两个地理位置分散的位置,其中一个位置被视为“主要”位置,另一个位置纯粹是数据库层的“灾难恢复”位置。数据库层灾难恢复位置将在主要位置因任何原因停止服务的情况下使用。此外,此示例中的 Web 层将显示为双活,这意味着用户将根据各种规则(例如最低延迟、最低连接数、IP 地址范围或您认为合适的其他路由规则)定向到任一位置。
如上例所示,如果在同一位置发生故障转移,则会发生自动故障转移,并且本地网络设备现在指向新的主机。用户仍然连接到任一位置的 Web 服务器, Web 服务器及其关联的 CSP 网关继续指向位置 A。
在下一个示例中,考虑在位置 A 发生的整个故障转移或中断,其中主要和备份故障转移镜像成员都无法使用。然后,DR 异步镜像成员将被手动提升为主要和备份故障转移镜像成员。在升级后,新指定的主镜像成员将允许位置 B 的负载均衡设备使用前面讨论的 API 方法(轮询方法也是一个选项)报告“Up”。由于本地负载均衡器现在报告“启动”,基于 DNS 的设备将识别这一点并将流量从位置 A 重定向到现在的位置 B 以用于数据库服务器服务。
结论
在没有虚拟 IP 的情况下设计镜像故障转移有许多可能的排列。这些选项可应用于最简单的高可用性场景或具有多层的多地理区域部署,包括故障转移和 DR 异步镜像成员,以获得高可用性和容灾解决方案,旨在为您的应用程序维持最高水平的运营弹性.
希望本文提供了一些关于成功部署具有故障转移的数据库镜像的可能的不同组合和用例的见解,这些组合和用例适合您的应用程序和可用性要求。
文章
姚 鑫 · 六月 29, 2021
# 第二十二章 计算XPath表达式
`XPath`(XML路径语言)是一种基于XML的表达式语言,用于从XML文档获取数据。使用类中的`%XML.XPATH.Document`,可以轻松地计算`XPath`表达式(给定提供的任意XML文档)。
注意:使用的任何XML文档的XML声明都应该指明该文档的字符编码,并且文档应该按照声明的方式进行编码。如果未声明字符编码,InterSystems IRIS将使用本书前面的“输入和输出的字符编码”中描述的默认值。如果这些默认值不正确,请修改XML声明,使其指定实际使用的字符集。
## IRIS中XPath表达式求值概述
要使用InterSystems IRIS XML支持使用任意XML文档计算`XPath`表达式,请执行以下操作:
1. 创建`%XML.XPATH.Document`的实例。为此,请使用以下类方法之一:`CreateFromFile()`、`CreateFromStream()`或`CreateFromString()``。使用这些方法中的任何一种,都可以将输入XML文档指定为第一个参数,并接收%XML.XPATH.Document`的一个实例作为输出参数。
这一步使用内置的XSLT处理器解析XML文档。
2. 使用`%XML.XPATH.Document`实例的`EvaluateExpression()`方法。对于此方法,需要指定节点上下文和要计算的表达式。
节点上下文指定要在其中计算表达式的上下文。这使用`XPath`语法来表示到所需节点的路径。例如:
```java
"/staff/doc"
```
要计算的表达式还使用`XPath`语法。例如:
```java
"name[@last='Marston']"
```
可以将结果作为输出参数(作为第三个参数)接收。
注意:如果要迭代一大组文档并计算每个文档的`XPath`表达式,建议在处理完文档后,在打开下一个文档之前将该文档的`OREF`设置为`NULL`。这绕过了第三方软件的一个限制。在循环中处理大量文档时,此限制会导致CPU使用率略有增加。
# 创建XPath文档时的参数列表
若要在创建`%XML.XPATH.Document`的实例,请使用该类的`CreateFromFile()`、`CreateFromStream()`或`CreateFromString()`类方法。对于这些类方法,完整的参数列表按顺序如下:
1. PSource、pStream或pString-源文档。
- 对于`CreateFromFile()`,此参数是文件名。
- 对于`CreateFromStream()`,此参数是二进制流。
- 对于`CreateFromString()`,此参数是一个字符串。
2. PDocument-作为输出参数返回的结果。这是`%XML.XPATH.Document`的实例。
3. PResolver-解析源时使用的可选实体解析器。
4. PErrorHandler-一个可选的自定义错误处理程序。
5. PFlags-控制SAX解析器执行的验证和处理的可选标志。
6. PSchemaSpec-可选的架构规范,用于验证文档源。此参数是一个字符串,其中包含以逗号分隔的命名空间/URL对列表:
```java
"namespace URL,namespace URL"
```
这里,`Namespace`是用于模式的XML名称空间,URL是提供模式文档位置的URL。名称空间和URL值之间有一个空格字符。
7. PPrefix Mappings-可选的前缀映射字符串。
`CreateFromFile()`、`CreateFromStream()`和`CreateFromString()`方法返回应检查的状态。例如:
```java
Set tSC=##class(%XML.XPATH.Document).CreateFromFile("c:\sample.xml",.tDocument)
If $$$ISERR(tSC) Do $System.OBJ.DisplayError(tSC)
```
# 为默认命名空间添加前缀映射
当XML文档使用默认名称空间时,这会给`XPath`带来问题。请考虑以下示例:
```xml
Mr. Marston
Mr. Bertoni
Mr. Leslie
Ms. Farmer
```
在本例中, `` 元素属于名称空间,但没有名称空间前缀。`XPath`不提供访问 `` 元素的简单方法。
- 可以设置`%XML.XPATH.Document`实例的`Prefix Mappings`属性。该属性旨在为源文档中的每个默认名称空间提供唯一的前缀,以便`XPath`表达式可以使用这些前缀,而不是使用完整的名称空间URI。
`PrefixMappings` 属性是一个由逗号分隔的列表组成的字符串;每个列表项都是一个前缀,后跟一个空格,后跟一个命名空间URI。
- 调用`CreateFromFile()`、`CreateFromStream()`或`CreateFromString()`时,可以指定`PrefixMappings`参数。此字符串的格式必须与前面描述的相同。
然后以与使用任何名称空间前缀相同的方式使用这些前缀。
例如,假设将前面的XML读入`%XML.XPATH.Document`的实例时,按如下方式指定了前缀映射:
```java
"s http://www.staff.org"
```
在本例中,可以使用`"/s:staff/s:doc"`访问`` 元素。
请注意,可以使用实例方法`GetPrefix()`来获取先前为文档中的给定路径指定的前缀。
# 计算XPath表达式
要计算`XPath`表达式,请使用`%XML.XPATH.Document`实例的`EvaluateExpression()`方法。对于此方法,请按顺序指定以下参数:
1. PContext-节点上下文,指定在其中计算表达式的上下文。指定一个字符串,该字符串包含指向所需节点的路径的`XPath`语法。例如:
```java
"/staff/doc"
```
2. PExpression-选择特定结果的谓词。指定包含所需XPath语法的字符串。例如:
```java
"name[@last='Marston']"
```
注意:对于其他技术,通常的做法是将谓词连接到节点路径的末尾。类中的`%XML.XPATH.Document`不支持此语法,因为基础XSLT处理器需要节点上下文和谓词作为单独的参数。
3. PResults-作为输出参数返回的结果。
`EvaluateExpression()`方法返回应该检查的状态。例如:
```java
Set tSC=tDoc.EvaluateExpression("/staff/doc","name[@last='Smith']",.tRes)
If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC)}
```
# 使用XPath结果
XPath表达式可以返回XML文档的一个子树、多个子树或标量结果。在`%XML.XPATH.Document`的`EvaluateExpression()`方法旨在处理所有这些情况。具体地说,它返回一个结果列表。该列表中的每个项目都有一个Type属性,该属性具有下列值之一:
- `$$$XPATHDOM`-指示该项包含XML文档的子树。此项目是`%XML.XPATH.DOMResult`实例,提供导航和检查子树的方法。
- `$$$XPATHVALUE`-指示该项是单个标量结果。此项目是`%XML.XPATH.ValueResult`实例。
这些宏在`%occXSLT.inc`包含文件中定义。
## 检查XML子树
本节介绍如何导航由`%XML.XPATH.DOMResult`表示的XML子树,以及如何获取有关您在该子树中当前位置的信息。
### 导航子树
要导航`%XML.XPATH.DOMResult`的实例,可以使用该实例的以下方法:`Read()`、`MoveToAttributeIndex()`、`MoveToAttributeName()`、`MoveToElement()`和`Rewind()`。
要移动到文档中的下一个节点,请使用`read()`方法。`Read()`方法返回TRUE值,直到没有更多节点可读为止(即,直到到达文档末尾)。
导航到某个元素时,如果该元素具有属性,则可以使用以下方法导航到这些属性:
- 使用`MoveToAttributeIndex()`方法按索引(属性在元素中的序号位置)移动到特定属性。此方法只有一个参数:属性的索引号。请注意,可以使用`AttributeCount`属性来了解给定元素有多少个属性。
- 使用`MoveToAttributeName()`方法按名称移动到特定属性。此方法有两个参数:属性名称和命名空间URI(可选)。
完成当前元素的属性后,可以通过调用其中一个导航方法(如`read()`)移动到文档中的下一个元素。或者,可以调用`MoveToElement()`方法返回到包含当前属性的元素。
这里描述的所有方法都在文档中前进,但`Rewind()`方法除外,它导航到文档的开头并重置所有属性。
### 节点的属性
除`Type`属性外,`%XML.XPATH.DOMResult`的以下属性还提供有关当前位置的信息。
#### AttributeCount
如果当前节点是元素,则此属性指示元素的属性数。
#### EOF
如果读取器已到达源文档的末尾,则为true;否则为false。
#### HasAttributes
如果当前节点是一个元素,则如果该元素具有属性,则此属性为true(如果没有属性,则为false)。如果当前节点是属性,则此属性为true。
对于任何其他类型的节点,此属性为False。
#### HasValue
如果当前节点是具有值的节点类型(即使该值为空),则为True。否则,此属性为false。
#### LocalName
对于属性或元素类型的节点,这是当前元素或属性的名称,不带命名空间前缀。对于所有其他类型的节点,此属性为`NULL`。
#### Name
当前节点的完全限定名称,视节点类型而定。
#### NodeType
当前节点的类型,如下之一:`attribute`, `chars`, `cdata`, `comment`, `document`, `documentfragment`, `documenttype`, `element`, `entity`, `entityreference`, `notation`,或处理指令。
#### Path
对于元素类型的节点,这是到元素的路径。
对于所有其他类型的节点,此属性为空。
#### ReadState
表示总体读状态,有以下几种:
- `“initial”`表示`Read()`方法还没有被调用。
- `“cursoractive”`意味着`Read()`方法至少被调用过一次。
- `“eof”`表示已经到达文件的末尾。
#### Uri
当前节点的URI。
返回的值取决于节点的类型。
#### Value
值(如果有的话),适合于节点类型。
如果该值小于`32kb`,则为字符串。
否则,它是一个字符流。
## 检查标量结果
本节介绍在类中使用由`%XML.XPATH.ValueResult`表示的`XPath`结果。除`Type`属性外,该类还提供`Value`属性。
请注意,如果该值的长度大于32KB,则会自动将其放入流对象中。除非确定将收到的结果类型,否则应该检查`Value`是否为流对象。为此,可以使用`$IsObject`函数。(也就是说,如果此值是对象,则它是流对象,因为它是唯一可以是对象的类型。)
```java
// 如果结果长度大于32KB,则值可以是流
Set tValue=tResult.Value
If $IsObject(tValue){
Write ! Do tValue.OutputToDevice()
} else {
Write tValue
}
```
## 一般方法
除非可以确定在计算`XPath`表达式时会收到什么样的结果,否则应该编写代码来处理最常见的情况。代码的可能组织如下:
1. 查找返回结果列表中的元素数量。遍历此列表。
2. 对于每个列表项,检查`Type`属性。
- 如果`Type`为`$$$XPATHDOM`,, 在类中使用`%XML.XPATH.DOMResult`的方法导航并检查此XML子树。
- 如果`Type`为`$$$XPATHVALUE`,请检查`Value`属性是否为流对象。如果是流对象,则使用常用的流接口访问数据。否则,`Value`属性为字符串。
# 示例
本节中的示例针对以下`XML`文档计算`XPath`表达式:
```xml
Yao Xin
Mr. Bertoni
Mr. Leslie
Ms. Farmer
Ms. Midy
Mr. Dick
Mr. Boag
Mr. Curcuru
Mr. Kesselman
Mr. Auriemma
```
## 计算具有子树结果的XPath表达式
```java
/// 计算返回DOM Result的XPath表达式
ClassMethod Example1()
{
Set tSC=$$$OK
do {
Set tSC=##class(%XML.XPATH.Document).CreateFromFile(filename,.tDoc)
If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC) Quit}
Set context="/staff/doc"
Set expr="name[@last='Marston']"
Set tSC=tDoc.EvaluateExpression(context,expr,.tRes)
If $$$ISERR(tSC) Quit
Do ##class(%XML.XPATH.Document).ExampleDisplayResults(tRes)
} while (0)
If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC)}
Quit
}
```
本例选择了``元素的`last`属性等于`Yao`的任何节点。
该表达式在``元素的``节点中计算。
请注意,此示例使用`%XML.XPATH.Document`的`ExampleDisplayResults()`类方法。
执行`example1()`方法时,将先前的XML文件作为输入提供,您会看到以下输出:
```java
DHC-APP>d ##class(PHA.TEST.Xml).Example1("E:\temp\xmlXpath.txt")
XPATH DOM
element: name
attribute: first Value: Xin
attribute: last Value: Yao
chars : #text Value: Yao Xin
```
## 计算具有标量结果的`XPath`表达式
下面的类方法读取XML文件并计算返回标量结果的XPath表达式:
```java
/// 计算返回值结果的XPath表达式
/// d ##class(PHA.TEST.Xml).Example2("E:\temp\xmlXpath.txt")
ClassMethod Example2(filename)
{
Set tSC=$$$OK
do {
Set tSC=##class(%XML.XPATH.Document).CreateFromFile(filename,.tDoc)
If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC) Quit}
Set tSC=tDoc.EvaluateExpression("/staff","count(doc)",.tRes)
If $$$ISERR(tSC) Quit
Do ##class(%XML.XPATH.Document).ExampleDisplayResults(tRes)
} while (0)
If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC)}
Quit
}
```
这个例子统计``子节点。
该表达式在``元素中求值。
当执行`Example2()`方法,提供前面的XML文件作为输入时,会看到以下输出:
```java
DHC-APP> d ##class(PHA.TEST.Xml).Example2("E:\temp\xmlXpath.txt")
XPATH VALUE
2
```
文章
姚 鑫 · 四月 19, 2021
# 第三章 优化表(二)
# 调整表计算值
调优表操作根据表中的代表性数据计算和设置表统计信息:
- `ExtentSize`,它可能是表中的实际行数(行数),也可能不是。
- 表中每个属性(字段)的选择性。
可以选择性地阻止单个属性的选择性计算。
- 属性的离群选择性,其中一个值比其他值出现得更普遍。
有效的查询可以利用离群值优化。
- 标识某些属性特征的每个属性的注释。
- 每个属性的平均字段大小。
- 表的SQL `Map Name`、`BlockCount`和`Source of BlockCount`。
## 区段大小和行计数
从管理门户运行Tune Table工具时,`ExtentSize`是表中当前行的实际计数。默认情况下,`GatherTableStats()`方法还将实际行数用作`ExtentSize`。当表包含大量行时,最好对较少的行执行分析。可以使用SQL tune table命令并指定`%SAMPLE_PERCENT`来仅对总行的一定百分比执行分析。在针对包含大量行的表运行时,可以使用此选项来提高性能。此`%SAMPLE_PERCENT`值应该足够大,以便对代表性数据进行采样。如果`ExtentSize`。
如果`TuneTable`返回异常值选择性,则正常选择性仍然是整个行集内每个非异常值数据值的百分比。例如,如果在`1000`个随机选择的值中检测到`11`个不同的值,其中一个是异常值,则选择性为`1/11(9.09%)`:平均每个条目出现的几率为十一分之一。如果异常值选择性是`80%`,常规选择性是`1%`,那么除了异常值之外,还可以找到大约`20((1-0.80)/0.01)`个额外的非异常值。
如果优化表初始采样仅返回单个值,但附加采样返回多个不同的值,则这些采样结果会修改正常选择性。例如,990个值的初始随机采样仅检测一个值,但后续采样检测其他不同值的10个单个实例。在这种情况下,初始离群值会影响选择性值,该值现在被设置为`1/1000(0.1%)`,因为10个非离群值中的每一个在1000个记录中只出现一次。
异常值选择性的最常见示例是允许`NULL`的属性。如果某个特性具有`NULL`的记录数大大超过该特性具有任何特定数据值的记录数,则`NULL`为异常值。以下是`FavoriteColors`字段的选择性和异常值选择性:
```java
SELECTIVITY of FIELD FavoriteColors
CURRENT = 1.8966%
CALCULATED = 1.4405%
CURRENT OUTLIER = 45.0000%, VALUE =
CALCULATED OUTLIER = 39.5000%, VALUE =
```
如果一个字段只包含一个不同的值(所有行都具有相同的值),则该字段的选择性为`100%`。选择性为`100%`的值不被视为异常值。调谐表通过采样数据来建立选择性和异常值选择值。为了确定这一点,优选表首先测试少量或几条记录,如果这些记录都具有相同的字段值,它将测试多达`100,000`条随机选择的记录,以支持非索引字段的所有值都相同的假设。只有在字段已编制索引,字段是索引的第一个字段,并且字段和索引具有相同的排序规则类型的情况下,优化表才能完全确定该字段的所有值是否相同。
- 如果已知未编制索引的字段具有在测试`100,000`条随机选择的记录中可能检测不到的其他值,则应手动设置选择性和离群值选择性。
- 如果已知非索引字段没有其他值,则可以手动指定`100%`的选择性,删除任何异常值选择性,并设置`CALCSELECTIVITY=0`以防止优选表尝试计算选择性或将此值指定为异常值。
要修改这些选择性、异常值选择性和异常值计算值,请从调谐表显示中选择单个字段。这会在显示屏右侧的详细信息区域中显示该字段的这些值。可以将选择性、异常值选择性和/或异常值修改为更适合预期完整数据集的值。
- 可以将选择性指定为带有百分号(`%`)的行的百分比,也可以指定为整数行(没有百分号)。如果指定为整数行数,InterSystems IRIS将使用区大小来计算选择性百分比。
- 可以为以前没有异常值的字段指定异常值选择性和异常值。将异常值选择性指定为带百分号(`%`)的百分比。如果仅指定异常值选择性,则Tune Table假定异常值为``。如果仅指定异常值,则除非还指定异常值选择性,否则调谐表不会保存此值。
## CALCSELECTIVITY参数与不计算选择性
在某些情况下,可能不希望优化表工具计算属性的选择性。要防止计算选择性,请将属性的`CALCSELECTIVITY`参数的值指定为`0`(默认值为`1`)。在Studio中,可以在“新建属性向导”的“属性参数”页上设置`CALCSELECTIVITY`,也可以在检查器中的属性参数列表中设置`CALCSELECTIVITY`(可能需要收缩并重新展开属性参数列表才能显示它)。
应该指定`CALCSELECTIVITY=0`的一种情况是,如果该字段未编制索引,则已知该字段在所有行中只包含一个值(`选择性=100%`)。
## 离群值的优化
默认情况下,查询优化器假定查询不会选择离群值。
例如,查询通常选择特定的字段值并从数据库返回少量记录,而不是返回大量记录,其中该字段值是离群值。
查询优化器总是使用选择性来构造查询计划,除非执行一些要求考虑离群选择性的操作。
根据选择离群值,可以执行以下几个操作来调整查询优化:
- 如果异常值是``,则在查询`WHERE`子句中为该字段指定一个`is null`或`is NOT null`条件。
这将导致查询优化器在构造查询时使用离群值选择性。
- 如果离群值是一个数据值,查询优化器会假定选择的字段值不是离群值。
例如,总部位于马萨诸塞州的公司的员工记录可能有`Office_State`字段离群值`MA` (`Massachusetts`)。
优化器假设查询不会选择`' MA '`,因为这将返回数据库中的大多数记录。
但是,如果正在编写一个查询来选择离群值,可以通过将离群值封装在双括号中来通知优化器。
在该字段上查询时,指定一个`WHERE`子句,如下所示`:WHERE Office_State=(('MA'))`。
这种技术抑制了文字替换,并迫使查询优化器在构建查询计划时使用离群值选择性。
对于动态SQL查询,以及在使用ODBC/JDBC提供的InterSystems IRIS之外编写的查询,这种语法是必需的。
对于类查询、嵌入式SQL查询或通过视图访问的查询,则不需要这样做。
- 根据参数值SQL设置配置系统范围的优化查询。
该选项为离群值设置了运行时计划选择(RTPC)优化和作为离群值(BQO)优化的偏差查询的适当组合。
注意,更改此配置设置将清除所有名称空间中的所有缓存查询。
使用管理门户,选择System Administration、Configuration、SQL和Object Settings、SQL来查看和更改此选项。
可用的选择有:
- 假设查询参数值不是字段离群值(`BQO=OFF`, `RTPC=OFF`,初始默认值)
- 假设查询参数值经常匹配字段离群值(`BQO=ON`, `RTPC=OFF`)
- 在运行时优化实际查询参数值(`BQO=OFF`, `RTPC=ON`)
要确定当前设置,调用`$SYSTEM.SQL.CurrentSettings()`。
- 覆盖查询的系统范围配置设置。
通过指定`%NORUNTIME restrict`关键字,可以覆盖单个查询的`RTPC`。
如果查询`SELECT Name,HaveContactInfo FROM t1 WHERE HaveContactInfo=?`
将导致`RTPC`处理,查询`SELECT %NORUNTIME Name,HaveContactInfo FROM t1 WHERE HaveContactInfo=?`
将覆盖RTPC,从而产生一个标准的查询计划。
通过指定注释选项`/*#OPTIONS {"BiasAsOutlier":1} */`,可以覆盖偏见查询作为单个查询的离群值。
## “备注”列
管理门户优化表信息选项为每个字段显示一个备注列。此字段中的值是系统定义的,不可修改。它们包括以下内容:
- `RowID`字段:一个表有一个`RowID`,由系统定义。它的名称通常是ID,但可以有不同的系统分配的名称。由于其所有值(根据定义)都是唯一的,因此其选择性始终为1。如果类定义包括`SqlRowIdPrivate`,则`Notes`列值为`RowID`字段、`Hidden`字段。
- 隐藏字段:隐藏字段定义为私有,`SELECT*`不显示。默认情况下,`CREATE TABLE`将`RowID`字段定义为隐藏;可以指定`%PUBLICROWID`关键字以使`RowID`不隐藏和公开。默认情况下,由持久化类定义定义的表将`RowID`定义为非隐藏;可以指定`SqlRowIdPrivate`将`RowID`定义为隐藏和私有。容器字段定义为隐藏。
- 流字段:表示使用流数据类型定义的字段,可以是字符流(`CLOB`),也可以是二进制流(`BLOB`)。流文件没有平均字段大小。
- 父引用字段:引用父表的字段。
注释列中未标识标识字段、`ROWVERSION`字段、序列字段或`UNIQUEIDENTIFIER(GUID)`字段。
## 平均字段大小
运行调谐表根据当前表格数据集计算所有非流字段的平均字段大小(以字符为单位)。这与`AVG($length(Field))`相同(除非另有说明),四舍五入到小数点后两位。可以更改各个字段的平均字段大小,以反映字段数据的预期平均大小。
- NULL:因为`$LENGTH`函数将`NULL`字段视为长度为0,所以将长度为0的`NULL`字段取平均值。这可能会导致平均字段大小小于一个字符。
- 空列:如果列不包含数据(所有行都没有字段值),则平均字段大小值为1,而不是0。对于不包含数据的列,`AVG($length(Field))`为0。
- `ExtentSize=0`:将`ExtentSize`设置为0时,所有字段的平均字段大小将重置为0。
- 逻辑字段值:平均字段大小始终根据字段的逻辑(内部)值计算。
- 列表字段:InterSystems IRIS列表字段根据其逻辑(内部)编码值计算。此编码长度大于列表中元素的总长度。
- 容器字段:集合的容器字段大于其集合对象的总长度。例如,在`Sample.Person`中,`Home`容器字段的`Average Field` Size大于`Home_Street`、`Home_City`、`Home_State`和`Home_Zip`平均字段大小的总和。
- 流字段:流字段没有平均字段大小。
如果特性/字段的特性参数`CALCSELECTIVITY`设置为0,则调谐表不会计算该特性/字段的平均字段大小。
可以通过从调谐表显示中选择单个字段来修改平均字段大小计算值。这将在显示屏右侧的详细信息区域中显示该字段的值。可以将“平均字段大小”修改为更适合预期的完整数据集的值。由于设置此值时优化表不执行验证,因此应确保该字段不是流字段,并且指定的值不大于最大字段大小(`MaxLen`)。
平均字段大小还显示在管理门户目录详细信息选项卡字段选项表中。必须已为字段选项表运行了调整表,才能显示平均字段大小值。
## map BlockCount选项卡
调优表Map `BlockCount`选项卡显示SQL映射名称、`BlockCount`(作为正整数)和`BlockCount`的来源。
块计数的来源可以在类定义中定义、由类编译器估计或由TuneTable度量。
将类编译器估计的调优表更改运行到TuneTable测量;
它不影响在类定义中定义的值。
通过从调优表显示中选择单个SQL映射名称,可以修改`BlockCount`计算值。
这将在显示器右侧的详细信息区域中显示该地图名称的块计数。
可以将块计数修改为一个更适合预期的完整数据集的值。
因为在设置该值时,Tune Table不执行验证,所以应该确保块计数是一个有效值。
修改`BlockCount`会将`BlockCount`的来源更改为类定义中定义的。
# 导出和重新导入调优表统计信息
可以从一个表或一组表导出调优表统计信息,然后将这些调优表统计信息导入一个表或一组表。
以下是可能希望执行此导出/导入的三种情况。
(为简单起见,这些描述了从单个表导出/导入统计数据;
在实际使用中,通常会从多个相互关联的表中导出/导入统计数据):
- 为生产系统建模:生产表完全填充了实际数据,并使用`Tune table`进行优化。
在测试环境中,创建的表具有相同的表定义,但数据少得多。
通过从生产表导出调优表统计信息并将它们导入测试表,可以在测试表上对生产表优化建模。
- 要复制生产系统:生产表完全填充了实际数据,并使用tune Table进行了优化。将创建具有相同表定义的第二个生产表。(例如,生产环境及其备份环境,或者多个相同的表定义,每个表包含不同医院的患者记录。)。通过从第一个表导出调优表统计信息并将其导入第二个表,您可以为第二个表提供与第一个表相同的优化,而无需第二次运行调优表或等待第二个表填充有代表性的数据。
- 要恢复到以前的统计信息集:可以通过运行tune Table或显式设置统计信息来创建表的优化统计信息。通过导出这些统计信息,可以在尝试其他统计信息设置时保留它们。一旦确定了最佳统计信息集,就可以将它们重新导入到表中。
可以使用`$SYSTEM.SQL.Stats.Table.Export()`方法将调优表统计信息导出到`XML`文件。此方法可以导出名称空间中一个、多个或所有表的优化表统计信息,如以下示例所示:
```java
DO $SYSTEM.SQL.Stats.Table.Export("C:\AllStats.xml")
/* 导出当前命名空间中所有架构/表的TuneTable统计信息 */
```
```java
DO $SYSTEM.SQL.Stats.Table.Export("C:\SampleStats.xml","Sample")
/* 导出Sample模式中所有表的可调统计信息 */
```
```java
DO $SYSTEM.SQL.Stats.Table.Export("C:\SamplePStats.xml","Sample","P*")
/* 导出Sample模式中所有以字母“P”开头的表的可调统计信息 */
```
```java
DO $SYSTEM.SQL.Stats.Table.Export("C:\SamplePersonStats.xml","Sample","Person")
/* 导出Sample的可调统计信息Person */
```
可以使用`$SYSTEM.SQL.Stats.Table.Import()`方法重新导入使用`$SYSTEM.SQL.Stats.Table.Import()`方法导出的调优表统计信息。
`$SYSTEM.SQL.Stats.Table.Import()`有一个`KeepClassUpToDate boolean`选项。
如果为真(并且`update`为真),`$SYSTEM.SQL.Stats.Table.Import()`将用新的`EXTENTSIZE`和选择性值更新类定义,但类定义将保持最新。
但是,在许多情况下,最好在调优了类表之后重新编译类,这样类定义中的查询就可以重新编译,SQL查询优化器就可以使用更新后的数据统计信息。
默认值为`FALSE(0)`。请注意,如果该类已部署,则类定义不会更新。
`$SYSTEM.SQL.Stats.Table.Import()`有一个`ClearCurrentStats boolean`选项。
如果为`TRUE`, `$SYSTEM.SQL.Stats.Table.Import()`将在导入统计信息之前从现有表中清除所有先前的区段大小、选择性、块计数和其他调优表统计信息。
如果您想要完全清除导入文件中没有指定的那些表状态,而不是让它们在表的`persistent`类中定义,则可以使用此方法。
默认值是`FALSE(0)`。
如果`$SYSTEM.SQL.Stats.Table.Import()`没有找到相应的表,它将跳过该表并继续导入文件中指定的下一个表。
如果找到了一个表,但是没有找到一些字段,那么这些字段将被跳过。
无法继承类存储定义中映射的`BlockCount`。`BlockCount`只能出现在映射起源的类的存储定义中。如果映射源自超类,则`$SYSTEM.SQL.Stats.Table.Import()`仅设置投影表的`BlockCount`元数据,而不设置类存储`BlockCount`元数据。
文章
姚 鑫 · 四月 28, 2021
# 第八章 解释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语句:

这将显示包含原始查询的语句哈希的`Statement`列和包含生成的查询版本的语句哈希的`ParentHash`列。
`%PARALLEL`查询的SQL语句的计划状态为“未冻结/并行”,不能被冻结。
- 不包含FROM子句(因此不引用任何表)的查询仍然创建SQL语句。
例如:`SELECT $LENGTH('this string')`创建一个SQL语句,表列值`%TSQL_sys.snf`。
文章
Michael Lei · 六月 23, 2021
部分 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 的使用经验或任何其他系统架构话题。
文章
Nicky Zhu · 一月 11, 2021
本文将描述通过ObjectScript包管理器(见https://openexchange.intersystems.com/package/ObjectScript-Package-Manager-2)运行单元测试的过程,包括测试覆盖率测量(见https://openexchange.intersystems.com/package/Test-Coverage-Tool)。
## ObjectScript中的单元测试
关于在ObjectScript中编写单元测试,已经有很好的文档,因此我就不再赘述了。您可以在这里找到单元测试教程:https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=TUNT_preface
最好的做法是将单元测试代码单独放在源代码树中,无论它只是“/tests”还是其他名字。在InterSystems中,我们最终使用/internal/testing/unit_tests/作为我们事实上的标准,这是有意义的,因为测试是内部/非发布的,而且除了单元测试还有其他类型的测试,但这对于简单的开源项目来说可能有点复杂。您可以在我们的一些GitHub仓库中看到这种结构。
从工作流的角度来看,这在VSCode中非常简单,您只需创建目录并将类放在里面。对于较老的以服务器为中心的源代码控制方法(Studio中使用的方法),您需要正确地地映射这个包,使用的方法会因源代码控制程序而异。
从单元测试类命名的角度来看,我个人的偏好(以及我的团队的最佳实践)是:
UnitTest.[.]
例如,如果在类MyApplication.SomeClass 中对方法Foo进行单元测试,单元测试类将被命名为UnitTest.MyApplication.SomeClass.Foo;如果测试是针对整个类的,那么名字就是UnitTest.MyApplication.SomeClass。
## ObjectScript 包管理器中的单元测试
让ObjectScript包管理器知道您的单元测试,很简单!只需按如下所示向module.xml中添加一行代码(来自https://github.com/timleavitt/ObjectScript-Math/blob/master/module.xml - 这是Open Exchange上的@Peter Steiwer的出色数学扩展包,我以它作为简单的正面例子):
```objectscript
...
```
这些代码的意思是:
- 单元测试位于模块根目录下的“tests”目录中。
- 单元测试在“UnitTest.Math”包中。这样很直观,因为被测试的类就在“Math”包中。
- 单元测试在包生命周期的“测试”阶段运行。(当然还有一个可以运行它们的“验证”阶段,这里不赘述。)
## 运行单元测试
对于上述定义的单元测试,包管理器提供了一些实用工具来运行它们。您仍然可以像平常使用%UnitTest.Manager那样设置^UnitTestRoot等,但下面的方法可能更简单,尤其在同一个环境下做几个项目的时候。
您可以克隆上述objectscript-math仓库,然后用 ```zpm "load /path/to/cloned/repo/" ```加载,或者在您自己的包上使用包名(和测试名)替换“objectscript-math”来尝试所有这些方法。
重新加载模块,然后运行所有单元测试:
```zpm "objectscript-math test" ```
只运行单元测试(不重新加载):
```zpm "objectscript-math test -only" ```
只运行单元测试(不重新加载)并提供详细输出:
```zpm "objectscript-math test -only -verbose" ```
运行一个特定的测试套件(指一个测试目录 - 在本例中,是UnitTest/Math/Utils中的所有测试)而不重新加载,并提供详细输出:
```zpm "objectscript-math test -only -verbose -DUnitTest.Suite=UnitTest.Math.Utils" ```
运行一个特定的测试用例(在本例中,是UnitTest.Math.Utils.TestValidateRange)而不重新加载,并提供详细输出:
```zpm "objectscript-math test -only -verbose -DUnitTest.Case=UnitTest.Math.Utils.TestValidateRange" ```
如果您只是想解决单个测试方法中的小问题:
```zpm "objectscript-math test -only -verbose -DUnitTest.Case=UnitTest.Math.Utils.TestValidateRange -DUnitTest.Method=TestpValueNull" ```
## 通过ObjectScript包管理器进行测试覆盖率测量
怎样评估单元测试的质量?测量测试覆盖率虽然并不全面,但至少有参考意义。早在2018年的全球峰会上,我就展示过。 见 - https://youtu.be/nUSeGHwN5pc。
首先需要安装“测试覆盖率”包:
```zpm "install testcoverage" ```
注意,并不需要ObjectScript包管理器才能安装/运行;可以在Open Exchange上了解更多信息:https://openexchange.intersystems.com/package/Test-Coverage-Tool
不过如果您已经在用ObjectScript包管理器,那么您可以更好地利用这个“测试覆盖率”工具。
运行测试前,需要指定测试所覆盖的类/routine宏。这一点很重要,因为在非常大的代码库中(例如,HealthShare),测试和收集项目中所有文件的测试覆盖率所需要的内存可能超出您的系统内存。(提一句,如果您感兴趣,可以使用逐行监视器的gmheap。)
文件列表在您的单元测试根目录下的coverage.list文件中;单元测试的不同子目录(套件)可以拥有它们自己的副本,以覆盖在测试套件运行时将跟踪的类/例程。
有关objectscript-math的简单示例,见:https://github.com/timleavitt/ObjectScript-Math/blob/master/tests/UnitTest/coverage.list;测试覆盖率工具用户指南有更详细的介绍。
要在启用测试覆盖率测量的情况下运行单元测试,只需再向命令添加一个参数,指定应使用TestCoverage.Manager而非%UnitTest.Manager 来运行测试:
```zpm "objectscript-math test -only -DUnitTest.ManagerClass=TestCoverage.Manager" ```
输出(即使是非详细模式)将包括一个URL,供您查看您的类/routine(宏)的哪些行被单元测试覆盖了,以及一些汇总统计信息。
## 接下来的步骤
这些能不能在CI中自动化?能不能报告单元测试的结果和覆盖率分数/差异?答案是:能!您可以使用Docker,Travis CI和codecov.io来试一下这个简单示例,见https://github.com/timleavitt/ObjectScript-Math;我打算以后写篇文章来详细讲讲,介绍几种不同的方法。
文章
姚 鑫 · 五月 13, 2021
# 第五章 管理全局变量(二)
# 在全局变量中查找值
“查找全局变量字符串”页使可以在下标或选定全局变量的值中查找给定的字符串。
要访问和使用此页,请执行以下操作:
1. 显示“全局变量”页。
2. 选择要使用的全局变量。为此,请参阅“全局页简介”一节中的步骤2和3。
3. 单击查找按钮。
4. 对于查找内容,输入要搜索的字符串。
5. (可选)清除大小写匹配。默认情况下,搜索区分大小写。
6. 单击Find First或Find All。
然后,页面显示选定全局变量中下标或值包含给定字符串的第一个节点或所有节点。该表左侧显示了节点下标,右侧显示了相应的值。
7. 如果使用的是Find First,请根据需要单击Find Next以查看下一个节点。
8. 完成后,单击关闭窗口。
## 执行批量更换
注意:在进行任何编辑之前,请确保知道IRIS使用哪个全局系统,以及应用程序使用哪个全局系统;参见“一般建议”此选项会永久更改数据。不建议在生产系统中使用。
出于开发目的,“查找全局字符串”页面还提供了对全局节点中的值进行整体更改的选项。要使用此选项:
1. 显示“全局”页面。
2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
3. 单击替换按钮。
4. 使用此页面查找上一节中描述的值。
5. 为“替换为”指定一个值。
6. 单击全部替换。
7. 单击确定确认此操作。然后,页面会显示变更的预览。
8. 如果结果可以接受,请单击保存。
9. 单击确定确认此操作。
# 导出全局变量
注意:因为导入全局是非常容易的(这是一个不可逆转的变化),所以最好只导出需要导入的全局。请注意,如果导出所有全局变量,导出将包括所有包含代码的全局变量。请确保知道IRIS使用哪些全局系统,以及应用程序使用哪些全局系统;
“导出全局”页面允许导出全局。
要访问和使用此页面:
1. 显示“全局”页面。
2. 指定要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
3. 单击导出按钮。
4. 指定要将全局文件导出到的文件。为此,请在输入服务器上的导出路径和名称字段中输入文件名(包括其绝对或相对路径名),或者单击浏览并导航到该文件。
5. 使用字符集列表选择导出文件的字符集。
6. 在页面的中央框中:选择输出格式,选择记录格式
7. 选择或清除“在此检查”以在后台运行导出...
8. 单击导出。
9. 如果文件已经存在,请单击“确定”用新版本覆盖它。
导出会创建一个. gof文件。
# 导入全局变量
注意:在导入任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。将全局导入现有全局(从而合并数据)后,无法将全局恢复到其以前的状态。
“导入全局”页面允许导入全局。要访问和使用此页面:
1. 显示“全局”页面。
2. 单击导入按钮。
3. 指定导入文件。为此,请在输入导入文件的路径和名称字段中输入文件(包括其绝对或相对路径名),或者单击浏览并导航到该文件。
4. 使用字符集列表选择导入文件的字符集。
5. 选择下一步。
6. 使用表中的复选框选择要导入的全局。
7. 也可以选择在后台运行导入。如果选择此项,任务将在后台运行。
8. 单击导入。
# 删除全局变量
注意:在删除任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。无法恢复已删除的全局。
“删除全局”页面允许删除全局。要访问和使用此页面:
1. 显示“全局”页面。
2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
3. 单击删除按钮。
4. 单击确定确认此操作。
# 管理任务的应用程序接口
InterSystems IRIS还提供了以下应用编程接口来执行本章中描述的一些任务:
- 类`%SYSTEM.OBJ`提供了以下方法:
- `Export()`使能够将全局导出到一个XML文件。
- `Load()`和`LoadDir()`使能够导入包含在`XML`文件中的全局。
这两者都可以通过$SYSTEM变量获得,例如:`$SYSTEM.OBJ.Export`
- 类别`%Library.Global`提供了以下方法:
- `Export()`使能够将全局导出到`.gof`和其他文件格式(不包括XML)。
- `Import()`使能够将全局导入到`.gof`和其他文件格式(不包括XML)。
`%Library.Global` 还提供了`Get()`类查询,根据给定的搜索条件,可以使用该查询来查找全局。

文章
姚 鑫 · 五月 13, 2021
# 第五章 管理全局变量(二)
# 在全局变量中查找值
“查找全局变量字符串”页使可以在下标或选定全局变量的值中查找给定的字符串。
要访问和使用此页,请执行以下操作:
1. 显示“全局变量”页。
2. 选择要使用的全局变量。为此,请参阅“全局页简介”一节中的步骤2和3。
3. 单击查找按钮。
4. 对于查找内容,输入要搜索的字符串。
5. (可选)清除大小写匹配。默认情况下,搜索区分大小写。
6. 单击Find First或Find All。
然后,页面显示选定全局变量中下标或值包含给定字符串的第一个节点或所有节点。该表左侧显示了节点下标,右侧显示了相应的值。
7. 如果使用的是Find First,请根据需要单击Find Next以查看下一个节点。
8. 完成后,单击关闭窗口。
## 执行批量更换
注意:在进行任何编辑之前,请确保知道IRIS使用哪个全局系统,以及应用程序使用哪个全局系统;参见“一般建议”此选项会永久更改数据。不建议在生产系统中使用。
出于开发目的,“查找全局字符串”页面还提供了对全局节点中的值进行整体更改的选项。要使用此选项:
1. 显示“全局”页面。
2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
3. 单击替换按钮。
4. 使用此页面查找上一节中描述的值。
5. 为“替换为”指定一个值。
6. 单击全部替换。
7. 单击确定确认此操作。然后,页面会显示变更的预览。
8. 如果结果可以接受,请单击保存。
9. 单击确定确认此操作。
# 导出全局变量
注意:因为导入全局是非常容易的(这是一个不可逆转的变化),所以最好只导出需要导入的全局。请注意,如果导出所有全局变量,导出将包括所有包含代码的全局变量。请确保知道IRIS使用哪些全局系统,以及应用程序使用哪些全局系统;
“导出全局”页面允许导出全局。
要访问和使用此页面:
1. 显示“全局”页面。
2. 指定要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
3. 单击导出按钮。
4. 指定要将全局文件导出到的文件。为此,请在输入服务器上的导出路径和名称字段中输入文件名(包括其绝对或相对路径名),或者单击浏览并导航到该文件。
5. 使用字符集列表选择导出文件的字符集。
6. 在页面的中央框中:选择输出格式,选择记录格式
7. 选择或清除“在此检查”以在后台运行导出...
8. 单击导出。
9. 如果文件已经存在,请单击“确定”用新版本覆盖它。
导出会创建一个. gof文件。
# 导入全局变量
注意:在导入任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。将全局导入现有全局(从而合并数据)后,无法将全局恢复到其以前的状态。
“导入全局”页面允许导入全局。要访问和使用此页面:
1. 显示“全局”页面。
2. 单击导入按钮。
3. 指定导入文件。为此,请在输入导入文件的路径和名称字段中输入文件(包括其绝对或相对路径名),或者单击浏览并导航到该文件。
4. 使用字符集列表选择导入文件的字符集。
5. 选择下一步。
6. 使用表中的复选框选择要导入的全局。
7. 也可以选择在后台运行导入。如果选择此项,任务将在后台运行。
8. 单击导入。
# 删除全局变量
注意:在删除任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。无法恢复已删除的全局。
“删除全局”页面允许删除全局。要访问和使用此页面:
1. 显示“全局”页面。
2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
3. 单击删除按钮。
4. 单击确定确认此操作。
# 管理任务的应用程序接口
InterSystems IRIS还提供了以下应用编程接口来执行本章中描述的一些任务:
- 类`%SYSTEM.OBJ`提供了以下方法:
- `Export()`使能够将全局导出到一个XML文件。
- `Load()`和`LoadDir()`使能够导入包含在`XML`文件中的全局。
这两者都可以通过$SYSTEM变量获得,例如:`$SYSTEM.OBJ.Export`
- 类别`%Library.Global`提供了以下方法:
- `Export()`使能够将全局导出到`.gof`和其他文件格式(不包括XML)。
- `Import()`使能够将全局导入到`.gof`和其他文件格式(不包括XML)。
`%Library.Global` 还提供了`Get()`类查询,根据给定的搜索条件,可以使用该查询来查找全局。

文章
TZ Zhuang · 二月 3, 2023
# 目的
这两个工具(RanRead 和 RanWrite)用于在数据库(或一对数据库)内生成随机读写事件,以测试每秒输入/输出的操作数 (IOPS)。它们可以一起使用或分开单独使用,以测试 IO 硬件容量、验证目标 IOPS 并确保系统拥有可接受的磁盘响应时间。从 IO 测试中收集的结果将因配置而异,具体取决于 IO 子系统。在运行这些测试之前,请确保相应的操作系统监控和存储级别监控已配置,这些捕获的 IO 性能指标可以为以后的分析提供帮助。我们推荐使用 IRIS 中捆绑的系统性能工具,例如^SystemPerformance。
请注意,这里使用的工具是对先前版本的更新。之前的版本可在[这里](https://community.intersystems.com/post/random-read-io-storage-performance-tool)找到。
# 安装
从 GitHub 下载 **PerfTools.RanRead.xml** 和 **PerfTools.RanWrite.xml** 工具 点击[这里](https://github.com/intersystems-community/perftools-io-test-suite)。
将工具导入 USER 命名空间。
USER> do $system.OBJ.Load("/tmp/PerfTools.RanRead.xml","ckf")
USER> do $system.OBJ.Load("/tmp/PerfTools.RanWrite.xml","ckf")
运行帮助方法以查看所有入口点。所有命令都在 USER 中运行。
USER> do ##class(PerfTools.RanRead).Help()
- do ##class(PerfTools.RanRead).Setup(Directory,DatabaseName,SizeGB,LogLevel)
创建具有相同名称的数据库和命名空间。日志级别必须在 0 到 3 的范围内,其中 0 是“无”,3 是“详细”。
- do ##class(PerfTools.RanRead).Run(Directory,Processes,Count,Mode)
运行随机读取 IO 测试。模式参数,1(默认)代表时间,以秒为单位 ,2是循环次数,用前面的 Count 参数控制。
- do ##class(PerfTools.RanRead).Stop()
终止所有后台作业。
- do ##class(PerfTools.RanRead).Reset()
删除先前运行的统计信息。在测试之间运行这个很重要,否则之前运行的统计数据将平均到当前运行的统计数据中。
- do ##class(PerfTools.RanRead).Purge(Directory)
删除同名的命名空间和数据库。
- do ##class(PerfTools.RanRead).Export(Directory)
将所有随机读取测试历史的摘要导出到逗号分隔的文本文件。
USER> do ##class(PerfTools.RanWrite).Help()
- do ##class(PerfTools.RanWrite).Setup(Directory,DatabaseName)
创建具有相同名称的数据库和命名空间。
- do ##class(PerfTools.RanWrite).Run(Directory,NumProcs,RunTime,HangTime,HangVariationPct,Global name length,Global node depth,Global subnode length)
运行随机写入 IO 测试。除目录外的所有参数都有默认值。
- do ##class(PerfTools.RanWrite).Stop()
终止所有后台作业。
- do ##class(PerfTools.RanWrite).Reset()
删除先前运行的统计信息。
- do ##class(PerfTools.RanWrite).Purge(Directory)
删除同名的命名空间和数据库。
- do ##class(PerfTools.RanWrite).Export(Directory)
将所有随机写入测试历史的摘要导出到逗号分隔的文本文件。
# 配置
创建一个名为 RAN 的空(预扩展)数据库,其大小至少是要测试的物理主机内存的两倍。同时确保这个空数据库至少是存储控制器缓存大小的四倍。数据库需要大于物理内存以确保读取的数据不会缓存在文件系统缓存中。您可以手动创建或使用以下方法自动创建命名空间和数据库。
USER> do ##class(PerfTools.RanRead).Setup("/ISC/tests/TMP","RAN",200,1)
Created directory /ISC/tests/TMP/
Creating 200GB database in /ISC/tests/TMP/
Database created in /ISC/tests/TMP/
注意:RanRead 和 RanWrite 可以使用相同的数据库。如果需要一次测试多个磁盘或用于特定目的,也可以使用分开的数据库。 RanRead 代码允许指定数据库的大小,但 RanWrite 代码不允许,因此最好使用 RanRead Setup 命令来创建所需的任何预先确定大小的数据库,即使要创建 RanWrite 做测试的数据库也可以。
# 方法论
从少量进程和 30-60 秒运行时间开始测试。然后增加进程数,例如从 10 个作业开始,然后增加 10、20、40 等。继续运行单个测试,直到响应时间始终超过 10 毫秒或计算出的 IOPS 不再以线性方式增加。
该工具使用 ObjectScript VIEW 命令读取内存中的数据库块,因此如果您没有获得预期的结果,则可能所有数据库块都已在内存中。
作为指南,全闪存阵列通常可以接受以下 8KB 和 64KB 数据库随机读取(非缓存)的响应时间:
* 平均 do ##class(PerfTools.RanRead).Export("/ISC/tests/TMP/ ")
Exporting summary of all random read statistics to /usr/iris/db/zranread/PerfToolsRanRead_20221023-1408.txt
Done.
# 分析
建议使用内置的 [SystemPerformance 工具](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCM_systemperf)来获取被分析的系统的真实情况。 SystemPerformance 的命令需要在 %SYS 命名空间中运行。要切换到那个命名空间,请使用 ZN 命令:
USER> ZN "%SYS"
要查找系统瓶颈的详细信息,或者如果需要系统如何以目标 IOPS 运行的更多详细信息,则应创建具有高频率数据采集的 SystemPerformance 配置文件:
%SYS> set rc=$$addprofile^SystemPerformance("5minhighdef","A 5-minute run sampling every second",1,300)
然后运行该配置文件(从 %SYS)并立即切换回 USER 并使用“job”而不是“do”来启动 RanRead 和/或 RanWrite:
%SYS> set runid=$$run^SystemPerformance("5minhighdef")
%SYS> ZN “USER”
USER> job ##class(PerfTools.RanRead).Run("/ISC/tests/TMP",5,60)
USER> job ##class(PerfTools.RanWrite).Run("/ISC/tests/TMP",1,60,.001)
然后可以等待 SystemPerformance 作业结束,并使用 [yaspe](https://github.com/murrayo/yaspe) 等工具分析生成的 html 文件。
# 清理
运行完测试后,需要删除历史记录:
%SYS> do ##class(PerfTools.RanRead).Reset()
文章
Michael Lei · 三月 2, 2023
在这里,您将找到一个在 IRIS 环境中使用 Python 的简单程序,以及另一个在 Python 环境中使用 ObjectScript 的简单程序。另外,我想分享一些我在学习实践时遇到的麻烦。
IRIS 环境中的 Python
比方说,您在 IRIS 环境中想要解决一个您认为使用 Python 更容易或更有效的问题。
您可以简单地更改环境:像创建任何其他方法一样创建您的方法,并在其名称和规范的末尾添加 [Language = python]:
您可以在该方法中使用任何类型的参数,并且要访问它们,您可以执行与在 COS 中完全相同的操作:
假设您有这个 %String 参数 Arg 和一个来自自定义类的参数 OtherArg。这个其他类可能具有标题和作者等属性。您希望像这样访问:
此方法提供如下输出:
而且,对于访问类方法,它几乎是一样的。假设我们在 Demo.Books.PD.Books 中有一个名为“CreateString”的方法,它将标题和作者连接成类似于“Title: <Title>; Author: <Author>”的内容。
将其添加到我们的 python 方法的末尾:
将提供以下输出:
(要访问该方法,您可以使用 OtherArg.CreateString(),但我选择将 OtherArg 中的相同值传递给 CreateString 方法,以便输出看起来相似并且代码看起来更简单)
Python 环境中的 ObjectScript
此外,在 Python 环境中,您也可能希望能用上ObjectScript 的代码或资源。
首先,您需要勾选此列表中的一些项目,以便能够以多种方式从 Python 环境访问您的 COS 文件(我不一定会在这里使用所有这些):
你有先决条件吗? 在这里查看
你可以使用 python 外部服务器吗? 在这里查看
你有你需要的驱动吗?在此处下载它们或在此处了解更多信息
如果您觉得有帮助,可以随时返回这些链接或检查我在首次使用 COS 创建 python 文件时遇到的错误。
那么让我们开始编程吧!
首先,我们必须将一些东西从 COS 调整到 Python。幸运的是,InterSystems 已经做到了,我们只需输入“import iris”即可访问所有内容!
在下一步中,我们创建到所需命名空间的连接,使用包含主机、端口和命名空间的路径 (host:port/namespace),并提供用户和密码:
请注意我们最后是如何创建一个 IRIS 对象的,因此我们可以使用此连接来访问该命名空间中我们想要的所有内容。
最后,就可以编写你想要写的内容:
您可以通过提供类名、方法名和参数来使用 irispy.classMethodValue() 访问方法,使用 .set() (用于属性)和许多其他可能性操作对象,同时按照您喜欢的方式处理 python 上的所有内容。
更多iris提供的功能和使用方法,查看Native SDK for Python简介
在这个例子中,我在第 16 行实例化了一个 Persistent 类,在接下来的几行中将它的属性 Title 和 Author 设置为指环王和 Tolkien。
在第 20 行,我从另一个类中调用了一个方法,该方法将对象保存到一个表中,如果有效则返回一个状态。最后,我在第 23 行打印状态。
混合环境
在 ObjectScript 环境中,您可能希望使用 Python 的已知库或您自己的带有函数和例程的自定义文件。
您可以将“导入”命令与 Numpy、SciPy 和任何您想要的东西一起使用(前提是您已正确安装它们: 在此处检查如何执行此操作)
而且,如果您想访问您的本地文件,有几种方法可以做到这一点,并且很容易找到相关教程,因为 Python 非常流行。
对我来说,最容易使用的是以下内容:
在这里,我从位于 C:/python 的文件 testesql.py 中导入了所有内容,并打印了 select() 函数的结果
彩蛋——我遇到的麻烦
SHELLS :使用 Shell 时,请记住 Windows PowerShell 是作为基于 UNIX 的系统工作的(因为它基于 .NET),而命令提示符将与官方文档中的 Windows 示例一起使用。对于一些更有经验的程序员来说,这听起来很基础,但如果您没有给予足够的重视,您可能会在这上面浪费很多时间,所以我发现写一些关于它的内容很重要。
用户和权限:在 Python 环境中使用 ObjectScript 进行编码的当前用户需要拥有命名空间资源的权限。请记住,如果您不选择任何用户,则当前为 UnknownUser,如果您不选择任何名称空间,则当前为 USER。因此,在最简单的访问中,您可能需要遵循:管理门户 > 系统管理 > 安全 > 用户 > 未知用户 > 角色并选择 %DB_USER 并保存。
我不知道发生了什么:要检查有关您遇到的错误的更多信息,您可能需要关注管理门户 > 系统资源管理器 > SQL 并键入“SELECT * FROM %SYS.Audit ORDER BY UTCTimeStamp Desc”以获取最近的审计。在那里你会找到错误的原因,比如 IRIS_ACCESSDENIED() 以及更多你甚至可能在 IRIS 环境之外得到的错误。
PYTHON COMPILING ERROR:您将希望避免方法名称,例如 try() 或已在 Python 中构建的函数。编译器不会理解从方法到函数的区别。
感谢您的阅读,请随时分享建议、评论、疑问或您正在开发的任何内容!
文章
姚 鑫 · 七月 12, 2022
# 第三章 嵌入式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 · 四月 17, 2022
在最近一次探索马里兰小镇的 "度假 "期间,我偶然发现了一家非常令人愉快的书店,在那里我愉快地消磨了一下午。我和我的家人都是读者,喜欢各种类型的书--新的、二手的、印刷的、电子的。我们尽量在当地购物,以帮助零售店保持运营。
这次访问促使我思考图书行业所发生的事情与我们的医疗保健系统所发生的事情之间的一些相似之处。
医疗保健行业与图书行业的趋势
数字化
我们阅读内容的格式已经发生了根本性的变化。在2020年,电子书几乎占美国市场的四分之一。音频书占美国图书收入的10亿美元。许多印刷书籍是按需出版的,而不是保存在库存中。同样,医疗保健早已不再是一个“伸出舌头说啊 ”的行业,基因组测试、由人工智能算法读取的X射线、可植入设备和远程医疗访问已经改变了医疗的面貌。
虚拟服务
书店现在有多种形式,医疗机构也是如此。订阅图书服务,从当地独立的小公司、大的连锁店、电子零售的网上订单。而与你的本地门诊竞争的是你手机上的一个应用程序。同样,你的治疗师可能是一个机器人,你的基层医疗服务可能由你社区附近药店的驻店医师提供,你可能在一个办公园区做手术。在所有这些竞争中,我们如何确保在我们需要时仍有健康的、提供全面服务的医院?
更智能的算法
分析和预测模型现在几乎和个人推荐一样重要。过去,当我想要一本书的建议,或者一个医生,我就会问朋友。虽然我仍然这样做,但我也同样有可能去看Goodreads,或查看在线医生评论。当我进行搜索时,亚马逊、苹果或谷歌也同样可能提供他们的建议,不管我是否要求它们。他们知道我是谁,我的购买模式是什么,我检查过哪些疾病和症状,以及在当地急诊科订购书籍或看医生的等待时间是什么。
合并和收购
无论你是卖书还是卖医疗服务,改变或死亡都是关键词。我们附近的一家大的巴诺书店(Barnes & Nobles,美国最大的实体书店)最近搬到了一个不到以前一半的地方。大多数独立书店出售的礼品和书籍一样多,而且许多书店同时出售新货和二手货。像Alibris这样的网站将当地的小企业与世界各地的买家联系起来。同样,根据普华永道的数据,2021年医疗保健业的合并和收购增长了56%,预计这一趋势在2022年还会继续。IQVIA艾昆纬研究所的一份报告发现,在主要的应用程序商店中,有大约35万个数字健康应用程序。而美国最大的零售商都在医疗保健领域进行了大量投资。例如,亚马逊是颠覆性的电子书业务的主要参与者,现在也有实体书店,它正积极地进入医疗保健服务领域,包括线上和线下服务。
对未来医疗的影响
对未来医疗的影响是什么?有趣的是,这两种业务都唤起了人们对也许是神话般的过去的相当大的怀念。虽然现在的实体书店比以前少了,但书籍实际上比以前更容易获得。对很大一部分人来说,亲切的家庭医生上门服务从来就不是那么容易的,而且不同地区采用的差异巨大的护理标准也不一定能带来最好的结果。
新世界秩序的便利性和选择令人难以置信地吸引人,无论我是在手机上购买一本书,还是在街上走到我附近的CVS公司购买Covid 疫苗加强针。但是,一方面浏览完所有这些选择也会让人感到困惑,另一方面我不想失去浏览当地商店的货架或者我年迈的母亲随时获得住院床位的选择。
没有任何整齐划一的策略可以向前推进。因此,作为一个消费者,我将继续光顾本地小店来帮助他们经营下去。作为一名医疗IT专业人士,我将继续关注如何利用信息来指导未来,带着一点点害怕,但更多的还是兴奋来展望未来。
关于作者:
Kathleen Aller负责InterSystems公司的医疗市场战略。她在医疗和技术领域有多年的经验,在分析、患者管理、电子健康记录、医疗信息共享以及质量和绩效评估方面有专长。
博客原文:https://www.intersystems.com/pulse-blog/browsing-the-future-of-healthcare
文章
Frank Ma · 三月 2, 2022
好人不需要规则。
神秘博士
要成为日期和时间的主人并不是一件容易的事,在任何编程语言中,这总是一个问题,有时会让人感到困惑,我们将澄清并提出一些提示,使这项任务尽可能简单。
坐上TARDIS,我将把你变成一个时间领主。
让我们从基本知识开始
如果你通常使用其他语言,请记住,Intersystems Object Script(以下简称IOS,不要与苹果手机混淆)的日期有点特殊。当我们在终端运行$HOROLOG 命令时,为了得到当前的日期和时间,你会看到它被分为两部分:
WRITE $HOROLOG
> 66149,67164
第一个值是天数,确切地说,是自1840年12月31日以来的天数,也就是说,值1是1841年1月1日;第二个值是自今天00:00以来的秒钟。
在这个例子中,66149对应于09/02/2022(欧洲格式的日/月/年的2月9日),67164对应于18:39:24。我们将这种格式称为数据和时间的内部格式。
感到困惑吗?好吧,我们将开始揭示宇宙的伟大秘密(日期和时间)。
如何将内部格式转换为更清晰的格式?
为此,我们将用到命令 $ZDATETIME
基本的命令是
SET RightNow = $HOROLOG
WRITE RightNow
> 66149,67164
WRITE $ZDATETIME(RightNow)
> 02/09/2022 18:39:24
默认情况下,它使用美国格式月/日/年(mm/dd/yyyy)。如果你想使用其他格式的日期,我们将使用第二个参数,比如欧洲格式日/月/年(dd/mm/yyyy),在这种情况下,我们将给它一个值4(关于更多的格式,见文档$ZDATETIME.dformat)。
SET RightNow = $HOROLOG
WRITE RightNow
> 66149,67164
WRITE $ZDATETIME(RightNow,4)
> 09/02/2022 18:39:24
该选项使用我们在本地变量中定义的分隔符和年份格式。
如果我们还想放另一种时间格式,例如12小时格式(AM/PM)而不是24小时格式,我们使用第三个参数,其值为3,如果我们不想显示秒,我们将使用值4(见文件$ZDATETIME.tformat)。
SET RightNow = $HOROLOG
WRITE RightNow
> 66149,67164
WRITE $ZDATETIME(RightNow,4,3)
> 09/02/2022 06:39:24PM
WRITE $ZDATETIME(RightNow,4,4)
> 09/02/2022 06:39PM
现在是不是更清楚了?那么让我们更深入地了解一下。
ODBC 格式
这个格式与你的本地配置无关,它将始终显示为年/月/日格式 yyyy-mm-dd,其值为3。 如果我们想创建要导出文件的数据,如CSV、HL7文件等,建议使用它。
SET RightNow = $HOROLOG
WRITE RightNow
> 66149,67164
WRITE $ZDATETIME(RightNow,3)
> 2022-02-09 18:39:24
一周的日子,星期名称,一年中的某天
Value 值
描述
10
一周的日子将是一个介于0和6之间的值,0代表星期天,6代表星期六。
11
星期的缩写名称,它将根据你定义的本地配置返回,IRIS的默认安装是 enuw (English, United States, Unicode)
12
长格式的星期名称,与11相同。
14
一年中的某一天,自1月1日以来的天数。
如果我们只是想分别处理日期和时间,应该分别使用$ZDATE和$ZTIME命令。格式的参数与 $ZDATETIME.dformat 和 $ZDATETIME.tformat中定义的参数相同。
SET RightNow = $HOROLOG
WRITE RightNow
> 66149,67164
WRITE $ZDATE(RightNow,10)
> 3
WRITE $ZDATE(RightNow,11)
> Wed
WRITE $ZDATE(RightNow,12)
> Wednesday
那我如何将日期转换为内部格式?
好了,现在我们来看看相反的步骤,即有一个带有日期的文本,并将其转换成IOS格式。对于这个任务,我们将使用命令 $ZDATETIMEH。
这一次,我们必须指出日期和时间的格式(如果我们使用$ZDATETIMEH),或者分别指出日期($ZDATEH)和时间($ZTIMEH)。
格式是相同的,也就是说,如果我们有一个ODBC格式(yyyy-mm-dd)的日期字符串,那么我们将使用值3。
SET MyDatetime = "2022-02-09 18:39:24"
SET Interna1 = $ZDATETIMEH(MyDatetime, 3, 1) // ODBC Format
SET MyDatetime = "09/02/2022 18:39:24"
SET Interna2 = $ZDATETIMEH(MyDatetime, 4, 1) // European format
SET MyDatetime = "02/09/2022 06:39:24PM"
SET Interna3 = $ZDATETIMEH(MyDatetime, 1, 3) // American format with time in 12h AM/PM
WRITE Interna1,!,Interna2,!,Interna3
> 66149,67164
66149,67164
66149,67164
从逻辑上讲,如果我们说字符串使用的是一种特殊的格式,而我们给它提供了错误的参数,那么任何事情都可能发生,比如它理解为2月9日,而不是9月2日。
不要混合格式,这样以后会出现问题。
SET MyDatetime = "09/02/2022"
/// American format
SET InternalDate = $ZDATEH(MyDatetime, 1)
/// European format
SET OtherDate = $ZDATETIME(InternalDate, 4)
WRITE InternalDate,!,OtherDate
> 66354
02/09/2022
不用说,如果我们试图设定一个欧洲的日期并试图将其转化为美国的日期...... 在情人节会发生什么?
SET MyDatetime = "14/02/2022"
SET InternalDate = $ZDATEH(MyDatetime, 1) // American format. month 14 doesn't exists!!!
^
<ILLEGAL VALUE>
嗯,就像所有的情人节一样......破碎的心,嗯......在这种情况下,破碎的代码。
好吧,让我们用你已经学到的东西做一些事情。
READ !,"Please indicate your date of birth (dd/mm/yyyy): ",dateOfBirth
SET internalFormat = $ZDATEH(dateOfBirth, 4)
SET dayOfWeek= $ZDATE(internalFormat, 10)
SET nameOfDay = $ZDATE(internalFormat, 12)
WRITE !,"The day of the week of your birth is: ",nameOfDay
IF dayOfWeek = 5 WRITE "you always liked to party!!!" // was born on friday
以后我们将看到其他的做事方法,以及如何处理错误。
下一章:如何进行时间旅行
好奇
如果你想知道为什么01/01/1841的值被当作1的值,那是因为选择这个日期是因为它是在世的最年长的美国公民出生前的非闰年,当MUMPS编程语言被设计时,他是一个121岁的内战老兵,它从这个语言中扩展了对象脚本。
文章
姚 鑫 · 七月 27, 2021
# 第二十九章 类关键字 - PropertyClass
向该类添加属性参数。
# 用法
要向该类添加属性参数,请使用以下语法:
```java
Class PropClass.MyClass Extends %RegisteredObject [ PropertyClass = PropClass.MyPropertyClass ] { //class members }
```
其中·propertyclasslist·是下列之一:
```java
[ PropertyClass = PropClass.MyPropertyClass ]
```
- 用逗号分隔的类名列表,用括号括起来。
# 详情
如果需要添加自定义属性参数,请执行以下操作:
1. 定义并编译一个定义一个或多个类参数的类。例如:
```java
Class PropClass.MyPropertyClass
{
Parameter MYPARM As %String = "XYZ";
}
```
这些类参数在下一步中成为属性参数。
2. 在定义属性的类中,指定`PropertyClass`关键字。
# 对子类的影响
子类继承这个关键字添加的自定义行为。如果子类为关键字指定了一个值,则该值会指定一个或多个为该类的属性指定参数的附加类。
# 第三十章 类关键字 - ServerOnly
指定此类是否被投影到Java客户端。
# 用法
要覆盖将类投影到`Java`客户端的默认方式,请使用以下语法:
```java
Class Sample.NewClass1 [ ServerOnly = serveronlyvalue ] { //class members }
```
其中`serveronlyvalue`是以下值之一:
- 0表示可以投影此类。
- 1表示这个类不会被投影。
# 详解
如果该关键字为`1`,则该类不会被投影到`Java`客户端。如果该关键字为`0`,则将投影该类。
# 对子类的影响
此关键字不是继承的。
# 默认
如果省略这个关键字,这个类如果不是存根就会被投影(但是如果是存根就不会被投影)。
# 第三十一章 类关键字 - Sharded
指定此类是否被分片。仅适用于包含分片集群的环境中的持久类。
# 用法
要将类定义为分片类,请使用以下语法:
```java
Class MyApp.MyClass Extends %Persistent [ Sharded = 1 ]
{ //class members }
```
否则,省略此关键字。
# 详解
分片是一种水平扩展数据存储的机制。如果一个类被分片,该类的实例将分布在分片集群中任何已定义的数据节点上。
如果有一个分片环境,并且将一个类定义为未分片,那么该类的实例只存储在第一个数据节点上,尽管所有节点都可以看到该数据。
# 对子类的影响
这个关键字是继承的。
# 默认
如果省略这个关键字,类就不会被分割。
# 第三十二章 类关键字 - SoapBindingStyle
指定此类中定义的任何web方法使用的绑定样式或SOAP调用机制。仅适用于定义为web服务或web客户端的类。
# 用法
要指定此类中定义`web method`使用的绑定样式,请使用以下语法:
```java
Class MyApp.MyClass [ SoapBindingStyle = soapbindingstyle ] { //class members }
```
其中`soapbindingstyle`是下列之一:
- `document` 文档(默认)—默认情况下,此类中的`web method`使用文档样式的绑定。
使用这种绑定风格,`SOAP`消息被格式化为文档,并且通常只有一个部分。
在`SOAP`消息中,``元素通常包含一个子元素。``元素的每个子元素对应于一个消息部分。
- `rpc` —默认情况下,此类中的`web method`使用`rpc`(远程过程调用)样式的绑定。
使用这种绑定风格,`SOAP`消息被格式化为具有多个部分的消息。
在`SOAP`消息中,``元素包含一个子元素,其名称取自相应的操作名称。这个元素是一个生成的包装元素,它为方法的参数列表中的每个参数包含一个子元素。
如果`SoapBindingStyle`是文档,如果`ARGUMENTSTYLE`是消息,那么消息样式与`RPC`非常相似;
重要提示:对于手动创建的`web service`,该关键字的默认值通常是合适的。当使用`SOAP`向导从`WSDL`生成`web客户端或服务`时,InterSystems IRIS会将此关键字设置为适合该`WSDL`;如果修改该值,`web客户端或服务`可能不再工作。
# 详解
此关键字允许指定此类中定义的任何`web method`使用的默认绑定样式。它影响`SOAP`主体的格式(但不影响任何SOAP头)。
通过使用`SoapBindingStyle`方法关键字或`SoapBindingStyle`查询关键字,可以重写单个方法的绑定样式。
# 对子类的影响
此关键字不是继承的。
# 默认
默认值为文档。
# 与WSDL的关系
`SoapBindingStyle`类关键字指定了`WSDL`的``部分中``元素的样式属性的值。例如,如果`SoapBindingStyle`是文档,则`WSDL`可能如下所示:
```xml
...
...
```
如这里所示,在`WSDL`的``部分中,`SoapBindingStyle`类关键字还指定了``元素的样式属性的默认值;该属性由`SoapBindingStyle`方法关键字进一步控制。
相比之下,如果`SoapBindingStyle`是`rpc`,则`WSDL`可以改为如下所示:
```xml
...
...
```
绑定样式也会影响``元素,如下所示:
- 如果绑定样式是文档,默认情况下,消息只有一个部分。例如:
```xml
```
如果`ARGUMENTSTYLE`参数是`message`,那么一条消息可以有多个部分。例如:
```xml
```
- 如果绑定样式是`rpc`,消息可以有多个部分。例如:
```xml
```
# 对SOAP消息的影响
对`SOAP`消息的主要影响是控制`SOAP`主体是否可以包含多个子元素。
对于使用`RPC`样式绑定和编码样式消息的`web method`,下面显示了请求消息正文的示例:
```xml
10
5
17
2
```
相比之下,下面显示了使用文字绑定和编码样式消息的`web method`的请求消息正文的示例:
```xml
10
5
17
2
```
在这种情况下,`SOAP`主体只有一个子元素。
# 与 `%XML.DataSet` 一起使用
对于 `%XML.DataSet`, 类型的对象,并非所有 `SoapBindingStyle` 和 `SoapBodyUse` 关键字的排列都是允许的,,如下表总结:
type | supported?| supported?
---|---|---
空 |SoapBodyUse=literal(默认) | SoapBodyUse=encoded
SoapBindingStyle=document(default) |supported| not supported
SoapBindingStyle=rpc| supported |supported