清除过滤器
文章
Michael Lei · 五月 24, 2021
。之前我展示了如何运行 pButtons 来收集我们在下列帖子中研究的性能指标。
- [第 1 部分 - 入门:收集指标。](https://cn.community.intersystems.com/post/intersystems-数据平台和性能-–-第-1-篇)
- [第 2 部分 - 研究收集的指标。](https://cn.community.intersystems.com/post/intersystems-数据平台和性能-–-第-2篇)
## 更新:2020 年 5 月。
_自四年前写下本帖以来,我们已从 Caché 迁移到 IRIS。 有关 pButton (Caché) 和 SystemPerformance (IRIS) 文档的更新链接,请参见评论。 另请注意如何将系统更新到最新版本的性能工具。_
pButtons 与 Caché 5 及更高版本兼容,并已包含在最近的 InterSystems 数据平台分发版(HealthShare、Ensemble 和 Caché)中。 本帖旨在提醒您下载并安装最新版的 pButttons。
最新版本始终可以通过 WRC 下载。需要有WRC登录账户。
https://wrc.intersystems.cn/wrc/coDistTools.csp
您需要下载以下文件:
pButtons
它是xml文件,以确保为您的 Caché 版本安装正确的文件。
要检查您现在已安装哪个版本,可以运行:
%SYS>write $$version^pButtons()
备注 1:
- 当前版本的 pButtons 需要一个许可证单元才能运行,将来的发行版将满足此要求。
- 此 pButtons 发行版的版本已更改。 — 之前的 pButtons 版本为 1.16c — 新发行版为版本 5。
备注 2:
- pButtons 版本 5 还更正了版本 1.16a 引入的一个问题,该问题可能导致收集时间非常长。 版本 1.16a 包含在 Caché 2015.1.0 中。 如果您有 pButtons 版本 1.16a 至 1.16c,则应该从 ftp 站点下载 pButtons 版本 5。
有关 pButtons 的更多详细信息,请参见下载的文件以及在线 Caché 文档。
文章
Michael Lei · 五月 24, 2021
您的应用程序已部署,一切运行正常。 很好,击个掌! 然后电话突然响个不停 – 用户投诉应用程序有时很“慢”。 这是什么意思? 有时? 您有哪些工具,查找和解决这个缓慢问题应查看哪些统计数据? 您的系统基础架构是否能承担用户负载的任务? 在投入生产之前,应该询问哪些基础架构设计问题? 如何自信地为新硬件规划容量,而不会过度规定? 如何停止电话铃声? 如何一开始就不让它响?
* * *
[本系列其他帖子的列表](https://cn.community.intersystems.com/post/intersystems-数据平台的容量规划和性能系列文章)
* * *
## 这将是一次旅程
这是本系列的第一个帖子,本系列将探讨可用于对系统性能进行监视、审查和故障排除的工具和指标,以及影响性能的系统和架构设计注意事项。 在旅程中,我们将朝多个方向前进,了解 Caché、操作系统、硬件、虚拟化以及根据评论中的反馈变为热门的其他方面的性能。
我们将关注反馈循环,其中性能数据提供了一个视角,通过它可查看已部署的应用程序和基础架构的优势和局限性,然后形成反馈以实现更好的设计和容量计划。
不言而喻,您应该不断检查性能指标,但不幸的是,如果客户只是查看数据,他们会多次对长期可见的性能问题感到惊讶。 但问题当然是 - 什么数据? 我们将从收集一些基本的 Caché 和系统指标来开始这一旅程,这样我们可以对您的系统当前的运行状况有一个感觉。 在以后的帖子中,我们将深入了解主要指标的含义。
有多个可用于系统监视的选项 – 来自 Caché 内部和外部,我们将在本系列中探讨其中的许多选项。
首先,我们将了解我最喜欢的用于连续收集数据的必备工具 ^pButtons,它已经安装在每个 Caché 系统上。
为确保您拥有最新的 pButtons 副本,请查看以下帖子:
## 收集系统性能指标 - ^pButtons
Caché 的 pButtons 实用工具可根据其创建的日志文件生成可读的 HTML 性能报告。 pButtons 输出的性能指标可以被轻松提取、绘图和审核。
pButtons html 文件中收集的数据包括:
* Caché 设置:配置、驱动器映射等。
* mgstat:Caché 性能指标 - 大多数的值是每秒平均数。
* Unix:vmstat 和 iostat:操作系统资源和性能指标。
* Windows:性能监视器:Windows 资源和性能指标。
* 其他有用的指标。
pButtons 数据收集对系统性能的影响非常小,这些指标已经由系统收集,pButtons 只是将它们打包以便归档和传输。
要保存基准以进行趋势分析和故障排除,好的做法是在一个完整的业务周期内每天收集 24 小时 pButtons(午夜到午夜)。 一个业务周期可能是一个月或更长时间,例如,从月末处理中捕获数据。 如果没有任何其他外部性能监视或收集,则可以全年运行 pButtons。
应注意以下关键点:
* 将日志目录更改为远离生产数据的位置,以存储累积的输出文件,避免出现磁盘满的问题!
* 定期运行操作系统脚本或以其他方式压缩和存档 pButtons 文件,这在 Windows 上尤其重要,因为文件可能很大。
* 定期审核数据!
如果出现需要立即分析的问题,可以预览(立即收集)pButtons 数据,同时继续存储指标,以便在当天运行结束时进行收集。
有关 pButtons 的更多信息(包括预览、停止运行和添加自定义数据收集),请参见最新的 Caché 文档中的 _Caché 监视指南_:
http://docs.intersystems.com
可以将 pButtons HTML 文件数据分离并提取(例如提取为 CSV 文件),以通过脚本或简单的剪切和粘贴将其处理成图形或进行其他分析。 我们以后将在下一个帖子中看到图表输出的例子。
当然,如果您有紧急的性能问题,请联系 WRC。
### 计划 24 小时 pButtons 数据收集
^pButtons 可以手动从终端提示符启动,也可以按计划启动。 要计划每天 24 小时收集:
1. 启动 Caché 终端,切换到 %SYS 命名空间,然后手动运行一次 pButtons 以设置 pButtons 文件结构:
%SYS>d ^pButtons
Current log directory: /db/backup/benchout/pButtonsOut/
Available profiles:
1 12hours - 12 hour run sampling every 10 seconds
2 24hours - 24 hour run sampling every 10 seconds
3 30mins - 30 minute run sampling every 1 second
4 4hours - 4 hour run sampling every 5 seconds
5 8hours - 8 hour run sampling every 10 seconds
6 test - A 5 minute TEST run sampling every 30 seconds
选择选项 6 test - 5 minute TEST run sampling every 30 seconds。 请注意,您的编号可能有所不同,但 test 应该是显而易见的。
在运行期间,运行 Collect^pButtons(如下所示),您将看到包括 runid 在内的信息。 在此例中为“20160303\_1851\_test”。
%SYS>d Collect^pButtons
Current Performance runs:
20160303_1851_test ready in 6 minutes 48 seconds
nothing available to collect at the moment.
%SYS>
注意到这个 5 分钟运行还剩 6 分 48 秒吗? pButtons 为所有运行增加了 2 分钟宽限期,以便有时间将日志收集和整理成 html 格式。
2. 重要!更改 pButtons 日志输出目录 – 默认的输出位置为 <Caché安装路径>/mgr 文件夹。 例如,在 unix 上,日志目录的路径可能如下所示:
do setlogdir^pButtons("/somewhere_with_lots_of_space/perflogs/")
确保 Caché 有该目录的写权限,并且有足够的磁盘空间用于累积输出文件。
3. 运行以下命令以创建一个新的 24 小时配置文件,间隔为 30 秒:
write $$addprofile^pButtons("My_24hours_30sec","24 hours 30 sec interval",30,2880)
检查该配置文件是否已添加到 pButtons:
%SYS>d ^pButtons
Current log directory: /db/backup/benchout/pButtonsOut/
Available profiles:
1 12hours - 12 hour run sampling every 10 seconds
2 24hours - 24 hour run sampling every 10 seconds
3 30mins - 30 minute run sampling every 1 second
4 4hours - 4 hour run sampling every 5 seconds
5 8hours - 8 hour run sampling every 10 seconds
6 My_24hours_30sec- 24 hours 30 sec interval
7 test - A 5 minute TEST run sampling every 30 seconds
select profile number to run:
注意:您可以更改收集间隔 – 30 秒对于例行监视是可以的。 对于例行 24 小时运行,我不会选择低于 5 秒的间隔 (…”,5,17280),因为 pButtons 在每个间隔都会收集数据,而导致输出文件变得非常大。 如果您要对一天中的某个特定时间进行故障排除,并需要更细化的数据,请使用默认的配置文件之一,或创建一个新的自定义配置文件,取较短的时间段,例如 1 小时,间隔为 5 秒 (...",5,720)。 多个 pButtons 可以同时运行,因此可以同时运行一个间隔为 5 秒的短 pButtons 和一个 24 小时 pButtons。
4. 提示 对于 UNIX 站点,可审查磁盘命令。 “iostat”命令使用的默认参数可能未包括磁盘响应时间。 首先显示当前配置了哪些磁盘命令:
%SYS>zw ^pButtons("cmds","disk")
^pButtons("cmds","disk")=2
^pButtons("cmds","disk",1)=$lb("iostat","iostat ","interval"," ","count"," > ")
^pButtons("cmds","disk",2)=$lb("sar -d","sar -d ","interval"," ","count"," > ")
要收集磁盘统计数据,请使用适合您的 UNIX 安装版的命令来编辑语法。 注意尾部空格。 以下是一些示例:
LINUX: set $li(^pButtons("cmds","disk",1),2)="iostat -xt "
AIX: set $li(^pButtons("cmds","disk",1),2)="iostat -sadD "
VxFS: set ^pButtons("cmds","disk",3)=$lb("vxstat","vxstat -g DISKGROUP -i ","interval"," -c ","count"," > ")
您可以同时运行 iostat 和 sar 命令来创建非常大的 pButtons html 文件。 对于定期的性能审查,我通常只使用 iostat。 要仅配置一个命令:
set ^pButtons("cmds","disk")=1
有关配置 pButtons 的更多详细信息,请参见在线文档。
5. 在 Management Portal > System Operation > Task Manager 中安排 pButtons 在午夜启动:
Namespace: %SYS
Task Type: RunLegacyTask
ExecuteCode: Do run^pButtons("My_24hours_30sec")
Task Priority: Normal
User: superuser
How often: Once daily at 00:00:01
### 收集 pButtons 数据
最新版的 InterSystems 数据平台中随附的 pButtons 包括自动收集功能。 要手动收集数据并整理成一个 html 文件,请在 %SYS 命名空间中运行以下命令以生成未完成的 pButtons html 输出文件:
do Collect^pButtons
该 html 文件将位于您在步骤 2 设置的日志目录中(如果您未设置,就**现在去设置!**)。 否则默认位置为 <Caché 安装目录/mgr>
文件将命名为 <主机名\_实例\_名称\_日期\_时间\_配置文件名.html>,例如 vsan-tc-db1\_H2015\_20160218\_0255_test.html
### Windows 性能监视器注意事项
如果操作系统是 Windows,则可以使用 Windows 性能监视器 (perfmon) 收集数据,与所收集的其他指标同步。 在 pButtons 的旧版 Caché 发行版中,需要手动配置 Windows perfmon。 如果帖子评论有需求,我将写一篇帖子介绍如何创建 perfmon 模板来定义要监视的性能计数器,并安排与 pButtons 相同的运行期间和间隔。
### 总结
我们从本帖开始收集一些要研究的数据。 在本周晚些时候,我将开始研究一些样本数据及其含义。 您可以使用您在自己的系统上收集的数据。 到时见。
文章
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提供透明的连接。
文章
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关键字。
公告
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
公告
Claire Zheng · 八月 11, 2022
Hi 社区成员们,
我们很高兴地宣告 社区网络研讨会 回归了!
我们诚邀您参加 @sween 主讲的 使用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 (北京时间)👨🏫 主讲人: @sween, 全栈架构师,来自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 · 十二月 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 代码。**_
希望该模板对您有用。 欢迎提供反馈,尤其是拉取请求!
文章
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()方法。
文章
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。如果您的证书链接到同一电子邮件,您的证书将会自动加载:
如果没有,请按照页面上描述的详细步骤操作:
完成后,每个人都可以看到你的认证资质哦 ;)
预祝每位社区成员成功添加认证并通过!
文章
Louis Lu · 四月 15, 2021
我喜欢事务型分析DBMS和高级分析细分概念
VSCode的IRIS 1.0版插件很炫酷。社区和InterSystems IRIS之间的合作对此有着非常积极的作用。但在不久的将来,减少代码开发量对于IRIS来说将是一个非常合适的选项。
动态网关/原生API将IRIS定位为面向市场上主要开放语言的最先进的数据平台,但有必要使用这些语言的公共包管理器,尤其是Maven公共仓库。
API管理器是一个不错的方案,但社区版IRIS不能用,且仅能用于由IRIS创建的API。这个关键问题亟待解决。
用于物联网的OPC-UA适配器和用于实时事件的Kakfa适配器都很棒,我很喜欢。未来,也可以考虑用于Salesforce、SAP、Office365和其他最常用平台的新适配器。
云支持目前很完美,采用ICM和IKO进行部署和管理,SAM进行监控。
FHIR加速器对于FHIR项目是一个很棒的方案,真的很有用。
IRIS的速度真的越来越快了,它能够让已经非常快的东西变得更快,非常出色。
自适应分析重磅推出后广受欢迎,它能够加速和简化分析数据立方体的创建(以完全可视化和直观的方式),并以极好的方式向Excel和其他数据查看器展示数据。未来,我很想看到InterSystems发布自己的查看器,因为IRIS本身的报告功能比较有限,且无法提供社区版,因而使它不可能在社区中推广。
IntegratedML是我最喜欢的IRIS的新功能。它非常易用,适合所有的DBA。AutoML也是超棒的技术,将会被广泛使用。现在,InterSystems需要向最终用户提供一个预测和分类查看器,比如它可以使用与SAM的Grafana相同的技术。
文章
Kelly Huang · 七月 12, 2023
在本文中,我将分享我们在 2023 年全球峰会技术交流室中提出的主题。我和@Rochael.Ribeiro
借此机会,我们就以下话题进行探讨:
用于快速 API 的开放交换工具
开放API规范
传统与快速 Api 开发
复合 API(互操作性)
规范优先或 API 优先方法
API 治理和监控
演示(视频)
用于快速 API 的开放交换工具
当我们谈论快速现代 API 开发(Rest / json)时,我们将使用两个 Intersystems Open Exchange 工具:
第一个是用于快速开发 API 的框架,我们将在本文中详细介绍。
https://openexchange.intersystems.com/package/IRIS-apiPub
第二种是使用 Swagger 作为用户界面,用于 IRIS 平台上开发的 Rest API 的规范和文档,以及它们的使用/执行。其运行的基础是开放 API 规范 (OAS) 标准,如下所述:
https://openexchange.intersystems.com/package/iris-web-swagger-ui
什么是开放 API 规范 (OAS)?
它是全球范围内用于定义、记录和使用 API 的标准。在大多数情况下,API 甚至在实现之前就已经设计好了。我将在下一个主题中详细讨论它。
它很重要,因为它定义并记录了 Rest API 供其在提供者和消费者方面使用。但这种模式也有助于加快市场上工具(Rest API 客户端)的测试和 API 调用,例如 Swagger、Postman、Insomnia 等……
使用 IRIS 发布 API 的传统方式
想象一下,我们必须从现有的 IRIS 方法构建并发布 Rest API(如下图)。
以传统方式:
1:我们必须考虑消费者会如何称呼它。例如:将使用哪个路径和动词以及如何响应。无论是 JSON 对象还是纯文本。
2:在 %CSP.REST 类中构建一个新方法,该方法将处理调用它的 http 请求。
3:处理方法对最终用户预期的 http 响应的响应。
4:考虑一下我们将如何提供成功代码以及我们将如何处理异常。
5:为我们的新方法绘制路线。
6:向最终用户提供API文档。我们可能会手动构建 OAS 内容。
7:例如,如果我们有一个请求或响应有效负载(对象),则实施时间将会增加,因为它也必须记录在 OAS 中。
我们怎样才能更快?
只需使用 [WebMethod] 属性标记 IRIS 方法即可。无论是什么,该框架都会使用 OAS 3.x 标准来处理其发布。
为什么 OAS 3.x 标准如此重要?
因为它还详细记录了输入和输出有效负载的所有属性。
这样,市场上的任何 Rest Client 工具都可以立即耦合到 API,例如 Insomnia、Postman、Swagger 等,并提供示例内容以便轻松调用它们。
使用 Swagger,我们已经可以可视化我们的 API(上图)并调用它。这对于测试也非常有用。
API定制
但是如果我需要自定义 API 该怎么办?
例如:我希望路径是其他东西,而不是方法的名称。我希望输入参数位于路径中,而不是像查询参数那样。
我们在方法之上定义了一个特定的符号,我们可以在其中补充方法本身不提供的元信息。
在此示例中,我们为 API 定义了另一条路径,并补充了信息,以便最终用户获得更友好的体验。
Rest API 的投影图
该框架支持多种类型的参数。
在此图中,我们可以突出显示复杂类型(对象)。它们将自动公开为 JSON 有效负载,并且每个属性都将为最终用户正确记录 (OAS)。
互操作性(复合 API)
通过支持复杂类型,您还可以公开互操作性服务。
这是构建复合 API 的有利场景,复合 API 使用多个外部组件(出站)的编排。
这意味着用作请求或响应的对象或消息将自动发布并由 swagger 等工具读取。
这是测试互操作性组件的绝佳方法,因为通常已经加载了有效负载模板,以便用户知道 API 使用哪些属性。
首先,开发人员可以专注于测试,然后通过定制来塑造 Api。
规范优先或 API 优先方法
当今广泛使用的另一个概念是在实现之前就定义 API。
有了这个框架,就可以导入 Open Api 规范。它自动创建方法结构(规范),只缺少它们的实现。
API 治理和监控
对于Api的治理,也建议一起使用IAM。
除了拥有多个插件之外,IAM 还可以通过 OAS 标准快速耦合到 API。
apiPub 为 API 提供额外的跟踪(参见演示视频)
演示
下载和文档
Intersystems 开放交换: https://openexchange.intersystems.com/?search=apiPub
完整文档:https: //github.com/devecchijr/apiPub
@Claudio Devecchi 致敬原创作者