清除过滤器
文章
Claire Zheng · 七月 8, 2021
2021年7月9日-11日,2021(16th) 中国卫生信息技术/健康医疗大数据应用交流大会暨软硬件与健康医疗产品展览会(CHITEC)在武汉国际博览中心(湖北省武汉市汉阳区鹦鹉大道619号)盛大召开,欢迎莅临InterSystems展位A6-16,了解备受瞩目的InterSystems IRIS医疗版互联互通套件。
InterSystems致力于部署、创建互联互通医疗解决方案,为医院数字化转型提供技术支持。针对医院信息互联互通标准化成熟度测评指标要求,InterSystems IRIS 医疗版互联互通套件从安全管理、监控、数据管理、互联互通文档、互联互通服务、集成与交换六大方面助力医院互联互通建设,以满足医疗机构内部标准化的要求,使医院可基于信息平台提供较为完善的临床决策支持、闭环管理,实现丰富的人工智能和大数据应用,实现丰富的跨机构的业务协同和互联互通应用。
如果您希望进一步了解详细信息,欢迎莅临展位(A6-16),或通过下方二维码联系小助手,添加时注明“CHITEC预约沟通”,确认您的专属预约沟通时段,如果您完成预约并与专家实现现场沟通,将有机会获得神秘礼品!
InterSystems IRIS医疗版互联互通套件具备如下优点:
专——专注医疗领域40余年,针对中国医疗信息化市场量身定制,遵循国家卫生信息标准,公立医院互联互通标准化成熟度测评需求的基石;加强健康数据标准应用,提高数据质量;全——全面支持2020最新版医院互联互通标准化成熟度测评规定的文档、监控、服务、Schema 等组件;快——卓越的互操作性助力医疗机构快速落地互联互通标准化成熟度测评标准化改造;省——有效缩短实施周期,降低实施成本,超高性能有效降低硬件成本;稳——稳定高效,连续多年支持超百家大型公立医院海量数据稳定运行。主流 PC 服务器单实例下,支持日消息吞吐量可达 27亿;目前已知支持国内公立医院日消息吞吐量高达1200万/天(非集群);强——功能强大,该套件具备持久化数据能力,可全面助力医院快速实现创新型数据应用,包括数据库管理、敏捷开发、 API 管理、FHIR资源仓库、分布式扩展、一体化机器学习、自适应分析等;广——广泛专业的本土化生态合作伙伴,具备丰富的互联互通成熟度测评经验与强大的落地能力。
截至2020 年,InterSystems已助力中南大学湘雅医院(五级乙等)、河南省人民医院(五级乙等)、安徽省立医院(五级乙等)、武汉中心医院(五级乙等)、四川大学华西第二医院(五级乙等)、深圳市宝安区妇幼保健院(五级乙等)、广州医科大学附属第二医院(五级乙等)、吉林大学中日联谊医院(五级乙等)、浙江大学医学院附属第四医院(五级乙等)、北京协和医院(四级甲等)等一百余家医院通过互联互通标准化成熟度测评,在全国大型医院中市场份额最高。
2021 CHITEC (7月9日-11日)期间,转发这篇文章到微信朋友圈,可以到InterSystems展位领取小礼品哦~
文章
Michael Lei · 十二月 13, 2022
嵌入式 Python 模板
今天你们分享一个简单的[嵌入式 Python 模板](https://openexchange.intersystems.com/package/iris-embedded-python-template),我建议将其作为任何使用 InterSystems IRIS 并将使用嵌入式 Python 的通用项目的起点。
功能:
* 随时可用的嵌入式 Python;
* 3 种嵌入式 Python 开发方式示例;
* 随时可用的 VSCode 开发;
* 支持 Docker;
* 支持在线演示;
* 随时可用的 ZPM 优先开发。
下面讨论一下这些功能!
我们先来谈谈嵌入式 Python。 此功能在 InterSystems IRIS 2021.2 中提供,它允许使用 InterSystems IRIS 通过 Python 开发解决方案。 IRIS 2021.2 及以上版本允许使用 InterSystems IRIS 在共享内存环境中执行 Python 脚本,这为 Python 开发者在使用代码与数据相近的数据库时提供了独特的选项。
### 3 种使用嵌入式 Python 进行开发的模式
### **从 ObjectScript 调用 Python 库**
此操作因 [%SYS.Python](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?&LIBRARY=%25SYS&CLASSNAME=%25SYS.Python) 类而变为可能,该类允许导入 Python 库并通过 ObjectScirpt 调用 Python。 [文档](https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=AEPYTHON#AEPYTHON_callpython_library)、[示例](https://github.com/intersystems-community/iris-embedded-python-template/blob/be578226b7a0c583df1f7b693b1bdae074efb1bd/src/dc/python/PersistentClass.cls#L17)。 请查看以下代码:
<span class="hljs-keyword">ClassMethod</span> Today() <span class="hljs-keyword">As</span> <span class="hljs-built_in">%Status</span>
{
<span class="hljs-keyword">Set</span> sc = <span class="hljs-built_in">$$$OK</span>
<span class="hljs-keyword">Set</span> dt = <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%SYS.Python</span>).Import(<span class="hljs-string">"datetime"</span>)
<span class="hljs-keyword">write</span> dt.date.today().isoformat()
<span class="hljs-keyword">Return</span> sc
}
### 使用 Python 编写 ObjectScript 类方法
事实上,现在开发者可以使用纯 Python 在方法签名和代码中添加 [Language=python] 标签。 还有一个辅助 Python 库“iris”,可以用于引用 ObjectScript 类和全局变量。 [文档](https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=AEPYTHON#AEPYTHON_runpython_method)、[示例](https://github.com/intersystems-community/iris-embedded-python-template/blob/224be7f5bf80ea0f588f555c7f9e8c8d10c90c10/src/dc/python/PersistentClass.cls#L17)、示例代码:
ClassMethod CreateRecordPython(propValue As %VarString, ByRef id As %Integer) [ Language = python ]
{
<span class="hljs-keyword">import</span> iris
obj=iris.cls(__name__)._New()
obj.Test=propValue
sc=obj._Save()
id=obj._Id()
<span class="hljs-keyword">return</span> sc
}
### 使用纯 Python 编写 InterSystems IRIS 解决方案代码
这是开发者可以选择的第三种处理 IRIS 的方式。 这里,Python 脚本需要连接到 IRIS,此操作可以通过环境变量和设为“On”的 CallIn 服务来完成,请参见下面的详细信息。 设置完后,使用 IRIS 在共享内存中执行 Python 脚本。 在这里,“iris”库也非常有用。 [文档](https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=AEPYTHON#AEPYTHON_runpython_script)、[示例](https://github.com/intersystems-community/iris-embedded-python-template/blob/master/python/irisapp.py)。
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_rec</span><span class="hljs-params">(var)</span>:</span>
obj=iris.cls(<span class="hljs-string">'dc.python.PersistentClass'</span>)._New()
obj.Test=var
obj._Save()
id=obj._Id()
<span class="hljs-keyword">return</span> id
<span class="hljs-comment"># test record creation</span>
<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime
now=str(datetime.now())
print(<span class="hljs-string">"Creating new record in dc.python.PersistentClass"</span>)
print(create_rec(now))
<span class="hljs-comment">## run SQL and print data</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">run_sql</span><span class="hljs-params">(query)</span>:</span>
rs=iris.sql.exec(query)
<span class="hljs-keyword">for</span> idx, row <span class="hljs-keyword">in</span> enumerate(rs):
print(<span class="hljs-string">f"[<span class="hljs-subst">{idx}</span>]: <span class="hljs-subst">{row}</span>"</span>)
query=<span class="hljs-string">"Select * from dc_python.PersistentClass"</span>
print(<span class="hljs-string">"Running SQL query "</span>+query)
run_sql(query)
### 支持 Docker
模板仓库在容器中运行 IRIS,并设置嵌入式 Python 调整所需的所有内容。
环境变量。 嵌入式 Python 需要设置特定的环境变量来连接到 IRIS 并运行 Python 脚本。 下面是 [dockerfile](https://github.com/intersystems-community/iris-embedded-python-template/blob/be578226b7a0c583df1f7b693b1bdae074efb1bd/Dockerfile#L13-L17) 中有帮助的设置:
# init Python env
ENV PYTHON_PATH=/usr/irissys/bin/irispython
ENV SRC_PATH=/irisrun/repo
ENV IRISUSERNAME "SuperUser"
ENV IRISPASSWORD "SYS"
ENV IRISNAMESPACE "USER"
此外,嵌入式 Python 需要将 CallIn 服务设为“on”,此操作在构建 docker 的阶段在 [iris.script](https://github.com/intersystems-community/iris-embedded-python-template/blob/224be7f5bf80ea0f588f555c7f9e8c8d10c90c10/iris.script#L7) 中完成:
<span class="hljs-comment">; enabling callin for Embedded Python</span>
<span class="hljs-keyword">do</span> <span class="hljs-keyword">##class</span>(Security.Services).Get(<span class="hljs-string">"%Service_CallIn"</span>,.prop)
<span class="hljs-keyword">set</span> prop(<span class="hljs-string">"Enabled"</span>)=<span class="hljs-number">1</span>
<span class="hljs-keyword">set</span> prop(<span class="hljs-string">"AutheEnabled"</span>)=<span class="hljs-number">48</span>
<span class="hljs-keyword">do</span> <span class="hljs-keyword">##class</span>(Security.Services).Modify(<span class="hljs-string">"%Service_CallIn"</span>,.prop)
另外,您的解决方案可能需要安装一些 Python 库。 这通过仓库根目录中的 [requirements.txt](https://github.com/intersystems-community/iris-embedded-python-template/blob/master/requirements.txt) 和 [dockerfile](https://github.com/intersystems-community/iris-embedded-python-template/blob/224be7f5bf80ea0f588f555c7f9e8c8d10c90c10/Dockerfile#L22) 中的 pip3 调用来实现:
pip3 install -r requirements.txt && \
### 随时可用的 VSCode 开发
使用 docker 在 VSCode 中开发非常方便。 如果想要在 docker IRIS 解决方案中使用嵌入式 Python 进行开发,VSCode 需要切换到 [Devcontainer 模式](https://code.visualstudio.com/docs/remote/containers)。 为此,将 [devcontainer.json 文件](https://github.com/intersystems-community/iris-embedded-python-template/blob/master/.devcontainer/devcontainer.json)添加到 .devcontainer 文件夹中。 该文件描述了需要使用的 docker 服务(本例中为 iris),这可以帮助在 VSCode 中运行 Python 脚本,这些脚本将由在容器中运行的 IRIS 使用的 Python 引擎提供服务。 devcontainer.json 文件还有一个部分,其中说明了需要在容器模式下使用哪些[扩展](https://github.com/intersystems-community/iris-embedded-python-template/blob/224be7f5bf80ea0f588f555c7f9e8c8d10c90c10/.devcontainer/devcontainer.json#L43):
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"intersystems-community.vscode-objectscript",
"intersystems.language-server",
"intersystems-community.servermanager",
"ms-vscode.docker"
],
### 通过 ZPM 安装嵌入式 Python 解决方案
此模板被设置为“ZPM 优先”开发仓库。 这意味着所有开发代码都已在 module.xml 中介绍,并且每次构建 docker 镜像时都作为 ZPM 模块安装,开发者每次编码时都在 iris.script 中使用[以下行](https://github.com/intersystems-community/iris-embedded-python-template/blob/224be7f5bf80ea0f588f555c7f9e8c8d10c90c10/iris.script#L13)开头:
zpm "load /home/irisowner/irisbuild/ -v":1:1
ZPM 模块中还介绍了嵌入式 Python 代码,它通过 [FILECOPY](https://github.com/intersystems-community/iris-embedded-python-template/blob/224be7f5bf80ea0f588f555c7f9e8c8d10c90c10/module.xml#L11) 进行安装:
<FileCopy Name="python/" Target="${libdir}python/"/>
此表达式表示,我们想要将所有 Python 脚本打包在仓库中的 /python 文件夹下,并将其安装在目标 IRIS 安装程序的 libdir 的 python/ 文件夹中。 如果将 Python 脚本复制到 ${libdir}python/ 文件夹下,则可以在目标 IRIS 机器中通过 ObjectScirpt 或 Python 对其进行导入调用。
_**注意: 确保您的文件夹名称唯一,以免意外替换其他 Python 代码。**_
希望该模板对您有用。 欢迎提供反馈,尤其是拉取请求!
公告
Hao Ma · 一月 12, 2023
我很高兴地宣布 ObjectScript 包管理器 ZPM 的生命周期中的一个里程碑。包管理器使开发人员能够以一种方便的方式巧妙地打包 ObjectScript 代码和部署配置设置以及版本信息。在过去的几年里,它已经发展成为许多开发工作流程中不可或缺的一部分。
事实证明,InterSystems 决定使用它来打包我们自己的组件非常重要,这促使我们决定将 GitHub 存储库从社区移至我们的公司存储库,并将其重命名为InterSystems Package Manager (IPM) 。 IPM 仍将是开源的。社区成员将能够审查代码并提交拉取请求。但这种变化使我们能够以非员工能够直接对代码库进行更改的方式确保软件的安全性。更高级别的安全性和信任对于可以将代码与您的数据一起安装的软件来说至关重要。
所以请和我一起庆祝 ZPM 的生命,欢迎 IPM 的诞生,并感谢贡献者——尤其是 Nikolay Soloviev 和@Dmitry.Maslennikov,他们再次展示了对开发人员需求的惊人洞察力,以及技能和奉献精神构建出色的软件!
---
https://github.com/intersystems/ipm
文章
Louis Lu · 一月 19, 2023
您可能还记得在 InterSystems 2022年全球峰会以及 2022.2 版本发布的网络研讨会上,我们发布了一项令人兴奋的新功能——列存储,它可以纳入您 InterSystems IRIS 的解决方案中。 列存储引入了一种存储SQL表数据的替代方法,它为分析查询提供了数量级的加速。 最新的2022.3开发预览版在原有的基础上包括一系列我们认为值得在这里宣布的更新。
快速回顾
如果您不熟悉InterSystems IRIS 的列存储,请观看这段简短的介绍视频以及相关该主题的2022全球峰会内容。 简而言之,我们使用新的$vector数据类型将表数据编码为每列 64k 个值的块。 $vector是一种仅限内部使用的数据类型(目前),它利用自适应编码方案来实现稀疏和密集数据的高效存储。 编码还针对一系列专用$vector操作进行了优化,例如一次计算64k 值的整个块的聚合、分组和过滤,并在可能的情况下利用芯片的SIMD指令。
在SQL查询时,我们构建对这些块直接进行操作的查询计划,正如您所想象的,与传统的逐行处理相比,这大大减少了执行查询所需的IO量和ObjectScript指令数。 当然,与面向行且对于单值的操作相比,这个的IO消耗更大,$vector的操作也更重一些,但收益是巨大的。 我们使用一个专业术语矢量化查询计划来表示处理$vector数据的执行策略,通过一系列快速的对于整个块的操作来提高整体的效率。
就一个字“快”
最重要的是,事情变得更快了。 我们扩展了优化器对列索引的理解,现在您将看到更多的查询使用列索引,即使某些请求的字段没有存储在列索引或数据映射中。 此外,您将看到它在许多情况下联合收割机了列索引和位图索引,如果您是从向现有模式添加列索引开始的,那么这将非常有用。
新工具包还包括一系列跨堆栈的更改,以提高性能,从优化到一些查询处理增强功能上的低级$vector操作,以及一组更广泛的可并行化的矢量化查询计划。 加载数据的某些方法,如通过INSERT.. SELECT语句,现在还将使用我们已经用于构建索引的缓冲模型,并且现在能够以真正高的性能构建整个表。
向量化的 JOIN 操作
我们在此版本中添加的最令人兴奋的功能是支持以矢量化方式连接列数据。 在Python 2022.2中,当您希望在一个查询中联合收割机两个表中的数据时,我们仍然会求助于健壮的逐行JOIN策略,该策略同样适用于按列和按行组织的数据。 现在,当JOIN的两端都以列格式存储时,我们使用一个新的内核API在内存中对它们进行JOIN,同时保留它们的$vector格式。 这是实现完全矢量化查询计划的又一重要步骤,即使对于最复杂的查询也是如此。
下面是一个利用新函数的查询示例,该查询对纽约Taxi数据集执行
SELECT
COUNT(*),
MAX(r1.total_amount - r2.total_amount)
FROM
NYTaxi.Rides r1,
NYTaxi.Rides r2
WHERE
r1.DOLocationID = r2.PULocationID
AND r1.tpep_dropoff_datetime = r2.tpep_pickup_datetime
AND r2.DOLocationID = r1.PULocationID
AND r1.passenger_count > 2
AND r2.passenger_count > 2
此查询查找有两名以上乘客的旅行对,第二次旅行在第一次旅行结束的地方开始,在完全相同的时间,并且第二次旅行将乘客带回第一次旅行开始的地方。 这不是一个非常有用的分析,但是我在这个模式中只有一个真正的表,复合JOIN键使这个分析变得不那么琐碎。 在此语句的查询计划中,您将看到类似Apply vector operation %VHASH(用于构建复合JOIN键)和Read vector-join temp-file A的片段,它们表明我们的新矢量化连接器正在工作! 这听起来像是一个冗长的查询计划中的一个微不足道的小问题,但它涉及到内部的大量智能工程,而且有相当多的知名柱状数据库供应商根本不允许这样做,并对您的模式布局施加了严格的约束,所以请加入我们,一起来享受这个! :—)
当查询计划继续读取该临时文件时,您可能会注意到在连接后的工作中仍有一些逐行处理,这就把我们带到了......
接下来我们会...
列存储在2022.3版本中仍被标记为"实验性",但我们正在接近生产就绪状态,并为多表查询提供完整的端到端矢量化操作。 这包括上面提到的Post-Join 工作、查询优化器中更广泛的支持、更快地加载列式表以及对连接器的进一步增强(如共享内存支持)。 简而言之:现在是使用 InterSystems IRIS 2022.3社区版在纽约出租车数据集上进行第一次尝试的好时机,并且在我们发布2023.1时,您只需按下"运行"按钮就可以!
如果您对如何将列式存储用于您自己的数据和查询感兴趣,请直接联系 InterSystems 客户团队,也许我们会在InterSystems 2023年全球峰会上见面;-).
文章
Claire Zheng · 一月 30, 2023
亲爱的社区开发者们,
有一些开发者已经通过了InterSystems 官方认证,并希望在个人资料头像上有一个漂亮的绿色标识
以及您社区中的所有证书,以便其他人更好地了解你的能力……
那么,要将认证添加到您的社区资料中,您需要执行 3 个简单的步骤:
1️⃣ 打开社区个人主页(点击右上角头像即可)
2️⃣ 找到InterSystems 认证( InterSystems Certification)
3️⃣ 点击加载我的认证(Load my certification)
就这么简单!
系统将通过您社区关联的电子邮件将请求发送到 Credly。如果您的证书链接到同一电子邮件,您的证书将会自动加载:
如果没有,请按照页面上描述的详细步骤操作:
完成后,每个人都可以看到你的认证资质哦 ;)
预祝每位社区成员成功添加认证并通过!
文章
Jingwei Wang · 七月 21, 2022
视图为存储查询,提供了物理表的所有灵活性和安全权限。所有的视图都是可更新的或只读的。
注意:不能对只读的数据库中的数据创建视图。不能对存储在通过ODBC或JDBC网关连接的Informix表中的数据创建视图。这是因为InterSystems IRIS查询转换在FROM子句中使用子查询,而Informix不支持FROM子句的子查询。
创建视图
视图名称可以是合格的的或不合格的。一个没有限定的视图名称是一个简单的标识符。MyView。一个合格的视图名称由两个简单的标识符组成,一个schema名称和一个视图名称,用句号隔开, 例如MySchema.MyView。视图名和表名遵循相同的命名规则,并对未限定的名称执行相同的schema名称解析。同一模式中的视图和表不能有相同的名称。
你可以通过几种方式定义视图:
使用SQL CREATE VIEW命令(在DDL脚本中或通过JDBC或ODBC)。
CREATE VIEW MySchema.MyView (ViewCol1, ViewCol2, ViewCol3) AS
SELECT TableCol1, TableCol2, TableCol3
FROM MyTable
使用管理门户的创建视图界面。 系统操作 -> SQL -> 操作 -> 创建视图
这将显示 "创建视图 "窗口,如图所示: schema:您可以决定将视图包含在现有schema中,或者创建一个新的schema。 视图名称:一个有效的视图名称。在同一模式中,你不能对表和视图使用相同的名称。 带检查选项:选项有READONLY、LOCAL、CASCADED。 授予视图的所有权限给_PUBLIC:如果选择了这个选项,这个选项会给所有用户执行这个视图的权限。默认是不给所有用户访问该视图的权限。 查看文本:你可以通过以下三种方式中的任何一种指定视图文本。 在 "查看文本 "区域键入一个SELECT语句。 使用查询生成器创建一个SELECT语句,然后按确定将此查询提供给视图文本区。 如果你在管理门户SQL界面左侧选择一个缓存查询名称(例如%sqlcq.USER.cls4),然后调用创建视图,这个缓存查询就会提供给视图文本区。注意,在视图文本区,你必须在保存视图文本之前用实际值替换变量引用(问号)。
使用Objectscript执行DDL
ClassMethod CreateTable() As %String
{
&sql(CREATE VIEW Sample.VSrStaff
AS SELECT Name AS Vname,Age AS Vage
FROM Sample.Person WHERE Age>75)
IF SQLCODE=0{ WRITE "Created a view"}
ELSEIF SQLCODE=-201 { WRITE "View already exists" RETURN SQLCODE}
ELSE { WRITE "Serious SQL Error, returing SQLCODE " RETURN SQLCODE_" "_%msg}
}
$SYSTEM.SQL.Schema.ViewExists()方法可以用来确定一个视图名称是否已经存在。
查看视图定义
视图信息
SELECT * FROM INFORMATION_SCHEMA.VIEWS
视图的依赖表 NFORMATION.SCHEMA.VIEWTABLEUSAGE持久化类显示当前命名空间中的所有视图和它们所依赖的表。
通过SQL脚本显示视图依赖表
SELECT View_Schema,View_Name, Table_Schema,Table_Name FROM INFORMATION_SCHEMA.VIEW_TABLE_USAGE
通过ObjectScript显示依赖表
SET statemt = ##class(%SQL.Statement).%New()
SET cqStatus = statemt.%PrepareClassQuery("%Library.SQLCatalog","SQLViewDependsOn")
IF cgStatus '= 1 { WRITE "PrepareClassQuery failed:" DO $System.Status.DisplayError(cgStatus) QUIT }
SET rset = statemt.%Execute("vschema.vname")
DO rset.%Display()
如果调用者没有视图所依赖的表的权限,该表及其模式将被列为NOT PRIVILEGED。这允许没有表权限的调用者确定视图所依赖的表的数量,但不能确定表的名称。
View ID: %VID
InterSystems IRIS为视图或FROM子句返回的每条记录分配一个整数的视图ID(%VID)。与表的行ID号一样,这些视图的行ID号也是系统分配的、唯一的、非空的、非零的和不可修改的。这个%VID通常对用户来说是不可见的,只有在明确指定时才会返回。它作为数据类型INTEGER返回。因为%VID值是连续的整数,如果视图返回有序的数据,它们就更有意义;视图只有在与TOP子句配对时才能使用ORDER BY子句。
CREATE VIEW Sample.VSrStaff
AS SELECT TOP ALL Name AS Vname,Age AS Vage
FROM Sample.Person WHERE Age>75
ORDER BY Name
下面的例子返回VSrStaff视图定义的所有数据(使用SELECT *),并且还指定要返回每一行的视图ID。与表的行ID不同,视图的行ID在使用星号语法时不会显示;只有在SELECT中明确指定时才会显示。
SELECT *,%VID AS ViewID FROM Sample.VSrStaff
%VID可以用来进一步限制SELECT从视图返回的行数,如下面的例子所示。
SELECT *,%VID AS ViewID FROM Sample.VSrStaff WHERE %VID BETWEEN 5 AND 10
因此,%VID可以用来代替TOP(或者作为TOP的补充)来限制查询返回的记录的数量。一般来说,TOP子句用于返回数据记录的一个小子集;%VID用于返回大部分或全部数据记录,将记录返回到小子集的中。这个功能可能很有用,特别是对于Oracle查询(%VID很容易映射到Oracle ROWNUM)。然而,用户应该意识到,与TOP相比,使用%VID有一些性能限制。
%VID不执行time-to-first-row的优化。TOP的优化是尽可能快地返回第一行数据。%VID的优化是为了尽可能快地返回完整的数据集。
如果查询指定了排序的结果,%VID不会执行有限的排序(这是由TOP执行的特殊优化)。查询首先对完整的数据集进行排序,然后使用%VID对返回的数据集进行限制。TOP是在排序前应用的,所以SELECT执行的是有限的排序,只涉及一个受限制的行子集。
为了保持对第一行的优化时间和有限的排序优化,你可以使用一个带有TOP和%VID组合的FROM子句子查询。
SELECT *,%VID AS SubQueryID
FROM (SELECT TOP 10 Name,Age
FROM Sample.Person
WHERE Age > 75
ORDER BY Name)
WHERE %VID > 4
不能对指定了%VID的查询进行并行执行,即使明确指定了%PARALLEL关键字。
文章
Jingwei Wang · 七月 29, 2022
什么时候使用索引
索引提供了一种机制,通过维护常用数据的分类子集来优化查询。确定哪些字段应该被编入索引需要一些思考:太少或错误的索引,关键查询会运行得太慢;太多的索引会减慢INSERT和UPDATE的性能(因为索引值必须被设置或更新)。
索引什么
为了确定添加索引是否能提高查询性能,从管理门户的SQL界面运行查询,并在Performance中注意 global引用的数量。添加索引,然后重新运行查询,注意 global引用的数量。一个有用的索引应该减少 global引用的数量。你可以通过使用%NOINDEX关键字作为WHERE子句或ON子句条件的前言来阻止索引的使用。
你应该对JOIN中指定的字段(属性)进行索引。例如,LEFT OUTER JOIN从左表开始,然后查看右表,因此,你应该对右表的字段进行索引。在下面的例子中,你应该为T2.f2编制索引。一个INNER JOIN应该在两个ON子句字段上都有索引。
FROM Table1 AS T1 LEFT OUTER JOIN Table2 AS T2 ON T1.f1 = T2.f2
如果查询计划中的第一个项目是 "read master map",或者查询计划调用的模块的第一个项目是 "read master map",那么查询的第一个map就是master map而不是索引map。因为主图读取的是数据本身,而不是数据的索引,这几乎总是表明一个低效的查询计划。除非该表相对较小,否则你应该创建一个索引,以便当你重新运行这个查询时,查询计划的第一张map显示 "read index map"。
你应该为WHERE子句中指定的相等条件的字段建立索引。
你可能希望为WHERE子句范围条件中指定的字段,以及GROUP BY和ORDER BY子句中指定的字段建立索引。
在某些情况下,基于范围条件的索引会使查询变慢。如果绝大多数的记录都满足指定的范围条件,就会出现这种情况。例如,如果查询子句WHERE Date < CURRENT_DATE被用于一个数据库,其中大部分记录来自以前的日期,在Date上建立索引实际上会降低查询速度。这是因为查询优化器假定范围条件将返回相对较少的行,并针对这种情况进行优化。你可以通过在范围条件前加上%NOINDEX来确定是否发生了这种情况,然后再次运行查询。
如果你使用一个索引字段进行比较,那么在比较中指定的字段应该具有和它在相应索引中相同的collation类型。例如,在SELECT的WHERE子句或JOIN的ON子句中的Name字段应该具有与为Name字段定义的索引相同的排序方式。如果字段的排序和索引的排序不匹配,那么索引的有效性就会降低,或者根本就不能使用。
在map中存储的表
一个SQL表被存储为一组map。每个表都有一个主图,包含了表中的所有数据;表还可能有其他图,如索引图和位图。每个map可以被想象成一个多维global,一些字段的数据在一个或多个子标中,其余字段存储在节点值中。这些子标控制着哪些数据被访问。
对于主映射,RowID或IDKEY字段通常被用作映射子标。
对于索引map,通常使用其他字段作为前导下标,RowID/IDKEY字段作为额外的低级下标。
对于一个位图,位图层可以被认为是一个额外的RowID下标层。然而,位图只能用于正整数的RowIDs。
Master Map
系统为每个表自动定义了一个主图(Data/Master)。master map不是一个索引,它是一个使用其map下标字段直接访问数据本身的map。默认情况下,主图的下标字段是系统定义的RowID字段。默认情况下,这种使用RowID字段的直接数据访问是用SQL map名称(SQL索引名称)IDKEY表示的。
默认情况下,一个用户定义的主键不是IDKEY。这是因为使用RowID整数的Master Map查询几乎总是比通过主键值查询更有效率。然而,如果你指定主键是IDKEY,主键索引就会被定义为表的主映射,SQL Map Name是主键SQL索引的名称。
对于一个单字段的主键/IDKEY,主键索引是Master Map,但是Master Map的数据访问列仍然是RowID。这是因为记录的唯一主键字段值和它的RowID值之间存在一对一的匹配,而且RowID被认为是更有效的查询。对于多字段主键/IDKEY,主映射被赋予主键索引名称,主映射的数据访问列是主键字段。
选择一个索引类型
下面是在位图索引和标准索引之间进行选择的一般指导原则:
一般来说,可以对这些类型使用标准索引:主键、外键、唯一键、关系、简单的对象引用。
否则,位图索引通常是比较好的(假设该表使用系统分配的数字ID号)。
其他因素:每个属性上的独立位图索引通常比多个属性上的位图索引有更好的性能。这是因为SQL引擎可以使用AND和OR操作有效地组合独立的位图索引。
如果一个属性(或一组你真正需要一起索引的属性)有超过10,000-20,000个不同的值(或值组合),请考虑标准索引。然而,如果这些值的分布非常不均匀,以至于少量的值占了相当一部分行,那么位图索引可能会好得多。一般来说,我们的目标是减少索引所需的总体大小。
定义和建立索引
以下是建立索引的原则:
你可以在表中的字段值上定义一个索引,或者在类中的相应属性上定义一个索引。
你也可以在几个字段/属性的组合值上定义一个索引。无论你是用SQL字段和表的语法,还是用类的属性语法来定义,都会创建相同的索引。
当某些类型的字段(属性)被定义时,InterSystems IRIS会自动定义索引,例如主键和唯一值属性。
你可以为同一个字段(属性)定义一个以上的索引,为不同的目的提供不同类型的索引。
每当对数据库进行数据插入、更新或删除操作时,InterSystems IRIS都会填充和维护索引(默认情况下),无论是使用SQL字段和表语法,还是使用类属性语法。你可以覆盖这个默认值(通过使用%NOINDEX关键字)来快速对数据进行修改,然后作为一个单独的操作建立或重建相应的索引。你可以在用数据填充一个表之前定义索引,你也可以为一个已经填充了数据的表定义索引,然后作为一个单独的操作填充(建立)索引。
InterSystems IRIS在准备和执行SQL查询时,会使用可用的索引。默认情况下,它选择使用哪些索引来优化查询性能。你可以覆盖这个默认值,以防止在特定的查询或所有的查询中使用一个或多个索引,视情况而定。
索引属性
每个索引都有一个唯一的名字。这个名称用于数据库管理目的(报告、建立索引、删除索引等等)。像其他SQL实体一样,一个索引也有一个SQL索引名和一个相应的索引属性名;这些名称在允许的字符、大小写敏感度和最大长度上有所不同。
如果使用SQL CREATE INDEX命令定义,系统会生成一个相应的索引属性名。如果使用持久化类定义,SqlName关键字允许用户指定一个不同的SQL索引名称(SQL映射名称)。管理门户SQL界面的目录详情显示了每个索引的SQL索引名称(SQL映射名称)和相应的索引属性名称(索引名称)。
索引类型是由两个索引类关键字Type和Extent定义的。InterSystems IRIS可用的索引类型包括:
标准索引(类型=索引)- 一个持久的数组,将索引值与包含该值的行的RowID联系起来。任何没有明确定义为位图索引、位片索引或范围索引的索引都是一个标准索引。
位图索引(类型=位图)--一种特殊的索引,它使用一系列的bitstring来表示对应于给定索引值的RowID值的集合;InterSystems IRIS包括一些针对位图索引的性能优化。
位片索引(类型=位片)--一种特殊的索引,能够非常快速地评估某些表达式,如sum范围条件。某些SQL查询会自动使用位片索引。
Extent Indices(位图范围索) - 一个范围内所有对象的索引。
一个表(类)的最大索引数是400。
系统自动定义的索引
当你定义一个表时,系统会自动定义某些索引。以下索引在你定义表时自动生成,并在你添加或修改表数据时被填充。如果你定义了:
一个不是IDKEY的主键,系统会生成一个Unique类型的相应索引。主键索引的名称可以是用户指定的,也可以是从表的名称中衍生出来的。例如,如果你定义了一个未命名的主键,相应的索引将被命名为tablenamePKEY#,其中#是每个唯一键和主键约束的一个连续的整数。
一个UNIQUE字段,InterSystems IRIS为每个UNIQUE字段生成一个索引,名称为tablameUNIQUE#,其中#是每个唯一键和主键约束的连续整数。
一个UNIQUE约束,系统为每个具有指定名称的UNIQUE约束生成一个索引,为共同定义一个唯一值的字段生成索引。
一个分片Shard Key,系统为分片键字段生成一个索引,名为ShardKey。
你可以通过管理门户的SQL目录细节标签查看这些索引。CREATE INDEX命令可以用来添加一个UNIQUE字段约束;DROP INDEX命令可以用来删除一个UNIQUE字段约束。
默认情况下,系统会在RowID字段上生成IDKEY索引。定义一个IDENTITY字段并不产生索引。然而,如果你定义了一个IDENTITY字段并使该字段成为主键,InterSystems IRIS会在IDENTITY字段上定义IDKEY索引并使其成为主键索引。这在下面的例子中显示。
CREATE TABLE Sample.MyStudents (
FirstName VARCHAR(12),
LastName VARCHAR(12),
StudentID IDENTITY,
CONSTRAINT StudentPK PRIMARY KEY (StudentID) )
同样,如果你定义了一个IDENTITY字段,并给该字段一个UNIQUE约束,InterSystems IRIS会明确地在IDENTITY字段上定义一个IdKey/Unique索引。这在下面的例子中显示。
CREATE TABLE Sample.MyStudents (
FirstName VARCHAR(12),
LastName VARCHAR(12),
StudentID IDENTITY,
CONSTRAINT StudentU UNIQUE (StudentID) )
这些IDENTITY索引操作只在没有明确定义的IdKey索引和表不包含数据的情况下发生。
手动定义索引
有两种方法来定义索引。
使用类定义来定义索引,其中包括:
可以被索引的属性
多个属性的索引
索引的整理
在索引中使用Unique、PrimaryKey和IdKey关键字
定义SQL搜索索引
用索引存储数据
对NULL进行索引
索引集合
阵列集合的索引
用(ELEMENTS)和(KEYS)对数据类型属性进行索引
为嵌入式对象(%SerialObject)属性建立索引
关于在类中定义的索引的说明
使用DDL定义索引
使用类定义来定义索引
Class MyApp.Student Extends %Persistent [DdlAllowed]
{
Property Prop1 As %String;
Property Prop2 As %String;
//单一属性定义索引
Index Prop1IDX On Prop1;
//多属性值定义索引
Index MIXIDX On (Prop1, Prop2)
//使用单一属性和collation定义索引
Index Prop2IDX On Prop2 As Exact;
//使用多属性和collation定义索引
Index MIX2IDX On (Prop1 As SQLUPPER,Prop2 As Exact);
//使用Unique关键字定义索引
Index Prop1IDX On Prop1 [Unique]
//使用PrimaryKey关键字定义索引
Index Prop1IDX On Prop1 [PrimaryKey]
//使用IdKey关键字定义索引
Index Prop1IDX On Prop1 [IdKey]
//定义查询索引 -- 供文档查询使用
Index Prop1IDX On (Prop1) As %iFind.Index.Basic
//List或者Array属性定义索引
Index EmpIndex On Employees(KEYS)
Index EmpIndex On Employees(ELEMENTS)
Index EmpIndex On (Employees(KEYS), Employees(ELEMENTS));
//对象属性定义索引
Index StateInx On Home.State;
//定义位图索引
Index ReginIDX On Region [Type = bitmap]
//定义位片索引
Index AgeIDX On Age [Type = bitslice]
}
在类定义中处理索引时,有几点需要注意:
索引定义只从主(第一)超类中继承。
如果你使用Studio为一个有数据存储的类添加(或删除)一个索引定义,你必须通过构建索引来手动填充索引。
使用类定义定义%BID位图索引
如果表的ID不是正整数,你可以创建一个%BID属性,用来创建位图索引定义。你可以对具有任何数据类型的ID字段的表,以及由多个字段组成的IDKEY(其中包括子表)使用这个选项。可以为任一数据存储类型创建%BID位图:默认结构表或%Storage.SQL表。这个功能被称为 "任何表的位图Bitmaps for Any Table",或BAT。
要在这样的表中启用位图索引,你必须做以下工作。
为该类定义一个%BID属性/字段。这可以是该类的一个现有属性,也可以是一个新的属性。它可以有任何名字。如果这是一个新的属性,你必须为表中的所有现有行填充这个属性/字段。这个%BID字段必须用一个数据类型来定义,限制字段数据值为唯一的正整数。例如:
Property MyBID As %Counter;
定义一个新的类参数来定义哪个属性是%BID字段。这个参数被命名为BIDField。这个参数被设置为%BID属性的SQLFieldName。例如,参数BIDField = "MyBID"。
Parameter BIDField = "MyBID";
为%BID定义一个索引。例如,
Index BIDIdx On MyBID [ Type = key, Unique ] 。
定义%BID定位器索引。这将%BID索引与表的ID关键字段联系起来。下面的例子是关于一个有两个字段组成的复合IDKey的表。
Index IDIdx On (IDfield1, IDfield2) [ IdKey, Unique ] 。
Index BIDLocIdx On (IDfield1, IDfield2, MyBID) [ Data = IdKey, Unique ] 。
这个表现在支持位图索引。你可以使用标准语法根据需要定义位图索引。例如:
Index RegionIDX On Region [Type = bitmap]
该表现在也支持位片索引。你可以使用标准语法定义位片索引。
注意: 要建立或重建一个%BID位图索引,你必须使用%BuildIndices()。%ConstructIndicesParallel()方法不支持%BID位图索引。
使用DDL定义索引
DDL索引命令做了以下工作。
它们更新相应的类和表的定义,在这些定义上添加或删除索引。修改后的类定义被重新编译。
它们根据需要在数据库中添加或删除索引数据。CREATE INDEX命令使用当前存储在数据库中的数据来填充索引。同样,DROP INDEX命令从数据库中删除了索引数据(也就是实际的索引)。
CREATE INDEX
//标准索引
CREATE INDEX StateIdx ON TABLE Sample.Person (Home_State)
//位图范围索引
CREATE BITMAPEXTENT INDEX Patient ON TABLE Sample.Patient
//位图索引
CREATE BITMAP INDEX RegionIDX ON TABLE MyApp.SalesPerson (Region)
//位片索引
CREATE BITSLICE INDEX AgeIDX ON TABLE MyApp.SalesPerson (Age)
DROP INDEX
DROP INDEX PeopleIndex ON TABLE Employee
为嵌入式对象(%SerialObject)属性创建索引
要为嵌入式对象中的一个属性建立索引,你要在引用该嵌入式对象的持久化类中创建一个索引。属性名必须指定表(%Persistent类)中的引用字段的名称和嵌入对象(%SerialObject)中的属性,如下面的例子所示。
Class Sample.Person Extends (%Persistent) [ DdlAllowed ]
{ Property Name As %String(MAXLEN=50);
Property Home As Sample.Address;
Index StateInx On Home.State;
}
这里Home是Sample.Person中的一个属性,它引用嵌入式对象Sample.Address,其中包含State属性,如下例所示。
Class Sample.Address Extends (%SerialObject)
{ Property Street As %String;
Property City As %String;
Property State As %String;
Property PostalCode As %String;
}
只有与持久化类属性引用相关的嵌入式对象实例中的数据值被索引。你不能直接索引%SerialObject属性。%Library.SerialObject(以及所有没有明确定义SqlCategory的%SerialObject的子类)的SqlCategory是STRING。
你也可以使用SQL CREATE INDEX语句在嵌入式对象属性上定义一个索引,如下面的例子所示。
CREATE INDEX StateIdx ON TABLE Sample.Person (Home_State)
位图索引
位图索引是一种特殊类型的索引,它使用一系列的bit字符串来表示与给定的索引数据值相对应的ID值集合。
位图索引有以下重要特点:
位图是高度压缩的:位图索引可以比标准索引小得多。这大大减少了磁盘和缓存的使用。
位图操作为事务处理进行了优化:你可以在表内使用位图索引,与使用标准索引相比,没有性能损失。
位图上的逻辑操作(计数、和、以及OR)被优化为高性能。
SQL引擎包括一些特殊的优化,可以利用位图索引的优势。
位图索引的创建取决于表的唯一标识字段的性质。
如果表的ID字段被定义为一个具有正整数值的单一字段,你可以使用这个ID字段为一个字段定义位图索引。这种类型的表要么使用系统分配的唯一正整数ID,要么使用IdKey定义自定义ID值,其中IdKey基于类型为%Integer且MINVAL>0的单一属性,或类型为%Numeric且SCALE=0且MINVAL>0的单一属性。
如果表的ID字段没有定义为具有正整数值的单一字段(例如,一个子表),你可以定义一个%BID(位图ID)字段,它采取正整数,作为一个代理ID字段;这允许你为这个表中的字段创建位图索引。
本章讨论了与位图索引有关的下列主题“
位图索引操作
通过使用类定义来定义位图索引
使用DDL定义位图索引
生成一个位图范围索引
选择一个索引类型
对位图索引的限制
维护位图索引
位图分块的SQL操作
位图索引操作原理
位图索引的工作方式如下。假设你有一个包含若干列的Person表,这个表中的每一行都有一个系统分配的RowID号码(一组递增的整数值)。一个位图索引使用一组bitstring(一个包含1和0值的字符串)。在一个bitstring内,一个bit的序号位置对应于被索引表的RowID。对于一个给定的值,例如State是 "NY",有一个bitstring,对应于包含 "NY "的行,每个位置都是1,其他不包含'NY'的行,位置都是0。
例如,一个关于State的位图索引可能看起来像这样:
而关于年龄的索引可能看起来像这样。
注意: 这里显示的年龄字段可以是一个普通的数据字段,也可以是一个可以靠其他字段得出的字段(计算和SQLComputed)。
除了在标准操作中使用位图索引外,SQL引擎还可以使用位图索引,使用多个索引的组合有效地执行基于集合的特殊操作。例如,为了找到所有24岁并居住在纽约的Person实例,SQL引擎可以简单地执行Age和State索引的 AND 逻辑。结果如下:
SQL引擎可以使用位图索引进行以下操作:
在一个给定的表上对多个条件进行ANDing。
在一个给定的表上的多个条件的ORing。
在一个给定的表上的RANGE条件。
在一个给定的表上进行COUNT操作。
位图索引的限制
所有的位图索引都有以下限制:
不能在一个UNIQUE列上定义一个位图索引。
不能在位图索引中存储数据值。
不能在一个字段上定义位图索引,除非该字段的SqlCategory是INTEGER, DATE, POSIXTIME, 或NUMERIC (with scale=0) 。
对于一个包含超过100万条记录的表来说,当唯一值的数量超过10,000时,位图索引的效率就不如标准索引。因此,对于一个大表,建议你避免对任何包含(或可能包含)超过10,000个唯一值的字段使用位图索引;对于任何规模的表,避免对任何可能包含超过20,000个唯一值的字段使用位图索引。这些都是一般的近似值,不是精确的数字。
你必须创建一个%BID属性来支持表上的位图索引,该表
使用一个非整数字段作为唯一的ID键。
使用一个多字段的ID键。
是父-子关系中的一个子表。
你可以使用$SYSTEM.SQL.Util.SetOption()方法SET status=$SYSTEM.SQL.Util.SetOption("BitmapFriendlyCheck",1,.oldval)来设置一个系统范围的配置参数,在编译时检查这个限制,确定%Storage.SQL类中是否允许定义位图索引。这个检查只适用于使用%Storage.SQL的类。你可以使用$SYSTEM.SQL.Util.GetOption("BitmapFriendlyCheck")来确定这个选项的当前配置。
应用逻辑的限制:
一个位图结构可以用一个bitstring数组来表示,其中数组的每个元素代表一个具有固定bit数的 "块"。因为未定义等同于一个全部为0位的块,所以数组可以是稀疏的。一个代表所有0 bit的块的数组元素根本就不需要存在。由于这个原因,应用逻辑应该避免依赖0值位的$BITCOUNT(str,0)计数。
因为一个bitstring包含内部格式化,应用逻辑不应该依赖于一个bitstring的物理长度,也不应该依赖于对两个具有相同位值的bitstring进行等价。在回滚操作之后,一个bitstring被恢复到事务之前的位值。然而,由于内部格式化,回滚的bitstring可能不等同于事务之前的bitstring,也不具有相同的物理长度。
维护位图索引
在一个不稳定的表(一个经历了许多INSERT和DELETE操作的表),位图索引的存储可能会逐渐变得不那么有效。
为了维护位图索引,你可以运行%SYS.Maint.Bitmap工具方法来压缩位图索引,将其恢复到最佳效率。你可以使用OneClass()方法来压缩单个类的位图索引。或者你可以使用Namespace()方法来压缩整个命名空间的位图索引。这些维护方法可以在一个生产系统上运行。
d ##class(%SYS.Maint.Bitmap).Namespace("Samples",1,1,"2014-01-17 09:00:00")
d ##class(%SYS.Maint.Bitmap).OneClass("BitMap.Test",1,1)
运行%SYS.Maint.Bitmap实用方法的结果被写到调用该方法的进程中。这些结果也被写入%SYS.Maint.BitmapResults类中。
位片索引
当一个数字数据字段被用于某些数字操作时,位片索引被用于该字段。位片索引将每个数字数据值表示为一个二进制位串。比起使用布尔标志对数字数据值进行索引(如在位图索引中),位片索引用二进制表示每个值,并为二进制值中的每个数字创建一个位图来记录哪些行的二进制数字为1。这是一种高度专业化的索引类型,可以大幅度提高以下操作的性能。位片索引适用于以下类型:
SUM, COUNT, 或AVG聚合计算。(位片索引不用于COUNT(*)计算) 位片索引不用于其他聚合函数。
在 TOP n ... ORDER BY field 的操作。
在一个范围条件操作中指定的字段,例如 WHERE field > n or WHERE field BETWEEN lownum AND highnum
SQL优化器决定是否应该使用定义的位片索引。通常情况下,优化器只在处理大量的行(成千上万)时才使用位片索引。
在一个不稳定的表(一个经历了许多INSERT、UPDATE和DELETE操作的表)中,位片索引的存储效率会逐渐降低。%SYS.Maint.Bitmap工具方法同时压缩了位图索引和位片索引,恢复了效率。更多细节,请看 "维护位图索引"。
位图范围索引
位图范围索引是针对表的行的位图索引,而不是针对表的任何指定字段。在位图范围索引中,每个位代表一个连续的RowID整数值,每个位的值指定相应的行是否存在。InterSystems SQL使用这种索引来提高COUNT()的性能,COUNT()返回表中的记录数(行数)。一个表最多可以有一个位图范围索引。试图创建一个以上的位图范围索引,会导致SQLCODE -400错误,并出现%msg ERROR #5445: 定义了多个范围索引。
一个位图索引需要一个位图范围索引。只有在定义了一个或多个位图索引的情况下,定义一个持久化类才会生成一个位图范围索引。因此,当编译一个包含位图索引的持久化类时,如果没有为该类定义位图范围索引,类编译器会生成一个位图范围索引。
如果你从持久化类定义中删除了所有位图索引,那么位图范围索引会自动删除。但是,如果你重命名位图范围索引(例如,使用CREATE BITMAPEXTENT INDEX命令),删除位图索引并不会删除位图范围索引。
当为一个类建立索引时,如果你明确地建立了位图范围索引,或者你建立了位图索引而位图范围索引是空的,那么位图范围索引就会被建立。
一个类从主超类继承它的位图范围索引,如果它存在的话,可以定义或生成。一个位图范围索引被定义为:type = bitmap, extent = true。这意味着从主超类继承的位图范围索引被认为是一个位图索引,如果在子类中没有明确定义位图范围索引,则会触发在子类中生成一个位图范围索引。
InterSystems IRIS不会根据未来的可能性在超类中生成一个位图范围索引。这意味着InterSystems IRIS永远不会在一个持久性类中生成一个位图范围索引,除非有一个类型=位图的索引存在。假设某个未来的子类可能引入一个类型=位图的索引是不够的。
注意:在生产系统上为一个类添加位图索引的过程中需要特别注意(当用户使用一个特定的类,编译该类,并随后为它建立位图索引结构)。在这样的系统上,位图范围索引可能会在编译完成和索引建立过程的中间阶段被填充。这可能导致索引构建程序没有隐含地构建位图范围索引,这导致了部分位图范围索引不能被构建。
在一个经历了许多DELETE操作的表中,位图范围索引的存储会逐渐变得不那么有效。你可以通过使用BUILD INDEX命令重建位图范围索引,或者从管理门户选择表的Catalog Details标签,Maps/Indices选项并选择Rebuild Index。
构建索引
构建一个索引会做以下事情。
移除索引的当前内容。
扫描(读取每一行)主表,为表中的每一行添加索引条目。如果可能的话,使用特殊的$SortBegin和$SortEnd函数来确保建立大型索引的效率。当建立标准索引时,除了在内存中缓存数据外,这种对$SortBegin/$SortEnd的使用会占用IRISTEMP数据库的空间。因此,当建立一个非常大的标准索引时,InterSystems IRIS可能需要IRISTEMP中的空间,大致相当于最终索引的大小。
注意:建立索引的方法只提供给使用InterSystems IRIS默认存储结构的类(表)。
当前的数据库访问决定了你应该如何重建一个现有的索引:
非活跃系统(在索引建立或重建期间没有其他进程访问数据)
只读活跃系统(其他进程在索引建立或重建期间能够查询数据)
读和写活跃系统(其他进程能够修改数据,并在索引建立或重建期间查询数据)。
你可以按以下方式建立/重新建立索引。
使用BUILD INDEX SQL命令来构建指定的索引,或构建为一个表、一个Schema或当前命名空间定义的所有索引:
//为整个表构建索引
BUILD INDEX FOR TABLE table-name
//为一个Schema下的所有表构建索引
BUILD INDEX FOR SCHEMA schema-name
//为整个命名空间构建索引
BUILD INDEX FOR ALL
使用管理门户为一个指定的类(表)重建所有的索引。 系统资源管理器 - SQL- 操作 - 重建表索引
使用%BuildIndices()(或%BuildIndicesAsync())方法,如本节所述。
建立索引的首选方法是使用%BuildIndices()方法或%BuildIndicesAsync()方法。
%Library.Persistent.%BuildIndices()。%BuildIndices()作为一个后台进程执行,但调用者必须等待%BuildIndices()完成后才能接收控制权。
%Library.Persistent.%BuildIndicesAsync()。BuildIndicesAsync()将%BuildIndices()作为一个后台进程启动,调用者立即收到控制权。%BuildIndicesAsync()的第一个参数是queueToken输出参数。其余的参数与%BuildIndices()相同。
%BuildIndicesAsync()返回一个%Status值:成功表示%BuildIndices()工作任务成功排队;失败表示工作任务没有成功排队。
%BuildIndicesAsync()向queueToken输出参数返回一个值,表示%BuildIndices()完成状态。为了获得完成状态,你通过引用queueToken值传递给%BuildIndicesAsyncResponse()方法。你还指定了wait布尔值。如果wait=1,%BuildIndicesAsyncResponse()将等待,直到由queueToken识别的%BuildIndices()作业完成。如果wait=0,%BuildIndicesAsyncResponse()将尽可能快地返回一个状态值。如果%BuildIndicesAsyncResponse()的queueToken在返回时不是NULL,那么%BuildIndices()作业还未完成。在这种情况下,queueToken可以用来再次调用%BuildIndicesAsyncResponse()。当%BuildIndicesAsyncResponse()的queueToken最终为空时,那么%BuildIndicesAsyncResponse()返回的%Status值就是由%BuildIndicesAsync()调用的作业的完成状态。
在一个不活跃的系统上构建索引:
系统自动生成方法(由%Persistent类提供),用于构建(即为其提供数值)或清除(即为其移除数值)为类(表)定义的每个索引。你可以通过以下两种方式使用这些方法。
通过管理门户: 系统资源管理器 - SQL- 操作 - 重建表索引 注意:当其他用户正在访问该表的数据时,不要重建索引。要在一个活跃系统上重建索引,请看在活跃系统上建立索引。
以编程方式调用。
构建所有索引:调用%BuildIndices(),没有参数,为给定的类(表)构建(提供)所有索引。
SET sc = ##class(MyApp.SalesPerson).%BuildIndices()
IF sc=1 {WRITE !, "Successful index build" }
ELSE {WRITE !, "Index build failed",!
DO $System.Status.DisplayError(sc) QUIT}
构建指定的索引:调用%BuildIndices(),以$List的索引名称作为第一个参数,为一个给定的类(表)建立(提供)指定的索引。
SET sc = ##class(MyApp.SalesPerson).%BuildIndices($ListBuild("NameIDX", "SSNKey"))
IF sc=1 {WRITE !, "Successful index build" }
ELSE {WRITE !, "Index build failed",!
DO $System.Status.DisplayError(sc) QUIT}
构建所有索引,除了某些指定索引:调用%BuildIndices(),将$List的索引名称作为第七个参数,为一个给定的类(表)构建(提供值)所有定义的索引,除了指定的索引。
SET sc = ##class(MyApp.SalesPerson).%BuildIndices("",,,,,,$ListBuild("NameIDX", "SSNKey"))
IF sc=1 {WRITE !, "Successful index build" }
ELSE {WRITE !"Index build failed",!
DO $System.Status.DisplayError(sc) QUIT}
%BuildIndices()方法做了以下工作:
在任何要重建的(非位图)索引上调用$SortBegin函数(这将为这些索引启动一个高性能的排序操作)。
循环处理该类的主要数据(表),收集索引使用的值,并将这些值添加到索引中(有适当的整理转换)。
调用$SortEnd函数来完成对索引的排序过程。
如果索引已经有了值,你必须用两个参数调用%BuildIndices(),其中第二个参数的值为1,为这个参数指定1会使该方法在重建索引之前清除这些值。比如说:
SET sc = ##class(MyApp.SalesPerson).%BuildIndices(,1)
IF sc=1 {WRITE !, "Successful index build" }
ELSE {WRITE !, "Index build failed",!
DO $System.Status.DisplayError(sc) QUIT}
这就清除并重建了所有的索引。你也可以清除和重建索引的一个子集,如:
SET sc = ##class(MyApp.SalesPerson).%BuildIndices($ListBuild("NameIDX", "SSNKey"),1)
IF sc=1 {WRITE !, "Successful index build" }
ELSE {WRITE !, "Index build failed",!
DO $System.Status.DisplayError(sc) QUIT}
注意:当其他用户正在访问该表的数据时,不要重建索引。要在一个活跃系统上重建索引,请看在活跃系统上建立索引。
在活跃系统上建立索引
当在一个活跃系统上建立(或重建)索引时,有两个问题:
活跃查询可能会返回不正确的结果,除非正在建立的索引被隐藏在SELECT查询之外。在建立索引之前,可以使用SetMapSelectability()方法来处理这个问题。
在建立索引期间对数据的主动更新可能不会反映在索引条目中。这可以通过在构建索引时让构建操作锁定个别行来处理。
注意:如果一个应用程序在一个事务中对数据进行大量的更新,可能会出现锁表争夺的问题。
在一个只读活跃系统上构建索引
如果一个表目前只用于查询操作(只读),你可以在不中断查询操作的情况下建立新的索引或重建现有索引。这是通过在重建这些索引时使索引对查询优化器不可用来实现的。
如果你想建立一个或多个索引的所有类目前都是只读的,使用 "在读和写活跃系统上建立索引 "中描述的相同系列操作,但有以下区别:当你使用%BuildIndices()时,设置pLockFlag=3(共享范围锁)。
在读和写活跃系统上建立索引
如果一个持久化的类(表)目前正在使用,并且可以进行读和写访问(查询和数据修改),你可以建立新的索引或者重建现有的索引而不中断这些操作。如果你想重建一个或多个索引的类目前可以被读和写访问,建立索引的首选方法是使用表的持久化类提供的%BuildIndices()(或%BuildIndicesAsync())方法。
在并发的读和写访问中,建立一个或多个索引需要进行以下一系列操作:
使你希望建立的索引对查询不可用(READ访问)。这是用SetMapSelectability()完成的。这使得该索引不能被查询优化器使用。这个操作应该在重建一个现有的索引和创建一个新的索引时执行。比如说
SET status=$SYSTEM.SQL.Util.SetMapSelectability("Sample.MyStudents", "StudentNameIDX",0)
第一个参数是Schema.Table名称,即SqlTableName,而不是持久化类的名称。例如,默认的Schema是SQLUser,而不是User。这个值是区分大小写的。 第二个参数是SQL索引图名称。这通常是索引的名称,指的是索引存储在磁盘上的名称。对于一个新的索引,这是你在创建索引时要使用的名称。这个值是不区分大小写的。 第三个参数是MapSelectability标志,其中0定义索引图为不可选择(关闭),1定义索引图为可选择(打开)。指定为0。 你可以通过调用GetMapSelectability()方法来确定一个索引是否是不可选择的。如果你已经明确地将一个索引标记为不可选择,这个方法返回0。在所有其他情况下,它返回1;它不对表或索引的存在进行验证检查。注意,Schema.Table名称是SqlTableName,并且区分大小写。 SetMapSelectability()和GetMapSelectability()仅适用于当前命名空间的索引映射。如果该表被映射到多个命名空间,并且需要在每个命名空间建立索引,则应在每个命名空间调用SetMapSelectability()。
在索引建立的过程中建立并发操作。
对于一个新的索引 在类中创建索引定义(或者在类的%Storage.SQL中创建新的SQL索引映射规范),编译该类。对于一个新的索引,这是合适的,因为索引还没有被填充。在可以对表进行查询之前,需要对范围索引进行填充。
对于一个现有的索引。清除任何引用该表的缓存查询。索引构建所执行的第一个操作是杀死索引。因此,当索引被重建时,你不能依靠任何被优化的代码来使用该索引。
//删除当前命名空间的所有缓存查询
PURGE CACHED QUERIES
//删除当前命名空间n天内的所有缓存查询
PURGE CACHED QUERIES BY AGE n
Do $SYSTEM.SQL.Purge(n)
//删除某个表的缓存查询
PURGE CACHED QUERIES BY TABLE table-name
或者
Do $SYSTEM.SQL.PurgeForTable("MedLab.Patient")
//删除当类的缓存查询
PURGE [CACHED] QUERIES BY NAME class-name
使用你的持久化类(表)的%BuildIndices()方法,使用pLockFlag=2(行级锁)来建立索引。pLockFlag=2标志在重建过程中对个别行建立了一个排他性的写锁,这样并发的数据修改操作就可以与构建索引操作相协调。 默认情况下,%BuildIndices()会构建所有为持久化类定义的索引;你可以使用pIgnoreIndexList来排除重建索引。 默认情况下,%BuildIndices()为所有ID建立索引条目。然而,你可以你可以使用pStartID和pEndID来定义一个ID的范围。%BuildIndices()将只为该范围内的ID建立索引条目。例如,如果你使用带有%NOINDEX限制的INSERT将一系列新记录添加到表中,你可以随后使用带有ID范围的%BuildIndices()来为这些新记录建立索引条目。 %BuildIndices()返回一个%Status值。如果%BuildIndices()由于检索数据的问题而失败,系统会产生一个SQLCODE错误和一个消息(%msg),其中包括遇到错误的%ROWID。
一旦你完成了建立索引,请启用查询优化器的MapSelectability。设置第三个参数,MapSelectability标志为1,如下面的例子所示。
SET status=$SYSTEM.SQL.Util.SetMapSelectability("Sample.MyStudents", "StudentNameIDX",1)
再一次,清除任何引用该表的缓存查询。这将消除在此过程中创建的无法使用索引的缓存查询,因此,与使用索引的相同查询相比,缓存查询的效果较差。
这样就完成了这个过程。索引被完全填充,并且查询优化器能够考虑该索引。
注意:%BuildIndices()只能用于为有正整数ID值的表重建索引。如果父表有正的整数ID值,你也可以使用%BuildIndices()来重建子表的索引。对于其他表,使用%ValidateIndices()方法。因为%ValidateIndices()是建立索引的最慢的方法,所以只有在没有其他选择的情况下才应该使用它。
验证索引
你可以使用以下任一方法来验证索引:
$SYSTEM.OBJ.ValidateIndices()验证一个表的索引,同时也验证该表的集合子表的任何索引。
%Library.Storage.%ValidateIndices()验证一个表的索引。集合子表的索引必须用单独的%ValidateIndices()调用进行验证。
这两种方法都检查指定表的一个或多个索引的数据完整性,并可选择纠正发现的任何索引完整性问题。它们分两步进行索引验证。
确认为表(类)中的每一行(对象)正确定义了一个索引实体。
遍历每个索引,对于每个被索引的条目,确保在表(类)中有一个值和匹配的条目。
如果任何一种方法发现不一致的地方,它可以选择性地纠正索引结构和内容。它可以验证并选择性地纠正标准索引、位图索引、位图范围索引和位片索引。默认情况下,这两个方法都验证索引,但不纠正索引。
只有在遵循以下条件的情况下,%ValidateIndices()才能用于纠正(建立)读和写活跃系统中的索引:
SetMapSelectability()被使用,如上所述;%ValidateIndices()参数必须包括autoCorrect=1和lockOption>0。 因为%ValidateIndices()的速度明显较慢,%BuildIndices()是在活跃系统上建立索引的首选方法。
%ValidateIndices()通常从终端运行。它显示输出到当前设备。这个方法可以应用于指定的%List索引名称,或者为指定表(类)定义的所有索引。它只对那些起源于指定类的索引进行操作;如果一个索引起源于一个超类,该索引可以通过在超类上调用%ValidateIndices()进行验证。
只读类不支持%ValidateIndices()。
%ValidateIndices()被支持用于分片类和分片主类表(Sharded=1)。你可以调用%ValidateIndices,要么直接作为一个类方法,要么从$SYSTEM.OBJ.ValidateIndices上调用分片主类。然后在每个分片上的分片本地类上执行索引验证,并将结果返回给分片主类上的调用者。当在分片类上使用%ValidateIndices()时,verbose标志被强制为0,没有输出到当前设备。任何发现/纠正的问题都会在byreference errors()数组中返回。
下面的例子使用%ValidateIndices()来验证和纠正Sample.Person表的所有索引:
SET status=##class(Sample.Person).%ValidateIndices("",1,2,1)
IF status=1 {WRITE !, "Successful index validation/correction" }
ELSE {WRITE !, "Index validation/correction failed",!
DO $System.Status.DisplayError(status) QUIT}。
第一个参数("")指定要验证所有的索引; 第二个参数(1)指定要修正索引差异; 第三个参数(2)指定要对整个表进行独占锁定; 第四个参数(1)指定使用多个进程(如果有的话)来执行验证。该方法返回一个%Status值。
通过名称验证索引:
SET IndList=$LISTBUILD("NameIDX", "SSNKey")
SET status=##class(Sample.Person).%ValidateIndices(IndList,1,2,1)
IF status=1 {WRITE !, "Successful index validation/correction" }
ELSE {WRITE !, "Index validation/correction failed",!
DO $System.Status.DisplayError(status) QUIT}。
%ValidateIndices()的第一个参数或$SYSTEM.OBJ.ValidateIndices()的第二个参数指定哪些索引将作为%List结构被验证。不管第一个参数的值如何,IdKey索引总是被验证的。你可以通过指定一个空字符串值("")来验证表的所有索引。你可以通过指定一个列表结构来验证该表的单个索引。下面的例子验证了IdKey索引和两个指定的索引。NameIDX和SSNKey。
对于这两种方法,如果索引列表包含一个不存在的索引名称,该方法不执行索引验证,并返回%Status错误。如果索引列表中包含一个重复的有效索引名称,该方法将验证指定的索引,忽略重复的索引,不发出错误。
索引信息查询
INFORMATION.SCHEMA.INDEXES持久化类显示当前命名空间中所有列索引的信息。它为每个被索引的列返回一条记录。它提供了一些索引的属性,包括索引的名称,表的名称,以及索引所对应的列的名称。每条列记录还提供了该列在索引图中的序号位置;除非索引映射到多个列,否则这个值是1。它还提供了布尔属性PRIMARYKEY和NONUNIQUE(0=索引值必须是唯一的)。
SELECT Index_Name,Table_Schema,Table_Name,Column_Name,Ordinal_Position,
Primary_Key,Non_Unique
FROM INFORMATION_SCHEMA.INDEXES WHERE NOT Table_Schema %STARTSWITH '%'
你可以使用管理界面的目录详情的映射/索引选项列出所选表的索引。这将为每个索引显示一行,并显INFORMATION.SCHEMA.INDEXES没有提供的索引信息。
使用索引对一个对象进行open、exist和delete的方法
InterSystems IRIS的索引支持以下操作:
通过索引键打开一个实例
检查一个实例是否存在
删除一个实例
通过索引key打开一个实例
对于ID键、主键或唯一索引,indexnameOpen()方法(其中indexname是索引的名称)允许你打开其索引属性值与所提供的值一致的对象。
例如,假设一个类包括下面的索引定义。
Index SSNKey On SSN [ Unique ] 。
那么,如果被引用的对象已经被存储到磁盘,并且有一个唯一的ID值,你可以按以下方式调用该方法。
SET person = ##class(Sample.Person).SSNKeyOpen("111-22-3333",2,.sc)
第一个参数对应于索引中的属性。
第二个参数指定要打开对象的并发值(这里是2 - 共享锁)。
第三个参数可以接受%Status代码,以防该方法无法打开一个实例,如果找到一个匹配的实例,该方法会返回一个OREF。如果该方法没有找到一个与所提供的值相匹配的对象,那么一个错误信息将被写入状态参数sc中。
这个方法被实现为%Compiler.Type.Index.Open()方法;这个方法类似于%Persistent.Open()和%Persistent.OpenId()方法,除了它使用索引定义中的属性而不是OID或ID参数。
检查一个对象是否存在
indexnameExists()方法(其中indexname是索引的名称)检查是否存在一个具有该方法的参数所指定的索引属性值的实例。该方法有一个参数对应于索引中的每个属性;它的最后一个可选参数可以接收对象的ID,如果有一个与提供的值相匹配的话。该方法返回一个布尔值,表示成功(1)或失败(0)。这个方法被实现为%Compiler.Type.Index.Exists()方法。
例如,假设一个类包括下面的索引定义。
Index SSNKey On SSN [ Unique ];
那么,如果被引用的对象已经被存储到磁盘,并且有一个唯一的ID值,你可以按以下方式调用该方法。
SET success = ##class(Sample.Person).SSNKeyExists("111-22-3333",.id)
成功完成后,success等于1,id包含与找到的对象匹配的ID。
该方法返回所有索引的值,除了。
位图索引,或位图范围索引。
当索引包括(ELEMENTS)或(KEYS)表达式时。
删除一个对象
indexnameDelete()方法(其中indexname是索引的名称)是为了与Unique、PrimaryKey和/或IdKey索引一起使用;它删除其键值与提供的键属性/列值相匹配的实例。有一个可选的参数,你可以用它来指定该操作的并发设置。该方法返回一个%Status代码。它被实现为%Compiler.Type.Index.Delete()方法。
文章
Michael Lei · 六月 7, 2022
我今天遇到了一个有趣的ObjectScript用例,有一个通用的解决方案,我想与大家分享。
用例:
我有一个JSON数组(在本例中具体而言是一个来自Jira的问题数组),我想在几个字段上进行聚合--例如,类别、优先级和问题类型。然后,我想把聚合的数据编平化到一个简单的列表中,其中包含每个组的总数。当然,对于聚合来说,使用一个本地数组的形式是有意义的,即:
agg(category, priority, type) = total
这样,对于输入数组中的每一条记录,我可以只用:
Do $increment(agg(category, priority, type))
但是一旦我做了聚合,我想把它变成一种更容易迭代的形式,比如一个整数下标的数组:
summary = n
summary(1) = $listbuild(total1, category1, priority1, type1)
...
summary(n) = $listbuild(totalN, categoryN, priorityN, typeN)
基础解决方案:
简单的方法是,用$Order嵌套三个For循环--比如说:
Set category = ""
For {
Set category = $Order(agg(category))
Quit:category=""
Set priority = ""
For {
Set priority = $Order(agg(category,priority))
Quit:priority=""
Set type = ""
For {
Set type = $Order(agg(category,priority,type),1,total)
Quit:type=""
Set summary($i(summary)) = $listbuild(total,category,priority,type)
}
}
}
这就是我开始使用的方法,但它有很多代码,而且如果我有更多的维度来聚合,它很快就会变得不方便了。这让我想知道--是否有一个通用的解决方案来完成同样的事情?事实证明是有的!
使用 $Query的优化方案:
我决定使用$query会有帮助。请注意,这个解决方案假定整个本地数组中子标/值的深度是一致的;如果没有了这个假设,就会导致奇怪的结果.
ClassMethod Flatten(ByRef deep, Output flat) [ PublicList = deep ]
{
Set reference = "deep"
For {
Set reference = $query(@reference)
Quit:reference=""
Set value = $listbuild(@reference)
For i=1:1:$qlength(reference) {
Set value = value_$listbuild($qsubscript(reference,i))
}
Set flat($i(flat)) = value
}
}
所以以上片段被替换为:
Do ..Flatten(.agg,.summary)
几点需要注意的:
deep 需要在PublicList中,以便$query能够对其进行操作
在每个迭代中,reference 被改变为引用deep中下一组有价值的子标号--例如,值可能是:deep("foo", "bar")
$qlength 返回reference中子标的数量
$qsubscript 返回reference中第i个下标的值
当$listbuild列表被串联起来时,一个有效的$listbuild列表与合并后的列表就得出结果(这比使用任何其他分隔符都要好!)
总结
$query, $qlength, 和 $qsubscript 在处理任意深度的Global/Local数组时非常方便。
进阶阅读
$Query: https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_FQUERY$QSubscript: https://docs.intersystems.com/irisforhealthlatest/csp/docbook/Doc.View.cls?KEY=RCOS_fqsubscript$QLength: https://docs.intersystems.com/irisforhealthlatest/csp/docbook/Doc.View.cls?KEY=RCOS_fqlength
文章
Jingwei Wang · 七月 14, 2022
本文概述了InterSystems SQL的特点,特别是那些没有被SQL标准所涵盖或与InterSystems IRIS 数据平台的统一数据架构有关的特点。假定你已有SQL的知识,本文不是SQL概念或语法的介绍。
表
在InterSystems SQL中,数据是在表内呈现的。每个表都被定义为包含若干列。一个表可以包含零个或多个数据值的行。以下术语大致上是等同的。
数据
关系型数据库术语
InterSystems SQL术语
InterSystems IRIS术语
database
schema
schema
package
database
table
table
persistent class
field
column
column
property
record
row
row
schema
SQL schema提供了一种将相关表、视图、存储过程和缓存查询分组的方法。模式的使用有助于防止在表一级的命名冲突,因为一个表、视图或存储过程的名称必须只在其schema内是唯一的。一个应用程序可以在多个schema中指定表。
SQL schema对应于IRIS中持久化的类包。通常情况下,一个模式的名称与它对应的包的名称相同,但是这些名称可能会因为不同的模式命名惯例而不同,或者因为不同的名称被故意指定。
命名:schema.name。如果没有指定schema:例如只有name。InterSystems IRIS会按以下方式提供模式。
对于DDL操作,InterSystems IRIS使用全系统默认的schema名称SQLUser。这个默认是可配置的。它适用于所有命名空间。
对于DML操作,InterSystems IRIS可以使用用户提供的schema搜索路径或全系统默认的schema名称。在动态SQL、嵌入式SQL和SQL Shell中,使用不同的技术来提供schema搜索路径。
在管理门户中查看一个命名空间内的所有现有schema:
从管理门户中选择系统资源管理器,然后选择SQL。用页面顶部的切换选项选择一个命名空间;这将显示可用的命名空间的列表。选择一个命名空间。选择屏幕左边的模式下拉列表。这将显示当前命名空间中的schema列表。从这个列表中选择一个schema;
SQL 查询
查询有两种类型:检索数据的查询(SELECT语句)和修改数据的查询(INSERT、UPDATE和DELETE语句)。
你可以以多种方式使用SQL查询:
在ObjectScript中使用嵌入式SQL。
在ObjectScript中使用动态SQL。
调用使用CREATE PROCEDURE或CREATE QUERY创建的存储过程。
使用一个类查询。
使用来自其他环境的ODBC或JDBC接口。
SELECT查询在本指南的查询数据库一章中有描述。
查询是InterSystems IRIS对象或ObjectScript程序的一部分。
Privileges 特权
InterSystems SQL提供了一种方法,通过权限来限制对表、视图等的访问。你可以定义一组用户和角色,并授予他们各种权限(读、写等等)。
数据显示 SelectMode
InterSystems SQL使用SelectMode选项来指定数据的显示或存储方式。可用的选项有逻辑模式、显示模式和ODBC模式。
数据在内部以逻辑模式存储,并可以以任何模式显示。每个数据类型类都可以通过使用LogicalToDisplay(), LogicalToODBC(), DisplayToLogical(), ODBCTtoLogical()方法来定义内部逻辑格式和显示格式或ODBC格式之间的转换。
当SQL SelectMode是显示模式时,LogicalToDisplay转换被应用,返回的值被格式化以用于显示。默认的SQL选择模式是逻辑模式;因此默认返回的值是以其存储格式显示的。
模式会影响查询结果集数据的显示格式,也会影响数据值的提供格式,如果提供的数据值与SelectMode不匹配,可能会导致错误或错误的结果。
例如,例如在WHERE子句中,如果DOB是一个以$HOROLOG逻辑格式存储的日期,而WHERE子句指定WHERE DOB > 2000-01-01(ODBC模式),SelectMode = ODBC返回预期的结果。但是,如果SelectMode = 显示模式,则生成SQLCODE -146 无法将日期输入转换为有效的逻辑日期值。如果SelectMode = 逻辑模式,则试图将2000-01-01解析为一个逻辑日期值,并返回0行。
对于大多数数据类型,三种SelectMode模式返回相同的结果。以下数据类型受到SelectMode选项的影响。
日期、时间和时间戳数据类型。
%List数据类型 - 在ODBC选择模式下,列表项之间用逗号分隔符显示。在显示选择模式下,列表项之间以空白分隔符显示。
指定VALUELIST和DISPLAYLIST的数据类型。对于必填字段,如果您在显示模式下,在字段有DISPLAYLIST的表中插入一个值,您输入的显示值必须与DISPLAYLIST中的一个项目完全匹配。对于非必填字段,不匹配的值被转换为NULL值。
空字符串,和空BLOB(流字段)。在逻辑模式下,空字符串和BLOBs由非显示字符$CHAR(0)表示。在显示模式下,它们由一个空字符串("")表示。
SQL的SelectMode可以按以下方式指定。
对于当前进程,使用SetOption("SelectMode")方法。
对于InterSystems SQL Shell会话,使用SET SELECTMODE命令。
对于来自管理门户 "执行查询 "用户界面(系统浏览器,SQL)的查询结果集,使用 "显示模式 "下拉列表。
对于一个动态SQL %SQL.Statement实例,使用%SelectMode属性。
对于嵌入式SQL,使用ObjectScript的 #SQLCompile Select 预处理器指令设置。这个指令允许第四个值,Runtime,它将选择模式设置为RuntimeMode属性的任何设置。RuntimeMode默认为逻辑模式。
#SQLCOMPILE SELECT=Logical
#SQLCOMPILE SELECT=Display
#SQLCOMPILE SELECT=ODBC
对于的SQL命令CREATE QUERY、CREATE METHOD、CREATE PROCEDURE和CREATE FUNCTION, 使用SELECTMODE关键字。
对于SQL查询中的单个列,使用%EXTERNAL、%INTERNAL和%ODBCOUT函数。
SELECT TOP 5 DOB,%EXTERNAL(DOB) AS ExtDOB
Data Collation
data collation规定了数值的排序和比较方式,它是InterSystems SQL和InterSystems IRIS对象的一部分。
你可以指定collation类型作为字段/属性定义的一部分。除非另有规定,否则字符串字段/属性默认为命名空间的默认排序。默认情况下,字符串的命名空间默认排序是SQLUPPER。SQLUPPER排序法将字符串转换为大写字母,以便进行排序和比较。因此,除非另有规定,否则字符串的排序和比较是不分大小写的。
你可以指定一个collation类型作为索引定义的一部分,或者使用被索引字段的collation类型。
一个SQL查询可以通过对字段名应用一个collation函数来覆盖已定义的collation类型的字段/属性。ORDER BY子句指定了一个查询的结果集顺序;如果一个指定的字符串字段被定义为SQLUPPER,查询结果顺序是不区分大小写的。
SQL 执行
编写和执行SQL代码的方法包括:
嵌入式SQL:在ObjectScript代码中嵌入SQL代码。
动态SQL:从ObjectScript中执行SQL代码,使用%SQL.Statement类。
Execute()方法:使用%SYSTEM.SQL类的Execute()方法执行SQL代码。
包含SQL代码的存储过程:使用CREATE PROCEDURE或CREATE QUERY创建。
SQL Shell:从终端界面执行的SQL语句。
从管理门户执行的SQL语句:系统资源管理器 -> SQL。
使用InterSystems IRIS对象(类和方法):
定义一个持久的类(一个SQL表)。
定义一个索引。
定义和使用一个类查询。
文章
Jingwei Wang · 七月 14, 2022
创建表
可以通过以下方式定义表:
通过DDL定义表
使用任意数据库管理工具执行DDL(使用ODBC,JDBC连接)
MyApp.Person表可以使用DDL CREATE TABLE语句来定义,指定SQL schema.table名称。成功执行这个SQL语句会生成一个相应的持久化类,包名MyApp,类名Person。当使用DDL命令定义一个表时,你不需要指定USEEXTENTSET或创建一个位图范围索引。InterSystems SQL会自动应用这些设置,并将它们包含在预测的持久化类中。默认情况下,CREATE TABLE在相应的类定义中指定了Final类的关键字,表示它不能有子类。
CREATE TABLE MyApp.Person (
Name VARCHAR(50) NOT NULL,
SSN VARCHAR(15) DEFAULT 'Unknown',
DateOfBirth DATE,
Sex VARCHAR(1)
)
使用Objectscript执行DDL
在ObjectScript中使用 Embedded SQL.
ClassMethod CreateTable() As %String
{
&sql(CREATE TABLE Sample.Employee (
EMPNUM INT NOT NULL,
NAMELAST CHAR (30) NOT NULL,
NAMEFIRST CHAR (30) NOT NULL,
STARTDATE TIMESTAMP,
SALARY MONEY,
ACCRUEDVACATION INT,
ACCRUEDSICKLEAVE INT,
CONSTRAINT EMPLOYEEPK PRIMARY KEY (EMPNUM)))
IF SQLCODE=0{ WRITE "Table created" RETURN "Success"}
ELSEIF SQLCODE=-201 { WRITE "Table already exists" RETURN SQLCODE}
ELSE { WRITE "Serious SQL Error, returing SQLCODE " RETURN SQLCODE_" "_%msg}
}
这个方法试图创建一个Sample.Employee表(以及相应的Sample.Employee类)。如果成功,SQLCODE变量被设置为0;如果不成功,SQLCODE包含一个SQL错误代码,表明失败的原因。 像这样的DDL命令,最常见的失败原因是: SQLCODE -99(特权侵犯)。这个错误表明你没有权限执行所需的DDL命令。一般来说,这是因为应用程序没有确定谁是当前用户。你可以使用$SYSTEM.Security.Login()方法以编程方式完成这个任务。
DO $SYSTEM.Security.Login(username,password)
使用 Dynamic SQL.
Class Sample.NewT
{
ClassMethod DefTable(user As %String, pws As %String) [Language = objectscript]
{
Do ##class(%SYSTEM.Security).Login(user,pws)
SET myddl = 2
SET myddl(1)="CREATE TABLE Sample.MyTest "
SET myddl(2)="(NAME VARCHAR(30) NOT NULL,SSN VARCHAR(15) NOT NULL)"
SET tStatement = ##class(%SQL.Statement).%New()
SET qStatus = tStatement.%Prepare(.myddl)
IF qStatus '= 1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT }
SET rset = tStatement.%Execute()
IF rset.%SQLCODE =0 {WRITE "Created a table"}
ELSEIF rset.%SQLCODE =-201 {WRITE "table already exists"}
ELSE {WRITE "Unexpected error SQLCODE=",rset.%SQLCODE}
}
}
批量执行DDL脚本文件
使用$SYSTEM.SQL.Schema.Run()方法从终端会话中交互式地导入InterSystems SQL DDL脚本文件
使用$SYSTEM.SQL.Schema.ImportDDL("IRIS")方法作为后台作业。这个方法可以导入和执行多个SQL命令,使你能够使用一个txt脚本文件来定义表和视图,并为它们填充数据。
针对特定供应商的%SYSTEM.SQL.Schema Load方法。特定供应商的SQL被转换为InterSystems的SQL并执行。错误和不支持的功能被记录在日志文件中。例如下面的Oracle示例。
SET $namespace = "MYNAMESPACE"
DO $SYSTEM.SQL.Schema.LoadOracle()
通过持久化类定义表 当编译时,这个持久化类会自动投射到一个与类定义相对应的关系表中:每个类代表一个表;每个属性代表一个列,以此类推。这个定义在MyApp schema中创建了MyApp.Person持久化类和相应的SQL表Person。持久类的名称Person被用作SQL表的名称。要提供一个不同的SQL表名,你可以使用SqlTableName类的关键字。
Class MyApp.Person Extends %Persistent
{
Parameter USEEXTENTSET = 1; //USEEXTENTSET 类参数被定义并设置为1,这个参数将表的存储组织成一个更有效的globals集合。
Property Name As %String(MAXLEN=50) [Required];
Property SSN As %String(MAXLEN=15) [InitialExpression = "Unknown"];
Property DateOfBirth As %Date;
Property Sex As %String(MAXLEN=1);
Index BitmapExtent [Extent, Type = bitmap ] //位图范围索引为范围集中的所有ID创建一个索引。这种设置使得计数和其他操作更加有效。
}
使用持久化类定义在编译时可以创建的相应的表,但是这个表定义不能使用SQL DDL命令进行修改或删除(或者使用管理门户的Drop操作),这些命令会给你提示"'schema.name'类不启用DDL...")。你必须在表类定义中指定[DdlAllowed]以允许这些操作。
Class MyApp.Person Extends %Persistent [DdlAllowed]
根据现有的表(或表或视图)来定义和填充一个新的表。 你指定一个查询和一个新的表名。现有的表名和/或新的表名可以是限定的或非限定的。查询可以包含JOIN语法。查询可以提供列名别名,成为新表中的列名。 可以使用:
CREATE TABLE AS SELECT命令
CREATE TABLE Sample.YoungPeople
AS SELECT Name,Age
FROM Sample.People
WHERE Age < 21
$SYSTEM.SQL.Schema.QueryToTable()方法执行。
DO $SYSTEM.SQL.Schema.QueryToTable("SELECT Name,Age,AVG(Age) AS AvgInit FROM Sample.Person WHERE Age < 21","Sample.Youth",1,.errors)
QueryToTable()复制了现有表的DDL定义,并将其指定为新表的名称。它复制查询中指定的字段的定义,包括数据类型、最大长度和最小值/最大值,但不复制字段的数据约束,如默认值、要求值或唯一值。它不会从一个字段复制引用到另一个表中。 如果查询指定SELECT *或SELECT %ID,原始表的RowID字段将被复制为数据类型为整数的非必填、非唯一的数据字段。QueryToTable()为新表生成了一个唯一的RowID字段。如果复制的RowID被命名为ID,生成的RowID被命名为ID1。
QueryToTable()为这个新表创建一个相应的持久化类。该持久化类被定义为DdlAllowed。新表的所有者是当前用户。 新表被定义为默认存储=YES和支持位图索引=YES,不管源表中的这些设置如何。为新表创建的唯一索引是IDKEY索引,没有生成位图范围索引。被复制的字段的索引定义不会被复制到新表中。
$SYSTEM.SQL.Schema.TableExists()方法可以用来确定一个表的名字是否已经存在。
RowID
在SQL中,每条记录都由一个唯一的整数值来标识,称为RowID。
在InterSystems SQL中,你不需要指定一个RowID字段。当你创建一个表并指定所需的数据字段时,会自动创建一个RowID字段。这个RowID在内部使用,但没有被映射到一个类属性。默认情况下,只有当一个持久化类被投射到一个SQL表时,它的存在才是可见的。在这个投射表中,会出现一个额外的RowID字段。
默认情况下,这个字段被命名为 "ID "并被分配到第1列,且这个字段是自增的,不会被重复使用。因此,如果记录被插入和删除,RowID值将是升序的数字序列,但可能不是数字连续的。且ALTER TABLE不能修改或删除RowID字段的定义。RowID计数器可以通过TRUNCATE TABLE命令被重置,但它不会被DELETE命令重置,即使DELETE命令删除了表中的所有记录。如果没有数据被插入到表中,或者TRUNCATE TABLE被用来删除所有表的数据,IdLocation存储关键字的global是未定义的。
默认情况下,InterSystems IRIS将这个字段命名为 "ID",然而这个字段名并不保留。RowID字段名在每次编译表的时候都会重新建立。如果用户定义了一个名为 "ID "的字段,当表被编译时,InterSystems IRIS将RowID命名为 "ID1"。例如,如果用户随后使用ALTER TABLE来定义一个名为 "ID1 "的字段,表的编译就会将RowID重命名为 "ID2",以此类推。在一个持久化的类定义中,你可以使用SqlRowIdName类关键字来直接指定这个类所投射的表的RowID字段名。由于这些原因,应该避免用名字来引用RowID字段。
InterSystems SQL提供了%ID伪列名(别名),它总是返回RowID值,不管分配给RowID的字段名是什么。(InterSystems TSQL提供了$IDENTITY伪列名,它也做同样的事情)。
默认情况下,使用CREATE TABLE定义的表使用$SEQUENCE执行ID分配,允许多个进程同时快速填充表。当$SEQUENCE被用来填充表时,一个RowID值的序列被分配给一个进程,然后按顺序分配它们。因为并发的进程使用他们自己分配的序列来分配RowID,所以不能假设由一个以上的进程插入的记录是按照插入的顺序进行的。
当使用CREATE TABLE创建一个表时,RowID默认是隐藏的。一个隐藏的字段不会被SELECT *显示,并且是PRIVATE。当你创建一个表的时候,你可以指定%PUBLICROWID关键字来使RowID不被隐藏并且是公开的。这个可选的%PUBLICROWID关键字可以在CREATE TABLE逗号分隔的表元素列表中的任何地方指定,但不能在ALTER TABLE中指定。
当创建一个投射为表的持久化类时,RowID默认不会被隐藏。它通过SELECT *显示,并且是公共的。你可以通过指定类的关键字SqlRowIdPrivate来定义一个RowID为隐藏和PRIVATE的持久化类。
作为外键引用的RowID必须是公共的。默认情况下,一个具有公共RowID的表不能被用作源表或目标表,例如使用 INSERT INTO Sample.DupTable SELECT * FROM Sample.SrcTable 将数据复制到一个重复的表。
你可以使用管理门户SQL界面来查看RowID是否被隐藏。
基于其他字段的RowID
通过定义一个投射表的持久化类,你可以将RowID定义为来自一个字段或一个字段组合的值。要做到这一点,用IdKey索引关键字指定一个索引。例如,通过PatientName字段的值指定索引定义
IdxId On PatientName [IdKey];
或者通过PatientName和SSN字段的合并值指定索引定义
IdxId On (PatientName,SSN) [IdKey]
但是,基于字段的RowID比采取系统分配的连续正整数的RowId效率低。
在INSERT中:为构成RowId的字段或字段组合指定的值必须是唯一的。指定一个非唯一的值会产生一个SQLCODE -119 "UNIQUE或PRIMARY KEY约束在INSERT时唯一性检查失败"。
在UPDATE中:默认情况下,组成RowId的每个字段的值是不可修改的。试图修改这些字段之一的值会产生一个SQLCODE -107 "不能UPDATE RowID或基于字段的RowID"。
当一个RowID基于多个字段时,RowID的值是由||操作符连接的每个组成字段的值。例如,Ross,Betsy||123-45-6789。InterSystems IRIS试图确定基于多个字段的RowID的最大长度;如果它不能确定最大长度,RowID长度默认为512。
主键
InterSystems IRIS提供两种方法来唯一地识别表中的行:RowID和主键。
可选的主键是一个有意义的值,应用程序可以用它来唯一地识别表中的一行(例如在连接中)。一个主键可以是用户指定的数据字段,也可以是一个以上的数据字段的组合。主键值必须是唯一的,但不要求是整数值。RowID是一个内部使用的整数值,用于识别表中的一行。通常情况下,主键是一个由应用程序生成的值,而RowID是一个由InterSystems IRIS生成的唯一整数值。
系统会自动创建一个master map来访问使用RowID字段的数据行。如果你定义了一个主键字段,系统会自动创建并维护一个主键索引。
显然,有两个不同的字段和索引来识别行,这种双重性不一定是好事。你可以通过以下两种方式中的任何一种解决单一的行标识符和索引:
使用应用程序生成的主键值作为IDKEY。你可以通过在类定义中使用关键字PrimaryKey和IdKey来识别主键索引,当然,也可以从DDL中这样做。这使得主键索引成为表的主映射。因此,主键将被用作行的主要内部地址。如果主键由一个以上的字段组成,或者主键值不是整数,这可能会降低效率。
不要使用应用程序生成的主键值,而是使用应用程序内系统生成的RowID整数作为应用程序使用的主键(例如在连接中)。这样做的好处是,整数的RowID适合于更有效的处理,包括使用位图索引。
根据应用程序的性质,你可能希望解决一个单一的行标识符和索引,或者为应用程序生成的主键和系统生成的RowID设置单独的索引。
特殊字段:RowVersion字段 , 自增字段, 和 Serial 计数器字段
InterSystems SQL支持三种特殊用途的数据类型,用于自动递增计数器的值。这三种数据类型都是扩展%Library.BigInt数据类型类的子类。
%Library.RowVersion: 计算对所有RowVersion表的插入和更新的命名空间。只有包含ROWVERSION字段的表的插入和更新才会增加这个计数器。ROWVERSION值是唯一的,不可修改的。这个全命名空间的计数器从不重置。可以通过指定一个数据类型为ROWVERSION(%Library.RowVersion)的字段来创建一个RowVersion字段。你只能在每个表中指定一个ROWVERSION数据类型字段。试图创建一个有一个以上ROWVERSION字段的表会导致5320编译错误。RowVersion字段不应该被包含在唯一键或主键中。RowVersion字段不能成为IDKey索引的一部分。
%Library.Counter(也被称为SERIAL计数器字段)。计算插入到表中的次数。默认情况下,这个字段接收一个自动递增的整数。然而,用户可以为这个字段指定一个非零的整数值。用户可以指定一个重复的值。如果用户提供的值大于系统提供的最高值,自动递增计数器被设置为从用户指定的值开始递增。这个计数器通过TRUNCATE TABLE命令被重置为1。它不会被DELETE命令重置,即使DELETE命令删除了表中的所有记录。分片的表不能有SERIAL计数器字段。
%Library.AutoIncrement: 计算插入到表中的次数。默认情况下,这个字段接收一个自动递增的整数。然而,用户可以为这个字段指定一个非零的整数值。用户可以指定一个重复的值。指定一个用户值对自动递增计数器没有影响。
所有这三个字段和IDENTITY字段都返回AUTO_INCREMENT = YES,如下面的例子所示。
查看表定义
表信息 INFORMATION.SCHEMA.TABLES持久化类显示当前命名空间中所有表(和视图)的信息。包括模式和表名,表的所有者,以及是否可以插入新记录。TABLETYPE属性表明它是一个基础表还是一个视图。
SELECT Table_Type,Table_Schema,Table_Name,Owner FROM INFORMATION_SCHEMA.TABLES
INFORMATION.SCHEMA.CONSTRAINTTABLEUSAGE持久化类为当前命名空间中的每个表定义的每个主键(显性或隐性)、外键或唯一约束。 INFORMATION.SCHEMA.KEYCOLUMNUSAGE为当前命名空间中的每个表的每个已定义的约束。 也可以使用管理门户SQL界面中的目录详情来查看表信息。
列信息
GetColumn 方法
SET stat = ##class(%SYSTEM.SQL.Schema).GetAllColumns("MyApp.Person",.byname,.bynum)
IF stat=1
{
SET i=1
WHILE $DATA(bynum(i))
{
WRITE "name is ",bynum(i), "column is ", i, !
}
}ELSE{ WRITE "GetAllColumns() cannot locate specified table"}
生成结果:
name is ID col num is 1
name is Age col num is 2
name is Home col num is 3
name is Name col num is 4
SQL脚本 INFORMATION_SCHEMA.COLUMNS可以列出指定schema的所有列名:
SELECT TABLE_NAME,COLUMN_NAME,ORDINAL_POSITION,DATA_TYPE,CHARACTER_MAXIMUM_LENGTH,COLUMN_DEFAULT,IS_NULLABLE,UNIQUE_COLUMN,PRIMARY_KEY
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='MyApp'
或者
SELECT TOP 0 * FROM tablename.
Constraints信息
INFORMATION.SCHEMA.TABLECONSTRAINTS持久化类列出了表名,约束类型和约束名称。约束类型包括UNIQUE, PRIMARY KEY, 和 FOREIGN KEY。
SELECT Table_Schema,Table_Name,Constraint_Type,Constraint_Name FROMINFORMATION_SCHEMA.TABLE_CONSTRAINTS
INFORMATION.SCHEMA.CONSTRAINTCOLUMNUSAGE持久化类列出了表名、列名和约束名称。如果一个约束涉及到多个列,那么每一个列都会列出一个单独的项目。
SELECT Table_Schema,Table_Name,Column_Name,Constraint_Name FROMINFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE
INFORMATION.SCHEMA. REFERENTIALCONSTRAINTS持久化类列出了外键约束,包括引用表(CONSTRAINT_SCHEMA, CONSTRAINT_TABLE_NAME),被引用表(UNIQUE_CONSTRAINT_SCHEMA, UNIQUE_CONSTRAINT_TABLE),外键名称(CONSTRAINT_NAME),以及UPDATE和DELETE参考动作(UPDATE_RULE,DELETE_RULE),值为NO ACTION,SET DEFAULT,SET NULL,或CASCADE。
SELECT Constraint_Table_Name,Unique_Constraint_Table,Constraint_Name,Update_Rule,Delete_Rule FROMINFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS
连接外部表
在InterSystems SQL中,你也可以有 "外部表",即在SQL字典中定义的表,但存储在一个外部关系数据库中。外部表的作用就像它们是本地的InterSystems IRIS表一样:你可以对它们发出查询,执行INSERT、UPDATE和DELETE操作。
对外部数据库的访问是由InterSystems SQL Gateway提供的,它使用ODBC或JDBC提供透明的连接。
公告
Claire Zheng · 八月 11, 2022
Hi 社区成员们,
我们很高兴地宣告 社区网络研讨会 回归了!
我们诚邀您参加 @Ron.Sweeney1582 主讲的 使用ECP在AWS上扩展InterSystems FHIR Server 网络研讨会。
Join this webinar to make a technical deep dive, see a demonstration, and benchmark horizontal scaling InterSystems FHIR Server on Amazon Web Services with Enterprise Cache Protocol (ECP). 欢迎加入此次网络研讨会,深入了解技术,观看演示,并在AWS上使用企业缓存协议(ECP)基准测试水平扩展InterSystems FHIR Server。
⏱ 时间: 8月18日(周四)8am (美国东部时间) | 2:00pm (中欧夏季时间)| 8:00pm (北京时间)👨🏫 主讲人: @Ron.Sweeney1582, 全栈架构师,来自Integration Required
关于 Integration Required: 我们是一个针对InterSystems®工作负载的全栈技术交付团队,可根据您的安全状况和组织部署标准进行定制。凭借InterSystems®十年的合作伙伴关系和对客户满意度的严格坚持,客户对我们充满信任,我们能够实现最佳实践。
那么,不要错过这次机会!来了解更多关于扩展FHIR、ECP和AWS的知识吧!
Don't miss this opportunity to learn more about scaling FHIR, ECP and AWS and how to mix it all!
>> 点击注册 <<
文章
Michael Lei · 五月 12, 2021
部分 在上个帖子中,我们安排了使用 pButtons 进行 24 小时的性能指标收集。 在本帖中,我们将研究几个收集到的关键指标,以及它们与底层系统硬件的关系。 我们还将开始探索 Caché(或任一 InterSystems 数据平台)指标与系统指标之间的关系。 以及如何使用这些指标来了解系统的每日节拍率并诊断性能问题。
[本系列其他帖子的列表](https://cn.community.intersystems.com/post/intersystems-数据平台的容量规划和性能系列文章)
***2016 年 10 月编辑...*** *[用于将 pButtons 数据提取到 .csv 文件的脚本示例。](https://cn.community.intersystems.com/post/将-pbuttons-数据提取到-csv-文件以便绘制图表)* ***2018 年 3 月编辑...*** 图片消失,重新添加它们。
# 硬件食物群

您将会看到,随着本系列帖子的不断深入,影响性能的服务器组件可以逐项列出:
- CPU
- 内存
- 存储 IO
- 网络 IO
如果这些组件中的任何一个承受压力,那么系统性能和用户体验很可能会受到影响。 这些组件也都相互关联,对一个组件进行更改可能会影响另一个组件,有时会产生意外的后果。 我见过这样一个例子:修复某个存储阵列中的 IO 瓶颈使 CPU 的使用率跳增至 100%,导致用户体验更差,原因是系统突然可以做更多工作,但没有 CPU 资源来为增加的用户活动和吞吐量提供服务。
我们还将了解 Caché 系统活动如何直接影响服务器组件。 如果存储 IO 资源有限,那么可以做出的积极改变是增加系统内存,并增加 __Caché global 缓冲区__的内存,从而降低__系统存储读取 IO__(但可能会增加 CPU 使用率!)。
要定期监视或在用户报告问题时要检查的一个最明显的系统指标是 CPU 使用率。 在 Linux 或 AIX 中查看 _top_ 或 _nmon_,或在 Windows 中查看_性能监视器_。 由于大多数系统管理员会定期查看 CPU 数据,特别是当数据以图形方式呈现时,快速浏览一下就可以很好地了解系统当前的运行状况 - 正常情况或出现活动激增,后者可能是异常情况或表示出现问题。 在本帖中,我们将快速查看 CPU 指标,但会重点关注 Caché 指标,我们首先将查看 _mgstat_ 数据,了解以图形方式表示的数据如何让系统运行状况一目了然。
# mgstat 简介
mgstat 是 pButtons 中包含并在其中运行的一个 Caché 命令。 mgstat 是一个非常好的工具,可收集基本性能指标,帮助您了解系统运行状况。 我们将查看从 24 小时 pButtons 收集的 mgstat 数据,但是,如果您希望捕获 pButtons 之外的数据,也可以根据需要以交互方式运行 mgstat,或者从 Caché 终端将其作为后台作业运行。
要从 %SYS 命名空间按需运行 mgstat,一般格式是:
do mgstat(sample_time,number_of_samples,"/file_path/file.csv",page_length)
例如,运行一小时后台作业,采样周期为 5 秒,然后输出为 csv 文件:
job ^mgstat(5,720,"/data/mgstat_todays_date_and_time.csv")
要显示到屏幕,但丢弃几个列,则输入 dsp132。 给您留个作业,去查看一下输出以了解差异。
do dsp132^mgstat(5,720,"",60)
> mgstat 中的列的详细信息可以在最新的 Caché 文档([InterSystems 在线文档](https://docs.intersystems.com))中的 _Caché 监视指南_中找到
# 查看 mgstat 数据
pButtons 已设计为整理成一个 HTML 文件,以便导航和打包发送给 WRC 支持专家来诊断性能问题。 不过,当您自己运行 pButtons 并想以图形方式显示数据时,可以再次将其拆分为 csv 文件以处理成图形,例如可以使用 Excel、通过命令行脚本或简单的剪切和粘贴来完成。
在本帖中,我们将深入研究几个 mgstat 指标,来说明即使是快速浏览数据,也能让您感觉到系统是否运行良好,或者是否存在会影响用户体验的现有或潜在问题。
## Gloref 和 CPU
下图显示了一个以高事务处理速率运行医院应用程序的站点的数据库服务器 CPU 使用率。 注意活动的高峰期在上午,此时有许多门诊病人,而在午餐时间,活动大幅下降,在下午和晚上则逐渐消失。 此例中的数据来自于 Windows 性能监视器 _(_Total)\% Processor Time_ - 图形的形状符合工作日的情况 - 没有异常的峰值或低谷,所以对这个站点来说是正常的。 通过对您的站点执行相同分析,您可以开始获取“正常”情况的基准。 如果出现大的尖峰,尤其是延长的尖峰,可能说明出现了问题。将来会有一个帖子以 CPU 为重点。

作为参考,该数据库服务器是具有两个 E5-2670 8 核处理器的戴尔 R720,服务器的内存为 128 GB,global 缓冲区为 48 GB。
下图显示了来自 mgstat 的更多数据 — 与 CPU 图形同一天的 Gloref(Global 引用数)或数据库访问量。 Gloref 指示正在发生的工作量,代表当前工作负载;虽然 global 引用会消耗 CPU 时间,但由于 Caché 使用 global 内存缓冲池的方式,这些引用并不始终消耗物理读取等其他系统资源。

在典型的 Caché 应用程序中,Gloref 与 CPU 使用率之间有非常强的相关性。
> 这些 CPU 数据和 gloref 数据所展示出的另一方面是_减少 gloref 将降低 CPU 使用率_,从而可以在核心数较少的服务器上部署或在现有系统上进一步扩展。 还可以通过提高应用程序效率来减少 global 引用,我们将在以后的帖子中重新讨论这个概念。
## PhyRds 和 Rdratio
根据 mgstat 数据 _PhyRds_(物理读取数)和 _Rdratio_(读取比)绘制的图形的形状也可以深入了解系统性能的预期水平,帮助您制定容量计划。 我们将在以后的帖子中深入探讨 Caché 的存储 IO。
_PhyRds_ 只是从磁盘到 Caché 数据库的物理读取 IOPS,您应该看到逻辑磁盘和物理磁盘的操作系统指标中反映了相同的值。 注意,查看操作系统 IOPS 可能还会显示来自非 Caché 应用程序的 IOPS。 不考虑预期的 IOPS 就调整存储大小会导致灾难,您需要了解系统在高峰时段的 IOPS 是多少,才能制定适当的容量计划。 下图显示了午夜零点到 15:30 之间的 _PhyRds_。

注意 05:30 到 10:00 之间的读取数激增。 其他较短的峰值在 11:00 以及 14:00 之前。 您认为这些峰值是由什么引起的? 您的服务器有这些类型的峰值吗?
_Rdratio_ 更有趣一点,它是逻辑块读取数与物理块读取数的比值。 也就是内存中的 global 缓冲区(逻辑)的读取次数与磁盘的读取次数之比,后者的读取速度要慢几个数量级。 _Rdratio_ 高是好事情,但长时间降至接近于零就不好了。

注意读取数高的同时,_Rdratio_ 降至接近于零。 我曾被要求调查过这个站点,当时 IT 部门接到用户电话,报告系统长时间运行缓慢。 当我被要求查看系统时,这种情况已在几个星期内看似随机地出现。
> _**由于 pButtons 已设定为每天 24 小时运行,因此可以相对简单地回溯几周的数据,以查看与支持电话相关的高 _PhyRds_ 和低 _Rdratio_ 的模式。***
经过进一步分析后,原因追溯到一名新的轮班员工身上,他当时运行了多个报告,输入了“错误”参数,加上写得不好的查询,并且没有适当的索引,导致数据库读取数很高。 这就解释了看似随机的缓慢。 由于这些长时间运行的报告将数据读取到 global 缓冲区中,结果是交互用户的数据从物理存储中获取,而不是从内存以及用于为读取提供服务的存储中获取。
监视 _PhyRds_ 和 _Rdratio_ 将让您了解系统的节拍率,也许还可以追踪不良报告或查询。 高 _PhyRds_ 可能有合理的原因 - 也许必须在某个时间运行报告。 使用现代 64 位操作系统和具有大容量物理内存的服务器,您应该能够将生产系统上的 _PhyRds_ 降到最低。
> 如果您在系统上看到高 _PhyRds_,可以考虑以下几种策略: - 通过增加数据库 (global) 缓冲区(和系统内存)的数量来提高性能。 - 可以将长时间运行的报告或提取移出办公时间。 - 可以在单独的影子服务器或异步镜像上运行长时间运行的只读报告、批处理作业或数据提取,以最大限度地降低对交互用户的影响,并减少对 CPU 和 IOPS 等系统资源的使用。
通常,低 _PhyRds_ 是好事情,这也是我们在确定系统规模时的目标。 然而,如果您的 _PhyRds_ 较低,而用户正在抱怨性能,仍然可以进行一些检查以确保存储不是瓶颈 - 读数低的原因可能是系统无法再提供服务。 我们将在以后的帖子中进一步探讨存储。
# 总结
在本帖中,我们研究了将 pButtons 中收集的指标图形化如何让运行状况检查可以一目了然地进行。 在接下来的帖子中,我将深入探讨系统和 Caché 指标之间的关系,以及如何利用这些指标来规划未来。
文章
Michael Lei · 六月 15, 2021
本帖概述了通过为 InterSystems 数据平台(InterSystems IRIS、Caché 和 Ensemble)上的数据库磁盘创建 LVM 物理盘区 (PE) 条带来实现低延迟存储 IO 的最佳实践配置,并提供了有用链接。
一致的低延迟存储是获得最佳数据库应用程序性能的关键。 例如,对于在 Linux 上运行的应用程序,经常在数据库磁盘中使用逻辑卷管理器 (LVM) ,因为它能够扩展卷和文件系统,或者为在线备份创建快照。 对于数据库应用程序,在使用 LVM PE 条带化逻辑卷的情况下,并行写入还可提高数据 I/O 的效率,从而有助于提高大规模连续读取和写入的性能。
----
本帖重点介绍在 HCI 中使用 LVM PE 条带,也受到了社区中发布的[软件定义的数据中心 (SDDC) 和超融合基础架构 (HCI) – InterSystems 客户的重要注意事项](https://community.intersystems.com/post/software-defined-data-centers-sddc-and-hyper-converged-infrastructure-hci-%E2%80%93-important "SDDC whitepaper")白皮书的启发。 该白皮书推荐“对 Linux 虚拟机使用 LVM PE 条带化,从而将 IO 分布在多个磁盘组”以及“对于 Linux 虚拟机上的所有数据库和写入映像日志 (WIJ) 文件使用异步 IO 及 rtkaio 库”。 本帖提供了这些要求和示例的一些上下文信息。
----
__注:__
> 目前有多个超融合、融合和软件定义的供应商平台,我在本帖中不会提供每个平台的详细说明,而是以__在 VMware ESXi 和 vSAN 上运行的 Red Hat Enterprise Linux (RHEL) 7.4 上的 InterSystems IRIS 或 Caché __的配置作为示例进行说明。 不过,其他解决方案的基本过程是相似的,特别是在 InterSystems IRIS 或 Caché 和操作系统层面。 如果您不确定如何将这些说明转换到其他平台,请联系各供应商的支持人员,了解他们的最佳实践。 InterSystems 技术专家还可以直接向客户和供应商或通过社区提供建议。
还需要注意的是,本帖中关于 LVM PE 条带化的指南既适用于 HCI,也适用于“传统”存储。
----
## 是否必须使用 LVM 条带化?
对于磁盘阵列等传统存储,简短的答案是“否”。 对数据库磁盘运行 LVM 条带化卷并不是必需的,尤其是使用现代全闪存阵列的情况下;如果性能尚可,并且您没有 LVM 需求,则无需改动。
但是,如上文所述,建议在 Nutanix 和 VMware VSAN 等超聚合和存储解决方案上的数据库磁盘中使用 LVM 条带,以便在 IO 操作中可以使用更多主机节点和磁盘组。
## 为什么对数据平台使用 LVM 条带?
特别建议 HCI 上的数据库磁盘使用 LVM 条带,以降低某些架构功能的性能开销,例如减轻写入守护进程 (WD) 对数据库写入和日志写入的影响。 使用 LVM 条带将数据库突发写入分散到更多磁盘设备和多个磁盘组。 此外,本帖还将说明如何增加大规模 IO 写入映像日志 (WIJ) 的并行性,从而减少对其他 IO 的延迟影响。
> 注意:在本帖中,当我提到“磁盘”时,我指的是 NVMe、Optane、SATA 或 SAS SSD,或者任何其他闪存设备。
## vSAN 存储架构概述
HCI 存储(例如在 vSAN 上运行 ESXi 时)使用两个磁盘层:一个缓存层和一个容量层。 对于全闪存架构__(必须使用全闪存,不要使用旋转磁盘!)__,所有写入操作都在缓存层进行,随后数据最终会转移到容量层。 读取来自容量层(也可能来自缓存层上的缓存)。 HCI 集群中的每个主机都可以有一个或多个磁盘组。 在使用磁盘组的情况下(例如使用 VSAN 时),每个磁盘组都由一个缓存磁盘和多个容量磁盘组成。 例如,缓存磁盘是单个 NVMe 磁盘,容量磁盘是三个或更多写密集型 SAS SSD 磁盘。
有关 HCI(包括 vSAN 磁盘组)的更多详细信息,请参见社区上的帖子“[超融合基础架构 (HCI)](https://community.intersystems.com/post/intersystems-data-platforms-and-performance-%E2%80%93-part-8-hyper-converged-infrastructure-capacity "HCI")”或联系您的 HCI 供应商。
## LVM 条带化逻辑卷概述
[Red Hat 支持](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/logical_volume_manager_administration/lv_overview "Red Hat support")网站上提供了很好的 Linux LVM 概述,[其他地方,例如这里的面向系统管理员的教程也非常好](https://sysadmincasts.com/episodes/27-lvm-linear-vs-striped-logical-volumes "Striped")。
## 数据平台存储 IO
了解 InterSystems 数据平台生成的 IO 类型很重要。 [社区中提供了存储 IO 模式](https://community.intersystems.com/post/data-platforms-and-performance-part-6-cach%C3%A9-storage-io-profile "Storage IO")的概述。
----
# 创建 LVM PE 条带的过程
## 先决条件和步骤
在我们深入讨论该过程之前,您还应该记住,其他变数也可能影响存储性能。 仅创建 LVM 条带并不能保证实现最佳性能,还必须考虑存储类型,以及整个 IO 路径,包括 IO 队列和队列深度。
本示例适用于 VMware,您还应该阅读 [InterSystems IRIS VMware 最佳实践指南](https://community.intersystems.com/post/intersystems-data-platforms-and-performance-%E2%80%93-part-9-intersystems-iris-vmware-best-practice "VMware best practice guide"),并应用其中的建议。 尤其是存储方面的注意事项,例如跨 PVSCSI 控制器分离存储 IO 类型。
### 概述
以下示例展示了在 VMware ESXi 和 VSAN 6.7 上运行的 Red Hat Enterprise Linux (RHEL) 7.4 上使用 InterSystems IRIS 或 Caché 的最佳实践。
下文介绍以下步骤:
1. ESXi 配置
2. RHEL 配置
3. Caché/InterSystems IRIS 配置
----
## 1. ESXi 配置
### a) 创建 VMDK 磁盘
必须按照 [InterSystems IRIS VMware 最佳实践指南](https://community.intersystems.com/post/intersystems-data-platforms-and-performance-%E2%80%93-part-9-intersystems-iris-vmware-best-practice "VMware best practice guide")创建磁盘;数据库、日志和 WIJ 在不同的 PVSCI 设备上。
创建的 VMDK 数量取决于您的规模调整要求。 在本示例中,数据库文件系统将由四个 255 GB VMDK 磁盘组成,这些磁盘将一起条带化,为数据库文件系统创建一个 900GB 逻辑磁盘。
#### 步骤:
1. 在添加 VMDK 前关闭虚拟机。
2. 在 vCenter 控制台中创建多个磁盘 (VMDK),每个磁盘为 255GB,单个 LVM 条带中的所有磁盘都必须与同一个 PVSCSI 控制器关联。
3. 启动虚拟机。 在启动过程中,将在操作系统中创建新磁盘,例如 `/dev/sdi` 等。
> __为什么创建多个 255 GB VMDK?__在 vSAN 中,存储组件以 256 GB 区块为单位创建,我们将 VMDK 大小保持在恰好低于 256 GB,是为了强制使组件位于不同的磁盘组上。 从而实施另一个层面的条带化(在我的测试中是这样,但我不保证 vSAN 实际也是如此)。
注意:在创建过程中,vSAN 将磁盘组件分布到所有主机和磁盘组,以确保可用性。 例如,在允许的故障数 (FTT) 设置为 2 的情况下,每个磁盘组件有三个副本,加上两个小的见证组件,全部都在不同的主机上。 如果磁盘组、主机或网络发生故障,应用程序将使用其余磁盘组件继续运行,而不会丢失数据。 我们对这个过程可能多虑了! 在 vSAN 等 HCI 解决方案中,无法控制组成 VMDK 的组件在某个时间点位于哪个物理磁盘上。 事实上,由于维护、重新同步或重建的原因,随着时间的推移,VMDK 可能会移动到不同的磁盘组或主机上。 这是正常的。
----
## 2. RHEL 配置
### a) 确认对于每个磁盘设备,RHEL IO 调度器都为 NOOP。
最佳实践是使用 ESXi 内核的调度器。 有关设置调度器的更多信息,请参见 [Red Hat 知识库文章](https://access.redhat.com/solutions/109223 "Setting noop")。 我们建议在启动时为所有设备设置该选项。 要验证是否已正确设置调度器,可以显示磁盘设备(例如,在本例中为 `/dev/sdi`)的当前设置,如下所示:
[root@db1 ~]# cat /sys/block/sdi/queue/scheduler
[noop] deadline cfq
您可以看到 noop 已启用,因为它放在方括号中突出显示。
### b) 创建条带化的 LVM 和 XFS 文件系统
现在,我们准备在 RHEL 中创建 LVM 条带和数据库文件系统。 以下是所涉及步骤的示例,请注意,对于您的环境,需要替换虚构的名称 vgmydb、lvmydb01 和路径 /mydb/db。
#### 步骤
**1.** 使用 `vgcreate` 命令创建带有新磁盘设备的卷组。
vgcreate -s 4M
例如,如果创建磁盘 /dev/sdh、/dev/sdi、/dev/sdj 和 /dev/sdk:
vgcreate -s 4M vgmydb /dev/sd[h-k]
**2.** 使用 `lvcreate` 命令创建条带化逻辑卷。 建议至少四个磁盘。 从 4MB 条带开始,但是对于非常大的逻辑卷,系统可能会提示您选择更大的大小,如 16M。
lvcreate -n -L -i -I 4MB
例如,要创建带有 4 个条带的 900GB 磁盘,且条带大小为 4 MB:
lvcreate -n lvmydb01 -L 900G -i 4 -I 4M vgmydb
**3.** 使用 `mkfs` 命令创建数据库文件系统。
mkfs.xfs -K
例如:
mkfs.xfs -K /dev/vgmydb/lvmydb01
**4.** 创建文件系统挂载点,例如:
mkdir /mydb/db
**5.** 编辑 `/etc/fstab` 以添加以下挂载条目并挂载文件系统。 例如:
/dev/mapper/vgmydb-lvmydb01 /mydb/db xfs defaults 0 0
**6.** 挂载新的文件系统。
mount /mydb/db
----
## 3. Caché/InterSystems IRIS 配置
本节我们将配置:
- 异步和直接 IO,以实现数据库和 WIJ 的最佳写入性能。 这也为数据库读取操作启用了直接 IO。
> 注意:由于直接 IO 会绕过文件系统缓存,因此在配置直接 IO 后,操作系统文件复制操作(包括 Caché 在线备份)将非常慢。
为提高 **RHEL** 上的 WIJ 的性能并实现最低延迟(SuSE 9 及更高版本不支持),并减少对其他 IO 的影响,我们还将配置:
- 将 `rtkaio` 库用于使用 Caché 的 RHEL 系统。 **注意:IRIS 不需要这个库。**
> 注:对于 Linux 上版本号以 2017.1.0. 开头的 Caché、Ensemble 和 HealthShare 发行版 (仅当备份或异步镜像成员配置为使用 rtkaio 时),必须应用 [ RJF264,可通过 InterSystems 全球响应中心 (WRC) 的特别分发获取](https://www.intersystems.com/support-learning/support/product-news-alerts/support-alert/alert-linux-defects-can-corrupt-mirror-copies-journal-files/ "RJF264 from WRC")。
#### 步骤
步骤为:
1. 关闭 Caché
2. 编辑 `/cache.cpf` 文件
3. 重启 Caché
在 cache.cpf 文件中,将以下三行添加到 `[config]` 部分的顶部,其他行保持不变,如下例所示:
[config]
wduseasyncio=1
asyncwij=8
对于 RHEL Caché(不是 IRIS),还要将以下内容添加到 `[config]` 部分:
LibPath=/lib64/rtkaio/
注意:当 Caché 重新启动后,这些行将在 `[config]` 部分中按字母顺序排序。
----
## 总结
本帖给出了创建 900GB LVM PE 条带和为 vSAN 上的数据库磁盘创建文件系统的示例。 为了通过 LVM 条带获得最佳性能,您还学习了如何配置 Caché/InterSystems IRI 来为数据库写入和 WiJ 启用异步 IO。
公告
Michael Lei · 十月 26, 2021
InterSystems很高兴地宣布一个全新的 开发者下载网站 提供InterSystems IRIS社区版和InterSystems IRIS for Health社区版的完整配套版本。 这些都是免费提供给应用开发者使用的。
你可以选择直接从 InterSystems开发者社区 直接 下载 InterSystems IRIS.
这些实例包括一个免费的内置13个月的许可证。 它们限制在10GB的用户数据,将在8个核心的机器上运行,支持5个并发连接,并支持应用开发。可用的平台。RedHat, Ubuntu, SUSE, Windows和macOS
InterSystems IRIS 和 InterSystems IRIS for Health医疗版也可以从Docker Hub获得容器版.
请在这里查看如何开始,访问我们网站上的InterSystems IRIS 数据平台或InterSystems IRIS for Health 医疗版,了解更多关于我们的产品,并访问 开发者资源页面深入了解开发。
如果您之前注册了InterSystems登录账户(如开发者社区或WRC),您可以使用这些账户来访问开发者下载网站 。