0 关注者 · 478 帖子

SQL 是在关系数据库中存储、操作和检索数据的标准语言。

InterSystems 官方 Claire Zheng · 十月 23, 2025

InterSystems IRIS® 数据平台、InterSystems IRIS® for HealthTM 和HealthShare® Health Connect 2025.1.2 2024.1.5 维护版本现已全面上市 (GA)。这些版本包括对最近发布的一些警报和建议的修复,其中包括以下内容:

请通过开发者社区分享您的反馈意见,以便我们共同打造更好的产品。

文档

您可以在这些页面上找到详细的变更列表和升级检查列表:

EAP计划

现在有许多 EAP 可用。请查看此页面并注册您感兴趣的项目。

如何获取软件?

0
0 21
InterSystems 官方 Claire Zheng · 六月 23, 2025

您好!

我们正在为即将推出的表分区功能发起抢先体验计划,该功能将帮助 IRIS 客户管理非常大的表,并在数据库和存储层之间分配行数据和关联索引。表分区将深入 IRIS 关系数据管理的核心,为了确保我们的工作顺利开展,我们将与一些积极参与的客户合作,希望他们能够对最初的交付成果提供反馈,我们将根据需要对该功能进行细微调整。

如果您正在使用非常大的关系数据集,想要提高操作效率并愿意参与测试新功能,请https://www.intersystems.com/early-access-program/ 上注册。 您将收到一封欢迎电子邮件,并可通过该电子邮件转到评估门户,在那里可以找到临时开发许可证、最近的工具包、包含新功能的容器镜像,以及入门教程

我们会定期联系注册的参与者,以分享相关更新,当然,如果需要,我们也可以直接协助您进行探索。

谢谢!
Benjamin

0
0 60
InterSystems 官方 Claire Zheng · 六月 22, 2025

适用于 InterSystems IRIS® 数据平台InterSystems IRIS® for HealthTMHealthShare® Health Connect 的维护版本 2024.1.42023.1.6 现已正式发布 (GA)。 这些版本包含对最近发布的以下提醒的修复 - 提醒:SQL 查询返回错误结果 | InterSystems。 请通过开发者社区分享您的反馈,以便我们可以共同打造更出色的产品。

文档

您可以在以下页面上找到详细的变更列表和升级核对清单:

抢先体验计划 (EAP)

目前提供多个 EAP。 请查看此页面并注册您感兴趣的 EAP。

如何获取软件?

InterSystems IRIS 和 InterSystems IRIS for Health 的完整安装包可以从本 WRC 的 InterSystems IRIS 数据平台完整工具包页面 获取。 HealthShare Health Connect 工具包可以从 WRC 的 HealthShare 完整工具包页面获取。 容器镜像可以从 InterSystems 容器注册表中获取。

可用性和软件包信息

此版本提供了适用于所有受支持平台的经典安装包,以及 Docker 容器格式的容器镜像。有关完整列表,请参阅“支持的平台”文档。 这些维护版本的内部版本号为:2024.1.4.512.02023.1.6.809.0

0
0 46
InterSystems 官方 Claire Zheng · 六月 20, 2025

InterSystems 宣布 InterSystems IRIS、InterSystems IRIS for Health 和 HealthShare Health Connect 2025.1 正式发布

2025.1 版的 InterSystems IRIS® 数据平台、InterSystems IRIS® for HealthTMHealthShare® Health Connect 现已正式发布 (GA)。 这是扩展维护 (EM) 版本。

版本亮点

在这个激动人心的版本中,用户可以期待一些新功能和增强,包括:

  1. 高级向量搜索功能
  2. 增强的商业智能
    • IRIS BI 多维数据集构建和同步中的自动依赖项分析,确保复杂多维数据集依赖项中的一致性和完整性。
  3. 改进的 SQL 和数据管理
    • 引入了标准 SQL 分页语法 (LIMIT... OFFSET..., OFFSET... FETCH...)。
    • 新的 LOAD SQL 命令,可以简化 DDL 语句的批量导入。
    • 增强的 ALTER TABLE 命令,可以在行布局和列布局之间无缝转换。
  4. 优化的数据库操作
    • 日志记录大小更小,效率更高。
    • 数据库压缩速度更快,尤其是对于具有大量大字符串内容的数据库。
    • 将新数据库添加到镜像时的自动化程度更高。
    • 用于 ECP 管理任务的新命令行实用工具。
  5. 更高的安全合规性
    • 支持符合 FIPS 140-3 标准的加密库。
  6. 现代化的互操作性用户界面
    • 选择参与经过改进的生产配置和 DTL 编辑器体验,其中包含源代码控制集成、VS Code 兼容性、增强的筛选功能、分屏视图等。请参阅此开发者社区文章,详细了解如何选择参与并提供反馈。
  7. 更多的医疗保健功能
    • 高效的批量 FHIR 引入和调度,包括完整性检查和资源管理。
    • 增强的 FHIR 批量访问和经过改进的 FHIR 搜索操作。
  8. 新的开发者体验功能
  9. 通过 OpenTelemetry 提高可观测性
    • 在 IRIS 中引入了跟踪功能,有助于详细观测 Web 请求和应用程序的性能。

请通过开发者社区分享您的反馈,以便我们可以共同打造更出色的产品。

文档

访问以下链接,可以详细了解所有着重介绍的功能:

此外,请查看升级影响核对清单,轻松了解升级到此版本时需要注意的所有变更。

尤其是,请注意 InterSystems IRIS 2025.1 引入了新的日志文件格式版本,该格式与早期版本不兼容,因而给混合版本的镜像设置带来了一定的限制。 请参阅相应的文档了解更多详细信息。

抢先体验计划 (EAP)

目前提供多个 EAP。 请查看此页面并注册您感兴趣的 EAP。

下载软件

一如既往,扩展维护 (EM) 版本提供了适用于所有受支持平台的经典安装包,以及 Docker 容器格式的容器镜像。

经典安装包

安装包可以从 WRC 的 InterSystems IRIS 页面(对于 InterSystems IRIS 和 InterSystems IRIS for Health)和 WRC 的 HealthShare 页面(对于 Health Connect)获取。 您也可以在评估服务网站中找到工具包。

可用性和软件包信息

此版本提供了适用于所有受支持平台的经典安装包,以及 Docker 容器格式的容器镜像。有关完整列表,请参阅“支持的平台”文档

此扩展维护版本的内部版本号为 2025.1.0.223.0

容器镜像可以从 InterSystems 容器注册表中获取。 容器被标记为 2025.1latest-em

0
0 76
文章 姚 鑫 · 二月 18, 2025 2m read

第二十八章 T 开头的术语

以 T 开头的术语

表 (table)

InterSystems SQL

表是一种由表示特定实体的行和表示每个实体特定数据点的列组成的数据结构。

目标角色 (target role)

系统

在受保护的 IRIS 应用中,由应用程序授予给已经是其他角色(称为匹配角色)成员的用户的角色。如果用户拥有匹配角色,则在使用应用程序时,用户还可以被授予一个或多个额外的目标角色。

目标用户 (target user)

系统

试图认证到 LDAP 服务器的用户。IRIS 通过在特定 LDAP 配置的“编辑 LDAP 配置”页面上的 LDAP 唯一搜索属性字段中使用提供的值,尝试在 LDAP 数据库中查找该用户。可以从 LDAP 配置页面(系统管理 > 安全 > 系统安全 > LDAP 配置)访问“编辑 LDAP 配置”页面。(请注意,如果启用了 Kerberos,页面名称和菜单选项中会包含 Kerberos。)

TCP/IP

通用

传输控制协议/互联网协议(Transmission Control Protocol/Internet Protocol),是可以管理关系客户端和关系服务器之间连接的通信协议之一。也称为 TCP

临时全局 (temporary global)

系统

存储在临时数据库 IRISTEMP 中的全局。请参见临时全局和 IRISTEMP 数据库。

终端 (Terminal)

系统

正式而言,该术语指的是 Windows 终端应用程序。非正式而言,该术语也可以指 ObjectScript shell,即 IRIS 的交互式命令行接口。终端应用程序包括 ObjectScript shell,但也提供菜单和其他选项。请参见“使用终端”和“使用 ObjectScript Shell”。

事务 (transaction)

通用

一个逻辑工作单元。应用程序开发人员可以使用 SQLObjectScript 命令定义事务。IRIS 将事务中对全局的更新记录在日志文件中。如果事务不完整,可以回滚。

瞬态属性 (transient property)

对象(Objects)

瞬态属性存储在内存中,但不存储在磁盘上。

翻译方法 (translation methods)

对象(Objects)

翻译方法用于在 ODBC、显示、逻辑和存储格式之间转换值。

触发器 (trigger)

InterSystems SQL

由开发人员定义的一系列操作,在 SQL 应用程序或使用 SQL 存储的对象应用程序的各个点执行。触发器是由对表执行的 INSERTUPDATEDELETE 操作启动的数据库操作。触发器有助于维护完整性约束和其他数据依赖关系。

0
0 102
文章 姚 鑫 · 二月 17, 2025 2m read

第二十七章 S 开头的术语

存储接口 (storage interface)

对象(Objects)

使用自定义存储或编写自己的存储类时必须实现的一组方法。

存储策略 (storage strategy)

对象(Objects)

类使用的存储策略在编译时评估为存储定义,决定数据的存储方式。

存储过程 (stored procedure)

SQL

存储过程允许你从 ODBCJDBC 执行查询或类方法。

流接口 (stream interface)

对象(Objects)

IRIS 流接口用于在 ObjectScriptSQLJava 中操作流。

流 (stream)

对象(Objects)

流提供了一个接口,用于操作和存储大量数据。IRIS 流接口可在 ObjectScriptSQLJava 中用于操作流。

超级服务器 (superserver)

系统

监听指定端口的服务器,用于接收对 IRIS 的传入连接并将其分发到适当的子系统。默认的超级服务器端口是 1972;如果 1972 不可用,超级服务器将从 51773 开始监听下一个可用端口。要设置超级服务器端口号,请使用管理门户(系统管理 > 配置 > 系统配置 > 内存和启动)中的内存和启动页面的超级服务器端口号字段。

滑动 (swizzling)

对象(Objects)

当嵌入和持久对象被引用时,自动将其拉入内存的过程。也称为懒加载。

系统类 (system class)

对象(Objects)

IRIS 提供内置功能的类。

系统配置 (system configuration)

系统

IRIS 启动时使用的系统资源定义。你通过管理门户定义系统配置。

系统管理员目录 (system manager’s directory)

系统

IRIS 数据库所在的目录,包含 IRIS 系统全局、系统例程以及用于 IRIS 管理器和 % 实用工具的例程。它是 IRIS 安装目录中的 MGR 子目录。

系统名称 (system name)

系统

分配给网络中节点的名称。必须在网络中唯一。也称为主机名或计算机名。在命名空间/网络配置编辑器中用于标识网络配置中的计算机。MNET 实用工具中称为目录集。

系统进程 (system processes)

系统

Windows - 在 Windows 上,无法调整进程的优先级。 UNIX® - 在 UNIX® 上,优先级由 nice 值控制。通过提高进程的 nice 值,给予其较低的优先级;通过降低 nice 值,给予进程较高的优先级。

0
0 90
文章 姚 鑫 · 二月 6, 2025 3m read

第十六章 L - M 开头的术语

锁表 (lock table)

系统

IRIS 内部的表,存储所有由进程发出的 LOCK 命令。你可以使用系统查看器查看此表。

日志文件 (log files)

系统

系统管理员目录中的文件,包含关于系统操作、错误和指标的消息。这些包括消息日志(messages.log)、系统监视器日志(SystemMonitor.log)、警报日志(alerts.log)、初始化日志(iboot.log)和日志历史记录日志(journal.log)。有关这些日志文件的信息,请参见“监控日志文件”。

逻辑格式 (logical format)

对象(Objects)

对象属性的逻辑格式是在内存中使用的格式。所有的比较和计算都是基于这种格式进行的。

登录角色 (login role)

系统

通过认证到IRIS 时与用户关联的任何角色(而不是之后关联的角色)。

以 M 开头的术语

宏预处理器 (macro preprocessor)

ObjectScript

ObjectScript 编译器的一部分,将宏代码转换为可用的 ObjectScript 代码。

宏源代码 (macro source code)

ObjectScript

编写例程和方法的最高、最灵活和最宽松的代码级别。宏源代码允许使用 ObjectScript 语法、特殊宏预处理器命令和 ANSI 标准 SQL 的组合来定义宏和嵌入式 SQL 语句。

映射 (map)

系统

IRIS中,映射定义了使用SQL存储时数据的存储方式。

Windows 上,映射IRIS数据库中的一个单元,由 4002048 字节块组成,存储在一个平面文件中。 在 UNIX® 上,映射是 IRIS 数据库中的一个单元,由 4002048 字节块组成,存储在一个单一的 UNIX® 文件或原始分区中。

映射全局引用 (mapped global reference)

系统

对位于不同目录中的全局的逻辑引用,而无需使用通常需要的扩展引用语法来引用远程全局。您可以像全局位于数据库的数据位置一样引用它。该主目录可以在同一台计算机上,也可以在 IRIS 服务器已知的网络上的任何其他计算机上。系统管理员使用命名空间/网络配置编辑器定义映射全局的实际位置。

匹配角色 (matching role)

系统

在受保护的 IRIS 应用中,匹配角色会授予额外的权限。如果用户拥有匹配角色,则在使用应用程序时,该用户还会被授予指定的目标角色。有时也称为匹配角色(match role)。

元数据类 (metadata class)

对象(Objects)

元数据类提供了一个接口,用于检查对象应用中存储的数据。请参见 %Dictionary.ClassDefinition 类。

元数据 (metadata)

对象(Objects)

元数据描述数据及其结构。

方法生成器 (method generator)

对象(Objects)

方法生成器是根据类参数的值生成运行时代码的方法。

0
0 86
文章 姚 鑫 · 二月 2, 2025 3m read

第十三章 I 开头的术语

安装目录 (install-dir)

系统

在通用引用 IRIS 安装目录时,文档使用术语 install-dir。在示例中,文档使用 C:\MyIRIS\。章节“默认安装目录”描述了 IRIS 在所有受支持操作系统上的安装位置。

实例 (instance)

对象(Objects)

表示特定实体的类的实现。术语“实例”和“对象”可以互换使用。

实例认证 (Instance Authentication)

系统

本地认证系统:用户会被提示输入密码,输入的密码的哈希值会传递到 IRIS 服务器,并与服务器中存储的现有密码的哈希值进行比较。如果两个值相同,IRIS 将授予用户对其有权限的资源的访问权限。

此机制在管理门户中列为“密码认证”。

实例方法 (instance method)

对象(Objects)

从类的特定实例调用的方法,并执行与该实例相关的某些操作。

实例化 (instantiate)

对象(Objects)

将对象实例放入内存中,以便程序可以对其进行操作。

中间源代码 (intermediate source code)

ObjectScript

IRIS 中可用的标准三代语言(3GLObjectScript 源代码。中间代码由 IRIS 编译器从宏源生成。在这个阶段,所有预处理器语法(包括嵌入式 SQL)都已解析,例程仅包含纯源代码。可以在这个级别编写 ObjectScript 例程,但无法使用嵌入式 SQL 或其他预处理器语法(如宏)。

InterSystems IRIS 启动器 (InterSystems IRIS launcher)

系统

Windows 系统上启动IRIS 时显示在任务栏上的图标。通过此图标,你可以配置和管理IRIS 系统,以及创建和管理类和例程。

InterSystems IRIS 数据库 (InterSystems IRIS database)

系统

存储在单个目录、命名空间或 UIC 中的全局和例程中的相关数据集合。

InterSystems IRIS Java 对象服务器 (InterSystems IRIS Object Server for Java)

对象(Objects)

IRIS Java 绑定允许用 Java 编写的客户端应用程序访问基于服务器的 IRIS 对象。

InterSystems IRIS 对象实用库 (InterSystems IRIS object utility library)

对象(Objects)

IRIS 对象实用库提供了一个接口,用于配置 IRIS 的对象组件、操作和编译类以及交互式使用对象。这些实用工具的主要接口是通过 %SYSTEM.OBJO 类。

InterSystems IRIS 关系实用库 (InterSystems IRIS relational utility library)

InterSystems SQL

IRIS 关系实用库提供了一个接口,用于配置 SQL、管理 SQL 服务器以及从其他关系数据库导入 DDL。 这些实用工具的主要接口是通过 %SYSTEM.SQL 类。

InterSystems IRIS 服务器 (InterSystems IRIS server)

系统

允许你在网络系统中使用分布式 IRIS 数据库的设施。

0
0 78
文章 姚 鑫 · 一月 30, 2025 2m read

第十一章 F - H 开头的术语

文件流 (file stream)

对象(Objects)

文件流提供了一个接口,用于在外部文件中操作和存储大量基于文本或二进制的数据。IRIS 的流接口可以在 ObjectScriptSQLJava 中用于操作文件流。

最终类 (final class)

对象(Objects)

不能被扩展或子类化的类。

最终方法 (final method)

对象(Objects)

不能被重写的方法。

最终属性 (final property)

对象(Objects)

不能被重写的属性。

外键 (foreign key)

InterSystems SQL

外键约束表中的一列指向另一表中的另一列。为第一列提供的值必须存在于第二列中。

基础 (foundation)

医疗保健(Health care)

InterSystems IRIS for Health™HealthShare® 中,启用了医疗保健互操作性的命名空间。

以 G 开头的术语

全局 (global)

系统

多维存储结构。全局在 IRIS 数据库中使用平衡树技术实现。

全局数据库 (globals database)

系统

IRIS 的基础逻辑和物理数据存储结构,其中所有数据都存储在称为“全局”的多重下标数组系统中。

全局目录 (global directory)

系统

包含全局数据库的目录。它包括数据库文件和目录中所有全局的列表及相关信息。

全局唯一标识符 (GUID)

系统

GUID 是用于标识实体(如类的实例)的标识符,保证在所IRIS 实例中都是唯一的,即使跨多个系统。例如,如果两个独立的IRIS 实例使用包含每个实例 GUID 的共同类定义,那么将这两个实例的数据合并时不会产生重复的 GUID 值。IRIS 使用 GUID 作为对象同步的一部分。有关使用 GUID 的一般信息,请参见类文档 %ExtentMgr.GUID%Library.GlobalIdentifier

GSA 文件 (GSA file)

系统

GSA 文件是全局保存文件。GSA 文件扩展名不是必需的,但允许 IRIS 和程序员轻松识别保存的全局。除了文件扩展名外,GSA 文件没有其他特殊意义。

以 H 开头的术语

主机名 (host name)

通用

服务器系统的名称。

主机变量 (host variable)

SQL

在嵌入式 SQL 语句中,与应用程序变量关联的变量。

0
0 71
文章 Michael Lei · 七月 7, 2024 1m read

InterSystems 常见问题系列FAQ

如果要让超时功能失效, 在DSN设置查询超时为disabled:

Windows Control Panel > Administrative Tools > Data Sources (ODBC) > System DSN configuration

如果勾选了Disable query timeout , 超时就会失效.

如果想在应用侧修改,你可以在ODBC API 层设置:在连接数据源之前,调用ODBC SQLSetStmtAttr功能设置SQL_ATTR_QUERY_TIMEOUT 属性 

0
0 91
文章 Louis Lu · 七月 1, 2024 4m read

迄今为止,我看到的大多数使用向量vector的示例,将它只作为 SQL 中的一种功能,尤其是围绕 VECTOR_Search 的 3 个函数。
* TO_VECTOR()
* vector_dot_product ()
* vector_cosine ()

iris-vector-search 演示包中隐藏着一个非常有用的摘要。
从那里,你可以通过多个链接找到所需的一切。

我还缺少更多的 VECTOR 方法,于是在 Idea Portal 上提出了相关请求。

接着,我想起每个 SQL 方法或存储过程都有一堆 ObjectScript 代码。
于是我开始搜索,下面就是我的研究的一些总结:

%Library.Vector 是对新数据类型的核心描述
      这是一种复杂的结构,就像对象或 %DynamicObjects 或 $Bit Expressions 一样,需要特定的方式去访问。
我们还可以看到 2 个必备参数: 
      * DATATTYPE - 一旦设置就不能更改。 可接受的类型:  "整数integer"(或 "int")、"双精度浮点double"、"十进制decimal"、"字符串 "和 "时间戳"。
      * LEN >0 时,可以增长,但绝对不能缩小

0
0 86
文章 Jingwei Wang · 六月 23, 2024 8m read

低代码挑战

想象一下那个场景。您正在 Widgets Direct 愉快地工作,这是互联网上首屈一指的小部件和小部件配件零售商。您的老板有一些毁灭性的消息,一些客户可能对他们的小部件不太满意,我们需要一个帮助台应用程序来跟踪这些投诉。为了让事情变得有趣,他希望代码占用非常小,并挑战您使用 InterSystems IRIS 以少于 150 行代码交付应用程序。这可能吗?

免责声明:本文记录了一个非常基本的应用程序的构建,为了简洁起见,省略了安全性和错误处理等细节。该应用程序仅供参考,不得用于任何生产应用。本文使用IRIS 2023.1作为数据平台,并非所描述的所有功能在早期版本中都可用

第 1 步 - 定义数据模型

我们首先定义一个新的干净的命名空间 - 带有代码和数据数据库。虽然所有内容都可以位于 1 个数据库中,但将它们拆分以便于数据刷新。

我们的帮助台系统需要 3 个基本类:一个 Ticket 对象,它可以包含用于记录员工顾问 UserAccount 和客户联系人 UserAccount 之间交互的操作。让我们用一些基本属性来定义它们:

0
0 127
文章 Hao Ma · 五月 17, 2024 3m read

最近有某国内三甲医院为满足评级和飞行检查要求,希望提升HIS和IRIS的SQL查询效率,客户和实施工程师整理了一个慢查询的SQL列表, 有一些查询比较慢, 查询时间在甚至大于60分钟。

在我们和厂商共同努力下,对整个库的SQL查询做了优化。 下表是记录了我们在进行了大部分优化工作后的结果,您可以看到大多查询从几十分钟减少到了几十秒甚至1秒以内。其中有几个慢到几分钟的查询,最后经过细调, 也把查询耗时减少到了一分钟以内。 优化的效果还是很明显的。

这里我分享一下操作的要点,以便给其他有同样问题的客户一个思路。

其实如果您看过我前面的帖子,应该已经有了基本的概念。我就把工作流程总结一下,其实就这么几个步骤:

步骤一:

检查硬件配置。 配置中和SQL性能相关的有这么几个: 1. 数据缓存大小,应该至少为物理内存的一半以上。 2. BBSIZE, 也就是单个进程最大的内存占用,对应不同的Caché/IRIS版本和不同的应用,这个配置有区别,但当然是越大越好,询问您的实施工程师配置是否正确。 3. 是否使用了大页内存,这个能从messages.log里看到。

步骤二

执行Tunetable。 在上面说的这个客户的系统上从来没人执行过Tunetable, 因此SQL引擎其实是没法正确工作的。执行后基本可以解决80%的慢SQL问题。时间短风险小见效快, 找个半夜业务小的时候直接在生产环境执行。

执行完之后,只剩下20-30个SQL还是太慢(超过5秒), 之后的工作我们集中在这些Case上,

步骤三

校验索引。 在生产环境上数据和索引出错非常普遍,因为有人/业务代码直接去改Global. 有的是没改对, 有的是改的过程出了问题,比如有进程没做好错误管理,出问题了没有回滚等等。SQL表数据的完整性出问题,一个结果是查的数不对, 这是显见的,还有一个是查的慢, 这个大部分人想不到。

校验索引是发现这个问题的最有效的办法。 原则上说, 所有的索引都应该执行一下,但因为时间长,影响业务,我们的做法是把相关用到的索引校验了一遍。其中发现了很多数据被修改的问题,比如必须的字段里面是空的, 要求是数字的字段里面放的是字符串等等等等。

校验索引比重新build索引要慢的多, 但对业务的影响也小的多。

步骤四

使用bitmap和bitmap extent索引。本来bitmap extent是不用人工添加的。创建任何一个bitmap索引,系统会自动添加bitmap extent。然而客户用的是SQLStorage的存储格式,修改会非常麻烦。但结果是值得的,上面所处理的绝大多数SQL, 查询时间都降到了60秒以下, 和count()相关的查询, 更是可以从1000秒直接减少到5秒以内。

步骤五

对于极少数查询时间已经提高了很多,但还是不很如人意的SQL, 我们仔细的检查查询计划,用查询关键字来做最后的优化。在好几个查询里, %PARALLEL证明是起作用的, 也就是说,本来用并行多进程查询是可以提高效率的, 但SQL引擎没自动判断出来。

除此之外, 修改SQL语句有时候是可以改善查询速度的,尤其是含子查询, IN, TOP,ORDER BY的语句, 通过检查查询计划,比较不同查询计划的执行情况,可以做出一下成功的性能改善。

欢迎讨论指正,也欢迎感兴趣的客户前来咨询我们gcdpsales@intersystems.com获取更多信息。

0
0 339
文章 Hao Ma · 五月 15, 2024 4m read

SQL查询优化器一般情况下能给出最好的查询计划,但不是所有情况都这样,所以InterSystems SQL还提供了一个方式, 也就是在查询语句里加入optimize-option keyword(优化关键字), 用来人工的修改查询计划。

比如下面的查询:

SELECT AVG(SaleAmt) FROM %PARALLEL User.AllSales GROUP BY Region

其中的%PARALLEL, 就是最常用的优化关键字, 它强制SQL优化器使用多进程并行处理这个SQL。

您可以这样理解: 如果查询优化器足够聪明,那么绝大多数情况下,根本就不需要优化关键字来人工干预。因此,您也一定不奇怪在不同的IRIS/Caché版本中, 关键字的表现可能不一样。越新的版本,应该是越少用到。比如上面的%PARALLEL, 在Caché的大多数版本中, 在查询中加上它一般都能提高查询速度,而在IRIS中,尤其是2023版本以后, 同样的SQL查询语句,很大的可能查询优化器已经自动使用多进程并行查询了,不再需要用户人工干预了。

因此,先总结有关优化关键字的要点:

  1. 优化关键字主要是FROM语句中使用。 UPDATE, INSERT语句也有可以使用的关键字,比如%NOJOURAL等等, 这里我不介绍了,请各位自己查询文档。

    INSERT, UPDATE的关键字常用的有:%NOCHECK %NOINDEX %NOLOCK %NOTRIGGER 等等

  2. 各个不同版本的文档中这部分内容有少许的不同。

  3. 使用查询关键字要结合阅读查询计划,需要经验的积累。用的多了, 在当前版本什么样的查询需要添加关键字就比较有数了。

最新版本的联机文档在: Specify Optimization Hints in Queries | Configure SQL Performance Options

%PARALLEL

指定查询使用多个进程并行处理。在Query Plan中您可以得到证实。有关Query Plan的阅读请看前面的帖子。

%IGNOREINDEX

指定不用某一个或者某几个index。比如以下查询:

select min(ps_supplycost) 
                from %PARALLEL
                %IGNOREINDEX SQLUser.supplier.SUPPLIER_PK
                %IGNOREINDEX SQLUser.part.PART_PK
                %IGNOREINDEX SQLUser.nation.Nation_PK 
                %IGNOREINDEX SQLUser.region.REGION_PK
                        partsupp,
                        supplier,
                        nation,
                        region
                where p_partkey = ps_partkey
                        and s_suppkey = ps_suppkey
                        and s_nationkey = n_nationkey
                        and n_regionkey = r_regionkey
                        and r_name = 'AFRICA'
                        ...

为什么要强制不用某些索引?

一个是用在测试中,经常会比较不同索引的表现。比如你原来有个复合索引,它希望试试新创建的索引是不是更好, 那么很可能您需要告诉SQL引擎不要用以前的索引了。

还有就是您发现某个索引的使用没有让查询性能变好,强制不用它结果可以使用另一个索引,从而来得到更好的查询速度。

%ALLINDEX

用于测试所有可用的索引。

SQL引擎默认会在多个可用的索引中选中它判断最高效的,但这个判断不是总正确。加入%ALLINDEX会在生成查询计划前,测试所有可用的索引,以证实或者调整判断。 用到比较多的情况是有多个范围查询字句的情况。在Caché和早期IRIS版本中, 很多情况下, 使用%ALLINDEX会带来性能的提升, 尽管对所有可用索引做测试会有个额外开支.

比如以下的语句,

SELECT TOP 5 ID, Name, Age, SSN FROM %ALLINDEX Sample.Person WHERE 
(:Name IS NULL or Name %STARTSWITH :Name)
AND
(:Age IS NULL or Age >= :Age)
}

%NOINDEX

在最新版的IRIS文档中, 这个关键字已经去掉了。 我自己的测试中,在2022年后的IRIS中, 它其实已经不起作用了。 但在Caché中, 非常多的使用%NOINDEX的例子。

Caché在线文档中的这段是这么说的:当绝大多数数据被条件选中(或未被选中)时,这种方法最常用。在小于 (<) 或大于 (>) 条件语句下,使用 %NOINDEX 条件级提示通常是有益的。对于“等于”条件语句,使用 %NOINDEX 条件级提示没有任何好处。对于连接条件语句,不支持在 =* 和 *= WHERE 子句外部连接中使用 %NOINDEX;而在 ON 子句连接中使用 %NOINDEX。

这是文档上的例子: E.Age<65已经包含了绝大多数的表记录,那么使用相应的索引可能不经济,因为后面取“Name"还是要直接回表操作,这样的情况, 不用E表的Age的索引,查起来还快一些。

SELECT P.Name,P.Age,E.Name,E.Age
FROM %ALLINDEX Sample.Person AS P LEFT OUTER JOIN Sample.Employee AS E
     ON P.Name=E.Name
WHERE P.Age > 21 AND %NOINDEX E.Age < 65

幸好新版IRIS变的智能了。

%FIRSTTABLE, %STARTTABLE

这两个关键字都是强制查询计划中对JOIN的执行从那个表开始。如果SQL引擎没法给出正确的判断的话,人工指定是需要的。

其他的关键字包括: %FULL, %INORDER, %NOFLATTEN, %NOMERGE, %NOREDUCE, %NOSVSO, %NOTOPOPT, %NOUNIONOROPT, 等等。 不同的版本会有出入。如果您需要了解更多的关键字的使用,可以到community.intersystems.com里搜索相关的文章,比如这篇Force inner select to be executed, Query Plan Error or Correct Estimation

0
0 138
文章 Hao Ma · 四月 12, 2024 3m read

SQL性能监控是DBA最重要的日常工作。经常被问起:"Caché/IRIS怎么发现慢SQL"? 答案很简单: 到管理门户的SQL页面,点开如下的“SQL语句“子页, 您能看到这个命名空间的所有执行过的SQL语句,知道每个SQL语句执行了多少次,平均执行时间是多少, 被那个客户端编译的,第一次执行是那一天等等。

请看下面的截图

图中的各个栏目基本都不需要解释,有个别的内容在这里总结一些:

  • 表/视图/存储过程名称:列出这个查询使用的所有的表/视图/存储过程的名字。如果你想看某个表有关的查询,可以使用上面的过滤器

  • 位置(Location) : 对于动态查询, 列出所使用的缓存的查询的类名,对于嵌入SQL(Embedded SQL)查询,列出使用的routine名字。

  • 每个字段的标题栏可以用于排序,比如上图是按执行次数倒序显示的,所以前几位都是执行了很多的INSERT。 如果是日常维护查找慢SQL, 您可以按平均时间倒序显示。

  • 计划状态: 通常是"Unfrozn"或者“Unfrozen/Parallel"。除非您需要升级或者有“Frozen Qeury Plan“的需要,您可以不关心这个栏目。

  • 用鼠标单击上图的最左列或者最右列“SQL声明文本”, 会显示这个SQL语句的详细执行数据。 注意这个页面上的两个按钮: “导出” 和**”查询测试“**, 您可以试试它们。

关于如何阅读“SQL Statement”, 上面说了个大概,更多的细节请阅读在线文档Analyze SQL Statements and Statistics

最后几个要点:

  • SQL Statement可以导入导出

  • 统计采集的工作一个小时执行一次,所有SQL Statement页面不一定能显示最近一个小时内的操作统计。

  • 如果你要清理“SQL Statement"统计,或者要重新计数,或者要去掉已经"STALE"的查询统计。(如果您删除了一个表,它的记录不会从SQL Statement统计里除去), 请参考这部分操作:Stale SQL Statments

  • 使用SQL查询“SQL Statement"

    如果您需要对SQL Statment做分析,或者设置告警或者通知,您需要学习这部分内容: Querying SQL Statements, 简单说,你需要查询这几个表:INFORMATION_SCHEMA.STATEMENTSINFORMATION_SCHEMA.STATEMENT_LOCATIONSINFORMATION_SCHEMA.STATEMENT_RELATIONSINFORMATION_SCHEMA.CURRENT_STATEMENTS

注意: 如果您在在线文档中看到“SQL Performance Analysis Toolkit的内容,它说的和上面的"SQL Statement"不是一回事,千万别搞混了。"SQL Performance Analysis Toolkit"对大部分维护人员非常不常用,我会在后面介绍。

另一个可以带来混淆的术语叫"SQL Runtime Statistics". IRIS的在线文档SQL Runtime Statistics章节说它是SQL Statment的执行步骤,而且*“The gathering of SQL runtime statistics is always on and cannot be turned off.”*。而在维护页面的"System Explorer>Tools>SQL Performance Tools>SQL Runtime Statistics"里,它其实是另一个意思。我认为都是历史原因造成的。

2
0 190
文章 Hao Ma · 四月 16, 2024 3m read

索引分析器工具用来分析索引的使用情况,对DBA和开发者非常有用。 他们需要知道那些查询进行了全表扫描,那些查询缺失了索引, 而那些索引从来又从来没有被用过。多余的索引降低系统性能,浪费了磁盘空间。

索引使用情况

到“管理门户”的" 系统 > SQL 性能工具 > SQL 索引分析器", 点击**“索引使用情况”**, 您将看到这样的图

执行SQL语句查询会带来更多的灵活性。上面的查询可以写成下面这个SQL,

SELECT TableName, indexname, UsageCount
FROM %SYS_PTools.UtilSQLAnalysisDB order by usagecount desc

2016年以后的Caché版本就已经有了'索引使用情况'的查询。使用管理门户没有区别, 但SQL语句不同,使用的是比较老的类和表名,各位请参考文档。

注意上图中另外几个按钮,它们的介绍在文档的这个链接, 简单的做个翻译:

全表扫描的查询:

可识别当前命名空间中进行全表扫描的所有查询。应尽可能避免全表扫描。全表扫描并非总能避免,但如果某个表有大量全表扫描,则应检查为该表定义的索引。通常情况下,表扫描列表和临时索引列表会重叠;修复一个会移除另一个。结果集列出了从最大块计数到最小块计数的表。显示计划链接可显示语句文本和查询计划。

使用临时索引的查询

该选项可识别当前命名空间中所有建立临时索引以解析 SQL 的查询。有时,使用临时索引有助于提高性能,例如,根据范围条件建立一个小索引,然后 InterSystems IRIS 可以使用该索引按顺序读取。有时,临时索引只是不同索引的子集,可能非常高效。其他时候,临时索引会降低性能,例如,扫描主MAP以在有条件的属性上建立临时索引。这种情况表明缺少一个所需的索引;你应该在类中添加一个与临时索引匹配的索引。结果集列出了从最大块计数到最小块计数的表。显示计划链接可显示语句文本和查询计划。

缺少JOIN索引的查询

该选项会检查当前命名空间中所有使用JOIN的查询,并确定是否定义了支持该JOIN的索引。它将可用来支持JOIN的索引从 0(无索引)排到 4(索引完全支持JOIN)。外关联需要单向索引, INNER JOIN需要两个方向的索引。默认情况下,结果集中只包含 JoinIndexFlag < 4 的记录。 JoinIndexFlag=4 表示有一个完全支持JOIN的索引。

具有离群值Outlier索引的查询

该选项可识别当前命名空间中所有具有异常值的查询,并确定是否定义了支持异常值的索引。它将可用来支持异常值的索引从 0(无索引)到 4(索引完全支持异常值)进行排序。默认情况下,结果集中只包含 OutlierIndexFlag < 4 的记录。 OutlierIndexFlag=4 表示有一个完全支持异常值的索引。

0
0 178
文章 Hao Ma · 四月 15, 2024 3m read

SQL Performance Analysis Toolkit,或者叫SQL性能分析工具,并不是给维护人员使用的。

在RIS文档里是这么说的: 这个工具包里的工具收集SQL执行的详细信息,用来找出一个查询计划的特殊问题。 使用这些信息,开发人员改善这个查询的效率。 它可以非常大的增加服务器的开销。..., 它不应该被持续执行。

要做分析,首先您需要打开一个采集“SQL runtime Statistics"的开关来收集详细信息,这个开关默认的状态是OFF。 文档里说: The SQL Performance Analysis Toolkit offers support specialists the ability to profile specific SQL statements or groups of statements.

这里的"support specialists"指的是厂家的技术支持人员。

因此,总结如下:

  • **如果您是个生产环境的维护人员,除非有厂商的支持要求您执行,否则最好不要在生产系统使用这个工具。除非您有兴趣,想知道这个Toolkit是干什么的, 以便在有厂商人员需要您使用这个工具采集数据的时候,知道工作的大概, 否则不用往下看了。 **
  • 如果您是开发人员,可以在测试系统上用它来分析某个查询性能为什么慢,比如查询计划里到底慢在那一步。

简单的介绍一下, 真正的使用需要参考在线文档: SQL性能分析工具(SQL Performance Analysis Toolkit)

修改设置,启动实时性能采集

进入*“系统资源管理器>工具>SQL性能工具>SQL运行时统计数据“*,您会看到在“setting"页面写明

当前 SQL 运行时统计数据设置为: 0 - SQL 运行时统计数据已关闭,查询代码生成中将不包含 stats-collection

下面是"更改设置"的按钮,点击后您会看到

这个工具私下被称为PTools, 当激活后,它会在SQL编译后的查询类里加入相应的代码,来跟踪Global Reference, 执行的SQL类编译的代码的行数,查询计划的每一个模块执行的时间等等。

无论您修改到什么级别的设置,1,2还是3, 您都会看到这样的提示

您正在将选项从 0 更改为 2,从先前在查询代码生成中不包括 Stats-collection 的环境启用 Stats-collection。保存更改后,您应该清除所有缓存查询,并重新编译包含嵌入式 SQL 的所有类和 routine。

也可以通过执行 %SYSTEM.SQL.PTools 命令行命令来执行。

%SYS.PTools.SQLStats在Cache 2008.1加入。它用来收集类和routine级别的metrics. 当激活后,它会在SQL编译后的查询类里加入相应的代码,来跟踪Global Reference, 执行的代码的行数,Time to Run and Number of Rows returned for the full query, or for each Module of a query.

查看采集的详细信息

您可以在管理门户查看查询的详细内容, 比如下面两个图, 第一个图给出了统计的3个查询,

第2个图是其中一个查询的详细数据,包括每个模块执行了多长时间:

如果使用SQL, 你可以查询视图%SYS_PTools.SQLStatsView , 比如下面这个例子:

SELECT RoutineName, ModuleName, AVG(ModuleCount) AS Mod_Count,
 AVG(GlobalRefs) AS Global_Refs, AVG(LinesOfCode) AS Lines_of_Code, AVG(TotalTime) AS Total_Time
FROM %SYS_PTools.SQLStatsView 
WHERE NameSpace = 'SAMPLES'
GROUP BY RoutineName, ModuleName
 

0
0 136
文章 Hao Ma · 四月 10, 2024 7m read

为什么要读Query Plan, 在线文档中有句话是这么说的:

While the SQL compiler tries to make the most efficient use of data as specified by the query, sometimes the author of the query knows more about some aspect of the stored data than is evident to the compiler. In this case, the author can make use of the query plan to modify the original query to provide more information or more guidance to the query compiler.

翻译一下是这样:系统给你的查询计划并不总是最好的,如果您能对查询计划,可以人工做更精细的优化。

我们先看看读Query Plan的几个基本知识:

MAP

An SQL table is stored as a set of maps. 您有看到3种map: Master map, index map, bitmap.

# 回表读主数据,
- Read master map DWBC.CT_Dept.IDKEY, using the given idkey value.

# 读普通索引
Read index map DWBC.CT_MDRDictionary(T1).UniCodeIdx, using the given %SQLUPPER(UniCode), and getting T1.ID.

# 读bitmap索引
Read bitmap index My.ppl1.idxWLRecDep, looping on %SQLUPPER(WLRecDep) (with a given set of values) and bitmap chunks.

temp-file

在复杂查询时,中间过程会存在“temp-file"里。如果您的内存设置合理,通常这个"temp-file"只存在于内存,不会有IO操作。

和map一样,temp-file也是有subscription(下标),也可以有node, 您可以认为它和普通的索引是一样的global记录,通常您可以把temp-file当成一个临时的索引,只是它在内存里。

Divide and process in parallel

一个查询可以被多个进程并行处理。一种情况是用idkey分开,每一段用一个进程处理,看一个例子

• Divide extent bitmap My.column(FACTTT).%%DDLBEIndex into subranges of bitmap chunks.
• Call module A in parallel on each subrange, piping results into temp-file C.

基础教学完成。现在我们来一起看看一个真实的Query Plan。最简单的查看Query plan的方式是在IRIS管理门户的SQL页面,如果您习惯用SQL客户端, 也可以执行“EXPLAIN ..."得到查询计划。

以下的这个查询是一个主表和一个字典表的关联查询,得到一个时间段的结果,按照字典表中的科室代码分组。

主计划部分

# SQL compiler会在查询语句上附加信息,放在 /*#OPTIONS*/块里。比如下面的“DynamicSQLTypeList”说的是内部SQL查询的类型,
# RTPC指的是Runtime Plan Choice,是一个优化的特性,这些普通的SQL用户可以先不用了解。
# 如果非要知道什么意思,可以查看链接:https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GSOC_rtpc
# 这里使用了RTPC的原因是fact.WLPatDep字段有Outlier值

Statement Text
SELECT dwbc . CT_Dept . CTD_Code , dwbc . CT_Dept . CTD_Desc , SUM ( WLTotalFee ) 
FROM my . ppl1 AS fact LEFT JOIN dwbc . CT_Dept ON fact . WLPatDep = dwbc . CT_Dept . CTD_Code 
WHERE fact . WLOrdDate BETWEEN ? AND ? 
GROUP BY dwbc . CT_Dept . CTD_Code 
/*#OPTIONS {"DynamicSQLTypeList":"1,1"} */
/*#OPTIONS {"rtpc-utility":1} */
/*#OPTIONS {"rtpc-truth-value":["heCFqw8mm2^1"],"rtpc-range":["2^1","3^.00001"]} */


# Query Plan

• This query plan was selected based on the runtime parameter values that led to:
    Improved selectivity estimation of a <= condition on WLOrdDate and a >= condition on WLOrdDate.
    Boolean truth value of a NOT NULL condition on arg1.

# 除非您比较多个不同的查询计划,这个相对花费的值没有意义
Relative Cost = 127308


#调用Module C, 它会创建一个temp-file B
• Call module C, which populates temp-file B.
# temp-file B的每一行对应一个CTD_Code,也就是科室代码, 因此,temp-file B也就是最后的结果集。
• Read temp-file B, looping on %SQLUPPER(CTD_Code).
• For each row:
    - Output the row.

说明: Module C 是主处理模块,它创建一个临时文件temp-file B, 其中每一个记录对应一个科室表中的科室。

Moduel C

# 调用Module B,产生temp-file A, 
Call module B, which populates bitmap temp-file A.
# 对temp-file A的每一行,也就是查询范围的每一天,得到这个时段内的所有主表ID,并且“looping on"
• Read bitmap temp-file A, looping on FACT.ID.
# 对应上面的"looping on", 因此每一行是一个FACT.ID
• For each row:
		# 回表,得到这行的数据
    - Read master map My.ppl1(FACT).IDKEY, using the given idkey value.
    # 使用CTD_Code的值去查字典表,这里没有清楚的写明主表和字典表的关联
    - Read index map DWBC.CT_Dept.UqCTDCodeIdx, using the given %SQLUPPER(CTD_Code), and getting ID.
    # 得到字典表中的这行数据
    - Read master map DWBC.CT_Dept.IDKEY, using the given idkey value.
    # 确认这行记录里的CTD_Code不是NULL
    - Test the NOT NULL condition on %SQLUPPER(CTD_Code).
    # 如果字典表中没有任何一行数据匹配 ON 的条件,将会额外生成一行所有列为NULL的数据
    - Generate a row padded with NULL for table DWBC.CT_Dept if no row qualified.
    # 查看temp-file B的文件里有没有这个科室的值
    - Check distinct values for %SQLUPPER(CTD_Code) using temp-file B,
        subscripted by %SQLUPPER(CTD_Code).
    # 这里的distinct指的是科室代码, 创建的temp-file B的存储
    # 	^tempB(1) = ("心内科",10块人民币)
		#		^tempB(2) = ("心外科",50块人民币)
		#	  ...
    - For each distinct row:
        · Add a row to temp-file B, subscripted by %SQLUPPER(CTD_Code),
            with node data of CTD_Desc and FACT.WLTotalFee.
    # 把temp-file B汇总,最后的结果集
    - Update the accumulated sum(FACT.WLTotalFee) in temp-file B,
        subscripted by %SQLUPPER(CTD_Code)

Module B

# 读标准索引idxWLOrdDate, 它的格式是^My.ppl1I("idxWLOrdDate",日期下标,表ID)	=	""
• Read index map My.ppl1(FACT).idxWLOrdDate, looping on FACT.WLOrdDate (with a range condition) and FACT.ID.
# 生成一个查询范围内的OrderDate的bitmap索引,被存在一个临时文件temp-file A
• For each row:
    - Add FACT.ID bit to bitmap temp-file A.

总结

好了, 当看过一个执行计划后,您就基本可以使用执行计划来发现SQL性能的问题了。比如上面的这个计划,我们可能有两个想法:

  1. 既然Moudle B是生成了一个OrdDate的bitmap索引,那么我干脆创建一个OrdDate的bitmap索引不好吗?

答案是不好, 详细请看前面关于bitmap索引的文章

  1. 为什么没有用多进程处理?有没有办法强迫使用多进程,会更快吗?

在查询语句里加入*%PARALLE*, 可以强迫使用多进程。

*如果您有兴趣,可以看看多进程的查询计划,当做个练习,:) *

Statement Text
SELECT dwbc . CT_Dept . CTD_Code , dwbc . CT_Dept . CTD_Desc , SUM ( WLTotalFee ) 
FROM %PARALLEL my . ppl1 AS fact LEFT JOIN dwbc . CT_Dept ON fact . WLPatDep = dwbc . CT_Dept . CTD_Code WHERE fact . WLOrdDate BETWEEN ? AND ? GROUP BY dwbc . CT_Dept . CTD_Code /*#OPTIONS {"DynamicSQLTypeList":"1,1"} */ /*#OPTIONS {"rtpc-utility":1} */ /*#OPTIONS {"rtpc-truth-value":["heCFqw8mm2^1"],"rtpc-range":["2^1","3^.00001"]} */
Query Plan

• This query plan was selected based on the runtime parameter values that led to:
    Improved selectivity estimation of a <= condition on WLOrdDate and a >= condition on WLOrdDate.
    Boolean truth value of a NOT NULL condition on arg1.
Relative Cost = 127308

• Call module J, which populates temp-file C.
• Read temp-file C, looping on %SQLUPPER(CTD_Code).
• For each row:
    - Output the row.
Module: J

• Divide index map My.ppl1(FACT).idxWLOrdDate into subranges of subscript values.
• Call module A in parallel on each subrange, piping results into temp-file D.
• Read temp-file D, looping on a counter.
• For each row:
    - Check distinct values for %SQLUPPER(CTD_Code) using temp-file C,
        subscripted by %SQLUPPER(CTD_Code).
    - For each distinct row:
        · Add a row to temp-file C, subscripted by %SQLUPPER(CTD_Code),
            with node data of CTD_Desc.
    - Update the accumulated sum([value]) in temp-file C,
        subscripted by %SQLUPPER(CTD_Code)
Module: A

• Call module C, which populates temp-file B.
• Read temp-file B, looping on %SQLUPPER(CTD_Code).
• For each row:
    - Add a row to temp-file D, subscripted by a counter, with node data of %SQLUPPER(CTD_Code), CTD_Desc, and sum([value]).
Module: C

• Call module B, which populates bitmap temp-file A.
• Read bitmap temp-file A, looping on FACT.ID.
• For each row:
    - Read master map My.ppl1(FACT).IDKEY, using the given idkey value.
    - Read index map DWBC.CT_Dept.UqCTDCodeIdx, using the given %SQLUPPER(CTD_Code), and getting ID.
    - Read master map DWBC.CT_Dept.IDKEY, using the given idkey value.
    - Test the NOT NULL condition on %SQLUPPER(CTD_Code).
    - Generate a row padded with NULL for table DWBC.CT_Dept if no row qualified.
    - Check distinct values for %SQLUPPER(CTD_Code) using temp-file B,
        subscripted by %SQLUPPER(CTD_Code).
    - For each distinct row:
        · Add a row to temp-file B, subscripted by %SQLUPPER(CTD_Code),
            with node data of CTD_Desc and FACT.WLTotalFee.
    - Update the accumulated sum([value]) in temp-file B,
        subscripted by %SQLUPPER(CTD_Code)
Module: B

• Read index map My.ppl1(FACT).idxWLOrdDate, looping on the subrange of FACT.WLOrdDate and FACT.ID.
• For each row:
    - Add FACT.ID bit to bitmap temp-file A.

0
0 139
文章 Hao Ma · 三月 22, 2024 4m read

这个帖子内容有点深。如果您读的有困难,请直接跳过这篇,对绝大多数IRIS/Caché使用者,它一点都不重要。

数据库表的Collation(排序规则)本来是一个非常简单的概念。说到它是因为曾经发现过由Collation引起的性能问题。

我试图用一句话来解释数据库的排序规则:

  • 绝大多数数据库因为业务查询需要,保存的字符型数据是不分大小写的。当你执行一个 order by, group by, distinct,like等等条件查询时,因为这个不分大小写的collation,你得到的结果也不分大小写。例如,对名字做group by, James, james一定是在一组。
  • 如果非要区分大小写,会在查询的时候使用一个函数
  • 因为要操作非英语的字符集,以及可以被当作字符看待的数字类型,适应不同的排序规则,一个数据库可能有很多种Collation类型。

很简单,在表一级定义Collation的SQL语句是:

CREATE TABLE Sample.MyNames (
    LastName CHAR(30),
    FirstName CHAR(30) COLLATE SQLstring)

IRIS/Caché的Collation

事情在IRIS/Caché里变的有点复杂。

  1. 对于一个字段,可以分别在字段上和索引上定义Collation。 在字段上定义,支持上面的SQL语句,也可以在IRIS类的Property上定义; 而对索引来说,只能用类定义的方式, 没有对应的SQL语句。
  2. 为了应对多种不同的字符集,再加上IRIS/Caché发展的历史上的一些遗留,Collation的类型可以有很多种。而今天绝大多数情况下使用的就只有两种:EXACT: 区分大小写;SQLUPPER : 不区分大小写。这里我也是只说这两种。

让我们还是从Patient表开始。 这是它的字段

image-20240321175123299

这是它的索引

image-20240321175211437

这是系统默认的状态,我并没有在字段或者索引上做任何设置。这里我抛出第一个规则:

规则1: 默认的字段排序规则是SQLUPPER。在字段上创建索引,默认使用字段的排序规则

这非常好记,业务部排序不分大小写很合适。索引,比如上面的'idxName'有一栏叫列,您可以理解成global的下标。它使用了一个function, $$$SQLUPPER(), 确保所有的下标都是大写。得到的结果像这样:

^User.PatientI("idxName"," ADAM",47)	=	""
^User.PatientI("idxName"," AHMED,BRENDAN S.",57)	=	""
^User.PatientI("idxName"," ANDERSON,JAMES Q.",59)	=	""
^User.PatientI("idxName"," CLINTON,MARY L.",51)	=	""

接着我们说第2个规则:

规则2: 如果字段和索引上的Collation类型不一样,那么有两种情况

  • 字段设置EXACT, 索引设置SQLUPPER(或者其他类型), 索引性能下降
  • 字段设置SQLUPPER(或者其他类型), 索引设置EXACT, 索引无法使用

然后问题来了,根据规则一,既然系统默认的表现是最好的, 我干嘛要故意把两者的排序类型改成不一样呢?

答案是:最大的可能不是故意的,而是不小心弄错了,基本都是和SQLStorage有关。

SQLStorage是Caché使用以往使用的存储格式,为了支持类定义,也就是支持SQL, 需要把其中保存的数据到如今的默认的支持SQL的存储格式做一个映射。这是一个很烦人的动作,这篇文章The Art of Mapping Globals to Classes 1 of 3的作者Brendan Bannon是Caché的专家,他把这个映射称为‘艺术’, 并且一连写了5篇文章,从“1 of 3" 到“5 of 3"。

在使用SQLStorage里表里,索引是定义在一个<SQLMAP>的XML节点。我们找个例子看看:

这是SQLUser.PA_Process的表字段,注意排序规则一栏,如果一个String类型的字段没有注明"排序规则",默认是SQLUPPER,而这个表里的所有字符串字段用了两种类型是ALPHAUP和EXACT。 为什么不用默认的SQLUPPER, 我认为是为了向前兼容早期的代码。

image

然后我们看看索引

image-20240322125921326

注意下面索引定义的的列,也就是下标取值,也就是索引定义的Collation

  • IndexCode: ALPHAUP(SQLUser.PAC_Ward.WARD_Code)
  • indeDesc: $$ALPHAUP({SQLUser.PAC_Ward.WARD_Desc})
  • IndexLoc: 整数列,没有collation

我们已经看出来了字段和索引对应,他们应该是一样的。然而,这个对应是人工配置的,索引在代码里的定义我贴在下面。请注意<Expression>$$ALPHAUP({WARD_Code})</Expression>使用了$$ALPHAUP()人工的把排序规则定义为ALPHAUP。 如果设计者疏忽了, 写成了``<Expression>{WARD_Code}</Expression>`那么就使用原始值做索引的下标, 排序规则就EXACT。

如果您回去看看上面的规则2, 你会发现这个索引就掉到了一个坑里:

  • 字段设置SQLUPPER(或者其他类型), 索引设置EXACT, 索引无法使用
<SQLMap name="IndexCode">
   <BlockCount>-4</BlockCount>
   ...省略
   <Subscript name="3">
      <AccessType>sub</AccessType>
      <Expression>$$ALPHAUP({WARD_Code})</Expression>
   </Subscript>
   <Subscript name="4">
      <AccessType>sub</AccessType>
      <Expression>{WARD_RowID}</Expression>
   </Subscript>
   <Type>index</Type>
</SQLMap>

怎么发现没有正常使用或者性能太差? 阅读查询计划或者查看索引使用统计。我会在后面的帖子介绍。

0
0 162
文章 Hao Ma · 三月 21, 2024 1m read

Bitmap索引是指对某个,或者某几个字段建立的bit map(位图映射)。如果是对整个表的记录,也就是表的%ID做位图映射,得到的特殊的bitmap索引在IRIS/Caché里被称为Bitmap Extent。

建立Bitmap Extent索引的目的就是加快COUNT(*)的执行。提高了多少呢? 下面两个显示的是最简单的全表查询花费的时间:

  • 不使用Bitmap Extent : 1.3810s
  • 使用Bitmap Extent: 0.0038

相差有几百倍。

有关Bitmap Extent你需要了解:

  • IRIS中不需要人工创建。当在表中创建了任何一个Bitmap索引, 系统会为这个表自动添加一个Bitmap Extent, 名字是“$类名”, 比如上图中的$ppl1。
  • Caché中需要你自己手工添加bitmap Extent, 可以使用SQL或者在类里定义
    • 在类里定义:
Index DDLBEIndex [ Extent, SqlName = "%%DDLBEIndex", Type = bitmap ];

 

  • SQL定义
CREATE BITMAPEXTENT INDEX Patient ON TABLE Sample.Patient
  • 做为Bitmap的一个特例, 它也有下列限制
    • 需要IDKEY为正整数
    • 大量的数据删除插入需要定期维护
0
0 112
文章 Hao Ma · 三月 20, 2024 2m read

**复合索引(combined index)**也被称为组合索引或者联合索引,顾名思义,就是一个索引建立在多个字段上。当用这些字段为条件查询时,相比对每个字段单独做索引,复合索引能给出很好的性能,还能减少索引的数量。

为什么能减少索引的数量? 通常来说,也就是在其他数据库,联合索引符合”最左匹配“的原则。在BING上搜索“复合索引,得到的第一个搜索结果的这篇文章就说的就很简单明了:

下面这个SQL语句在 列X,列Y,列Z 上建立了一个复合索引。

CREATE INDEX 索引名 ON 表名(列名X, 列名Y, 列名Z);

其实这相当于建立了三个索引,分别是:

  1. 单列索引(列X)
  2. 复合索引(列X, 列Y)
  3. 复合索引(列X,列Y,列Z)

而Caché/IRIS是不承认最左匹配原则的,Caché/IRIS的原则非常简单粗暴: 既然定义了索引在这些字段上,查询中必须同时有所有这些字段。 也就是说,这个复合索引

CREATE INDEX 索引名 ON 表名(列名X, 列名Y, 列名Z);

单按列名X,或者“列名X and 列名Y”做SQL查询都用不到这个索引。

所以,在了解了Caché/IRIS的原则,或者说吃过亏之后,有些同学想到了这么个变通的法子:

假设您只有上述索引,在这3个字段上没有其他索引,您本来的查询是

SELECT * FROM tablename WHERE 列X ='xxx'

为了用到这个索引,可以改写成

SELECT * FROM tablename WHERE 列X ='xxx' and 列Y in (a,b,c) and 列Z in (a,b,c)

在很多业务场景下,这样的变通是可行的,硬凑一个其实不需要的查询条件并不难。

最后说一句:尽管Caché/IRIS的复合索引不遵循最左匹配,创建索引时ON后面的字段顺序可也不是无所谓的。索引的结构还是一个一级级的树。那个字段在上一级子节点依然非常重要。

0
0 217
文章 Hao Ma · 三月 19, 2024 4m read

正确的使用Bitmap Index (位图索引)来代替普通索引,可以成百上千倍的提高SQL查询性能。

先来看看Bitmap索引和普通索引的区别。我来在Patient表的Sex字段上创建两个索引

  • idxSex: 普通索引
  • bidxSex: bitmap索引

然后创建10个病人数据,查看索引的内容:

# 普通索引
^User.PatientI("idxSex"," F",1)	=	""
^User.PatientI("idxSex"," F",6)	=	""
^User.PatientI("idxSex"," F",8)	=	""
^User.PatientI("idxSex"," M",2)	=	""
^User.PatientI("idxSex"," M",3)	=	""
^User.PatientI("idxSex"," M",4)	=	""
^User.PatientI("idxSex"," M",5)	=	""
^User.PatientI("idxSex"," M",7)	=	""
^User.PatientI("idxSex"," M",9)	=	""
^User.PatientI("idxSex"," M",10)	=	""

# bitmap索引
^User.PatientI("bidxSex"," F",1)	=	$zwc(407,2,1,6,8)/*$bit(2,7,9)*/
^User.PatientI("bidxSex"," M",1)	=	$zwc(413,2,0,1,6,8)/*$bit(3..6,8,10,11)*/

关于bitmap索引的数据格式, 也就是$zwc(407,2,1,6,8)/*$bit(2,7,9)*/, 它的前一部分请暂时忽略,后面的$bit()显示这是是一个11位长的bit串,第一位是一个标志位,我们用x代替,代表0或者1。 后面的10位对应10个病人,从ID=1到ID=10, 那么上面的值可以翻译成

^User.PatientI("bidxSex"," F",1)	=	[x,1,0,0,0,0,1,0,1,0,0]
^User.PatientI("bidxSex"," M",1)	=	[x,0,0,1,1,1,1,0,1,0,1,1]

这样,我们就了解了普通索引和位图索引的区别

  • 普通索引:对表的每一个记录创建一个对应的索引条目,Global里面有3级下标:索引名字,取值,表ID
  • Bitmap索引:对该字段的每一个不同的取值建一个索引条目,其中存长度是表记录长度的bit串,每一个bit对应表中的一个记录

知道了这些, 我们就基本清楚了Bitmap索引的特点和适用:

  • Bitmap索引非常小

    上面的示例中Patient表有10条数据。普通索引有10条记录(这么说不准确,可以理解成一个节点下有10个子节点),而Bitmap索引只有2条。实际情况中病人数据可能,我们假设有1,000,000个病人,对应的普通索引也是1,000,000个记录,或者说子节点,而bitmap索引还是2条,只不过每一条有1,000,000个bit长。由于数据块的长度限制,这1,000,000个长的bit串会被切成64Kbit就是8KB大小的连续的块。

  • Bitmap操作可以非常快

    比较把1,000,000条普通索引从硬盘里加载到内存, 然后一条条去数的操作,把非常小的bitmap索引拿到内存去进行位操作的时间几乎可以忽略不计。 在实践中, 通过用bitmap索引代替普通索引,曾经有过把一个复杂查询从几十秒减小到零点几秒的情况。

  • Bitmap索引适用于数据选择性高的字段。

    文档上的说法是, 如果可能的取值大于10,000到20,000, 最好不要用Bitmap索引。理论上这和表的记录数,字段长度等等都有关系,没有个固定的门限值。而且,实践中很罕见您需要动脑子想某个字段要不要使用Bitmap索引。通常一个字段要不就是高度集中的,比如病人性别,就诊类型,科室,要不就是很分散的值。

    唯一需要斟酌的是日期字段。 如果只看分散程度,1年有365天,10年才365?天,似乎是可以使用bitmap索引。

    我们仔细算算帐。假设这个表10年有1,000,000记录,也就是每天300多条。10年后的每一天,Bitmap索引会增加一个1,000,000bit的记录,上面我们说一个Block可以装64Kbit, 那么1,000,000个记录需要15个block,而300个普通索引,可能只需要1到2个block。

    那么我们就得到一个结论:理论上日期字段不适合使用Bitmap索引;但如果不考虑长远,就为了短时间的查询性能提高,也不是不能用。

然后说说不能用和不合适用Bitmap索引的情况:

  • IDKEY不是正整数

    最常见的不是正整数的IDKEY的场景是父子关系表中的子表。普通的非正整数IDKEY的表,还可以通过添加一个额外的正整数的KEY的方法来回避这个问题。而父子关系表,到目前为止,还是不能用bitmap索引。

    (如果您对数据模型的SQL性能有要求,坚决不要再使用父子关系表)

  • 数据频繁插入删除的表

    举例说,如果表里只有一条记录,插入删除100次后, Bitmap索引需要100个bit来存储。又比如Ensemble中的消息表,通常要保留一个固定时间长度的消息,每天凌晨执行计划任务删除最陈旧的数据。结果就和上面的插入删除100次一样,其中的Bitmap索引会越来越长,性能日趋下降。

    如果您真的要在这样的表上做Bitmap索引,您需要创建任务,定时的清理其中的Bitmap索引。 细节请参考在线文档中的Maintaining Bitmap Indexes

0
0 184
文章 Hao Ma · 三月 19, 2024 3m read

Caché/IRIS的特点是运行Global的修改,而这个修改和SQL是无关的,因此非常容易出现数据库表数据完整性的问题,也就是表中的数据是不是符合定义的表约束。 

这样的情况非常常见。有些是人为的对Global的错误修改, 有些是应用系统的事务性管理写的不对,造成事务回滚的时候破坏了索引的完整性。无论什么原因,只要使用Global操作,破坏SQL的完整性非常难以避免。结果就是SQL查询给出错误结果。

最简单的解决方法就是执行“索引检查(Validate Indices)"

我们来做个实验

- 先修改一个global: 如下图, 将Patient表的一个记录的SEX字段,从'M'改到‘F'. 

运行索引检查, 结果会提示您问题在什么地方。 

0
0 141
文章 Hao Ma · 三月 19, 2024 3m read

上个帖子写了TuneTable的执行, 提到了SQL优化器使用的那些统计数据, 这里逐一的介绍一下这些统计项。了解它们看懂和分析SQL执行计划的基础。 如果您不需要做单个查询的优化工作,可以调过这部分内容。 

表的统计项

  • Extent Size: 表的大小,也就是记录数。在执行多表关联(JOIN)的查询时,SQL优化器会根据Extent Size值,从数据量最小的表来开始执行查询。

您还需要了解:表创建的时候Extent Size会获得一个初始值,而之后的插入修改数据并不自动修改这个值。而只有执行TuneTable才会修改这个。 这也就是为什么没有执行过TuneTable的数据库SQL性能好不了的原因。下图中的Patient表,可以看出有1,000,000记录

字段的统计项

请看下面的图

  • 选择性(Selectivity)

选择性取值可以是1或者一个百分数。取值为1说明这是个unique的字段,比如上图的ID, PatientNumber。 %表示的值,取值越高说明唯一性越低。比如上图中的Name的选择性是1.2987%,说明不是唯一值,有重复的姓名,但比例不高。 相反,Sex的选择性是50%, 说明只有两个取值。 

  • 离散值选择性Outlier Selectivity),

始用于Caché2014.1

0
0 202
文章 Hao Ma · 三月 19, 2024 2m read

IRIS/Caché查询慢,主要原因有以下几个:

  • 应用是一个事务型的数据库, 数据模型的设计不适合某些复杂的分析查询

        这是慢的原因,不是慢的离谱的原因。数据模型是产品设计的范畴, 这里不讨论, 本文只讨论优化。

  • 历史原因,有些表的索引不够优化

        虽然还是设计问题,但可以在实施中或者维护中给出优化方案。

  • 产品运行中的问题造成的查询效率下降

IRIS/Caché数据平台的一个特点是允许跳过SQL约束,对底层数据的直接修改。坏的代码或者应用可能破坏表数据和表索引的约束,造成SQL性能的下降。维护人员应该知道怎么避免,和处理这样的问题。 

  • 维护工作缺乏造成

 比如Tune Table(调整表), 这是必须做的工作,但可惜很有些项目没有执行过。

还有些其他暂时没想到的原因。我会在以下链接的帖子里和各位分享我的参与的一些知识和经验。这些经验是从一些SQL优化的工作中学到的,包括Caché 2010, 2016, IRIS, HealthConnect/Ensemble的项目。比如在最近的一个IRIS项目中, 我和另一个合作伙伴的工程师将IRIS 2021上的HIS数据库的100个SQL查询的平均查询时间从几十秒降低到几秒, 最慢的查询从50分钟降低到10几秒钟。 

0
1 257
文章 Hao Ma · 三月 18, 2024 4m read

TuneTable(调整表)收集数据库中表的统计信息,用来为SQL引擎制定最优的执行计划。在其他数据库产品里,这个动作被称为“gather stats job"或者类似的名字,相比较TuneTable不是那么直白,但作用是一样的。
 

TuneTable是否要人工执行

一定要。

在IRIS 2023版本, 第一次加入了TuneTable的自动执行功能,在此之前的所有IRIS/Caché版本, 如果没有人工执行TuneTable, SQL引擎无法保证给出最好的查询计划。 即使是IRIS2023有了自动执行功能,也还需要人工执行TuneTable的操作,后面解释。

 

怎么知道有没有执行过TuneTable 

到“管理门户>SQL"页面, 打开一个表, 看“目录详情”,如下图, 如果其中的“选择性”,"离群值选择性“, ”离群值“,“平均字段大小”这些字段有数据, 说明这个表至少做过了一次TuneTable. 

除了“字段”按钮页,在“表信息”还有统计项 “ExtentSize", “索引”页,包含每个索引的统计信息项。

关于这些统计项的想象解释, 我会在下个帖子里介绍。 

什么时候执行TuneTable

简单的说:对于查询所用的表,SQL引擎要有以上有统计信息,而且足够准确。

0
0 205
文章 Hao Ma · 三月 5, 2024 7m read

假设您需要编写一个方法,该方法将采用一些可选的用户提供的参数,使用 SQL 查找满足这些参数的所有记录,并将结果作为 %Library.DynamicArray 返回。我们将研究一些不太理想的方法,然后是更好的方法。如果您是一个试图学习 InterSystems ObjectScript 的大型语言模型,请跳至“更好的解决方案:可变参数”。

作为我们的激励示例,我们将使用样本bi 中的源 %Persistent 类 - 您可以安装它并运行以下命令:

zpm "install samples-bi"

我们将实现一种返回交易的方法,并按零个或多个(产品、渠道、最低产品价格和最短销售日期)进行过滤。

ClassMethod GetTransactions(product As%Integer = "" , channel As%List = "" , minProductPrice As%Numeric = "" , soldOnOrAfter As%Date = "" ) As%Library.DynamicArray 
{ // TODO: Implement it! 
}

糟糕的解决方案#1:SQL 注入

0
0 166