搜索​​​​

清除过滤器
文章
YuHao Wan · 十一月 5, 2022

Caché实现SM4分组密码算法

### 0. 算法概述 SM4算法是一种分组密码算法。其分组长度为128bit,密钥长度也为128bit。加密算法与密钥扩展算法均采用32轮非线性迭代结构,以字(32位)为单位进行加密运算,每一次迭代运算均为一轮变换函数F。SM4算法加/解密算法的结构相同,只是使用轮密钥相反,其中解密轮密钥是加密轮密钥的逆序。 ### 1. 密钥及轮密钥 密钥长度为128比特,表示为MK=(MK(0),MK(1),MK(2),MK(3)),其中MKi(i=0,1,2,3)为字。 轮密钥表示为(rk(0),rk(1),...,rk(31)),其中rk(i)(i=0,...,31)为32比特字。轮密钥由秘钥生成。 ![密钥及轮密钥](https://gitee.com/wanyuhao/intersystems/raw/master/%E5%9B%BD%E5%AF%86%E7%AE%97%E6%B3%95SM3,SM4/images/SM4-01.png) ### 2. 消息填充分组 首先,将明文转化为字节,由于SM4加密算法按照128个位进行分组,所以很大几率会出现最后一个分组不够128位的情况,需要进行**填充**,填充方式有很多,比如ZeroPadding、PKCS7Padding、PKCS5Padding,不管使用哪种方式,最后每个分组都是128位。每个分组按照**32位**一个字分成四个字。 #### ECB模式与CBC模式 - ECB模式 电子密码本模式,最古老,最简单的模式,将加密的数据分成若干组,每组的大小跟加密密钥相同。不足的部分进行填充。 按照顺序将计算所得的数据连在一起即可,各段数据之间互不影响。 优点:简单,有利于并行计算,误差不会被传递。 缺点:不能隐藏明文的模式,可能对明文进行主动攻击。 - CBC模式 密文分组链接模式,也需要进行分组,不足的部分按照**指定的数据**进行填充。 需要一个**初始化向量**,每个分组数据与上一个分组数据加密的结果进行**异或运算**,最后再进行加密。将所有分组加密的结果连接起来就形成了最终的结果。 优点:不容易进行主动攻击,安全性好于ECB。 缺点:不利于并行计算,误差传递,需要初始化向量。 #### 三种填充方式的比较 某些加密算法要求明文需要按一定长度对齐,叫做**块大小**(BlockSize),比如16字节,那么对于一段任意的数据,加密前需要对最后一个块填充到16 字节,解密后需要删除掉填充的数据。 - **ZeroPadding**,数据长度不对齐时使用**0**填充,否则不填充。 - **PKCS7Padding**,假设数据长度需要填充n(n>0)个字节才对齐,那么填充n个字节,每个字节都是**n**;如果数据本身就已经对齐了,则填充一块长度为块大小的数据,每个字节都是块大小。 - **PKCS5Padding**,PKCS7Padding的子集,块大小固定为**8**字节。 由于使用PKCS7Padding/PKCS5Padding填充时,最后一个字节肯定为填充数据的长度,所以在解密后可以准确删除填充的数据,而使用ZeroPadding填充时,没办法区分真实数据与填充数据,所以只适合以\0结尾的字符串加解密。 ### 3. 迭代运算 本加解密算法由32次迭代运算和1次反序变换R组成。 ![迭代运算](https://gitee.com/wanyuhao/intersystems/raw/master/%E5%9B%BD%E5%AF%86%E7%AE%97%E6%B3%95SM3,SM4/images/SM4-02.png) #### 3.1 轮函数F和合成置换T ![轮函数和合成置换T](https://gitee.com/wanyuhao/intersystems/raw/master/%E5%9B%BD%E5%AF%86%E7%AE%97%E6%B3%95SM3,SM4/images/SM4-03.png) ### 4. Caché实现 ``` /// SM4算法是一种分组密码算法。其分组长度为128bit,密钥长度也为128bit。 /// 加密算法与密钥扩展算法均采用32轮非线性迭代结构,以字(32位)为单位进行加密运算,每一次迭代运算均为一轮变换函数F。 /// SM4算法加/解密算法的结构相同,只是使用轮密钥相反,其中解密轮密钥是加密轮密钥的逆序。 /// 本方法适用于 ECB模式,ZeroPadding填充模式 Class Utility.SM4 Extends %RegisteredObject { /// Creator: wyh /// CreatDate: 2022-11-03 /// Description:SM4加密 /// Input: msg:原文 mk:128位密钥 /// Output: 密文 /// Debug: w ##class(Utility.SM4).Encrypt("342622199009262982", "F2D8D966CD3D47788449C19D5EF2081B") ClassMethod Encrypt(msg, mk) { #; 1. 密钥及轮密钥 #; a) 密钥长度为128比特,表示为MK=(MK(0),MK(1),MK(2),MK(3)),其中MKi(i=0,1,2,3)为字 #; b) 轮密钥表示为(rk(0),rk(1),...,rk(31)),其中rk(i)(i=0,...,31)为32比特字。轮密钥由密钥生成。 #; 密钥扩展算法: #; (K(0),K(1),K(2),K(3))=(MK(0)^FK(0),MK(1)^FK(1),MK(2)^FK(2),MK(3)^FK(3)) #; rk(i)=K(i+4)=K(i)^T'(K(i+1)^K(i+2)^K(i+3)^CK(i)),i=0,1,...,31 #; 系统参数FK(0)=(A3B1BAC6),FK(1)=(56AA3350),FK(2)=(677D9197),FK(3)=(B27022DC) #; 固定参数CK(i)(i=0,1,...,31)为: #; 00070E15, 1C232A31, 383F464D, 545B6269, #; 70777E85, 8C939AA1, A8AfB6BD, C4CBD2D9, #; E0E7EEF5, FC030A11, 181F262D, 343B4249, #; 50575E65, 6C737A81, 888F969D, A4ABB2B9, #; C0C7CED5, DCE3EAF1, F8FF060D, 141B2229, #; 30373E45, 4C535A61, 686F767D, 848B9299, #; A0A7AEb5, BCC3CAD1, D8DFE6ED, F4FB0209, #; 10171E25, 2C333A41, 484F565D, 646B7279. s mk = $zcvt(mk, "L") f i = 0 : 1 : 3 d .s MK(i) = $e(mk, 8 * i + 1, 8 * (i + 1)) s FK = "a3b1bac656aa3350677d9197b27022dc" f i = 0 : 1 : 3 d .s FK(i) = $e(FK, 8 * i + 1, 8 * (i + 1)) s CK = "00070e151c232a31383f464d545b626970777e858c939aa1a8afb6bdc4cbd2d9e0e7eef5fc030a11181f262d343b424950575e656c737a81888f969da4abb2b9c0c7ced5dce3eaf1f8ff060d141b222930373e454c535a61686f767d848b9299a0a7aeb5bcc3cad1d8dfe6edf4fb020910171e252c333a41484f565d646b7279" f i = 0 : 1 : 31 d .s CK(i) = $e(CK, 8 * i + 1, 8 * (i + 1)) f i = 0 : 1 : 3 d .s K(i) = ..HexXOR(MK(i), FK(i)) f i = 4 : 1 : 35 d .s K(i) = ..HexXOR(K(i - 4), ..T2(..HexXOR(..HexXOR(..HexXOR(K(i + 1 - 4), K(i + 2 - 4)),K(i + 3 - 4)), CK(i - 4)))) f i = 0 : 1 : 31 d .s rk(i) = K(i + 4) #; 2. 消息填充分组 #; 每组128位,每组再分X(0),X(1),X(2),X(3)作为明文输入. s hex = ..s2hex(msg) s len = $l(hex)/32 s rtn = "" f i = 0 : 1 : len-1 d .k X .s M(i) = $e(hex, 32 * i + 1, 32 * (i + 1)) .f j = 0 : 1 : 3 d ..s X(j) = $e(M(i), 8 * j + 1, 8 * (j + 1)) #; 3. 迭代运算,密文输出(Y(0),Y(1),Y(2),Y(3)) #; a) 32次迭代运算 #; X(i+4)=F(X(i),X(i+1),X(i+2),X(i+3),rk(i)),i=0,1,...,31 #; F(X(i),X(i+1),X(i+2),X(i+3),rk(i))=X(i)^T(X(i+1)^X(i+2)^X(i+3)^rk(i)),i=0,1,...,31 #; b)反序变换 #; (Y(0),Y(1),Y(2),Y(3))=R(X(32),X(33),X(34),X(35))=(X(35),X(34),X(33),X(32)) .f k = 0 : 1 : 31 d ..s X(k + 4) = ..HexXOR(X(k), ..T(..HexXOR(..HexXOR(..HexXOR(X(k + 1), X(k + 2)), X(k + 3)), rk(k)))) .s rtn = rtn_X(35)_X(34)_X(33)_X(32) q rtn } /// Creator: wyh /// CreatDate: 2022-11-03 /// Description:SM4解密 /// 解密变换与加密变换结构相同,不同的仅是轮密钥的使用顺序,解密时使用轮密钥序(rk(31),rk(32),...,rk(0)). /// Input: hex:密文 mk:128位密钥 /// Output: 明文 /// Debug: w ##class(Utility.SM4).Decrypt("5efcbbfdb7a326b340295acb1c0e20fe2622730932bdb5302b5a4ee308944ecc", "F2D8D966CD3D47788449C19D5EF2081B") ClassMethod Decrypt(hex, mk) { s mk = $zcvt(mk, "L") f i = 0 : 1 : 3 d .s MK(i) = $e(mk, 8 * i + 1, 8 * (i + 1)) s FK = "a3b1bac656aa3350677d9197b27022dc" f i = 0 : 1 : 3 d .s FK(i) = $e(FK, 8 * i + 1, 8 * (i + 1)) s CK = "00070e151c232a31383f464d545b626970777e858c939aa1a8afb6bdc4cbd2d9e0e7eef5fc030a11181f262d343b424950575e656c737a81888f969da4abb2b9c0c7ced5dce3eaf1f8ff060d141b222930373e454c535a61686f767d848b9299a0a7aeb5bcc3cad1d8dfe6edf4fb020910171e252c333a41484f565d646b7279" f i = 0 : 1 : 31 d .s CK(i) = $e(CK, 8 * i + 1, 8 * (i + 1)) f i = 0 : 1 : 3 d .s K(i) = ..HexXOR(MK(i), FK(i)) f i = 4 : 1 : 35 d .s K(i) = ..HexXOR(K(i - 4), ..T2(..HexXOR(..HexXOR(..HexXOR(K(i + 1 - 4), K(i + 2 - 4)),K(i + 3 - 4)), CK(i - 4)))) f i = 0 : 1 : 31 d .s rk(i) = K(35 - i) s len = $l(hex)/32 s rtn = "" f i = 0 : 1 : len-1 d .k X .s M(i) = $e(hex, 32 * i + 1, 32 * (i + 1)) .f j = 0 : 1 : 3 d ..s X(j) = $e(M(i), 8 * j + 1, 8 * (j + 1)) .f k = 0 : 1 : 31 d ..s X(k + 4) = ..HexXOR(X(k), ..T(..HexXOR(..HexXOR(..HexXOR(X(k + 1), X(k + 2)), X(k + 3)), rk(k)))) .s rtn = rtn_X(35)_X(34)_X(33)_X(32) q ..hex2str(rtn) } /// 非线性变换τ构成 /// τ由4个并行的S盒,设输入A=(a0,a1,a2,a3),输出为B=(b0,b1,b2,b3) /// (b0,b1,b2,b3)=τ(A)=(Sbox(a0),Sbox(a1),Sbox(a2),Sbox(a3)) /// w ##class(Utility.SM4).tau("942600f0") ClassMethod tau(a) { f i = 0 : 1 : 7 d .s a(i) = $e(a, i + 1) s s(0) = "d690e9fecce13db716b614c228fb2c05" s s(1) = "2b679a762abe04c3aa44132649860699" s s(2) = "9c4250f491ef987a33540b43edcfac62" s s(3) = "e4b31ca9c908e89580df94fa758f3fa6" s s(4) = "4707a7fcf37317ba83593c19e6854fa8" s s(5) = "686b81b27164da8bf8eb0f4b70569d35" s s(6) = "1e240e5e6358d1a225227c3b01217887" s s(7) = "d40046579fd327524c3602e7a0c4c89e" s s(8) = "eabf8ad240c738b5a3f7f2cef96115a1" s s(9) = "e0ae5da49b341a55ad933230f58cb1e3" s s(10) = "1df6e22e8266ca60c02923ab0d534e6f" s s(11) = "d5db3745defd8e2f03ff6a726d6c5b51" s s(12) = "8d1baf92bbddbc7f11d95c411f105ad8" s s(13) = "0ac13188a5cd7bbd2d74d012b8e5b4b0" s s(14) = "8969974a0c96777e65b9f109c56ec684" s s(15) = "18f07dec3adc4d2079ee5f3ed7cb3948" f i = 0 : 1 : 15 d .f j = 0 : 1 : 15 d ..s s(i, j) = $e(s(i), 2 * j + 1, 2 * (j + 1)) s rtn = "" f i = 0 : 1 : 3 d .s r = ..hex2int(a(2 * i)) .s c = ..hex2int(a(2 * i + 1)) .s rtn = rtn _ s(r, c) return rtn } /// 线性变换L /// 非线性变换τ的输出是线性变换L的输入.设输入为B,输出为C. /// C=L(B)=B^(B
文章
jieliang liu · 一月 7, 2021

精华文章---在 Windows 主机上运行的 Hyper-V Ubuntu 虚拟机中配置 Docker 使用环境

这次我想谈一谈不专门针对 InterSystems IRIS 的东西,不过如果你想使用 Docker,并且你工作环境是安装了 Windows 10 专业版或企业版的 PC 或笔记本电脑,那么我认为这个很重要。 你可能知道,容器技术基本上来自于 Linux 世界,如今在 Linux 主机上发挥出最大潜能。 那些平常使用 Windows 的人会看到,Microsoft 和 Docker 在过去的几年做出了重要的努力,让我们可以在 Windows 系统上以非常简单的方式运行基于 Linux 映像的容器... 但是生产系统不支持这种方式,这是个大问题,如果我们要将持久性数据保留在主机系统中的容器之外,这样做非常不可靠... 这主要是由于 Windows 和 Linux 文件系统之间的巨大差异导致的。 最终,_Docker for Windows 自身使用了一个小型 linux 虚拟机 (_MobiLinux_) 来运行容器... 此操作对于 Windows 用户是透明的,而且效果完美,只要你不需要你的数据库比容器存活的时间更长... 好了,我们进入正题,很多时候为了避免出现问题和简化操作,我们需要一个完整的 Linux 系统,而且如果我们的服务器基于 Windows,那么唯一的方法就是通过虚拟机来实现。 至少在 Windows 中的 WSL2 发布之前是这样,但发布后就是另一回事了,不过它要变得足够强大稳定肯定还需要一些时间。 在本文中,我将一步一步告诉你如何在 Windows 服务器中的 Ubuntu 系统上安装一个能使用 Docker 容器进行工作的环境。 我们开始吧... 1. 启用 Hyper-V 如果尚未启用,则转到添加 `Windows 功能`并启用 Hyper-V。 你将需要重启(图片上的文本是西班牙语,但这就是我当前的区域设置。 如果你不懂堂吉诃德的语言,我希望加上说明能帮助你“解密”😉) ![](/sites/default/files/inline/images/images/image(424).png)   2. 在 Hyper-V 上创建一个 Ubuntu 虚拟机 我认为创建虚拟机 (VM) 没有更简单的方法了。 只需打开 `Hyper-V 管理器`的窗口,然后转到选项快速创建...(屏幕的右上角),使用已经提供的任一 Ubuntu 版本来创建你的虚拟机(你可以下载任何其他 Linux 的 iso 文件,创建不同发行版的虚拟机)。 在我的示例中,我选择了最新的 Ubuntu 版本:19.10。 不过,你在这里看到的一切内容也都适用于 18.04。 在 15 或 20 分钟内,具体取决于你下载映像花费的时间,新的虚拟机就创建完毕并准备就绪。 重要: 保持选项默认交换机 不变。这将保证你可以从主机和虚拟机访问互联网。 ![](/sites/default/files/inline/images/images/vm_ubuntu_network_start_defaultswitch_eth0.jpg) 3. 创建本地子网 使用虚拟机经常遇到的问题之一与网络配置有关... 有时有效,有时无效,或者连接 Wi-Fi 时有效,但连接网线就无效,或者是相反情况;或者如果我在 Windows 主机中建立一个 VPN,那么在虚拟机中就无法访问互联网,或者是虚拟机 (Linux) 和主机 (Windows) 之间的通信中断... 总之,非常让人抓狂! 这使得我在使用笔记本电脑进行开发、小型快速演示或展示时无法信任我的环境,而在这些场景下访问互联网很可能不如确保在主机与虚拟机之间进行可靠通信来得重要。 在 Windows 主机和虚拟机之间共享一个临时本地子网,可以解决这个问题。 要让它们互相通信,使用该子网就可以了。 你只需要为主机和虚拟机分配特定 IP 即可。 通过以下步骤可以很容易实现。 只需转到虚拟交换机管理器...,你可以在 `Hyper-V 管理器`中找到: ![](/sites/default/files/inline/images/images/image(425).png) 然后,转到选项新建虚拟交换机(之后就像虚拟机的新网卡一样): ![](/sites/default/files/inline/images/images/image(461).png) 确保将其定义为_内部网络_,选择我们想要的名称,其他选项保持默认 ![](/sites/default/files/inline/images/images/image(427).png) 现在,如果转到 _`Windows 控制面板 --> 网络和共享中心`_,我们会看到那里已经有了我们刚才创建的交换机: ![](/sites/default/files/inline/images/images/image(429).png)   4. 配置主机和虚拟机共享的本地子网 此时,你可以完成新的本地网络的配置。 为此,将光标放在连接 _Mi Nuevo Conmutador LOCAL_ 上,单击并转到属性,再转到 IPv4 协议,以便分配一个固定 IP 地址: ![](/sites/default/files/inline/images/images/image(449).png)   重要:在此处分配的 IP 将是主机 (Windows) 在该本地子网中的 IP。   5. 将新的本地网络链接并配置到虚拟机 现在回到 `Hyper-V 管理器`。 如果虚拟机正在运行,将其停止。 停止后,转到其配置并添加新的内部虚拟交换机: ![](/sites/default/files/inline/images/images/image(431).png) _(注意:在图片上可以看到另一个交换机 Hyper-V Conmutador INTERNO。 它用于我的另一个子网。 此配置中不需要它)_ 单击“添加”后,你只需选择先前创建的交换机: ![](/sites/default/files/inline/images/images/image(432).png) 好了,完成此操作后,依次单击“应用”、“接受”... 一切就绪!你只需启动并再次登录虚拟机即可完成内部连接的配置。 为此,在虚拟机启动后,单击网络图标(右上角),你将看到两个网络:_eth0_ 和 _eth1_。 _eth1_ 目前显示为断开连接: ![](/sites/default/files/inline/images/images/image(450).png) 进入以太网 (eht1) 的配置,并为此本地子网分配一个固定 IP,例如:_155.100.101.1_,子网掩码:_255.255.255.0_ ![](/sites/default/files/inline/images/images/image(452).png) 这样就完成了。 你的虚拟机标识为 IP 155.100.101.1,与主机共享同一子网。 7. 允许从虚拟机访问 Windows 10 你可能会发现 Windows 10 默认不允许其他服务器连接,对于 Windows 系统来说,你刚刚创建的虚拟机正是一个可能存在危险的外部服务器。因此,必须在防火墙中添加规则,才能从这些虚拟机连接到主机。 如何操作? 非常简单,只需在 `Windows 控制面板`中查找 `Windows Defender 防火墙`,转到高级配置,然后创建一条新的*入站规则*: ![](/sites/default/files/inline/images/images/image(451).png) 你可以设置一个端口或者一个或多个端口范围...(也可以设置针对所有端口的规则)... ![](/sites/default/files/inline/images/images/image(453).png) 我们需要的操作是_允许连接_... ![](/sites/default/files/inline/images/images/image(454).png) 用于_所有网络类型_... ![](/sites/default/files/inline/images/images/image(455).png) 为规则指定名称... ![](/sites/default/files/inline/images/images/image(456).png) **这里很重要**,指定名称后要立即再次打开新创建的规则的属性并*限制应用程序范围*,以便只应用于本地子网内的连接... ![](/sites/default/files/inline/images/images/image(457).png) 8. 就绪。 在新的 Ubuntu 虚拟机中安装 Docker 和任何其他应用程序 完成整个安装过程后,新虚拟机即就绪且为最新,并可以访问互联网等等。 你可以安装所需的应用程序... 至少要安装 Docker,这是一开始就有的想法,如果你需要连接公司网络,还可以安装 VPN 客户端,还有 VS Code、Eclipse+Atelier 等等。 具体来说,要在虚拟机中安装 Docker,可以按照以下说明进行操作: 确保 Docker 运行时正在工作,下载一些测试映像等等... 仅此而已。 这样... _**你已完成所有工作!**_,现在你将能够在 Ubuntu 虚拟机中无限制(除了硬件能力限制)运行容器,你可以从 Windows 10 主机、浏览器或应用程序连接到虚拟机,以及反过来从 Ubuntu 虚拟机连接到 Windows 10 主机。 所有使用你在共享本地子网中设置的 IP 地址的操作都将有效,无论是否建立 VPN,是通过 Wi-fi 适配器还是通过以太网电缆接入互联网。 啊... 最后一个建议。 如果要在 Windows 10 和虚拟机之间交换文件,一个非常有用且简单的选项是使用 [WinSCP](https://winscp.net/eng/download.php)。 它是免费的,而且非常好用。 当然,还有其他配置,但这是我使用的配置,已经证明是比较可靠的。 希望你也觉得它有用。 如果我帮助你避免了令人头疼的问题,这篇文章就值了。 编码愉快!     
文章
Claire Zheng · 六月 19

2025.1 打造时尚现代的互操作性用户体验

互操作性用户界面现在包括可以在所有互操作性产品中使用的 DTL 编辑器和生产配置应用程序的现代化用户体验。您可以在现代化视图与标准视图之间切换。所有其他互操作性屏幕仍采用标准用户界面。请注意,仅对这两个应用程序进行了更改,我们在下面确定了当前可用的功能。 要在升级前试用新屏幕,您可以点击这里,从我们的社区工具包网页中下载 2025.1 版:https://evaluation.intersystems.com/Eval/。请观看“学习服务”中的简短教程构建集成:一种新的用户体验,了解对这些屏幕进行的用户增强! 生产配置 - 配置任务简介 生产配置:在以下版本的生产配置中受支持: 创建/编辑/复制/删除主机 停止/启动主机 编辑生产设置 停止/启动生产 源代码控制集成:支持上述配置功能的源代码控制集成。 分屏 视图:用户可以直接从“生产配置”屏幕打开“规则编辑器”和“DTL 编辑器”,在分屏视图中查看和编辑产品中包含的规则和转换。 增强的筛选功能:使用顶部的搜索框,您可以搜索和筛选各种业务组件,包括多种类别、DTL 和子转换。 使用左侧边栏可以独立于主面板进行搜索,查看各种主机和类别中的搜索结果。 批量编辑主机类别:通过从生产配置中添加主机,您可以为生产添加新类别或编辑现有类别。 可展开路由器:可以展开路由器,内联查看所有规则、转换和连接。 重新设计的主机连接:现在,在选择业务主机时,将呈现直接连接和间接连接,您可以查看消息能够采用的完整路径。 将鼠标悬停在任何出站或入站主机上可以进一步区分连接。如果开启仅显示连接的主机开关,将仅筛选所选主机及其连接。 DTL 编辑器 - DTL 工具简介 源代码控制集成:支持源代码控制集成。 VS Code 集成:用户可以在其 VS Code IDE 中查看此版本的 DTL 编辑器。 嵌入式 Python 支持:此版本的 DTL 编辑器现在支持嵌入式 Python。 DTL 测试:可以在此版本的 DTL 编辑器中使用 DTL 测试实用工具。 切换面板布局:DTL 编辑器支持侧面到侧面和顶部到底部布局。 点击顶部功能区的布局按钮可以体验此功能。 撤消/重做:用户可以使用撤消/重做按钮撤消和重做所有尚未保存为代码的操作。 “生成空段”参数:GENERATEEMPTYSEGMENTS 参数可用于为缺失的字段生成空段。 子转换查看:用户可以点击眼睛图标,在新选项卡中打开子转换 DTL,查看子转换。 滚动: 单独滚动:将光标放置在 DTL 的左右两部分(源和目标)其中之一的上方,并用滚轮或触控板垂直移动各段,可以单独滚动各个部分。 联合滚动:将光标放置在图的中间,可以联合滚动源部分和目标部分。 字段自动补全:自动补全适用于:“源”、“目标”和“条件”字段以及源类、源文档类型、目标类、目标文档类型。 顺序编号:使用可视化编辑器,您可以打开和关闭查看每个段的序数和完整路径表达式的功能。 轻松引用:当操作编辑器中的某个字段获得焦点时,在图形化编辑器中双击某个段会在操作编辑器中的当前光标位置插入相应的段引用。 同步:点击可视化编辑器中的一个元素,可以在操作编辑器中高亮显示相应的行。 📣号召性用语📣 如果您有任何反馈,请通过以下途径提供给我们: ✨跨所有互操作性的新功能:在 Ideas 门户中输入想法,或在 InterSystems Ideas 门户中参与其他想法。 对于新想法,请在您的帖子上添加“互操作性”标签或对列表中已提出的功能进行投票! 💻跨所有互操作性的一般用户体验反馈:请在下面输入您的反馈或参与其他评论。 🗒对现代化应用程序的建议/反馈(如上所述):请在下面输入您的反馈或参与其他评论。 请考虑利用 Global Masters 的机会,与团队进行互动,参与不公开的指导反馈会议,并获得积分! 点击此处,通过 Global Masters 报名参加这些会议。 如果您希望以私人形式提供任何其他反馈,请通过电子邮件将您的想法或问题发送至:ux@intersystems.com
文章
姚 鑫 · 七月 2, 2021

第二十五章 添加和使用XSLT扩展函数

# 第二十五章 添加和使用XSLT扩展函数 # 自定义错误处理 当出现错误时,XSLT处理器(`Xalan`或`Saxon`)执行当前错误处理程序的`error()`方法,将消息作为参数发送到该方法。类似地,当发生致命错误或警告时,XSLT处理器会根据需要执行`datalError()`或`Warning()`方法。 对于所有这三种方法,默认行为是将消息写入当前设备。 要自定义错误处理,请执行以下操作: - 对于`Xalan`或`Saxon`处理器,在创建`%XML.XSLT.ErrorHandler`的子类。在这个子类中,根据需要实现`Error()`、`FatealError()`和`Warning()`方法。 这些方法中的每一个都接受单个参数,即包含由XSLT处理器发送的消息的字符串。 这些方法不返回值。 - 要在编译样式表时使用此错误处理程序,请创建子类的实例,并在编译样式表时在参数列表中使用它。 - 若要在执行XSLT转换时使用此错误处理程序,请创建子类的实例,并在使用的`Transform`方法的参数列表中使用它。 # 指定样式表使用的参数 要指定样式表使用的参数,请执行以下操作: 1. 创建`%ArrayOfDataTypes`的实例在。 2. 调用此实例的`SetAt()`方法将参数及其值添加到此实例。对于`SetAt()`,将第一个参数指定为参数值,将第二个参数指定为参数名称。 根据需要添加任意多个参数。 ```java Set tParameters=##class(%ArrayOfDataTypes).%New() Set tSC=tParameters.SetAt(1,"myparameter") Set tSC=tParameters.SetAt(2,"anotherparameter") ``` 3. 将此实例用作`Transform`方法的`pParms`参数。 可以不使用`%ArrayOfDataType`,而是使用 IRIS多维数组,该数组可以具有任意数量的具有以下结构和值的节点: Node| Value ---|--- arrayname("parameter_name") |Value of the parameter named by parameter_name # 添加和使用XSLT扩展函数 可以在InterSystems IRIS中创建`XSLT`扩展函数,然后在样式表中使用它们,如下所示: - 对于`XSLT2.0`(`Saxon`处理器),可以使用名称空间`com.intersystems.xsltgateway.XSLTGateway`中的`evaluate`函数或名称空间`http://extension-functions.intersystems.com`中的`evaluate`函数 - 对于`XSLT1.0`(`Xalan`处理器),只能在名称空间`http://extension-functions.intersystems.com`中使用`evaluate`函数 默认情况下(举个例子),后一个函数反转它接收到的字符。但是,通常不使用默认行为,因为实现了一些其他行为。要模拟多个单独的函数,需要传递一个选择器作为第一个参数,并实现一个开关,该开关使用该值选择要执行的处理。 在内部,`evaluate`函数作为XSLT回调处理程序中的方法(`evaluate()`)实现。 要添加和使用XSLT扩展函数,请执行以下操作: 1. 对于`Xalan`或`Saxon`处理器,在创建`%XML.XSLT.CallbackHandler`的子类。在这个子类中,根据需要实现`evaluate()`方法。请参阅下一小节。 2. 在样式表中,声明`evaluate`函数所属的命名空间,并根据需要使用`evaluate`函数。请参阅下一小节。 3. 执行XSLT转换时,创建子类的实例,并在使用的`Transform`方法的参数列表中使用它。请参阅“执行XSLT转换”。 ## 实现evaluate()方法 在内部,调用`XSLT`处理器的代码可以将任意数量的位置参数传递给当前回调处理程序的`evaluate()`方法,该方法将它们作为具有以下结构的数组接收: Node| Value ---|--- Args| 参数数量 Args(index) |位置索引中参数的值 该方法只有一个返回值。返回值可以是: - 标量变量(如字符串或数字)。 - 流对象。这允许返回超过字符串长度限制的超长字符串。流必须包装在新窗口中的`%XML.XSLT.StreamAdapter`实例中,使XSLT处理器能够读取流。以下是部分示例: ```java Method evaluate(Args...) As %String { //create stream ///... // create instance of %XML.XSLT.StreamAdapter to // contain the stream Set return=##class(%XML.XSLT.StreamAdapter).%New(tStream) Quit return } ``` ## 在样式表中使用计算 要在XSLT中使用XSLT扩展函数,必须在XSLT样式表中声明扩展函数的名称空间。对于InterSystems evaluate函数,此命名空间是`http://extension-functions.intersystems.com`或`com.intersystems.xsltgateway.XSLTGateway`,如前所述。 下面的示例显示使用evaluate的样式表: ```xml ``` ## 使用ISC:计算缓存 XSLT2.0网关将`evaluate`函数调用缓存在`isc:evaluate`缓存中。缓存的默认最大大小为`1000`个项目,但可以将大小设置为不同的值。此外,还可以清除缓存、转储缓存,还可以从`%List`中预先填充缓存。使用以下格式: - 缓存条目总数 - 对于每个条目: 1. 求值参数总数 2. 所有求值参数 3. 计算值 缓存还包括可缓存的函数名称的过滤器列表。请注意以下事项: - 可以在筛选器列表中添加或删除函数名。 - 可以清除过滤器列表。 - 可以通过设置一个布尔值来覆盖筛选器列表,该布尔值将缓存每个`evaluate`调用。 将函数名添加到筛选器列表不会限制求值缓存的大小。可以对同一函数进行任意数量的调用,但具有不同的参数和返回值。函数名和参数的每个组合都是求值缓存中的一个单独条目。 可以使用`%XML.XSLT2.Transformer`中的方法来操作求值缓存。 # 使用XSL转换向导 Studio提供了一个执行XSLT转换的向导,当希望快速测试样式表或自定义XSLT扩展函数时,该向导非常有用。要使用此架构向导,请执行以下操作: 1. Tools > Add-Ins > XSLT Schema Wizard. 2. 指定以下必需的详细信息: - 对于XML文件,选择浏览以选择要转换的XML文件。 - 对于XSL文件,选择浏览以选择要使用的XSL样式表。 - 对于呈现为,选择文本或XML以控制转换的显示方式。 3. 如果已在要在此转换中使用的创建了`%XML.XSLT.CallbackHandler`的子类,请指定以下详细信息: - 对于XSLT Helper Class中的第一个下拉列表,选择一个命名空间。 - 对于XSLT Helper Class中的第二个下拉列表,选择该类。 4. 选择Finish(完成)。 对话框底部显示转换后的文件。可以从该区域复制和粘贴。 5. 要关闭此对话框,请选择取消。
文章
Michael Lei · 五月 3, 2022

Amazon EKS, IRIS 高可用与备份

所有源代码均在: https://github.com/antonum/ha-iris-k8s 在上一篇文章中,我们讨论了如何在k8s集群上建立具有高可用性的IRIS,基于分布式存储,而不是传统的镜像。作为一个例子,那篇文章使用了Azure AKS集群。在这一篇中,我们将继续探讨k8s上的高可用配置。这一次,基于Amazon EKS(AWS管理的Kubernetes服务),并将包括一个基于Kubernetes 快照进行数据库备份和恢复的选项。 安装 开始干活. 首先需要一个AWS账户,安装 AWS CLI, kubectl 和 eksctl 工具. 要创建新的集群,请运行以下命令: eksctl create cluster \ --name my-cluster \ --node-type m5.2xlarge \ --nodes 3 \ --node-volume-size 500 \ --region us-east-1 这个命令需要大约15分钟,部署EKS集群并使其成为你的kubectl工具的默认集群。你可以通过运行以下代码来验证你的部署: kubectl get nodes NAME STATUS ROLES AGE VERSION ip-192-168-19-7.ca-central-1.compute.internal Ready <none> 18d v1.18.9-eks-d1db3c ip-192-168-37-96.ca-central-1.compute.internal Ready <none> 18d v1.18.9-eks-d1db3c ip-192-168-76-18.ca-central-1.compute.internal Ready <none> 18d v1.18.9-eks-d1db3c 下一步是安装Longhorn分布式存储引擎. kubectl create namespace longhorn-system kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.1.0/deploy/iscsi/longhorn-iscsi-installation.yaml --namespace longhorn-system kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml --namespace longhorn-system 最后安装 IRIS : kubectl apply -f https://github.com/antonum/ha-iris-k8s/raw/main/tldr.yaml 现在,你就有了一个功能齐全的EKS集群,并安装了Longhorn分布式存储和IRIS。 你可以回到上一篇文章,尝试对集群和IRIS部署进行各种破坏,看看系统如何自我修复。请看模拟故障Simulate the Failure部分。 Bonus #1 IRIS on ARM IRIS EKS和Longhorn都支持ARM架构,所以我们可以使用AWS Graviton 2实例部署相同的配置,基于ARM架构。 你只需要将EKS节点的实例类型改为 "m6g "系列,将IRIS镜像改为基于ARM的。 eksctl create cluster \--name my-cluster-arm \--node-type m6g.2xlarge \--nodes 3 \--node-volume-size 500 \--region us-east-1 tldr.yaml containers: #- image: store/intersystems/iris-community:2020.4.0.524.0 - image: store/intersystems/irishealth-community-arm64:2020.4.0.524.0 name: iris 或只是用: kubectl apply -f https://github.com/antonum/ha-iris-k8s/raw/main/tldr-iris4h-arm.yaml 这就行了! 你就有了一个在在ARM平台上运行的IRIS Kubernetes集群. Bonus #2 - 备份与恢复 生产级架构的一个经常被忽视的部分是创建数据库的备份,并在需要时快速恢复和克隆这些备份的能力。 在Kubernetes中,常见的方式是使用持久化卷快照。 首先--你需要安装所有需要的k8s组件: #Install CSI Snapshotter and CRDs kubectl apply -n kube-system -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml kubectl apply -n kube-system -f https://github.com/kubernetes-csi/external-snapshotter/raw/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml kubectl apply -n kube-system -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml kubectl apply -n kube-system -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/deploy/kubernetes/snapshot-controller/setup-snapshot-controller.yaml kubectl apply -n kube-system -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/deploy/kubernetes/snapshot-controller/rbac-snapshot-controller.yaml 下一步 - 为Longhorn配置S3 bucket凭证 (请查看 详细指导): #Longhorn backup target s3 bucket and credentials longhorn would use to access that bucket #See https://longhorn.io/docs/1.1.0/snapshots-and-backups/backup-and-restore/set-backup-target/ for manual setup instructions longhorn_s3_bucket=longhorn-backup-123xx #bucket name should be globally unique, unless you want to reuse existing backups and credentials longhorn_s3_region=us-east-1 longhorn_aws_key=AKIAVHCUNTEXAMPLE longhorn_aws_secret=g2q2+5DVXk5p3AHIB5m/Tk6U6dXrEXAMPLE 下面的命令将从上一步骤中获取环境变量,并使用它们来配置Longhorn备份。 #configure Longhorn backup target and credentials cat <<EOF | kubectl apply -f - apiVersion: longhorn.io/v1beta1 kind: Setting metadata: name: backup-target namespace: longhorn-system value: "s3://$longhorn_s3_bucket@$longhorn_s3_region/" # backup target here --- apiVersion: v1 kind: Secret metadata: name: "aws-secret" namespace: "longhorn-system" labels: data: # echo -n '<secret>' | base64 AWS_ACCESS_KEY_ID: $(echo -n $longhorn_aws_key | base64) AWS_SECRET_ACCESS_KEY: $(echo -n $longhorn_aws_secret | base64) --- apiVersion: longhorn.io/v1beta1 kind: Setting metadata: name: backup-target-credential-secret namespace: longhorn-system value: "aws-secret" # backup secret name here EOF 它可能看起来很多,但它基本上是告诉Longhorn使用一个特定的S3 bucket,用指定的凭证来存储备份的内容。 搞定! 如果你现在进入Longhorn的用户界面,你就可以创建备份,恢复等等。 关于如何连接到Longhorn用户界面的快速练习: kubectl get pods -n longhorn-system # note the full pod name for 'longhorn-ui-...' pod kubectl port-forward longhorn-ui-df95bdf85-469sz 9000:8000 -n longhorn-system 这将把到Longhorn UI的流量转发到你的本地 http://localhost:9000 程序化的 备份/恢复 通过Longhorn用户界面进行备份和恢复可能是足够好的第一步--但我们将更进一步,使用k8s Snapshot APIs,以编程方式进行备份和恢复. 首先 - 快照本身. iris-volume-snapshot.yaml apiVersion: snapshot.storage.k8s.io/v1beta1 kind: VolumeSnapshot metadata: name: iris-longhorn-snapshot spec: volumeSnapshotClassName: longhorn source: persistentVolumeClaimName: iris-pvc 这个卷快照指的是源卷 "iris-pvc",我们确实在IRIS部署中使用。因此,只要应用这个就可以立即开始备份过程. 在快照之前/之后执行IRIS Write Daemon Freeze/Thaw是一个好主意. #Freeze Write Daemon echo "Freezing IRIS Write Daemon" kubectl exec -it -n $namespace $pod_name -- iris session iris -U%SYS "##Class(Backup.General).ExternalFreeze()" status=$? if [[ $status -eq 5 ]]; then echo "IRIS WD IS FROZEN, Performing backup" kubectl apply -f backup/iris-volume-snapshot.yaml -n $namespace elif [[ $status -eq 3 ]]; then echo "IRIS WD FREEZE FAILED" fi #Thaw Write Daemon kubectl exec -it -n $namespace $pod_name -- iris session iris -U%SYS "##Class(Backup.General).ExternalThaw()" 恢复过程是非常直接的。它基本上是创建一个新的PVC,并指定快照为源。 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: iris-pvc-restored spec: storageClassName: longhorn dataSource: name: iris-longhorn-snapshot kind: VolumeSnapshot apiGroup: snapshot.storage.k8s.io accessModes: - ReadWriteOnce resources: requests: storage: 10Gi 然后你就根据这个PVC创建一个新的部署。检查github repo中的这个测试脚本,它将依次进行。 创建全新的IRIS部署 向IRIS添加一些数据 冻结写Daemon,拍摄快照,解冻写Daemon 在快照的基础上,创建一个IRIS部署的克隆 验证所有的数据是否还在那里 验证所有的数据是否还在那里,现在你将有两个相同的IRIS部署,一个是另一个的克隆备份。 Enjoy!
文章
Qiao Peng · 一月 5, 2021

增强型日志监视器

各位开发者们大家好! 此前,我向各位介绍了一个非常好用的运行分析监控面板,它能使消息处理过程中的关键指标可视化,例如入站/出站消息的数量和平均处理时间等。 现在,我想用一项许多人已熟悉的工作流程,来展示一个增强型日志监视器——将警告信息作为Production中的消息来处理。我们可以通过创建路由规则来实现对告警消息的过滤和路由,并运用预先构建的组件(例如电子邮件适配器等)来发送粒度级别的通知。 如你所知,监视和管理警告信息是确保任何应用程序平稳运行的关键。对诸如HealthShare和IRIS医疗版这样支撑医疗系统运转的一级应用程序和集成引擎来说对告警信息的处理更显得尤为重要。 让我们先来梳理一下InterSystems产品中已经附带的警告信息监视和管理工具: 通过名为Ens.Alert的组件,你可以使用警告处理器(Alert Processor)为Production中的各类接口配置自定义警报。 系统监视器(System Monitor )能显示Production关键性能指标的实时状态。 日志监视管理器(Log Monitor Manager (^MONMGR) utility )程序能根据消息日志(现在InterSystems IRIS上称messages.log,以往称cconsole.log)生成各种严重级别的通知消息,再通过电子邮件将该通知发送给预设好的收件人。 Production监视器(Production Monitor )显示当前正在运行的Production及其接口(输入/输出连接)、队列、活动作业、事件日志、活动图表等的实时状态。 镜像监视器(Mirror Monitor)显示每个镜像及其构件的运行状态、镜像数据库状态以及关键镜像指标(例如日志传入速率)信息。 尽管由我制作的增强型日志监控器与上述这些日志监控器管理器(^MONMGR)非常相似,它的好处在于给用户提供了一个熟悉的界面和对警告信息的精准路由及管理能力——每个写进消息日志(messages.log文件)的告警条目都会被转换成一条Production里的消息,再按路由规则(Ens.Alert)精细过滤出特定的警告。这些警告可以通过Production中的操作(Business Operation)使用邮件和短信等方式发送出去。不仅如此,现在你还可以在Production中的邮件适配器设置来轻松编辑通知的收件人。 例如:日志监控管理器(^MONMGR)已经具备了按照指定的最低严重性级别发出警告的功能,你可以通过设置在发生二级日志事件时自动向系统管理员发出警告。 如果使用我即将介绍的增强型日志监视器,你就能进一步细化过滤,做到不是所有二级事项都发出,而是只在一个实例的发生了镜像故障转移切换(二级事件范畴下的一个具体情况)时才发出警告。在这个例子中,我们假设该系统部署了由镜像实现的高可用/灾备功能,并且包含这个日志监视器Production需要运行在每个镜像成员中的非镜像命名空间中。 使用增强型日志监控器前,请先从OpenExchange下载示例代码。下载的文件为xml格式,可以直接导入。导入时请转到管理门户,并导航至“Interoperability”->“管理”->“部署变化”->“部署”。 现在点选“Open Local Deployment”选项来打开从OpenExchange下载的xml文件,并在加载后单击“部署” 导航到刚刚部署的“Interoperability”(互操作性->列表->Production)。请勿在设定好全局^lasttimestamp(后面再做说明)之前启动Production。你应该能在Production中看到以下三个组件: “测试”服务 这其中包含了我所使用的定制底层代码(JK.MONMGR.CustomService class.)该代码会持续检查message.log文件是否被加入了新的行,再为每个新加的行项目创建Ens.AlertRequest消息并将其发送至Ens.Alert。你可以使用它的适配器设置来设定呼叫间隔——即在消息日志(messages.log文件)中检查新行的频率。为了能让设定值尽量贴近实际频率,你可以选则诸如1或5这样较小的整数。 “Ens.Alert”进程 这是一个名叫“Ens.Alerts”的路由规则,你可以利用它把特定的警告(基于警告文本)从消息日志路由到“EnsLib.EmailAlertOperation”以发送邮件通知。请注意要在条件中包含AlertText的内容(即Document.AlertText [ “”后面双引号内的警告文字)。你还可以创建其他的附加规则,也可以用DTL把警告消息转换为向下游发送的电子邮件模板或其他格式的通知。 “EnsLib.EmailAlertOperation”操作 这是一个预先构建好的出站电子邮件操作(BO),让你能直接发送邮件通知。你可以利用Production配置中的邮件适配器设置来设定要发送电子邮件的地址列表,SMTP服务器/端口以及凭据等。 启动Production前应先设置全局^ lasttimestamp以记录下此工具检查最后一行时的时间戳。你需要按“月/日/年 时:分:秒”的格式进行设置–例如,从终端输入: >> set ^lasttimestamp = “08/28/2020 08:00:00” 现在可以启动这个Production来亲身体会它的功效了!你还可以通过修改示例代码来满足你的特定需求。 如有任何疑问,请在下方留言评论或与我们的销售工程师联系!
文章
姚 鑫 · 十月 15, 2021

第四十六章 SQL命令 FROM(二)

[toc] # 第四十六章 SQL命令 FROM(二) ## %PARALLEL 这个可选关键字在查询的FROM子句中指定。 它建议 IRIS使用多个处理器(如果适用)并行处理查询。 这可以显著提高使用一个或多个`COUNT`、`SUM`、`AVG`、`MAX`或`MIN`聚合函数和/或`GROUP BY`子句的某些查询的性能,以及许多其他类型的查询。 这些通常是处理大量数据并返回小结果集的查询。 例如,`SELECT AVG(SaleAmt) FROM %PARALLEL User.AllSales GROUP BY Region`使用并行处理。 既指定单个字段又指定聚合函数且不包含`GROUP BY`子句的查询不能执行并行处理。 例如,`SELECT Name,AVG(Age) FROM %PARALLEL Sample`。 `Person`不执行并行处理,而是从`SELECT Name,AVG(Age) FROM %PARALLEL Sample.Person GROUP BY Home_State`执行并行处理。 `%PARALLEL`用于`SELECT`查询及其子查询。 `INSERT`命令子查询不能使用`%PARALLEL`。 指定`%PARALLEL`可能会降低某些查询的性能。 在具有多个并发用户的系统上使用`%PARALLEL`运行查询可能会导致整体性能下降。 注意:指定`%PARALLEL`的查询必须在读/写而不是只读的数据库中运行。 否则,可能发生``错误。 不管在`FROM`子句中是否存在`%PARALLEL`关键字,有些查询可能使用线性处理,而不是并行处理:有些查询不支持并行处理; 一些优化后的查询可能无法从并行处理中获益。 可以使用`Show Plan`确定 IRIS是否以及如何对查询进行了并行处理分区。 要确定当前系统上的处理器数量,使用 `%SYSTEM.Util.NumberOfCPUs()`方法。 ## %STARTTABLE 这个可选关键字指定查询优化器应该开始对`FROM`子句中列出的第一个表执行联接。 其余表的连接顺序留给查询优化器。 将此关键字与`%INORDER`进行比较,后者指定了完整的连接顺序。 `%STARTTABLE`不能与交叉连接或右外连接一起使用。 不能使用`%STARTTABLE`(或`%FIRSTTABLE`)从左`OUTER join`(或右`OUTER join`)的左边开始连接顺序。 如果指定的开始表与外部连接的要求不一致,则会生成一个`SQLCODE -34`错误:“优化器未能找到可用的连接顺序。” 为了避免这种情况,当与外部连接一起使用时,建议`%STARTTABLE`只与`ansi`风格的左外部连接或完整外部连接一起使用。 下表显示了在使用`%INORDER`和`%STARTTABLE`优化组合超查询父视图和内联视图时的合并行为: "" | 没有连接优化器的超查询 | 具有%STARTTABLE的超级查询 | 有`%INORDER`的超级查询 ---|---|---|--- 不带连接优化器的视图 | 如果可能,合并视图 |如果视图是超查询`start: don't merge`。否则,如果可能,合并视图。 |合并如果可能的话;视图的底层表是无序的。 使用`%STARTTABLE`查看 | 不合并 | 如果视图是超级查询`start: merge`,如果可能的话。视图的开始表变成了超级查询的开始表。否则,不合并。 | 不合并 使用`%INORDER`查看 | 不合并 | 不合并 | 如果视图不是由`%INORDER`控制的,则不要合并。否则,如果可能,合并视图;视图的顺序被替换为超级查询连接顺序。 `%FIRSTTABLE`提示在功能上与`%STARTTABLE`相同,但是提供了以任意顺序指定连接表序列的灵活性。 # FROM子句中的表值函数 表值函数是一个类查询,它被投影为一个存储过程,并返回单个结果集。 表值函数是任何具有`SqlProc TRUE`的类查询。 用作表值函数的类查询必须在`LOGICAL`或`RUNTIME`模式下编译。 当作为表值函数使用并在`RUNTIME`模式下编译时,表值函数查询将在`LOGICAL`模式下调用。 表值函数遵循与类查询的存储过程名称相同的命名约定。 参数括号是必须的; 括号可以是空的,可以包含一个字面值或一个主机变量,也可以包含一个用逗号分隔的字面值和主机变量列表。 如果不指定参数(空括号或空字符串),表值函数将返回所有数据行。 要使用表值函数发出查询,用户必须对定义表值函数的存储过程拥有`EXECUTE`权限。 用户还必须对表值函数查询访问的表或视图具有`SELECT`权限。 在下面的示例中,类查询`Sample.Person.ByName`被投影为一个存储过程,因此可以用作表值函数: ```sql SELECT Name,DOB FROM Sample.SP_Sample_By_Name('A') ``` 下面的动态SQL示例指定相同的表值函数。它使用`%Execute()`方法将参数值提供给`?`入参: ```java ClassMethod From() { s myquery="SELECT Name,DOB FROM Sample.SP_Sample_By_Name(?)" s tStatement = ##class(%SQL.Statement).%New() s qStatus = tStatement.%Prepare(myquery) if qStatus'=1 {w "%Prepare failed:" d $System.Status.DisplayError(qStatus) q} s rset = tStatement.%Execute("A") d rset.%Display() w !,"End of A data",!! s rset = tStatement.%Execute("B") d rset.%Display() w !,"End of B data" } ``` 表值函数只能在`SELECT`语句或`DECLARE`语句的`FROM`子句中使用。表值函数名可以用模式名限定,也可以用非限定名(没有模式名)限定;非限定名使用默认模式。在`SELECT`语句`FROM`子句中,只要可以使用表名,就可以使用表值函数。它可以在视图或子查询中使用,并且可以使用逗号分隔的列表或显式联接语法与其他表引用项联接。 表值函数不能直接用于`INSERT`、`UPDATE`或`DELETE`语句。但是,可以为这些命令指定子查询,以指定表值函数。 SQL没有为表值函数定义`EXTENTSIZE`,也没有为表值函数列定义`SELECTIVITY`。 # FROM子句中的子查询 可以在`FROM`子句中指定子查询。 这称为流子查询。 子查询被视为与表相同的处理方式,包括它在`JOIN`语法中的使用以及使用`as`关键字可选地分配别名。 `FROM`子句可以以任何组合包含多个表、视图和子查询,但要受`JOIN`语法的限制,如`JOIN`中所述。 ```sql SELECT name,region FROM (SELECT t1.name,t1.state,t2.region FROM Employees AS t1 LEFT OUTER JOIN Regions AS t2 ON t1.state=t2.state) GROUP BY region ``` 子查询可以指定`TOP`子句。 当与`TOP`子句配对时,子查询可以包含`ORDER BY`子句。 子查询可以使用`SELECT *`语法,但有以下限制:因为`FROM`子句的结果是值表达式,所以包含`SELECT *`的子查询只能生成一列。 子查询中的连接不能是`NATURAL`连接或接受`USING`子句。 ## 从子查询和%VID 当调用`FROM`子查询时,它为返回的每个子查询行返回一个`%VID`。 `%VID`是一个整数计数器字段; 它的值是系统分配的、唯一的、非空的、非零的、不可修改的。 `%VID`仅在显式指定时返回。 它以数据类型`INTEGER`返回。 因为`%VID`值是顺序整数,所以如果子查询返回的是顺序数据,则它们更有意义; 子查询只能在与`TOP`子句配对时使用`ORDER BY`子句。 因为`%VID`是一个顺序整数,所以可以用它来确定带有`ORDER BY`子句的子查询中项目的排名。 在下面的示例中,`10`条最新的记录按名称顺序列出,但是使用`%VID`值可以很容易地看到它们的时间戳排名: ``` SELECT Name,%VID,TimeStamp FROM (SELECT TOP 10 * FROM MyTable ORDER BY TimeStamp DESC) ORDER BY Name ``` `%VID`的一个常见用途是`“window”`结果集,将执行划分为符合显示窗口中可用行数的顺序子集。 例如,显示`20`条记录,然后等待用户按`Enter`键,然后显示下`20`条记录。 ```java ClassMethod From1() { s myq=4 s myq(1)="SELECT %VID,* " s myq(2)="FROM (SELECT TOP 60 Name,Age FROM Sample.Person " s myq(3)="WHERE Age > 55 ORDER BY Name) " s myq(4)="WHERE %VID BETWEEN ? AND ?" s tStatement = ##class(%SQL.Statement).%New() s qStatus = tStatement.%Prepare(.myq) if qStatus'=1 {w "%Prepare failed:" d $System.Status.DisplayError(qStatus) q} for i=1:10:60 { s rset = tStatement.%Execute(i, i+9) while rset.%Next() { d rset.%Print() } w !! } w "End of data" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQLCommand).From1() 1 "Ahmed,Elmo X." 78 2 "Alton,Phil T." 68 3 "Anderson,Mario L." 78 4 "Bachman,Susan O." 88 5 "Basile,Filomena X." 87 6 "Browne,Robert X." 83 7 "Bukowski,Mario V." 86 8 "Burroughs,Barbara H." 86 9 "Cerri,Stavros Q." 96 10 "Chadbourne,Barb B." 94 ``` # 可选FROM子句 如果`SELECT`项列表(直接或间接)没有引用表数据,则`FROM`子句是可选的。 这种`SELECT`可以用于从函数、运算符表达式、常量或宿主变量返回数据。 对于不引用表数据的查询: - 如果省略`FROM`子句,则不管`TOP`关键字值如何,最多返回一行数据; `TOP 0`不返回任何数据。 `DISTINCT`子句被忽略。 不需要特权。 - 如果指定了`FROM`子句,则必须指定当前命名空间中的现有表。 必须对该表具有`SELECT`权限,即使该表没有被引用。 除非指定了`TOP`或`DISTINCT`子句,或者用`WHERE`或`HAVING`子句限制它,否则返回的相同数据行数等于指定表中的行数。 指定DISTINCT子句将输出限制为单行数据。 `TOP`关键字将输出限制为`TOP`值指定的行数; `TOP 0`不返回任何数据。 无论是否有`FROM`子句,都可以指定后续子句(如`GROUP BY`、`HAVING`或`ORDER BY`)。 `WHERE`或`HAVING`子句可用于确定是否返回结果,或返回多少相同的结果行。 即使没有指定`FROM`子句,这些子句也可以引用表。 可以指定`GROUP BY`或`ORDER BY`子句,但这些子句没有意义。 下面是不引用表数据的`SELECT`语句示例。 两个示例都返回一行信息。 下面的例子省略了`FROM`子句。 `DISTINCT`关键字不是必需的,但是可以指定。 不允许使用`SELECT`子句。 ```sql SELECT 3+4 AS Arith, {fn NOW} AS NowDateTime, {fn DAYNAME({fn NOW})} AS NowDayName, UPPER('MixEd cASe EXPreSSioN') AS UpCase, {fn PI} AS PiConstant ``` 下面的示例包含一个`FROM`子句。 `DISTINCT`关键字用于返回单行数据。 `FROM`子句表引用必须是一个有效的表。 这里允许使用`ORDER BY`子句,但没有意义。 注意,`ORDER BY`子句必须指定一个有效的选择项别名: ```sql SELECT DISTINCT 3+4 AS Arith, {fn NOW} AS NowDateTime, {fn DAYNAME({fn NOW})} AS NowDayName, UPPER('MixEd cASe EXPreSSioN') AS UpCase, {fn PI} AS PiConstant FROM Sample.Person ORDER BY NowDateTime ``` 下面的例子都使用了`WHERE`子句来决定是否返回结果。 第一个包含`FROM`子句,并使用`DISTINCT`关键字返回单行数据。 第二个省略了`FROM`子句,因此最多返回一行数据。 在这两种情况下,`WHERE`子句表引用必须是具有`SELECT`权限的有效表: ```sql SELECT DISTINCT {fn NOW} AS DataOKDate FROM Sample.Person WHERE FOR SOME (Sample.Person)(Name %STARTSWITH 'A') ``` ```sql SELECT {fn NOW} AS DataOKDate WHERE FOR SOME (Sample.Person)(Name %STARTSWITH 'A') ```
文章
姚 鑫 · 三月 25, 2021

第十三章 使用动态SQL(三)

# 第十三章 使用动态SQL(三) # 执行SQL语句 有两种使用`%SQL.Statement`类执行SQL语句的方法: - `%Execute()`,它执行以前使用`%Prepare()`或`%PrepareClassQuery()`准备的SQL语句。 - `%ExecDirect()`,它同时准备和执行一条SQL语句。 也可以通过使用`$SYSTEM.SQL.Execute()`方法执行SQL语句而无需创建对象实例。此方法既准备又执行SQL语句。它创建一个缓存的查询。下面的终端示例显示`Execute()`方法: ```java USER>SET topnum=5 USER>SET rset=$SYSTEM.SQL.Execute("SELECT TOP :topnum Name,Age FROM Sample.Person") USER>DO rset.%Display() ``` ## %Execute() 准备查询后,可以通过调用`%SQL.Statement`类的`%Execute()`实例方法来执行查询。对于非`SELECT`语句,`%Execute()`调用所需的操作(例如执行`INSERT`)。对于`SELECT`查询,`%Execute()`生成一个结果集,用于后续遍历和数据检索。例如: ```java SET rset = tStatement.%Execute() ``` `%Execute()`方法为所有SQL语句设置`%SQL.StatementResult`类属性`%SQLCODE`和`%Message`。 `%Execute()`设置其他`%SQL.StatementResult`属性,如下所示: - `INSERT`,`UPDATE`,`INSERT`或`UPDATE`,`DELETE`和`TRUNCATE TABLE`语句将`%ROWCOUNT`设置为受操作影响的行数。 `TRUNCATE TABLE`无法确定删除的实际行数,因此将`%ROWCOUNT`设置为-1。 `INSERT`,`UPDATE`,`INSERT OR UPDATE`和`DELETE`将`%ROWID`设置为最后一条插入,更新或删除的记录的`RowID`值。如果该操作未插入,更新或删除任何记录,则`%ROWID`是未定义的,或保持设置为其先前值。 `TRUNCATE TABLE`没有设置`%ROWID`。 - `SELECT`语句在创建结果集时会将`%ROWCOUNT`属性设置为0。当程序遍历结果集的内容(例如,使用`%Next()`方法)时,`%ROWCOUNT`会增加。 `%Next()`返回1表示它位于一行上,返回0表示它位于最后一行之后(在结果集的末尾)。如果光标位于最后一行之后,则`%ROWCOUNT`的值指示结果集中包含的行数。 如果`SELECT`查询仅返回聚合函数,则每个`%Next()`都将设置`%ROWCOUNT = 1`。即使表中没有数据,第一个`%Next()`始终设置`%SQLCODE = 0`。任何后续的`%Next()`都会设置`%SQLCODE = 100`并设置`%ROWCOUNT = 1`。 `SELECT`还设置`%CurrentResult`和`%ResultColumnCount`。 `SELECT`未设置`%ROWID`。 可以使用`ZWRITE`返回所有`%SQL.StatementResult`类属性的值。 ## 具有输入参数的%Execute() `%Execute()`方法可以采用一个或多个与准备的SQL语句中的输入参数(以`“?”`表示)相对应的参数。 `%Execute()`参数对应于`“?”`的顺序字符出现在SQL语句中:第一个参数用于第一个`“?”`,第二个参数用于第二个`“?”`,依此类推。多个`%Execute()`参数以逗号分隔。可以通过指定占位符逗号来省略参数值。 `%Execute()`参数的数量必须与`“?”`相对应输入参数。如果`%Execute()`参数少于或大于相应的`“?”`输入参数,执行失败,并且`%SQLCODE`属性设置为`SQLCODE -400`错误。 可以使用输入参数为`SELECT`列表和其他查询子句(包括`TOP`子句和`WHERE`子句)提供文字值或表达式。不能使用输入参数为`SELECT`列表或其他查询子句提供列名或列名别名。 **当指定为显式`%Execute()`参数时,最大输入参数数为255。使用可变长度数组`%Execute(vals ...)`指定时,最大输入参数数为380。** 在执行`Prepare`之后,可以使用`Prepare`参数元数据来返回`?`的计数和所需的数据类型。输入参数。可以使用`%GetImplementationDetails()`方法返回`?`的列表。在准备好的查询中输入参数,并在查询文本中使用`?`输入参数显示在上下文中。 以下ObjectScript示例使用两个输入参数执行查询。它在`%Execute()`方法中指定输入参数值(21和26)。 ```java /// d ##class(PHA.TEST.SQL).PrepareClassQuery7() ClassMethod PrepareClassQuery7() { SET tStatement = ##class(%SQL.Statement).%New(1) SET tStatement.%SchemaPath = "MyTests,Sample,Cinema" SET myquery=2 SET myquery(1)="SELECT Name,DOB,Age FROM Person" SET myquery(2)="WHERE Age > ? AND Age < ? ORDER BY Age" SET qStatus = tStatement.%Prepare(.myquery) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET rset = tStatement.%Execute(21,26) WRITE !,"Execute OK: SQLCODE=",rset.%SQLCODE,!! DO rset.%Display() WRITE !,"End of data: SQLCODE=",rset.%SQLCODE } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).PrepareClassQuery7() Execute OK: SQLCODE=0 Name DOB Age Van De Griek,Dick U. 1998-12-21 22 Peterson,Kirsten R. 1997-12-13 23 Van De Griek,Phil S. 1996-09-26 24 Wijnschenk,Lydia G. 1997-01-17 24 Xiang,Kirsten U. 1996-08-06 24 Schaefer,Usha G. 1995-09-16 25 Peterson,Sophia A. 1995-12-05 25 Petersburg,Bill O. 1995-10-23 25 8 Rows(s) Affected End of data: SQLCODE=100 ``` 下面的ObjectScript示例执行相同的查询。 `%Execute()`方法形式参数列表使用可变长度数组(`dynd ...`)指定不确定数量的输入参数值;在这种情况下,为`dynd`数组的下标。 `dynd`变量设置为2以指示两个下标值。 ```java /// d ##class(PHA.TEST.SQL).PrepareClassQuery8() ClassMethod PrepareClassQuery8() { SET tStatement = ##class(%SQL.Statement).%New(1) SET tStatement.%SchemaPath = "MyTests,Sample,Cinema" SET myquery=2 SET myquery(1)="SELECT Name,DOB,Age FROM Person" SET myquery(2)="WHERE Age > ? AND Age < ? ORDER BY Age" SET dynd=2,dynd(1)=21,dynd(2)=26 SET qStatus = tStatement.%Prepare(.myquery) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET rset = tStatement.%Execute(dynd...) WRITE !,"Execute OK: SQLCODE=",rset.%SQLCODE,!! DO rset.%Display() WRITE !,"End of data: SQLCODE=",rset.%SQLCODE } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).PrepareClassQuery8() Execute OK: SQLCODE=0 Name DOB Age Van De Griek,Dick U. 1998-12-21 22 Peterson,Kirsten R. 1997-12-13 23 Van De Griek,Phil S. 1996-09-26 24 Wijnschenk,Lydia G. 1997-01-17 24 Xiang,Kirsten U. 1996-08-06 24 Schaefer,Usha G. 1995-09-16 25 Peterson,Sophia A. 1995-12-05 25 Petersburg,Bill O. 1995-10-23 25 8 Rows(s) Affected End of data: SQLCODE=100 ``` 可以对准备好的结果集执行多个`%Execute()`操作。这使可以多次运行查询,并提供不同的输入参数值。不必在`%Execute()`操作之间关闭结果集,如以下示例所示: ```java /// d ##class(PHA.TEST.SQL).PrepareClassQuery9() ClassMethod PrepareClassQuery9() { SET myquery="SELECT Name,SSN,Age FROM Sample.Person WHERE Name %STARTSWITH ?" SET tStatement = ##class(%SQL.Statement).%New() SET qStatus = tStatement.%Prepare(myquery) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET rset = tStatement.%Execute("A") DO rset.%Display() WRITE !,"End of A data",!! SET rset = tStatement.%Execute("B") DO rset.%Display() WRITE !,"End of B data" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).PrepareClassQuery9() Name SSN Age Alton,Martin S. 624-25-8488 47 Ahmed,Elmo X. 950-40-6135 77 Anderson,Mario L. 604-10-9256 77 Adams,Diane F. 640-77-5933 9 Anderson,Valery N. 882-50-4971 27 Alton,Phil T. 785-37-8519 68 Adams,Susan E. 947-66-8684 52 7 Rows(s) Affected End of A data Name SSN Age Bukowski,Mario V. 683-32-4214 85 Bachman,Susan O. 102-59-3932 88 Bush,Jules K. 547-97-7915 13 Basile,Filomena X. 888-66-1725 86 Browne,Robert X. 308-58-1444 82 Burroughs,Barbara H. 627-56-2213 86 Beatty,Molly Z. 794-64-5615 54 7 Rows(s) Affected End of B data ``` ### 使用TRY / CATCH处理%Execute错误 可以在`TRY`块结构内执行Dynamic SQL,将运行时错误传递给关联的`CATCH`块异常处理程序。对于`%Execute()`错误,可以使用`%Exception.SQL`类创建一个异常实例,然后将其扔到`CATCH`异常处理程序中。 下面的示例在发生`%Execute()`错误时创建一个SQL异常实例。在这种情况下,错误是数量之间的基数不匹配。输入参数(1)和`%Execute()`参数的数量(3)。它将`%SQLCODE`和`%Message`属性值(作为`Code`和`Data`)抛出到`CATCH`异常处理程序中。异常处理程序使用`%IsA()`实例方法测试异常类型,然后显示`%Execute()`错误: ```java /// d ##class(PHA.TEST.SQL).SQLTRY() ClassMethod SQLTRY() { TRY { SET myquery = "SELECT TOP ? Name,DOB FROM Sample.Person" SET tStatement = ##class(%SQL.Statement).%New() SET qStatus = tStatement.%Prepare(myquery) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET rset = tStatement.%Execute(7,9,4) IF rset.%SQLCODE=0 { WRITE !,"Executed query",! } ELSE { SET badSQL=##class(%Exception.SQL).%New(,rset.%SQLCODE,,rset.%Message) THROW badSQL } DO rset.%Display() WRITE !,"End of data" RETURN } CATCH exp { WRITE "In the CATCH block",! IF 1=exp.%IsA("%Exception.SQL") { WRITE "SQLCODE: ",exp.Code,! WRITE "Message: ",exp.Data,! } ELSE { WRITE "Not an SQL exception",! } RETURN } } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).SQLTRY() In the CATCH block SQLCODE: -400 Message: Dynamic SQL Execute, more parameter values passed than are specified in the dynamic statement ``` ## %ExecDirect() `%SQL.Statement`类提供`%ExecDirect()`类方法,该方法可以在单个操作中准备和执行查询。它可以准备指定的查询(如`%Prepare()`)或现有的类查询(如`%PrepareClassQuery()`)。 `%ExecDirect()`准备并执行指定的查询: ```java /// d ##class(PHA.TEST.SQL).ExecDirect() ClassMethod ExecDirect() { SET myquery=2 SET myquery(1)="SELECT Name,Age FROM Sample.Person" SET myquery(2)="WHERE Age > 21 AND Age < 30 ORDER BY Age" SET rset = ##class(%SQL.Statement).%ExecDirect(,.myquery) IF rset.%SQLCODE=0 { WRITE !,"ExecDirect OK",!! } ELSE { WRITE !,"ExecDirect SQLCODE=",rset.%SQLCODE,!,rset.%Message QUIT } DO rset.%Display() WRITE !,"End of data: SQLCODE=",rset.%SQLCODE } ``` ```java DHC-APP> d ##class(PHA.TEST.SQL).ExecDirect() ExecDirect OK Name Age Van De Griek,Dick U. 22 Peterson,Kirsten R. 23 Van De Griek,Phil S. 24 Wijnschenk,Lydia G. 24 Xiang,Kirsten U. 24 Schaefer,Usha G. 25 Peterson,Sophia A. 25 Petersburg,Bill O. 25 Ng,Josephine Z. 26 Munt,Valery W. 26 Ingleman,Martin T. 26 Eno,Diane U. 26 Pascal,Kim P. 27 Ipsen,Jane A. 27 Anderson,Valery N. 27 Gomez,Mo Q. 27 Xerxes,Angelo P. 28 Young,Barbara N. 29 18 Rows(s) Affected End of data: SQLCODE=100 ``` `%ExecDirect()`准备并执行现有的类查询: ```java /// d ##class(PHA.TEST.SQL).ExecDirect1() ClassMethod ExecDirect1() { SET mycallq = "?=CALL Sample.PersonSets('A','NH')" SET rset = ##class(%SQL.Statement).%ExecDirect(,mycallq) IF rset.%SQLCODE=0 { WRITE !,"ExecDirect OK",!! } ELSE { WRITE !,"ExecDirect SQLCODE=",rset.%SQLCODE,!,rset.%Message QUIT } DO rset.%Display() WRITE !,"End of data: SQLCODE=",rset.%SQLCODE } ``` 可以将输入参数值指定为`%ExecDirect()`类方法的第三个参数和后续参数,如以下示例所示: ```java /// d ##class(PHA.TEST.SQL).ExecDirect2() ClassMethod ExecDirect2() { SET myquery=2 SET myquery(1)="SELECT Name,Age FROM Sample.Person" SET myquery(2)="WHERE Age > ? AND Age < ? ORDER BY Age" SET rset = ##class(%SQL.Statement).%ExecDirect(,.myquery,12,20) IF rset.%SQLCODE'=0 { WRITE !,"1st ExecDirect SQLCODE=",rset.%SQLCODE,!,rset.%Message QUIT } DO rset.%Display() WRITE !,"End of teen data",!! SET rset2 = ##class(%SQL.Statement).%ExecDirect(,.myquery,19,30) IF rset2.%SQLCODE'=0 { WRITE !,"2nd ExecDirect SQLCODE=",rset2.%SQLCODE,!,rset2.%Message QUIT } DO rset2.%Display() WRITE !,"End of twenties data" } ``` ```java DHC-APP> d ##class(PHA.TEST.SQL).ExecDirect2() Name Age Bush,Jules K. 13 ... Eastman,Howard K. 18 9 Rows(s) Affected End of teen data Name Age Ingrahm,Susan N. 20 ... Young,Barbara N. 29 20 Rows(s) Affected End of twenties data ``` `%ExecDirect()`输入参数对应于`“?”`的顺序字符出现在SQL语句中:第三个参数用于第一个`“?”`,第四个参数用于第二个`“?”`,依此类推。可以通过指定占位符逗号来省略参数值。如果`%ExecDirect()`输入参数少于相应的`“?”`输入参数,则使用默认值(如果存在)。 在下面的示例中,第一个`%ExecDirect()`指定所有三个`“?”`输入参数,第二个`%ExecDirect()`仅指定第二个`?`输入参数,并省略第一个和第三个。它使用第三个输入参数的默认`Sample.PersonSets()('MA')`: ```java /// d ##class(PHA.TEST.SQL).ExecDirect3() ClassMethod ExecDirect3() { SET mycall = "?=CALL Sample.PersonSets(?,?)" SET rset = ##class(%SQL.Statement).%ExecDirect(,mycall,"","A","NH") IF rset.%SQLCODE'=0 {WRITE !,"1st ExecDirect SQLCODE=",rset.%SQLCODE,!,rset.%Message QUIT} DO rset.%Display() WRITE !,"End of A people data",!! SET rset2 = ##class(%SQL.Statement).%ExecDirect(,mycall,,"B") IF rset2.%SQLCODE'=0 {WRITE !,"2nd ExecDirect SQLCODE=",rset2.%SQLCODE,!,rset2.%Message QUIT} DO rset2.%Display() WRITE !,"End of B people data" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).ExecDirect3() Output Values: 0. 1 Dumping result #1 Name DOB Spouse ... 1 Rows(s) Affected End of B people data ``` `%ExecDirect()`可以调用`%SQL.Statement%Display()`实例方法或`%GetImplementationDetails(`)实例方法以返回当前准备好的语句的详细信息。因为`%ExecDirect()`可以准备并执行指定的查询或现有的类查询,所以可以使用`%GetImplementationDetails()` `pStatementType`参数来确定准备哪种查询: ```java /// d ##class(PHA.TEST.SQL).ExecDirect4() ClassMethod ExecDirect4() { SET mycall = "?=CALL Sample.PersonSets('A',?)" SET rset = ##class(%SQL.Statement).%ExecDirect(tStatement,mycall,,"NH") IF rset.%SQLCODE'=0 { WRITE !,"ExecDirect SQLCODE=",rset.%SQLCODE,!,rset.%Message QUIT } SET bool = tStatement.%GetImplementationDetails(.pclassname,.ptext,.pargs,.pStatementType) IF bool=1 { IF pStatementType=1 {WRITE "Type= specified query",! } ELSEIF pStatementType=45 { WRITE "Type= existing class query",! } WRITE "Implementation class= ",pclassname,! WRITE "Statement text= ",ptext,! WRITE "Arguments= ",$LISTTOSTRING(pargs),!! } ELSE {WRITE "%GetImplementationDetails() failed"} DO rset.%Display() WRITE !,"End of data" } ```
公告
Michael Lei · 七月 1, 2022

来自2022全球用户大会--最新在线培训内容上线

欢迎点击 2022在线培训6月刊, 查看我们在全球峰会上发布的新版本--包括基于云服务的在线培训, 此外,获得关于执行分析查询的最新信息,查看最新的认证考试,并尝试新的文档搜索功能。
文章
Michael Lei · 七月 6, 2021

精华文章--虚拟化大型数据库 - VMware CPU 容量规划

供应商或内部团队要求说明如何为 VMware vSphere 上运行的_大型生产数据库_进行 CPU 容量规划。 总的来说,在调整大型生产数据库的 CPU 规模时,有几个简单的最佳做法可以遵循: - 为每个物理 CPU 核心规划一个 vCPU。 - 考虑 NUMA 并按理想情况调整虚拟机规模,以使 CPU 和内存对于 NUMA 节点是本地的。 - 合理调整虚拟机规模。 仅在需要时才添加 vCPU。 通常,这会引出几个常见问题: - 由于使用超线程技术,VMware 创建的虚拟机的 CPU 数量可以是物理 CPU 数量的两倍。 那不就是双倍容量吗? 创建的虚拟机不应该有尽可能多的 CPU 吗? - 什么是 NUMA 节点? 我应该在意 NUMA 吗? - 虚拟机应该合理调整规模,但我如何知道什么时候合理? 我以下面的示例回答这些问题。 但也要记住,最佳做法并不是一成不变的。 有时需要做出妥协。 例如,大型生产数据库虚拟机很可能不适合 NUMA 节点,但我们会看到,其实是没问题的。 最佳做法是指必须针对应用程序和环境进行评估和验证的准则。 虽然本文中的示例是在 InterSystems 数据平台上运行的数据库,但概念和规则通常适用于任何大型(怪兽)虚拟机的容量和性能规划。 有关虚拟化最佳做法以及有关性能和容量规划的更多帖子,请参见 [InterSystems 数据平台和性能系列的其他帖子列表](https://cn.community.intersystems.com/post/intersystems-数据平台的容量规划和性能系列文章)。 # 怪兽虚拟机 本帖主要是关于部署_怪兽虚拟机_,有时也称为 _Wide 虚拟机_。 高事务数据库的 CPU 资源要求意味着它们通常部署在怪兽虚拟机上。 > 怪兽虚拟机是指虚拟 CPU 或内存多于物理 NUMA 节点的虚拟机。 # CPU 架构和 NUMA 当前的英特尔处理器架构采用非统一内存架构 (NUMA)。 例如,本帖中用来运行测试的服务器有: - 两个 CPU 插槽,每个插槽一个 12 核处理器(英特尔 E5-2680 v3)。 - 256 GB 内存(16 条 16GB RDIMM) 每个 12 核处理器都有自己的本地内存(128GB RDIMM 及本地高速缓存),还可以访问同一主机中其他处理器上的内存。 每个由 CPU、CPU 高速缓存和 128GB RDIMM 内存组成的 12 核套装都是一个 NUMA 节点。 为了访问其他处理器上的内存,NUMA 节点通过快速互连来连接。 处理器上运行的进程访问本地 RDIMM 和缓存内存的延迟比跨互连访问其他处理器上的远程内存的延迟要低。 跨互连访问会增加延迟,因此性能不一致。 同样的设计也适用于具有两个以上插槽的服务器。 一台四插槽英特尔服务器有四个 NUMA 节点。 ESXi 了解物理 NUMA,ESXi CPU 调度器设计为优化 NUMA 系统的性能。 ESXi 使性能最大化的方法之一是在物理 NUMA 节点上创建数据本地性。 在我们的示例中,如果虚拟机有 12 个 vCPU,并且内存不到 128GB,ESXi 将分配该虚拟机在一个物理 NUMA 节点上运行。 这就形成了规则: > 如果可能,将虚拟机规模调整为使 CPU 和内存对于 NUMA 节点是本地的。 如果需要比 NUMA 节点规模大的怪兽虚拟机也没有问题,ESXi 可以很好地自动计算和管理要求。 例如,ESXi 将创建能够智能调度到物理 NUMA 节点上的虚拟 NUMA 节点 (vNUMA),以获得最佳性能。 vNUMA 结构对操作系统公开。 例如,如果您有一台具有两个 12 核处理器的主机服务器和一个具有 16 个 vCPU 的虚拟机,ESXi 可能会使用每个处理器上的 8 个物理核心来调度虚拟机 vCPU,操作系统(Linux 或 Windows)将看到两个 NUMA 节点。 同样重要的是,应合理调整虚拟机的规模,并且分配的资源不要超过所需的资源,否则会导致资源浪费和性能损失。 除了有助于调整 NUMA 的规模,具有高(但安全的)CPU 利用率的 12 vCPU 虚拟机比具有中低 CPU 利用率的 24 vCPU 虚拟机更高效、性能更好,特别是该主机上还有其他虚拟机需要调度并且争用资源时。 这也再次强化了该规则: > 合理调整虚拟机规模。 __注意:__英特尔和 AMD 的 NUMA 实现有区别。 AMD 每个处理器有多个 NUMA 节点。 我已经有一段时间没有在客户服务器中看到 AMD 处理器了,但是如果你有这些处理器,请检查 NUMA 布局,作为规划的一部分。 ## Wide 虚拟机和授权 为实现最佳 NUMA 调度,请配置 Wide 虚拟机; 2017 年 6 月更正:按每个插槽 1 个 vCPU 配置虚拟机。 例如,默认情况下,一个具有 24 个 vCPU 的虚拟机应配置为 24 个 CPU 插槽,每个插槽一个核心。 > 遵守 VMware 最佳做法规则。 请参见 [VMware 博客上的这篇文章以查看示例。 ](https://blogs.vmware.com/performance/2017/03/virtual-machine-vcpu-and-vnuma-rightsizing-rules-of-thumb.html) 该 VMware 博客文章进行了详细介绍,但是作者 Mark Achtemichuk 建议遵循以下经验法则: - 虽然有许多高级 vNUMA 设置,但只有极少数情况下需要更改其默认值。 - 总是将虚拟机 vCPU 数配置为反映每插槽核心数,直到超过单个物理 NUMA 节点的物理核心数。 - 当需要配置的 vCPU 数量超过 NUMA 节点中的物理核心数量时,将 vCPU 均匀分配到最少数量的 NUMA 节点上。 - 当虚拟机规模超过物理 NUMA 节点时,不要分配奇数数量的 vCPU。 - 不要启用 vCPU 热添加,除非您不介意禁用 vNUMA。 - 不要创建规模大于主机物理核心总数的虚拟机。 Caché 授权以核心数为准,因此这不是问题,但是对于除 Caché 以外的软件或数据库,指定虚拟机有 24 个插槽可能会对软件授权产生影响,因此必须与供应商核实。 # 超线程和 CPU 调度器 超线程 (HT) 经常在讨论中出现,我听过“超线程使 CPU 核心数量翻倍”。 这在物理层面上显然是不可能的,物理核心有多少就是多少。 超线程应该被启用,并会提高系统性能。 预计应用程序性能可能会提高 20% 或更多,但实际数字取决于应用程序和工作负载。 但肯定不会翻倍。 正如我在 [VMware 最佳实践](https://cn.community.intersystems.com/post/intersystems-数据平台和性能-–-第-9-篇-intersystems-iris-vmware-最佳实践指南)中所述,_调整大型生产数据库虚拟机规模_的一个很好的起点是假定 vCPU 拥有服务器上完整的物理核心专用资源 — 在进行容量规划时基本忽略超线程。 例如: > 对于一台 24 核主机服务器,可规划总共多达 24 个 vCPU 的生产数据库虚拟机,且可能还有余量。 在您花时间监测应用程序、操作系统和 VMware 在峰值处理期间的性能后,您可以决定是否进行更高度的虚拟机整合。 在最佳做法帖子中,我将规则表述为: > 一个物理 CPU(包括超线程)= 一个 vCPU(包括超线程)。 ## 为什么超线程不会使 CPU 翻倍 英特尔至强处理器上的超线程是在一个物理核心上创建两个_逻辑_ CPU 的方法。 操作系统可以有效地针对两个逻辑处理器进行调度 — 如果一个逻辑处理器上的进程或线程正在等待,例如等待 IO,则物理 CPU 资源可以被另一个逻辑处理器使用。 在任何时间点都只能有一个逻辑处理器运行,因此虽然物理核心得到了更有效的利用,但_性能并没有翻倍_。 在主机 BIOS 中启用超线程后,当创建虚拟机时,可以为每个超线程逻辑处理器配置一个 vCPU。 例如,在一台启用了超线程的物理 24 核服务器上,可以创建具有多达 48 个 vCPU 的虚拟机。 ESXi CPU 调度器将通过首先在独立的物理核心上运行虚拟机进程来优化处理(同时仍然考虑 NUMA)。 在以后的帖子中,我将探讨在怪兽数据库虚拟机上分配比物理核心数更多的 vCPU 是否有助于扩展。 ### 协同停止和 CPU 调度 在监测主机和应用程序性能后,您可以决定是否让主机 CPU 资源过载。 这是否是一个好主意在很大程度上取决于应用程序和工作负载。 了解调度器和要监测的关键指标有助于确保没有使主机资源过载。 我有时听说,要让虚拟机正常运行,空闲逻辑 CPU 的数量必须与虚拟机中的 vCPU 数量相同。 例如,一个 12 vCPU 虚拟机必须“等待”12 个逻辑 CPU“可用”,才能继续执行。 不过应该注意,ESXi 在版本 3 之后就不是这样了。 ESXi 对 CPU 使用宽松的协同调度,以提高应用程序性能。 由于多个协作线程或进程经常相互同步,不一起调度它们可能会增加操作的延迟。 例如,在自旋循环中,一个线程等待被另一个线程调度。 为了获得最佳性能,ESXi 尝试将尽可能多的同级 vCPU 一起调度。 但是,当有多个虚拟机在整合环境中争用 CPU 资源时,CPU 调度器可以灵活地调度 vCPU。 如果一些 vCPU 的进展比同级 vCPU 领先太多(这个时间差称为偏移),领先的 vCPU 将决定是否停止自身(协同停止)。 请注意,协同停止(或协同启动)的是 vCPU,不是整个虚拟机。 这种机制即使在资源有些过载的情况下也非常有效,但正如您所预期,CPU 资源过载太多将不可避免地影响性能。 我在后面的示例 2 中展示了一个过载和协同停止的例子。 记住,这不是虚拟机之间全力争夺 CPU 资源的竞赛;ESXi CPU 调度器的工作是确保 CPU 共享、保留和限制等策略被遵守,同时最大限度地提高 CPU 利用率,并确保公平性、吞吐量、响应速度和可伸缩性。 关于使用保留和共享来确定生产工作负载优先级的讨论不在本帖范围之内,而且取决于应用程序和工作负载组合。 如果我以后发现任何特定于 Caché 的建议,我可能会重新讨论这个话题。 有许多因素会影响到 CPU 调度器,本节只是简单提一下。 要深入了解,请参见帖子末尾的参考资料中的 VMware 白皮书及其他链接。 # 示例 为了说明不同的 vCPU 配置,我使用一个基于浏览器的高事务速率医院信息系统应用程序运行了一系列基准测试。 与 VMware 开发的 DVD 商店数据库基准测试的概念类似。 基准测试的脚本是根据现场医院实施的观测值和指标创建的,包括高使用率的工作流程、事务和使用最多系统资源的组件。 其他主机上的驱动虚拟机以设置的工作流程事务速率执行具有随机输入数据的脚本,来模拟 Web 会话(用户)。 1 倍速率的基准为基线。 速率可以按比例递增和递减。 除了数据库和操作系统指标外,一个很好的用来衡量基准数据库虚拟机性能的指标是在服务器上测量的组件(也可以是事务)响应时间。 一个组件示例是一部分最终用户屏幕。 组件响应时间增加意味着用户将开始看到应用程序响应时间变差。 性能良好的数据库系统必须为最终用户提供_一致的_高性能。 在下面的图表中,我针对一致的测试性能进行测量,并通过对 10 个最慢的高使用率组件的响应时间取平均值来表示最终用户体验。 预计平均组件响应时间为亚秒级,用户屏幕可能由一个组件组成,或者复杂的屏幕可能有多个组件。 > 请记住,您始终针对峰值工作负载进行规模调整,并且为意外的活动峰值留出缓冲区。 我通常以平均 80% 的峰值 CPU 利用率为目标。 基准测试硬件和软件的完整列表在帖子末尾。 ## 示例 1. 合理调整规模 - 每个主机一个怪兽虚拟机 可以创建一个可以使用主机服务器所有物理核心的数据库虚拟机,例如 24 物理核心主机上的 24 vCPU 虚拟机。 数据库虚拟机不会在 Caché 数据库镜像中“裸机”运行服务器以实现 HA,也不会引入操作系统故障转移集群的复杂性,而是包含在 vSphere 集群中实现管理和 HA,例如 DRS 和 VMware HA。 我见过有客户遵循老派的思维,根据五年硬件寿命结束时的预期容量来确定主数据库虚拟机的规模,但从上文可知,最好合理调整规模;如果虚拟机没有过度调整,性能和整合度会更好,并且管理 HA 将更容易;如果需要维护或主机出现故障,并且数据库怪兽虚拟机必须迁移或在其他主机上重启,想想俄罗斯方块的玩法就知道了。 如果预计事务速率显著增加,可以在计划维护期间提前增加 vCPU。 > 注意,“热添加”CPU 选项会禁用 vNUMA,因此不要将其用于怪兽虚拟机。 考虑下图显示的在 24 核主机上进行的一系列测试。 对于这个 24 核系统,3 倍事务速率是甜蜜点和容量规划目标。 - 主机上运行一个虚拟机。 - 使用了四种虚拟机规模来展示 12、24、36 和 48 vCPU 的性能。 - 尽可能对每种虚拟机规模都运行一系列事务速率(1 倍、2倍、3 倍、4 倍、5 倍)。 - 性能/用户体验以组件响应时间(条形图)的形式显示。 - 客户机虚拟机的 CPU 利用率百分比为平均值(线条)。 - 所有虚拟机规模中,主机 CPU 利用率都在 4 倍速率时达到 100%(红色虚线)。 ![24 物理核心主机 单个客户机虚拟机平均 CPU 百分比和组件响应时间 ](https://community.intersystems.com/sites/default/files/inline/images/single_guest_vm.png "单个客户机虚拟机") 这个图表中有许多信息,但我们可以关注几个有趣的事情。 - 24 vCPU 虚拟机(橙色)平稳地增加到目标 3 倍事务速率。 在 3 倍速率时,客户机内虚拟机的平均 CPU 利用率为 76%(峰值为 91% 左右)。 主机 CPU 利用率并不比客户机虚拟机高多少。 在 3 倍速率之前,组件响应时间非常稳定,因此用户很满意。 就我们的目标事务速率而言 — _这个虚拟机已合理调整规模_。 关于合理规模调整先说这么多,那么增加 vCPU 也就是使用超线程又会如何。 性能和可伸缩性有可能翻倍吗? 简短回答是_不可能!_ 在这种情况下,可以通过查看 4 倍以上速率的组件响应时间来了解答案。 虽然在分配了更多逻辑核心 (vCPU) 后性能“更好”,但仍然不平稳,不像 3 倍速率之前那样一致。 4 倍速率时,用户将报告响应时间变慢,无论分配多少个 vCPU。 请记住,在 4 倍速率时,_主机_曲线已经持平于 100% CPU 利用率,如 vSphere 所报告。 在 vCPU 数量较多的情况下,即使客户机内 CPU 指标 (vmstat) 报告低于 100% 利用率,对于物理资源来说情况也并非如此。 请记住,客户机操作系统不知道它是虚拟化的,它只是报告它所看到的资源。 另外,客户机操作系统也看不到超线程,所有 vCPU 都表现为物理核心。 关键是,数据库进程(在 3 倍事务速率时有 200 多个 Caché 进程)非常繁忙,并且非常高效地使用处理器,逻辑处理器没有很多空闲资源来调度更多工作,或将更多虚拟机整合到该主机。 例如,很大一部分 Caché 处理是在内存中进行的,因此没有很多 IO 等待。 所以,虽然可以分配比物理核心更多的 vCPU,但由于主机已经被 100% 利用,并不会获益许多。 Caché 非常擅长处理高工作负载。 即使主机和虚拟机的 CPU 利用率达到 100%,应用程序仍在运行,并且事务速率仍在提高 — 扩展不是线性的,如我们所见,响应时间越来越长,用户体验将受到影响 — 但应用程序不会“一落千丈”,尽管情况不是很好,但用户仍可以工作。 如果您的应用程序对响应时间不是那么敏感,那么很高兴地告诉您,您可以将其推向边缘甚至更远,并且 Caché 仍然可以安全地工作。 > 请记住,您不会想要以 100% CPU 运行数据库虚拟机或主机。 您需要容量来应对虚拟机的意外峰值和增长,而 ESXi 虚拟机监控程序需要资源来进行所有网络、存储和其他活动。 我总是针对 80% CPU 利用率的峰值进行规划。 即便如此,vCPU 的规模最多也只调整到物理核心数,这样即使在极端情况下,仍然有余量让 ESXi 虚拟机监控程序处理逻辑线程。 > 如果您运行超融合 (HCI) 解决方案,还必须考虑主机级别的 HCI CPU 要求。 有关详细信息,请参见我[先前关于 HCI](https://community.intersystems.com/post/intersystems-data-platforms-and-performance-%E2%80%93-part-8-hyper-converged-infrastructure-capacity "previous post on HCI") 的帖子。 部署在 HCI 上的虚拟机的基本 CPU 规模调整与其他虚拟机相同。 请记住,您必须在您自己的环境中使用您的应用程序验证和测试所有内容。 ## 示例 2. 资源过载 我看到过客户站点报告应用程序性能“慢”,而客户机操作系统却报告有空闲的 CPU 资源。 记住,客户机操作系统并不知道它是虚拟化的。 不幸的是,客户机内指标(例如 vmstat 在 pButtons 中报告的指标)可能具有欺骗性,您还必须获得主机级指标和 ESXi 指标(例如 `esxtop`)才能真正了解系统运行状况和容量。 如上面的图表所示,当主机报告 100% 利用率时,客户机虚拟机可能报告较低的利用率。 36 vCPU 虚拟机(红色)在 4 倍速率时报告 80% 平均 CPU 利用率,而主机报告 100%。 即使规模调整合理的虚拟机也可能出现资源短缺的情况,例如,如果在启动后有其他虚拟机迁移到主机上,或者由于 DRS 规则配置不当而导致资源过载。 为了显示关键指标,在下面的一系列测试中,我进行了以下配置: - 主机上运行两个数据库虚拟机。 - - 一个 24 vCPU 虚拟机以恒定的 2 倍事务速率运行(图表上未显示)。 - - 一个 24 vCPU 虚拟机以 1 倍、2 倍、3 倍事务速率运行(图表上显示这些指标)。 在另一个数据库使用资源的情况下;在 3 倍速率时,客户机操作系统 (RHEL 7) vmstat 只报告 86% 平均 CPU 利用率,运行队列大小平均只有 25。 然而,该系统的用户将大声抱怨,因为组件响应时间随着进程变慢而迅速增加。 如下图所示,协同停止和就绪时间说明了为什么用户性能如此糟糕。 就绪时间 (`%RDY`) 和协同停止 (`%CoStop`) 指标显示 CPU 资源在目标 3 倍速率下大幅过载。 这实际并不奇怪,因为_主机_以 2 倍速率运行(其他虚拟机),_而_该数据库虚拟机以 3 倍速率运行。 ![](https://community.intersystems.com/sites/default/files/inline/images/overcommit_3.png "过载的主机") 该图表明,当主机上的 总 CPU 负载增加时,就绪时间也会增加。 > 就绪时间是指虚拟机已准备好运行,但由于 CPU 资源不可用而无法运行的时间。 协同停止也会增加。 没有足够的空闲逻辑 CPU 来允许数据库虚拟机运行(正如我在上面的超线程部分详细说明的那样)。 最终结果是由于对物理 CPU 资源的争用而导致处理延迟。 我曾在一个客户站点看到过这种情况,当时通过 pButtons 和 vmstat 获取的支持视图只显示了虚拟化的操作系统。 虽然 vmstat 报告还有 CPU 余量,但用户的性能体验非常糟糕。 这里的教训是,直到 ESXi 指标和主机级视图可用,才能诊断出真正的问题;一般的集群 CPU 资源短缺导致的 CPU 资源过载,以及使情况变得更糟的不良 DRS 规则,会使高事务数据库虚拟机一起迁移并使主机资源不堪重负。 ## 示例 3. 资源过载 在此示例中,我使用了一个以 3 倍事务速率运行的基准 24 vCPU 数据库虚拟机,然后使用两个以恒定 3 倍事务速率运行的 24 vCPU 数据库虚拟机。 虚拟机的平均基准 CPU 利用率(见上面的示例 1)为 76%,主机则为 85%。 单个 24 vCPU 数据库虚拟机会使用全部 24 个物理处理器。 运行两个 24 vCPU 虚拟机意味着这两个虚拟机将争用资源,并使用服务器上的全部 48 个逻辑执行线程。 ![](https://community.intersystems.com/sites/default/files/inline/images/overcommit_2vm.png "过载的主机") 请记住,在运行单个虚拟机时,主机并没有被 100% 利用,我们仍然可以看到,当两个非常繁忙的 24 vCPU 虚拟机试图使用主机上的 24 个物理核心(即使开启了超线程)时,吞吐量和性能显著下降。 尽管 Caché 非常有效地使用了可用的 CPU 资源,但每个虚拟机的数据库吞吐量仍然下降了 16%,更重要的是,组件(用户)响应时间增加了 50% 以上。 ## 总结 本帖的目的是回答几个常见问题。 要深入了解 CPU 主机资源和 VMware CPU 调度器,请参见下面的参考部分。 虽然有许多专业级的调整,并且要深入研究 ESXi 才能榨干系统的最后一点性能,但基本规则非常简单。 对于_大型生产数据库_: - 为每个物理 CPU 核心规划一个 vCPU。 - 考虑 NUMA 并按理想情况调整虚拟机规模,以使 CPU 和内存对于 NUMA 节点是本地的。 - 合理调整虚拟机规模。 仅在需要时才添加 vCPU。 如果您想要整合虚拟机,请记住,大型数据库非常繁忙,在高峰期会大量使用 CPU(物理和逻辑)。 在您的监视系统告诉您安全之前,不要超额预定 CPU。 ## 参考 - [VMware 博客 - 怪兽虚拟机何时过载 vCPU:pCPU](https://blogs.vmware.com/vsphere/2014/02/overcommit-vcpupcpu-monster-vms.html) - [2016 NUMA 深入研究系列介绍](http://frankdenneman.nl/2016/07/06/introduction-2016-numa-deep-dive-series) - [VMware vSphere 5.1 中的 CPU 调度器](http://www.vmware.com/content/dam/digitalmarketing/vmware/en/pdf/techpaper/vmware-vsphere-cpu-sched-performance-white-paper.pdf) ## 测试 我在一个 vSphere 集群上运行了本帖中的示例,该集群包括连接到一个全闪存阵列的双处理器 Dell R730。 在示例运行期间,网络或存储没有出现瓶颈。 - Caché 2016.2.1.803.0 PowerEdge R730 - 2 个 Intel(R) Xeon(R) CPU E5-2680 v3 @ 2.50GHz - 16 条 16GB RDIMM,2133 MT/s,双列,x4 数据宽度 - SAS 12Gbps HBA 外部控制器 - 超线程 (HT) 开启 PowerVault MD3420,12G SAS,2U-24 驱动器 - 24 个 960GB 固态硬盘 SAS 读取密集型 MLC 12Gbps 2.5 英寸热拔插驱动器,PX04SR - 2 个控制器,12G SAS,2U MD34xx,8G 缓存 VMware ESXi 6.0.0 build-2494585 - 按照最佳实践配置虚拟机;VMXNET3、PVSCSI 等 RHEL 7 - 大页面 基准 1 倍速率下平均每秒 700,000 gloref(每秒数据库访问次数)。 24 vCPU 在 5 倍速率下平均每秒超过 3,000,000 gloref。 测试以老化方式进行,直到达到稳定的性能,然后进行 15 分钟采样并取平均值。 > 这些示例只是为了说明理论,您必须使用自己的应用程序进行验证!
文章
姚 鑫 · 四月 20, 2021

第四章 缓存查询(一)

# 第四章 缓存查询(一) 系统自动维护已准备好的SQL语句(“查询”)的缓存。这允许重新执行SQL查询,而无需重复优化查询和开发查询计划的开销。缓存查询是在准备某些SQL语句时创建的。准备查询发生在运行时,而不是在编译包含SQL查询代码的例程时。通常,`PREPARE`紧跟在SQL语句的第一次执行之后,但在动态SQL中,可以准备查询而不执行它。后续执行会忽略`PREPARE`语句,转而访问缓存的查询。要强制对现有查询进行新的准备,必须清除缓存的查询。 所有SQL调用都会创建缓存查询,无论是在ObjectScript例程中调用还是在类方法中调用。 - 动态SQL、ODBC、JDBC和`$SYSTEM.SQL.DDLImport()`方法在准备查询时创建缓存查询。管理门户执行SQL接口、InterSystems SQL Shell和`%SYSTEM.SQL.Execute()`方法使用动态SQL,因此使用准备操作来创建缓存查询。 它们列在命名空间(或指定方案)的Management Portal常规缓存查询列表、每个正在访问的表的Management Portal Catalog Details缓存查询列表以及SQL语句列表中。动态SQL遵循本章中介绍的缓存查询命名约定。 - 类查询在准备(`%PrepareClassQuery()`方法)或第一次执行(调用)时创建缓存查询。 它们列在命名空间的管理门户常规缓存查询列表中。如果类查询是在持久类中定义的,则缓存的查询也会列在该类的Catalog Details缓存查询中。它没有列在正在访问的表的目录详细信息中。它没有列在SQL语句清单中。类查询遵循本章中介绍的缓存查询命名约定。 - 嵌入式SQL在第一次执行SQL代码或通过调用声明游标的`OPEN`命令启动代码执行时创建缓存查询。嵌入式SQL缓存查询列在管理门户缓存查询列表中,查询类型为嵌入式缓存SQL,SQL语句列表。嵌入式SQL缓存查询遵循不同的缓存查询命名约定。 所有清除缓存查询操作都会删除所有类型的缓存查询。 生成缓存查询的SQL查询语句包括: - `SELECT`:`SELECT`缓存查询显示在其表的目录详细资料中。如果查询引用了多个表,则会为每个被引用的表列出相同的缓存查询。从这些表中的任何一个清除缓存的查询都会将其从所有表中清除。从表的目录详细资料中,可以选择缓存的查询名称以显示高速缓存的查询详细资料,包括执行和显示计划选项。由`$SYSTEM.SQL.Schema.ImportDDL(“IRIS”)`方法创建的选择缓存查询不提供`Execute`和`Show Plan`选项。 `SELECT`的`DECLARE NAME CURSOR`创建缓存查询。但是,缓存的查询详细信息不包括执行和显示计划选项。 - `CALL`:为其架构创建缓存查询列表中显示的缓存查询。 - `INSERT`、`UPDATE`、`INSERT`或`UPDATE`、`DELETE`:创建其表的`Catalog Details`中显示的缓存查询。 - `TRUNCATE TABLE`:为其表创建一个缓存查询,该查询显示在目录详细信息中。 注意,`$SYSTEM.SQL.Schema.ImportDDL("IRIS")`不支持截断表。 - `SET TRANSACTION`, `START TRANSACTION`, `%INTRANSACTION, COMMIT`, `ROLLBACK`:为命名空间中的每个模式创建一个缓存查询,显示在缓存查询列表中。 **当准备查询时,将创建一个缓存的查询。 因此,不要将`%Prepare()`方法放入循环结构中是很重要的。 同一个查询的后续`%Prepare()`(仅在指定的文字值上有所不同)使用现有的缓存查询,而不是创建新的缓存查询。** 更改表的`SetMapSelectability()`值将使所有引用该表的现有缓存查询失效。 现有查询的后续准备将创建一个新的缓存查询,并从清单中删除旧的缓存查询。 清除缓存查询时,缓存查询将被删除。修改表定义会自动清除引用该表的所有查询。在更新查询缓存元数据时,发出准备或清除命令会自动请求独占的系统范围锁。系统管理员可以修改缓存查询锁定的超时值。 创建缓存的查询不是事务的一部分。缓存查询的创建不会被记录下来。 # 缓存查询提高了性能 第一次准备查询时,SQL引擎会对其进行优化,并生成将执行该查询的程序(一个或多个InterSystems IRIS®Data Platform例程的集合)。然后将优化的查询文本存储为缓存查询类。如果随后尝试执行相同(或类似)的查询,SQL引擎将找到缓存的查询并直接执行该查询的代码,从而绕过优化和代码生成的需要。 缓存查询提供以下好处: - 频繁使用的查询的后续执行速度更快。更重要的是,无需编写繁琐的存储过程即可自动获得这种性能提升。大多数关系数据库产品建议仅使用存储过程访问数据库。对于IRIS,这不是必需的。 - 单个缓存的查询用于类似的查询,这些查询只是在字面值上有所不同。例如,`SELECT TOP 5 Name FROM Sample.Person WHERE Name %STARTSWITH 'A' and SELECT TOP 1000 Name FROM Sample.Person WHERE Name %STARTSWITH 'Mc'`,只是`top`和`%startswith`条件的文本值不同。为第一查询准备的缓存查询自动用于第二查询。 - 查询缓存在所有数据库用户之间共享;如果用户1准备查询,则用户1023可以利用它。 - 查询优化器可以自由地使用更多的时间为给定的查询找到最佳解决方案,因为这个代价只需要在第一次准备查询时支付。 InterSystems SQL将所有缓存的查询存储在一个位置,即`IRISLOCALDATA`数据库。但是,缓存查询是特定于名称空间的。每个缓存的查询都由准备(生成)它的名称空间标识。只能从准备缓存查询的命名空间中查看或执行缓存查询。可以清除当前命名空间或所有命名空间的缓存查询。 缓存查询不包括注释。但是,它可以在查询文本后面包含注释选项,例如`/*#OPTIONS {"optionName":value} */`。 因为缓存查询使用现有的查询计划,所以它为现有查询提供了操作的连续性。对基础表的更改(如添加索引或重新定义表优化统计信息)不会对现有缓存查询产生任何影响。 # 创建缓存查询 当InterSystems IRIS准备查询时,它会确定: - 如果查询与查询缓存中已有的查询匹配。如果不是,则向查询分配递增计数。 - 如果查询准备成功。如果不是,则不会将递增计数分配给缓存的查询名称。 - 否则,递增计数被分配给缓存的查询名称,并且该查询被缓存。 ## 动态SQL的缓存查询名称 SQL引擎为每个缓存查询分配唯一的类名,格式如下: ```java %sqlcq.namespace.clsnnn ``` 其中,`NAMESPACE`为当前名称空间(大写),`NNN`为连续整数。例如,`%sqlcq.USER.cls16`。 缓存的查询以每个命名空间为基础按顺序编号,从1开始。下一个可用的`nnn`序列号取决于已保留或释放的编号: - 如果查询与现有缓存查询不匹配,则在开始准备查询时会保留一个数字。如果查询与现有的缓存查询仅在文字值上不同,则查询与现有的缓存查询匹配-这取决于某些其他注意事项:隐藏的文本替换、不同的注释选项或“单独的缓存查询”中描述的情况。 - 如果查询准备不成功,则保留但不分配号码。只有准备成功的查询才会被缓存。 - 如果缓存查询准备成功,则会保留一个编号并将其分配给缓存查询。无论是否从该表访问任何数据,都会为查询中引用的每个表列出该缓存查询。如果查询未引用任何表,则会创建缓存查询,但不能按表列出或清除。 - 清除缓存查询时会释放一个数字。该号码将作为下一个`NNN`序列号可用。清除与表关联的单个缓存查询或清除表的所有缓存查询将释放分配给这些缓存查询的编号。清除命名空间中的所有缓存查询会释放分配给缓存查询的所有编号,包括未引用表的缓存查询,以及保留但未分配的编号。 清除缓存查询将重置`nnn`整数。整数会被重复使用,但剩余的缓存查询不会重新编号。例如,缓存查询的部分清除可能会留下`cls1、cls3、cls4和cls7`。后续缓存查询将编号为`cls2、cls5、cls6和cls8`。 一条CALL语句可能会导致多个缓存查询。例如,SQL语句`CALL Sample.PersonSets('A','MA')` 生成以下缓存查询: ```sql %sqlcq.USER.cls1: CALL Sample . PersonSets ( ? , ? ) %sqlcq.USER.cls2: SELECT name , dob , spouse FROM sample . person WHERE name %STARTSWITH ? ORDER BY 1 %sqlcq.USER.cls3: SELECT name , age , home_city , home_state FROM sample . person WHERE home_state = ? ORDER BY 4 , 1 ``` 在动态SQL中,准备SQL查询(使用`%PrepareClassQuery()`或`%PrepareClassQuery()`实例方法)后,可以使用`%display()`实例方法或`%GetImplementationDetails()`实例方法返回缓存的查询名称。查看成功准备的结果。 缓存的查询名称也是由`%SQL.Statement`类的`%Execute()`实例方法(以及`%CurrentResult`属性)返回的结果集`OREF`的一个组件。以下示例显示了这两种确定缓存查询名称的方法: ```java /// w ##class(PHA.TEST.SQL).CacheQuery() ClassMethod CacheQuery(c) { SET randtop=$RANDOM(10)+1 SET randage=$RANDOM(40)+1 SET myquery = "SELECT TOP ? Name,Age FROM Sample.Person WHERE Age < ?" SET tStatement = ##class(%SQL.Statement).%New() SET qStatus = tStatement.%Prepare(myquery) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET x = tStatement.%GetImplementationDetails(.class,.text,.args) IF x=1 { WRITE "cached query name is: ",class,! } SET rset = tStatement.%Execute(randtop,randage) WRITE "result set OREF: ",rset.%CurrentResult,! DO rset.%Display() WRITE !,"A sample of ",randtop," rows, with age < ",randage } ``` ```java DHC-APP>w ##class(PHA.TEST.SQL).CacheQuery() cached query name is: %sqlcq.DHCdAPP.cls51 result set OREF: 5@%sqlcq.DHCdAPP.cls51 Name Age 姚鑫 7 姚鑫 7 O'Rielly,Chris H. 7 Orwell,John V. 4 Zevon,Heloisa O. 11 Smith,Kyra P. 7 6 Rows(s) Affected A sample of 6 rows, with age < 19 ``` 在本例中,选定的行数(`TOP`子句)和`WHERE`子句谓词值会随着每次查询调用而改变,但缓存的查询名称不会改变。 ## 嵌入式SQL的缓存查询名称 SQL引擎为每个嵌入式SQL缓存查询分配一个唯一的类名,格式如下: ```java %sqlcq.namespace.hash ``` 其中,`NAMESPACE`是当前的名称空间(大写),`HASH`是唯一的哈希值。例如,`%sqlcq.USER.xEM1h5QIeF4l3jhLZrXlnThVJZDh`。 管理门户为每个表列出了嵌入式SQL缓存查询,目录详细信息为每个表列出了具有这个类名的缓存查询,查询类型为嵌入式缓存SQL。 ## 单独的缓存查询 两个不应该影响查询优化的查询之间的差异仍然会生成单独的缓存查询: - 同一函数的不同语法形式会生成单独的缓存查询。因此,`ASCII(‘x’)`和`{fn ASCII(‘x’)}`生成单独的缓存查询,而`{fn CURDATE()}`和`{fn CURDATE}`生成单独的缓存查询。 - 区分大小写的表别名或列别名值以及可选的AS关键字的存在或不存在将生成单独的缓存查询。因此,`ASCII('x')`, `ASCII('x') AChar`, and `ASCII('x') AS AChar`会生成单独的缓存查询。 - 使用不同的`ORDER BY`子句。 - 使用`top all`代替具有整数值的`top`。 # 文字替换 当SQL引擎缓存一个SQL查询时,它会执行文字替换。 查询缓存中的查询用`“?”` 字符,表示输入参数。 这意味着,仅在文字值上不同的查询由单个缓存的查询表示。 例如,两个查询: ```sql SELECT TOP 11 Name FROM Sample.Person WHERE Name %STARTSWITH 'A' ``` ```sql SELECT TOP 5 Name FROM Sample.Person WHERE Name %STARTSWITH 'Mc' ``` 都由单个缓存查询表示: ```sql SELECT TOP ? Name FROM Sample.Person WHERE Name %STARTSWITH ? ``` 这最小化了查询高速缓存的大小,并且意味着不需要对仅在字面值上不同的查询执行查询优化。 使用输入主机变量(例如`:myvar`)和`?` 输入参数也在相应的缓存查询中用`“?”` ”字符。 因此, `SELECT Name FROM t1 WHERE Name='Adam', SELECT Name FROM t1 WHERE Name=?`, and `SELECT Name FROM t1 WHERE Name=:namevar` ,都是匹配查询,并生成单个缓存查询。 可以使用`%GetImplementationDetails()`方法来确定这些实体中的哪些实体由每个“?”特定准备的字符。 以下注意事项适用于文字替换: - 指定为文字一部分的加号和减号将生成单独的缓存查询。因此,`ABS(7)`、`ABS(-7)`和`ABS(+7)`各自生成一个单独的缓存查询。多个符号也会生成单独的缓存查询:`ABS(+?)`。`ABS(++?)`。因此,最好使用无符号变量`ABS(?)`。或`ABS(:Num)`,可以为其提供有符号或无符号数字,而无需生成单独的缓存查询。 - 精度和小数值通常不接受文字替换。因此,`ROUND(567.89,2)`被缓存为`ROUND(?,2)`。但是,`CURRENT_TIME(N)`、`CURRENT_TIMESTAMP(N)`、`GETDATE(N)`和`GETUTCDATE(N)`中的可选精度值不接受文字替换。 - `IS NULL`或`IS NOT NULL`条件中使用的文字不接受文字替换。 - `ORDER BY`子句中使用的任何文字都不接受文字替换。这是因为`ORDER BY`可以使用整数来指定列位置。更改此整数将导致根本不同的查询。 - 字母文字必须用单引号引起来。某些函数允许指定带引号或不带引号的字母格式代码;只有带引号的字母格式代码才接受文字替换。因此,`DATENAME(MONTER,64701)`和`DATENAME(‘MONTER’,64701)`在功能上是相同的,但是对应的缓存查询是`DATENAME(MONTER,?)`。和`DATENAME(?,?)` - 接受可变数量参数的函数会为每个参数计数生成单独的缓存查询。因此,`Coalesce(1,2)`和`Coalesce(1,2,3)`会生成单独的缓存查询。 ## DynamicSQLTypeList Comment Option 当匹配查询时,注释选项被视为查询文本的一部分。 因此,在注释选项中不同于现有缓存查询的查询与现有缓存查询不匹配。 注释选项可以作为查询的一部分由用户指定,也可以由SQL预处理器在准备查询之前生成并插入。 如果SQL查询包含文字值,SQL预处理器将生成`DynamicSQLTypeList`注释选项,并将其附加到缓存的查询文本的末尾。此注释选项为每个文字分配数据类型。数据类型按照文字在查询中出现的顺序列出。只列出实际文字,而不是输入主机变量或?输入参数。下面是一个典型的例子: ```sql SELECT TOP 2 Name,Age FROM Sample.MyTest WHERE Name %STARTSWITH 'B' AND Age > 21.5 ``` 生成缓存的查询文本: ```sql SELECT TOP ? Name , Age FROM Sample . MyTest WHERE Name %STARTSWITH ? AND Age > ? /*#OPTIONS {"DynamicSQLTypeList":"10,1,11"} */ ``` 在本例中,文字2被列为类型10(整数),文字`“B”`被列为类型1(字符串),而文字`21.5`被列为类型11(数字)。 请注意,数据类型分配仅基于文字值本身,而不是关联字段的数据类型。例如,在上面的示例中,`Age`被定义为数据类型`INTEGER`,但是文字值21.5被列为`NUMERIC`。因为InterSystems IRIS将数字转换为规范形式,所以文字值`21.0`将被列为整数,而不是数字。 `DynamicSQLTypeList`返回以下数据类型值: 数字 | 描述 ---|--- 1| 长度为1到32(包括1到32)的字符串 2| 长度为33到128(含)的字符串 3| 长度为129到512(含)的字符串 4| 长度大于512的字符串 10| Integer 11| Numeric 由于`DynamicSQLTypeList`注释选项是查询文本的一部分,因此更改文本以使其产生不同的数据类型会导致创建单独的缓存查询。例如,增加或减少文字字符串的长度,使其落入不同的范围。 ## 文字替换和性能 SQL引擎对`IN`谓词的每个值执行文字替换。大量`IN`谓词值可能会对缓存查询性能产生负面影响。可变数量的`IN`谓词值可能会导致多个缓存查询。将`IN`谓词转换为`%INLIST`谓词会导致谓词只有一个文字替换,而不管列出的值有多少。`%INLIST`还提供了一个数量级大小参数,`SQL`使用该参数来优化性能。 ## 取消文字替换 可以取消这种文字替换。在某些情况下,可能希望对文字值进行优化,并为具有该文字值的查询创建单独的缓存查询。若要取消文字替换,请将文字值括在双圆括号中。下面的示例显示了这一点: ```sql SELECT TOP 11 Name FROM Sample.Person WHERE Name %STARTSWITH (('A')) ``` 指定不同的 `%STARTSWITH`值将生成单独的缓存查询。请注意,对每个文字分别指定禁止文字替换。在上面的示例中,指定不同的`TOP`值不会生成单独的缓存查询。 要取消有符号数字的文字替换,请指定诸如 `ABS(-((7)))`之类的语法。 注意:在某些情况下,不同数量的括号也可能会抑制文字替换。InterSystems建议始终使用双圆括号作为此目的最清晰和最一致的语法。 # 共分注释选项 如果一个SQL查询指定了多个分割表,则SQL预处理器会生成一个共分片注释选项,并将该选项附加到缓存的查询文本的末尾。此共分选项显示是否对指定的表进行共分。 在下面的示例中,所有三个指定的表都进行了编码共享: ``` /*#OPTIONS {"Cosharding":[["T1","T2","T3"]]} */ ``` 在以下示例中,指定的三个表均未进行编码共享: ``` /*#OPTIONS {"Cosharding":[["T1"],["T2"],["T3"]]} */ ``` 在以下示例中,表`T1`未被编分,但表`T2`和`T3`被编分: ``` /*#OPTIONS {"Cosharding":[["T1"],["T2","T3"]]} */ ```
文章
Nicky Zhu · 一月 8, 2021

Web 服务业务操作客户端 – 响应超时行为

调用 Web 服务的过程中,在期望的时间内未返回响应时,后续发生的情况由业务操作的几个设置来控制。(请注意,这也与非 SOAP 简单 HTTP 调用等有关)   3 个主要设置为: 响应超时 指定从远程 Web 服务器获取响应的超时时间。 重试间隔 尝试与 Ensemble 之外的目标进行连接之间等待的秒数。 故障超时 尝试与 Ensemble 之外的目标保持连接的总秒数。 经过此秒数时间后,业务操作将丢弃消息数据并返回错误代码。   归纳一下就是这样: 我们将等待 Web 服务器的响应,时间为“响应超时”所用的时间。 如果此时间过后未收到响应,我们将在“重试间隔”时间过后再次调用 Web 服务器。 如此继续尝试,直到时间(从第一次尝试算起)达到“故障超时”设置的时间秒数。   以下举例进行说明:   假定以下设置:   就是说: 响应超时 - 等待 7 秒以响应 重试间隔 - 每 10 秒重试一次 故障超时 – 30 秒后“放弃”重试   假定在 8 秒后返回响应,则情况如下: 在 00:00,我们进行第一次调用 在 00:07,由于未返回任何响应,我们在内部确认发生了“响应超时”错误(并在“事件日志”中记录“错误事件”),并根据重试策略和设置再次尝试 – “故障超时”尚未到达,因此引发了“需要重试标志”。 [在 00:08,Web 服务器返回一个响应,但由于我们已经将其记录为超时错误,因此我们不会收到此响应] 在 00:10,重试间隔时间已到,并且由于引发了“需要重试标志”,我们将再次调用 Web 服务。 在 00:17,如果仍未返回任何响应,我们将再次达到“响应超时”时间(请注意,在上面的“00:08/第 3 段中,返回的响应已被“忽略/丢弃”),因此我们再次在内部将此认为是错误(但这次不会再添加一个“错误事件日志”条目,只会在第一次尝试时记录此错误,而不是每次重试失败时都记录),由于我们尚未达到“故障超时”时间,因此需要再次引发“需要重试标志”。 [在 00:18,Web 服务器返回一个响应,这次我们还是接收不到] 在 00:20,又达到重试间隔时间,我们将尝试第 3 次调用。 在 00:27,仍无响应,再次发生“响应超时”错误,需要重试(仍未达到“故障超时”)。 [在 00:28,服务器未发回响应] 在 00:30,再次达到重试间隔时间,我们进行第 4 次尝试(也是最后一次)。 在 00:37,“响应超时”再次发生 – 这次,已达到“故障超时”时间,因此我们不会引发“需要重试标志”,而是放弃 – 我们在事件日志中记录一个错误事件,指出已达到故障超时时间,并且返回业务操作调用错误。   以下是上述示例的调用“证据”。   首先是服务器端 [来自 SOAP 日志] – 可以看到,其收到了 4 次调用/请求,均相隔 10 秒,每次在请求发出 8 秒后返回一个响应: * * * 05/31/2016 14:18:45 ********************* Input to Web service with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse ...   05/31/2016 14:18:53 ********************* Output from Web service with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse ...   05/31/2016 14:18:55 ********************* Input to Web service with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse ...   05/31/2016 14:19:03 ********************* Output from Web service with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse ...   05/31/2016 14:19:05 ********************* Input to Web service with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse ...   05/31/2016 14:19:13 ********************* Output from Web service with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse ...   05/31/2016 14:19:15 ********************* Input to Web service with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse ...   05/31/2016 14:19:23 ********************* Output from Web service with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse ...   现在,从 Ensemble BO/客户端,可以看到,有 4 次尝试,均相隔 10 秒,每次在 7 秒后记录响应超时错误。   客户端   05/31/2016 14:18:45 ********************* Output from Web client with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse ...   05/31/2016 14:18:52 ********************* Input to Web client with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse ERROR #5922: Timed out waiting for response string**** SOAP client return error. method=GetResponse, action=http://tempuri.org/Test.WSTimeouts.WebService.GetResponse      ERROR #5922: Timed out waiting for response   05/31/2016 14:18:55 ********************* Output from Web client with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse ...   05/31/2016 14:19:02 ********************* Input to Web client with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse ERROR #5922: Timed out waiting for response string**** SOAP client return error. method=GetResponse, action=http://tempuri.org/Test.WSTimeouts.WebService.GetResponse      ERROR #5922: Timed out waiting for response   05/31/2016 14:19:05 ********************* Output from Web client with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse ...   05/31/2016 14:19:12 ********************* Input to Web client with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse ERROR #5922: Timed out waiting for response string**** SOAP client return error. method=GetResponse, action=http://tempuri.org/Test.WSTimeouts.WebService.GetResponse      ERROR #5922: Timed out waiting for response   05/31/2016 14:19:15 ********************* Output from Web client with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse ...   05/31/2016 14:19:22 ********************* Input to Web client with SOAP action = http://tempuri.org/Test.WSTimeouts.WebService.GetResponse ERROR #5922: Timed out waiting for response string**** SOAP client return error. method=GetResponse, action=http://tempuri.org/Test.WSTimeouts.WebService.GetResponse      ERROR #5922: Timed out waiting for response   以下是对 Ensemble 的可视化跟踪: 以下是事件日志条目:   以下为跟踪事件的日志示例[您可能需要放大以更好地读取文字]: 由此,可以看到上述示例的一些“内部运作”: 在日志行 ID #684,执行了首次调用 – 时间:17:09:16。 然后,在 7 秒之后 (09:23),我们收到响应超时错误 (#685)。 然后,操作记录了此错误 (#687),并确定等待 3 秒钟,直到达到重试间隔时间;10 秒重试间隔时间减去 7 秒响应超时时间 (#688-#690)。 经过 3 秒等待( 09:26;#691)之后,进行第 2 次尝试 (#692),结果相同,继续重复操作,直到第 4 次尝试 (#704) 。 在第 4 次尝试失败(09:53;#705)之后,由于达到超时时间(30 秒),因此未进行另一次尝试。  
文章
姚 鑫 · 二月 4, 2021

第二十四章 Caché 变量大全 $ZA 变量

# <center> 第二十四章 Caché 变量大全 $ZA 变量 包含当前设备上最后一次读取的状态。 # 大纲 ```java$ZA``` # 描述 `$ZA`包含当前设备上最后一次读取的状态。 不能使用`SET`命令修改此特殊变量。尝试这样做会导致`<SYNTAX>`错误。 # 注意 ## `$ZA`,带终端I / O `$ZA`被实现为一系列位标志,每个位表示一条特定的信息。下表显示了可能的值、它们的含义,以及如何使用模(`#`)和整数除(`\`)运算符测试它们: 位 | 测试| 含义---|---|---0 |`$ZA#2`| `<CTRL-C>`已到达,无论是否启用中断。1 |`$ZA\2#2`| 读取超时。2 |`$ZA\4#2`| I/O错误。8 |`$ZA\256#2`| Caché检测到无效的转义序列。9 |`$ZA\512#2`| 硬件检测到奇偶校验或成帧错误。11 |`$ZA\2048#2`| 进程已与其主设备断开连接。12 |`$ZA\4096#2`|对于COM端口:CTS(清除发送)。从调制解调器发送到其计算机的信号,表示传输可以进行。对于TCP设备:设备在服务器模式下运行。13 |`$ZA\8192#2`|对于COM端口:DSR(数据集就绪)。从调制解调器发送到其计算机的信号,表示调制解调器已准备好运行。对于TCP设备:设备当前处于与远程主机通话的已连接状态。14 |`$ZA\16384#2`| 如果为真,则设置振铃。15 |`$ZA\32768#2`| 载波检测设置为真。16 |`$ZA\65536#2`|CE_BREAK COM端口错误状态。17 |`$ZA\131072#2`| CE_FRAME COM端口错误状态。18 |`$ZA\262144#2`| CE_IOE COM端口错误状态。19 |`$ZA\524288#2`| CE_OVERRUN COM端口错误状态。20 |`$ZA\1048576#2`| CE_RXPARITY COM端口错误状态。21 |`$ZA\2097152#2`| CE_TXFULL COM端口错误状态。22 |`$ZA\4194304#2`| TXHOLD通讯端口错误状态。在`ClearCommError()`返回的错误掩码中,如果下列任一字段为真,则设置为:fCtsHold、fDsrHold、fRlsdHold、fXoffHold、fXoffSent。`24 & 25 |$ZA\16777216#4|`Caché请求DTR(数据终端就绪)设置:0 = DTR关闭。 1 = DTR =开。 2 = DTR握手。设置为(1)时,表示已准备好发送和接收数据。 尽管`$ZA`显示的许多条件都是错误,但它们不会通过捕获`$ZTRAP`来中断程序的流程。 (具有中断功能的`<CTRL-C>`会陷阱到`$ZTRAP`。)与这些错误有关的程序在每次读取后必须检查`$ZA`。 COM端口使用位12至15、24和25报告调制解调器控制引脚的状态。无论端口的Caché调制解调器控制检查是打开还是关闭,都可以执行此操作。用户可以通过设置`OPEN`或`USE`命令`portstate`参数(具体为字节8)来启用或禁用COM端口的`$ZA`错误报告。如果启用了错误报告,则端口错误状态将在位16到22中报告。 可以使用`%SYSTEM.Process`类的`DisconnectErr()`方法来检测当前进程的调制解调器断开连接。可以通过设置`Config.Miscellaneous`类的`DisconnectErr`属性来建立系统范围的默认行为。 ## 带磁带I / O的$ZA 对于磁带I / O,`$ZA`中的位字段指示错误和特殊情况。在每个引用磁带设备的命令之后,Caché更新`$ZA`。 下表显示了磁带I/O的`$ZA`位的含义。请注意Trap列。字母Y表示`<MAGTAPE>`错误。如果设置了`$ZTRAP`变量,则Caché会发出相关的`$ZTRAP`错误代码。 位 | 值| 陷阱 |含义注意---|---|---|---0 |1| Y| 逻辑错误(读写混合),用于在读取和写入之间切换,或者关闭然后打开设备,或者发出前进空格、退格键或倒带命令。2 |4| N| 写保护始终反映`OPEN`或`USE`只读参数的状态。此位不反映磁带的物理写保护状态(写环或写锁定),因为许多版本的UNIX®在尝试实际写入磁带之前不会通知磁带写保护。如果尝试打开不带只读参数的写保护9磁道磁带,则Caché会设置此位并以只读方式打开磁带。未出现错误。3 |8| Y| 错误摘要错误摘要是导致Caché错误的所有条件(在Trap下标记为Y的所有条件)的逻辑或。5 |32| N| 磁带的开始[BOT]在UNIX®系统上,该位在倒带时设置,并在打开磁带时清除。6 |64| N| On Line 7 |128| Y|控制器或驱动器错误。10 |1024| N|磁带结尾[EOT]在大多数UNIX®平台上不支持。14 |16384| Y| Tape MarkCaché在遇到Read,Read Block,Forward Space或Backspace上的磁带标记时将Tape Mark位置1。这会将“错误摘要”位置1,并在“读取”,“读取标签”和“读取块”上将陷阱陷阱为`$ZTRAP`。15 |32768| Y| 磁带未准备好 一些位指示错误条件,而另一些位指示不一定产生错误的条件。为了监视这些非错误情况,程序必须在每次磁带操作后测试`$ZA`的相应位。例如,如果程序可能写在磁带末尾之外,则它必须检查位10(磁带末尾)。 要测试某个位,请将`$ZA`除以表中该位列出的值,然后执行模2运算。例如,以下命令检查是否设置了位14(磁带标记): ```javaUSE 47 IF $ZA\16384#2 {DO Endfile}``` 其中16384等于2等于14的幂,而#2表示模2运算。由于任何等于0的幂等于1,因此无需除数即可检查位0(逻辑错误)。例如: ```javaUSE 47 GOTO Logerr:$ZA#2```
文章
姚 鑫 · 一月 31, 2023

第六十二章 使用 SNMP 监控 IRIS

# 第六十二章 使用 SNMP 监控 IRIS 本附录描述了 `IRIS` 数据平台和 `SNMP`(简单网络管理协议)之间的接口。 `SNMP` 是一种通信协议,作为一种管理 `TCP/IP` 网络(包括单个网络设备和一般计算机设备)的方法已获得广泛接受。它的流行扩大了它作为许多企业管理工具的底层结构和协议的用途。这是它对 `IRIS` 的主要重要性:一种向各种管理工具提供管理和监控信息的标准方法。 `SNMP` 既是一种标准消息格式,也是管理对象的一组标准定义。它还提供用于添加自定义管理对象的标准结构,这是 `IRIS` 用来定义其管理信息以供其他应用程序使用的功能。 # 将 `SNMP` 与 `IRIS` 结合使用 `SNMP` 定义了客户端-服务器关系,其中客户端(网络管理应用程序)连接到在远程网络设备或计算机系统上执行的服务器程序(称为 `SNMP` 代理)。客户端请求并从该代理接收信息。有四种基本类型的 `SNMP` 消息: - `GET` – 获取特定托管对象的数据 - `GETNEXT` – 获取分层树中下一个管理对象的数据,允许系统管理员遍历设备的所有数据 - `SET`——为特定的管理对象设置值 - `TRAP`——被管理设备或系统发送的异步警报 `SNMP MIB`(管理信息库)包含管理对象的定义。每个设备都会发布一个文件,也称为其 `MIB`,该文件定义了它支持的标准 `MIB` 的哪一部分,以及托管对象的任何自定义定义。对于 `IRIS`,这是位于 `install-dir\SNMP` 目录中的 ISC-IRIS.mib 文件。 # `IRIS` 作为子代理 `SNMP` 客户端连接到 `SNMP` 代理,该代理正在侦听众所周知的地址端口 `161`。由于客户端希望连接到该特定端口,因此计算机系统上只能有一个 `SNMP` 代理。为了允许访问系统上的多个应用程序,开发人员可以实现主代理,它可以扩展或连接到多个子代理。 已将 `IRIS SNMP` 接口实施为子代理,旨在通过 `SNMP` 主代理进行通信。 `IRIS` 支持的大多数操作系统都提供一个 `SNMP` 主代理,它可以通过某种方式扩展以支持多个子代理。然而,这些代理中的许多都以专有且不兼容的方式实现了它们的可扩展性。 `IRIS` 使用代理扩展性 (`AgentX`) 协议实现其子代理,该协议是 `IETF` 提议的标准,如 `RFC 2741`中所述。 一些标准的 `SNMP` 主代理支持 `AgentX`。如果操作系统提供的 `SNMP` 主代理与 `AgentX` 不兼容,可以将其替换为公共域 `Net-SNMP` 代理。 注意:`Windows` 标准代理例外,它不支持 `AgentX`,`Net-SNMP` 版本可能不适合。对于这个例外,提供了一个 `Windows` 扩展代理 `DLL`,`iscsnmp.dll`,它处理标准 `Windows SNMP` 服务扩展 `API` 和 `IRIS AgentX` 服务器之间的连接。 # 在 `IRIS` 中管理 `SNMP`。 由于 `SNMP` 是标准协议, `IRIS` 子代理的管理是最少的。最重要的任务是验证系统上的 `SNMP` 主代理是否与代理扩展性 (`AgentX`) 协议兼容(请参阅 `IRIS` 作为子代理)并且它处于活动状态并正在侦听标准 `AgentX TCP` 端口 `705` 上的连接。 `Windows`系统,系统自动安装一个`DLL`来连接标准的`Windows SNMP`服务。验证 `Windows SNMP` 服务是否已自动或手动安装和启动。 重要提示:一些 `SNMP` 主代理,特别是 `Linux` 上的 `Net-SNMP`,默认情况下不启用 `AgentX`,并且一旦启用,默认情况下不使用 `TCP` 端口 `705`。对于 `Net-SNMP`,必须修改 `snmpd.conf` 文件以启用与 `IRIS` 子代理的通信。最新版本的 `Net-SNMP` 还实现了 `VACM`(基于视图的访问控制模型)安全性,默认情况下,只允许访问 `mib-2.system` 子树; `IRIS` 子代理启动并正常运行,但没有 `SNMP` 请求转发到 `IRIS`。必须扩展 `snmpd.conf` 中定义的“视图”以包括 `IRIS MIB` 子树。 接下来,使用以下步骤启用监控服务: 1. 导航到管理门户中的服务页面(系统管理 > 安全 > 服务)。 2. 单击 `%Service_Monitor` 服务。 3. 选中启用服务复选框并单击保存。 4. 返回到服务列表页面并确保启用了 `%Service_Monitor` 服务。 最后,使用以下步骤将 `IRIS SNMP` 子代理配置为在 `IRIS` 启动时自动启动: 1. 导航到管理门户中的监控设置页面(系统管理 > 配置 > 其他设置 > 监控)。 2. 为系统启动时启动 `SNMP` 代理设置选择是,然后单击保存。 3. 当编辑此设置时,`SNMP` 接口的 `IRIS` 端会立即停止和启动。 还可以使用 `^SNMP` 例程手动或以编程方式启动和停止 `IRIS SNMP` 子代理: ```java Do start^SNMP(,) Do stop^SNMP ``` 其中 `` 是连接的 `TCP` 端口(默认为 `705`),`` 是 `TCP` 端口读取超时值(默认为 `20` 秒)。在达到 `` 值之前, `IRIS` 会在 `install-dir\mgr` 目录下的 `SNMP.LOG` 文件中记录在建立连接或应答请求时遇到的任何问题。 注意:当 `SNMP` 主代理重新启动时,可能需要使用 `^SNMP` 例程手动重新启动 `IRIS SNMP` 子代理,如前所述。
文章
姚 鑫 · 三月 14, 2021

第十章 SQL排序(一)

# 第十章 SQL排序 排序规则指定值的排序和比较方式,并且是InterSystems SQL和InterSystemsIRIS®数据平台对象的一部分。有两种基本排序规则:数字和字符串。 - 数值排序规则按以下顺序基于完整数字对数字进行排序:`null`,然后是负数,从最大到最小,零,然后是正数,从最小到最大。这将创建如下序列:`–210,–185,–54,–34,-.02、0、1、2、10、17、100、120`。 - 字符串归类通过对每个顺序字符进行归类来对字符串进行排序。这将创建以下顺序:`null,A,AA,AA,AAA,AAB,AB,B`。对于数字,这将创建以下顺序:`–.02,–185,–210,–34,–54 ,0、1、10、100、120、17、2`。 **默认的字符串排序规则是`SQLUPPER`;为每个名称空间设置此默认值。 `SQLUPPER`排序规则将所有字母都转换为大写(出于排序的目的),并在字符串的开头附加一个空格字符。此转换仅用于整理目的;在InterSystems中,无论所应用的排序规则如何,SQL字符串通常以大写和小写字母显示,并且字符串的长度不包括附加的空格字符。** 时间戳记是一个字符串,因此遵循当前的字符串排序规则。但是,由于时间戳是ODBC格式,因此如果指定了前导零,则字符串排序规则与时间顺序相同。 - 字符串表达式(例如使用标量字符串函数`LEFT`或`SUBSTR`的表达式)使其结果归类为`EXACT`。 - 两个文字的任何比较都使用`EXACT`归类。 可以使用“ObjectScript排序后”运算符来确定两个值的相对排序顺序。 可以按以下方式指定排序规则: - 命名空间默认值 - 表字段/属性定义 - 索引定义查询 - `SELECT`项 - 查询`DISTINCT`和`GROUP BY`子句 # 排序类型 排序规则可以在字段/属性的定义或索引的定义中指定为关键字。 可以通过对查询子句中的字段名应用排序规则函数来指定排序规则。 在指定排序函数时必须使用%前缀。 **排序规则采用升序的ASCII/Unicode序列**,具有以下转换: - **`EXACT` - 强制字符串数据区分大小写。 如果字符串数据包含规范数字格式的值(例如`123`或`-.57`),则不建议使用。** - **`SQLSTRING` - 去除末尾的空格(空格、制表符等),并在字符串的开头添加一个前导空格。 它将任何只包含空格(空格、制表符等)的值作为SQL空字符串进行排序。 `SQLSTRING`支持可选的`maxlen`整数值。** - **`SQLUPPER` - 将所有字母字符转换为大写,去除末尾的空格(空格、制表符等),然后在字符串的开头添加一个前导空格字符。 附加这个空格字符的原因是为了强制将数值作为字符串进行整理(因为空格字符不是有效的数字字符)。 这种转换还导致SQL将SQL空字符串(`"`)值和任何只包含空格(空格、制表符等)的值作为单个空格字符进行整理。 `SQLUPPER`支持可选的`maxlen`整数值。** 注意,`SQLUPPER`转换与SQL函数`UPPER`的结果不同。 - **`TRUNCATE` —增强字符串数据的区分大小写,并且(与`EXACT`不同)允许指定截断该值的长度。当索引比下标支持的数据长的精确数据时,此功能很有用。它采用`%TRUNCATE(string,n)`形式的正整数参数将字符串截断为前`n`个字符,从而改善了对长字符串的索引和排序。如果未为`TRUNCATE`指定长度,则其行为与`EXACT`相同;同时支持此行为。如果仅在定义了长度的情况下使用`TRUNCATE`而在没有定义长度的情况下使用`EXACT`,则定义和代码可能更易于维护。** - **`PLUS` —使值成为数字。非数字字符串值将返回0。** - **`MINUS` — 使数值成为数字并更改其符号。非数字字符串值将返回0。** 注意:还有多种传统排序规则类型,不建议使用。 **在SQL查询中,可以指定不带括号`%SQLUPPER Name`或带括号`%SQLUPPER(Name)`的排序规则函数。如果排序规则函数指定了截断,则必须使用括号`%SQLUPPER(Name,10)`。** **三种排序规则类型:`SQLSTRING`,`SQLUPPER`和`TRUNCATE`支持可选的`maxlen`整数值。如果指定,`maxlen`会将字符串的分析截断为前`n`个字符。在对长字符串进行索引和排序时,可以使用它来提高性能。可以在查询中使用`maxlen`进行排序,分组或返回截断的字符串值。** 还可以使用 `%SYSTEM.Util.Collation()`方法执行排序规则类型转换。 # 命名空间范围的默认排序规则 每个名称空间都有一个当前的字符串排序规则设置。此字符串排序规则是为`%Library.String`中的数据类型定义的。默认值为`SQLUPPER`。此默认值可以更改。 可以基于每个命名空间定义排序规则默认值。默认情况下,名称空间没有分配的排序规则,这意味着它们使用`SQLUPPER`排序规则。可以为命名空间分配其他默认排序规则。此名称空间默认排序规则适用于所有进程,并且在InterSystems上保持不变,IRIS会重新启动,直到明确重置为止。 ```java /// d ##class(PHA.TEST.SQL).Collation() ClassMethod Collation() { SET stat=$$GetEnvironment^%apiOBJ("collation","%Library.String",.collval) WRITE "初始排序 ",$NAMESPACE,! ZWRITE collval SetNamespaceCollation DO SetEnvironment^%apiOBJ("collation","%Library.String","SQLstring") SET stat=$$GetEnvironment^%apiOBJ("collation","%Library.String",.collnew) WRITE "user-assigned排序为 ",$NAMESPACE,! ZWRITE collnew ResetCollationDefault DO SetEnvironment^%apiOBJ("collation","%Library.String",.collval) SET stat=$$GetEnvironment^%apiOBJ("collation","%Library.String",.collreset) WRITE "恢复排序规则的默认值 ",$NAMESPACE,! ZWRITE collreset } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).Collation() 初始排序 DHC-APP user-assigned排序为 DHC-APP collnew="SQLstring" 恢复排序规则的默认值 DHC-APP ``` 注意,如果从未设置名称空间排序的默认值,那么`$$GetEnvironment`将返回一个未定义的排序变量,例如本例中的`.collval`。 这个未定义的排序规则默认为`SQLUPPER`。 注意:如果数据包含德语文本,大写排序规则可能不是理想的默认设置。 这是因为德语`eszett`字符(`$CHAR(223)`)只有小写形式。 相当于大写的是两个字母`“SS”`。 转换为大写的SQL排序规则不会转换`eszett,` `eszett`保持为单个小写字母不变。 # 表字段/属性定义排序 在SQL中,排序规则可以分配为字段/属性定义的一部分。字段使用的数据类型确定其默认排序规则。字符串数据类型的默认排序规则为`SQLUPPER`。非字符串数据类型不支持排序规则分配。 可以在`CREATE TABLE`和`ALTER TABLE`中为字段指定排序规则: ```SQL CREATE TABLE Sample.MyNames ( LastName CHAR(30), FirstName CHAR(30) COLLATE SQLstring) ``` 注意:使用`CREATE TABLE``和ALTER TABLE`为字段指定排序规则时,`%`前缀是可选的`:COLLATE SQLstring`或`COLLATE %SQLstring`。 在使用持久类定义定义表时,可以为属性指定排序规则: ```java Class Sample.MyNames Extends %Persistent [DdlAllowed] { Property LastName As %String; Property FirstName As %String(COLLATION = "SQLstring"); } ``` 注意:在为类定义和类方法指定排序规则时,请勿将`%`前缀用于排序规则类型名称。 **在这些示例中,`LastName`字段采用默认排序规则(`SQLUPPER`,不区分大小写),`FirstName`字段使用区分大小写的`SQLSTRING`排序规则进行定义。** **如果更改类属性的排序规则,并且已经存储了该类的数据,则该属性上的所有索引都将变为无效。必须基于此属性重建所有索引。** # 索引定义排序 `CREATE INDEX`命令无法指定索引排序规则类型。索引使用与要索引的字段相同的排序规则。 **定义为类定义一部分的索引可以指定排序规则类型。默认情况下,给定一个或多个给定属性的索引使用属性数据的排序规则类型。例如,假设已定义类型为`%String`的属性`Name`:** ```java Class MyApp.Person Extends %Persistent [DdlAllowed] { Property Name As %String; Index NameIDX On Name; } ``` 名称的排序规则为`SQLUPPER`(`%String`的默认值)。假设`“Person”`表包含以下数据: ID| Name ---|--- 1| Jones 2| JOHNSON 3| Smith 4| jones 5| SMITH 然后,`Name`上的索引将包含以下条目: Name| ID(s) ---|--- JOHNSON| 2 JONES| 1, 4 SMITH| 3, 5 SQL引擎可以将此索引直接用于`ORDER BY`或使用`“Name”`字段进行比较操作。 可以通过在索引定义中添加一个`As`子句来覆盖用于索引的默认排序规则: ```java Class MyApp.Person Extends %Persistent [DdlAllowed] { Property Name As %String; Index NameIDX On Name As SQLstring; } ``` 在这种情况下,`NameIDX`索引现在将以`SQLSTRING`(区分大小写)的形式存储值。使用上面示例中的数据: Name| ID(s) ---|--- JOHNSON| 2 Jones| 1 jones| 4 SMITH| 5 Smith| 3 在这种情况下,对于需要区分大小写排序规则的任何查询,SQL Engine都可以利用此索引。 通常,不必更改索引的排序规则。如果要使用其他排序规则,最好在属性级别定义它,然后让属性上的所有索引都采用正确的排序规则。 如果使用索引属性执行属性比较,则在比较中指定的属性应与相应索引具有相同的排序规则类型。例如,`SELECT`的`WHERE`子句或`JOIN`的`ON`子句中的`Name`属性应与为`Name`属性定义的索引具有相同的排序规则。如果属性归类和索引归类之间不匹配,则索引可能无效或根本不使用。 如果将索引定义为使用多个属性,则可以分别指定每个索引的排序规则: ```java Index MyIDX On (Name As SQLstring, Code As Exact); ```