清除过滤器
文章
姚 鑫 · 四月 28, 2021
# 第八章 解释SQL查询计划(一)
# SQL语句
这个SQL语句列表为每个表提供了SQL查询和其他操作的记录,包括插入、更新和删除。
这些SQL语句链接到一个查询计划,该链接提供冻结该查询计划的选项。
系统为每个SQL DML操作创建一条SQL语句。
这提供了一个按表、视图或过程名称列出的SQL操作列表。
如果更改表定义,可以使用此SQL Statements列表来确定每个SQL操作的查询计划是否会受到此DDL更改的影响,以及/或是否需要修改某个SQL操作。
然后,可以:
- 确定每个SQL操作使用哪个查询计划。
可以决定使用反映对表定义所做更改的修改后的查询计划。
或者可以冻结当前查询计划,保留在更改表定义之前生成的查询计划。
- 根据对表定义所做的更改,确定是否对对该表执行SQL操作的例程进行代码更改。
注意:SQL语句是一个SQL例程列表,它们可能会受到表定义更改的影响。
它不应该用作表定义或表数据更改的历史记录。
# 创建SQL语句操作
下面的SQL操作会创建相应的SQL语句:
数据管理(DML)操作包括对表的查询、插入、更新和删除操作。
每个数据管理(DML)操作(动态SQL和嵌入式SQL)在执行时都会创建一个SQL语句。
- 动态SQL `SELECT`命令在准备查询时创建SQL语句。
此外,在管理门户缓存查询列表中创建了一个条目。
- 嵌入式SQL基于指针的`SELECT`命令在`OPEN`命令调用声明的查询时创建SQL语句。管理门户缓存查询列表中不会创建单独的条目。
如果查询引用多个表,则在名称空间的SQL语句中创建一条SQL语句,该语句列出表/视图/过程名列中的所有被引用表,并且对于每个单独的被引用表,该表的SQL语句列表都包含该查询的条目。
SQL语句是在第一次准备查询时创建的。如果多个客户端发出相同的查询,则只记录第一次准备。例如,如果JDBC发出一个查询,然后ODBC发出一个相同的查询,那么SQL语句索引将只有关于第一个JDBC客户端的信息,而不是关于ODBC客户端的信息。
大多数SQL语句都有关联的查询计划。
创建该查询计划时,将解冻该查询计划;
可以随后将该查询计划指定为冻结计划。
带有查询计划的SQL语句包括涉及`SELECT`操作的DML命令。
下面的“计划状态”部分列出了没有查询计划的SQL语句。
注意:SQL语句只列出SQL操作的最新版本。
除非冻结SQL语句,否则InterSystems IRIS®数据平台将用下一个版本替换它。
因此,在例程中重写和调用SQL代码将导致旧的SQL代码从SQL语句中消失。
## 其他SQL语句操作
下面的SQL命令执行更复杂的SQL语句操作:
- `CREATE TRIGGER`: 在定义触发器的表中,无论是在定义触发器还是在提取触发器时,都不会创建SQL语句。
但是,如果触发器对另一个表执行DML操作,那么定义触发器将在被触发器代码修改过的表中创建一个SQL语句。
`Location`指定在其中定义触发器的表。
在定义触发器时定义SQL语句;
删除触发器将删除SQL语句。
触发触发器不会创建SQL语句。
- `CREATE VIEW` 不创建SQL语句,因为没有编译任何内容。
它也不会更改源表的SQL语句的Plan Timestamp。
然而,为视图编译DML命令会为该视图创建一个SQL语句。
# List SQL语句
本节介绍使用Management Portal界面列出SQL语句的详细信息。
也可以使用`^rINDEXSQL`全局返回SQL语句的索引列表。
注意,这个SQL语句List可能包含过时的(不再有效的)List
从Management Portal SQL界面可以列出如下SQL语句:
- SQL语句选项卡:此选项卡列出名称空间中的所有SQL语句,先按模式排序,然后按每个模式中的表名/视图名排序。此列表仅包括当前用户拥有权限的那些表/视图。如果SQL语句引用多个表,则表/视图/过程名列将按字母顺序列出所有被引用的表。
- 通过单击列标题,可以按表/视图/过程名、计划状态、位置、SQL语句文本或列表中的任何其他列对SQL语句列表进行排序。这些可排序列使能够快速查找,例如,所有冻结计划(计划状态)、所有缓存查询(位置)或最慢的查询(平均时间)。
- 可以使用此选项卡提供的`Filter`选项将列出的SQL语句缩小到指定的子集。
指定的筛选器字符串筛选SQL语句列表中的所有数据,最有用的是模式或模式。
表名、例程位置或SQL语句文本中找到的子字符串。
过滤字符串不区分大小写,但必须紧跟语句文本标点空格`(name , age, not name,age)`。
如果查询引用了多个表,如果它选择了表/视图/过程名称列中的任何引用表,则`Filter`包括SQL语句。
过滤选项是用户自定义的。
- 最大行选项默认为`1,000`。
最大值为`10,000`。
最小值为`10`。
要列出超过`10,000`条SQL语句,请使用`INFORMATION_SCHEMA.STATEMENTS`。
页面大小和最大行选项是用户自定义的。
- Catalog Details选项卡:选择一个表并显示其Catalog详细信息。
此选项卡提供了一个表的SQL语句按钮,用于显示与该表关联的SQL语句。
注意,如果一个SQL语句引用了多个表,那么它将在表的SQL语句列表中列出每个被引用的表,但只有当前选择的表在表名列中列出。
通过单击列标题,可以根据列表的任何列对表的SQL语句列表进行排序。
可以使用`SQLTableStatements()`目录查询或`INFORMATION_SCHEMA`。
语句,列出根据各种条件选择的SQL语句,如下面的查询SQL语句中所述。
## 列表列
SQL语句选项卡列出名称空间中的所有SQL语句。目录详细信息选项卡表的SQL语句按钮列出了所选表的SQL语句。这两个列表都包含以下列标题:
- `#`:列表行的顺序编号。这些数字与特定的SQL语句没有关联。
- 表/视图/过程名:限定的SQL表(或视图或过程)名:`schema.name`。如果SQL语句查询引用了多个表或视图,则所有这些表或视图都会在此处列出。
- 计划状态:请参阅下面的计划状态。
- 新计划:见“冻结计划”一章中不同的新计划。
- 自然查询:请参阅下面的语句详细信息部分。
- 计数:请参阅下面的性能统计数据。
- 平均计数:请参阅下面的性能统计数据。
- 总时间:请参阅下面的性能统计数据。
- 平均时间:请参阅下面的性能统计数据。
- 标准开发人员:请参阅下面的性能统计数据。
- Location(S):编译查询的位置,例程名称(对于嵌入式SQL)或缓存查询名称(对于动态SQL)。如果包名为`%sqlcq`,则SQL语句为缓存查询。
- SQL语句文本:规范化格式的SQL语句文本(截断为`128`个字符),可能与以下SQL语句文本中指定的命令文本不同。
## 计划状态
计划状态列出以下内容之一:
- 解冻Unfrozen:未冻结,可冻结。
- 解冻/平行Unfrozen/Parallel::未冻结,不能冻结。
- 冻结/显式Frozen/Explicit:由用户动作冻结,可以解冻。
- Frozen/Upgrade:被InterSystems IRIS版本升级冻结,可以解冻。
- blank:没有关联的查询计划:
- `INSERT... VALUES()` 命令创建的SQL语句没有关联的查询计划,因此无法解冻或冻结(计划状态列为空)。尽管此SQL命令不会生成查询计划,但它在SQL语句中的列表仍然很有用,因为它允许快速定位针对该表的所有SQL操作。例如,如果向表中添加一列,则可能需要找出该表的所有SQL插入的位置,以便可以更新这些命令以包括此新列。
- 基于游标的`UPDATE`或`DELETE`命令没有关联的查询计划,因此不能解冻或冻结(“计划状态”列为空)。对已声明的游标执行`OPEN`命令会生成一条带有关联查询计划的SQL语句。使用该游标的嵌入式SQL语句(`FETCH cursor, UPDATE...WHERE CURRENT OF cursor, DELETE...WHERE CURRENT OF cursor, and CLOSE cursor`)不生成单独的SQL语句。即使基于游标的`UPDATE`或`DELETE`不会产生查询计划,但SQL语句中列出的查询计划仍然很有用,因为它允许快速定位针对该表的所有SQL操作。
## SQL语句文本
SQL语句文本通常不同于SQL命令,因为SQL语句生成规范化了字母和空格。
其他差异如下:
如果从Management Portal接口或SQL Shell接口发出查询,所得到的SQL语句与在`SELECT`语句前面加上`DECLARE QRS CURSOR FOR`(其中“QRS”可以是各种生成的游标名称)的查询不同。
这允许语句文本与Dynamic SQL缓存的查询相匹配。
如果SQL命令指定了一个非限定的表或视图名,那么生成的SQL语句将使用模式搜索路径(如果提供了DML)或默认模式名来提供模式。
SQL语句文本在`1024`个字符之后被截断。
要查看完整的SQL语句文本,请显示SQL语句详细信息。
一个SQL命令可能会产生多个SQL语句。
例如,如果一个查询引用一个视图,SQL Statements将显示两个语句文本,一个列在视图名称下,另一个列在基础表名称下。
冻结任意一条语句都会导致两个语句的Plan State为Frozen。
当通过xDBC准备SQL语句时,如果需要这些选项来生成语句索引散列,则SQL语句生成会向语句文本添加SQL Comment Options (`# Options`)。
如下面的例子所示:
```sql
DECLARE C CURSOR FOR SELECT * INTO :%col(1) , :%col(2) , :%col(3) , :%col(4) , :%col(5) FROM SAMPLE . COMPANY /*#OPTIONS {"xDBCIsoLevel":0} */
```
# 陈旧的SQL语句
删除与SQL语句关联的例程或类时,不会自动删除SQL语句列表。这种类型的SQL语句列表称为陈旧。由于访问此历史信息以及与SQL语句相关联的性能统计信息通常很有用,因此这些过时的条目将保留在管理门户SQL语句列表中。
可以使用`Clean Stale`(清除陈旧)按钮删除这些陈旧条目。清除陈旧删除关联例程或类(表)不再存在或不再包含SQL语句查询的所有非冻结SQL语句。清除陈旧不会删除冻结的SQL语句。
可以使用`$SYSTEM.SQL.Statement.Clean()`方法执行相同的清除陈旧操作。
如果删除与SQL语句关联的表(持久化类),则会修改表/视图/过程名称列,如下例所示:`SAMPLE.MYTESTTABLE - Deleted??;` ;已删除表的名称将转换为全部大写字母,并标记为“`DELETED??`”。或者,如果SQL语句引用了多个表:`SAMPLE.MYTESTTABLE - Deleted?? Sample.Person`.
- 对于动态SQL查询,删除表时`Location`列为空,因为与该表关联的所有缓存查询都已自动清除。`CLEAN STALE`删除SQL语句。
- 对于嵌入式SQL查询,`Location`列包含用于执行查询的例程的名称。当更改例程使其不再执行原始查询时,位置列为空。`CLEAN STALE`删除SQL语句。删除查询使用的表时,该表被标记`“Deleted??”`;Clean Stale不会删除SQL语句。
注:系统任务在所有名称空间中每小时自动运行一次,以清除任何可能过时或具有过时例程引用的SQL语句的索引。执行此操作是为了维护系统性能。此内部清理不会反映在管理门户SQL语句列表中。可以使用管理门户监视此每小时一次的清理或强制其立即执行。要查看此任务上次完成和下次调度的时间,请依次选择系统操作、任务管理器、任务调度,然后查看清理SQL语句索引任务。可以单击任务名称查看任务详细信息。在Task Details(任务详细信息)显示中,可以使用Run(运行)按钮强制立即执行任务。请注意,这些操作不会更改SQL语句清单;必须使用Clean Stale来更新SQL语句清单。
# 数据管理(DML)SQL语句
创建SQL语句的数据管理语言(DML)命令包括:`INSERT`、`UPDATE`、`INSERT`或`UPDATE`、`DELETE`、`TRUNCATE TABLE`、`SELECT`和`OPEN CURSOR`(用于声明的基于游标的`SELECT`)。可以使用动态SQL或嵌入式SQL来调用DML命令。可以为表或视图调用DML命令,InterSystems IRIS将创建相应的SQL语句。
注意:系统在准备动态SQL或打开嵌入式SQL游标时(而不是在执行DML命令时)创建SQL语句。SQL语句时间戳记录此SQL代码调用的时间,而不是查询执行的时间(或是否)。因此,SQL语句可能表示从未实际执行的表数据更改。
准备动态SQL DML命令将创建相应的SQL语句。与此SQL语句关联的位置是缓存查询。动态SQL是在从管理门户SQL界面、SQL Shell界面执行SQL或从`.txt`文件导入时准备的。清除未冻结的缓存查询会将相应的SQL语句标记为清除陈旧删除。清除冻结的缓存查询会删除相应SQL语句的位置值。解冻SQL语句会将其标记为Clean Stale删除。
执行非游标嵌入式SQL数据管理语言(DML)命令将创建相应的SQL语句。每个嵌入式SQL DML命令都会创建相应的SQL语句。如果一个例程包含多个嵌入式SQL命令,则每个嵌入式SQL命令都会创建一个单独的SQL语句。(某些嵌入式SQL命令会创建多条SQL语句。)。SQL语句清单的Location列指定包含嵌入式SQL的例程。通过这种方式,SQL语句维护每个嵌入式SQL DML命令的记录。
打开基于游标的嵌入式SQL数据管理语言(DML)例程将创建带有查询计划的SQL语句。
关联的嵌入式SQL语句(`FETCH`游标、`CLOSE`游标)不会生成单独的SQL语句。
在FETCH游标之后,一个关联的`UPDATE table WHERE CURRENT OF cursor 或DELETE FROM table WHERE CURRENT OF cursor`会生成一个单独的SQL语句,但不会生成单独的`Query Plan`。
插入文字值的`INSERT`命令将创建一个“计划状态”列为空的SQL语句。
由于该命令不会创建查询计划,因此无法冻结SQL语句。
## select命令
调用查询将创建相应的SQL语句。
它可以是一个简单的`SELECT`操作,也可以是一个基于指针的`SELECT/FETCH`操作。
可以对表或视图发出查询。
- 包含`JOIN`的查询为每个表创建相同的SQL语句。
Location是清单中存储的每个表的相同查询。
如SQL语句详细信息例程和关系部分所述,该语句使用以下关系列出所有表。
- 包含选择项子查询的查询为每个表创建相同的SQL语句。
`Location`是清单中存储的每个表的相同查询。
如SQL语句详细信息例程和关系部分所述,该语句使用以下关系列出所有表。
- 引用外部(链接)表的查询不能被冻结。
- 一个包含`FROM`子句`%PARALLEL`关键字的查询可以创建多个SQL语句。
你可以通过调用来显示这些生成的SQL语句:
![image](E3927D1DDE6B49FFA01E0F96682173B7)
这将显示包含原始查询的语句哈希的`Statement`列和包含生成的查询版本的语句哈希的`ParentHash`列。
`%PARALLEL`查询的SQL语句的计划状态为“未冻结/并行”,不能被冻结。
- 不包含FROM子句(因此不引用任何表)的查询仍然创建SQL语句。
例如:`SELECT $LENGTH('this string')`创建一个SQL语句,表列值`%TSQL_sys.snf`。
文章
姚 鑫 · 四月 19, 2021
# 第三章 优化表(二)
# 调整表计算值
调优表操作根据表中的代表性数据计算和设置表统计信息:
- `ExtentSize`,它可能是表中的实际行数(行数),也可能不是。
- 表中每个属性(字段)的选择性。
可以选择性地阻止单个属性的选择性计算。
- 属性的离群选择性,其中一个值比其他值出现得更普遍。
有效的查询可以利用离群值优化。
- 标识某些属性特征的每个属性的注释。
- 每个属性的平均字段大小。
- 表的SQL `Map Name`、`BlockCount`和`Source of BlockCount`。
## 区段大小和行计数
从管理门户运行Tune Table工具时,`ExtentSize`是表中当前行的实际计数。默认情况下,`GatherTableStats()`方法还将实际行数用作`ExtentSize`。当表包含大量行时,最好对较少的行执行分析。可以使用SQL tune table命令并指定`%SAMPLE_PERCENT`来仅对总行的一定百分比执行分析。在针对包含大量行的表运行时,可以使用此选项来提高性能。此`%SAMPLE_PERCENT`值应该足够大,以便对代表性数据进行采样。如果`ExtentSize`。
如果`TuneTable`返回异常值选择性,则正常选择性仍然是整个行集内每个非异常值数据值的百分比。例如,如果在`1000`个随机选择的值中检测到`11`个不同的值,其中一个是异常值,则选择性为`1/11(9.09%)`:平均每个条目出现的几率为十一分之一。如果异常值选择性是`80%`,常规选择性是`1%`,那么除了异常值之外,还可以找到大约`20((1-0.80)/0.01)`个额外的非异常值。
如果优化表初始采样仅返回单个值,但附加采样返回多个不同的值,则这些采样结果会修改正常选择性。例如,990个值的初始随机采样仅检测一个值,但后续采样检测其他不同值的10个单个实例。在这种情况下,初始离群值会影响选择性值,该值现在被设置为`1/1000(0.1%)`,因为10个非离群值中的每一个在1000个记录中只出现一次。
异常值选择性的最常见示例是允许`NULL`的属性。如果某个特性具有`NULL`的记录数大大超过该特性具有任何特定数据值的记录数,则`NULL`为异常值。以下是`FavoriteColors`字段的选择性和异常值选择性:
```java
SELECTIVITY of FIELD FavoriteColors
CURRENT = 1.8966%
CALCULATED = 1.4405%
CURRENT OUTLIER = 45.0000%, VALUE =
CALCULATED OUTLIER = 39.5000%, VALUE =
```
如果一个字段只包含一个不同的值(所有行都具有相同的值),则该字段的选择性为`100%`。选择性为`100%`的值不被视为异常值。调谐表通过采样数据来建立选择性和异常值选择值。为了确定这一点,优选表首先测试少量或几条记录,如果这些记录都具有相同的字段值,它将测试多达`100,000`条随机选择的记录,以支持非索引字段的所有值都相同的假设。只有在字段已编制索引,字段是索引的第一个字段,并且字段和索引具有相同的排序规则类型的情况下,优化表才能完全确定该字段的所有值是否相同。
- 如果已知未编制索引的字段具有在测试`100,000`条随机选择的记录中可能检测不到的其他值,则应手动设置选择性和离群值选择性。
- 如果已知非索引字段没有其他值,则可以手动指定`100%`的选择性,删除任何异常值选择性,并设置`CALCSELECTIVITY=0`以防止优选表尝试计算选择性或将此值指定为异常值。
要修改这些选择性、异常值选择性和异常值计算值,请从调谐表显示中选择单个字段。这会在显示屏右侧的详细信息区域中显示该字段的这些值。可以将选择性、异常值选择性和/或异常值修改为更适合预期完整数据集的值。
- 可以将选择性指定为带有百分号(`%`)的行的百分比,也可以指定为整数行(没有百分号)。如果指定为整数行数,InterSystems IRIS将使用区大小来计算选择性百分比。
- 可以为以前没有异常值的字段指定异常值选择性和异常值。将异常值选择性指定为带百分号(`%`)的百分比。如果仅指定异常值选择性,则Tune Table假定异常值为``。如果仅指定异常值,则除非还指定异常值选择性,否则调谐表不会保存此值。
## CALCSELECTIVITY参数与不计算选择性
在某些情况下,可能不希望优化表工具计算属性的选择性。要防止计算选择性,请将属性的`CALCSELECTIVITY`参数的值指定为`0`(默认值为`1`)。在Studio中,可以在“新建属性向导”的“属性参数”页上设置`CALCSELECTIVITY`,也可以在检查器中的属性参数列表中设置`CALCSELECTIVITY`(可能需要收缩并重新展开属性参数列表才能显示它)。
应该指定`CALCSELECTIVITY=0`的一种情况是,如果该字段未编制索引,则已知该字段在所有行中只包含一个值(`选择性=100%`)。
## 离群值的优化
默认情况下,查询优化器假定查询不会选择离群值。
例如,查询通常选择特定的字段值并从数据库返回少量记录,而不是返回大量记录,其中该字段值是离群值。
查询优化器总是使用选择性来构造查询计划,除非执行一些要求考虑离群选择性的操作。
根据选择离群值,可以执行以下几个操作来调整查询优化:
- 如果异常值是``,则在查询`WHERE`子句中为该字段指定一个`is null`或`is NOT null`条件。
这将导致查询优化器在构造查询时使用离群值选择性。
- 如果离群值是一个数据值,查询优化器会假定选择的字段值不是离群值。
例如,总部位于马萨诸塞州的公司的员工记录可能有`Office_State`字段离群值`MA` (`Massachusetts`)。
优化器假设查询不会选择`' MA '`,因为这将返回数据库中的大多数记录。
但是,如果正在编写一个查询来选择离群值,可以通过将离群值封装在双括号中来通知优化器。
在该字段上查询时,指定一个`WHERE`子句,如下所示`:WHERE Office_State=(('MA'))`。
这种技术抑制了文字替换,并迫使查询优化器在构建查询计划时使用离群值选择性。
对于动态SQL查询,以及在使用ODBC/JDBC提供的InterSystems IRIS之外编写的查询,这种语法是必需的。
对于类查询、嵌入式SQL查询或通过视图访问的查询,则不需要这样做。
- 根据参数值SQL设置配置系统范围的优化查询。
该选项为离群值设置了运行时计划选择(RTPC)优化和作为离群值(BQO)优化的偏差查询的适当组合。
注意,更改此配置设置将清除所有名称空间中的所有缓存查询。
使用管理门户,选择System Administration、Configuration、SQL和Object Settings、SQL来查看和更改此选项。
可用的选择有:
- 假设查询参数值不是字段离群值(`BQO=OFF`, `RTPC=OFF`,初始默认值)
- 假设查询参数值经常匹配字段离群值(`BQO=ON`, `RTPC=OFF`)
- 在运行时优化实际查询参数值(`BQO=OFF`, `RTPC=ON`)
要确定当前设置,调用`$SYSTEM.SQL.CurrentSettings()`。
- 覆盖查询的系统范围配置设置。
通过指定`%NORUNTIME restrict`关键字,可以覆盖单个查询的`RTPC`。
如果查询`SELECT Name,HaveContactInfo FROM t1 WHERE HaveContactInfo=?`
将导致`RTPC`处理,查询`SELECT %NORUNTIME Name,HaveContactInfo FROM t1 WHERE HaveContactInfo=?`
将覆盖RTPC,从而产生一个标准的查询计划。
通过指定注释选项`/*#OPTIONS {"BiasAsOutlier":1} */`,可以覆盖偏见查询作为单个查询的离群值。
## “备注”列
管理门户优化表信息选项为每个字段显示一个备注列。此字段中的值是系统定义的,不可修改。它们包括以下内容:
- `RowID`字段:一个表有一个`RowID`,由系统定义。它的名称通常是ID,但可以有不同的系统分配的名称。由于其所有值(根据定义)都是唯一的,因此其选择性始终为1。如果类定义包括`SqlRowIdPrivate`,则`Notes`列值为`RowID`字段、`Hidden`字段。
- 隐藏字段:隐藏字段定义为私有,`SELECT*`不显示。默认情况下,`CREATE TABLE`将`RowID`字段定义为隐藏;可以指定`%PUBLICROWID`关键字以使`RowID`不隐藏和公开。默认情况下,由持久化类定义定义的表将`RowID`定义为非隐藏;可以指定`SqlRowIdPrivate`将`RowID`定义为隐藏和私有。容器字段定义为隐藏。
- 流字段:表示使用流数据类型定义的字段,可以是字符流(`CLOB`),也可以是二进制流(`BLOB`)。流文件没有平均字段大小。
- 父引用字段:引用父表的字段。
注释列中未标识标识字段、`ROWVERSION`字段、序列字段或`UNIQUEIDENTIFIER(GUID)`字段。
## 平均字段大小
运行调谐表根据当前表格数据集计算所有非流字段的平均字段大小(以字符为单位)。这与`AVG($length(Field))`相同(除非另有说明),四舍五入到小数点后两位。可以更改各个字段的平均字段大小,以反映字段数据的预期平均大小。
- NULL:因为`$LENGTH`函数将`NULL`字段视为长度为0,所以将长度为0的`NULL`字段取平均值。这可能会导致平均字段大小小于一个字符。
- 空列:如果列不包含数据(所有行都没有字段值),则平均字段大小值为1,而不是0。对于不包含数据的列,`AVG($length(Field))`为0。
- `ExtentSize=0`:将`ExtentSize`设置为0时,所有字段的平均字段大小将重置为0。
- 逻辑字段值:平均字段大小始终根据字段的逻辑(内部)值计算。
- 列表字段:InterSystems IRIS列表字段根据其逻辑(内部)编码值计算。此编码长度大于列表中元素的总长度。
- 容器字段:集合的容器字段大于其集合对象的总长度。例如,在`Sample.Person`中,`Home`容器字段的`Average Field` Size大于`Home_Street`、`Home_City`、`Home_State`和`Home_Zip`平均字段大小的总和。
- 流字段:流字段没有平均字段大小。
如果特性/字段的特性参数`CALCSELECTIVITY`设置为0,则调谐表不会计算该特性/字段的平均字段大小。
可以通过从调谐表显示中选择单个字段来修改平均字段大小计算值。这将在显示屏右侧的详细信息区域中显示该字段的值。可以将“平均字段大小”修改为更适合预期的完整数据集的值。由于设置此值时优化表不执行验证,因此应确保该字段不是流字段,并且指定的值不大于最大字段大小(`MaxLen`)。
平均字段大小还显示在管理门户目录详细信息选项卡字段选项表中。必须已为字段选项表运行了调整表,才能显示平均字段大小值。
## map BlockCount选项卡
调优表Map `BlockCount`选项卡显示SQL映射名称、`BlockCount`(作为正整数)和`BlockCount`的来源。
块计数的来源可以在类定义中定义、由类编译器估计或由TuneTable度量。
将类编译器估计的调优表更改运行到TuneTable测量;
它不影响在类定义中定义的值。
通过从调优表显示中选择单个SQL映射名称,可以修改`BlockCount`计算值。
这将在显示器右侧的详细信息区域中显示该地图名称的块计数。
可以将块计数修改为一个更适合预期的完整数据集的值。
因为在设置该值时,Tune Table不执行验证,所以应该确保块计数是一个有效值。
修改`BlockCount`会将`BlockCount`的来源更改为类定义中定义的。
# 导出和重新导入调优表统计信息
可以从一个表或一组表导出调优表统计信息,然后将这些调优表统计信息导入一个表或一组表。
以下是可能希望执行此导出/导入的三种情况。
(为简单起见,这些描述了从单个表导出/导入统计数据;
在实际使用中,通常会从多个相互关联的表中导出/导入统计数据):
- 为生产系统建模:生产表完全填充了实际数据,并使用`Tune table`进行优化。
在测试环境中,创建的表具有相同的表定义,但数据少得多。
通过从生产表导出调优表统计信息并将它们导入测试表,可以在测试表上对生产表优化建模。
- 要复制生产系统:生产表完全填充了实际数据,并使用tune Table进行了优化。将创建具有相同表定义的第二个生产表。(例如,生产环境及其备份环境,或者多个相同的表定义,每个表包含不同医院的患者记录。)。通过从第一个表导出调优表统计信息并将其导入第二个表,您可以为第二个表提供与第一个表相同的优化,而无需第二次运行调优表或等待第二个表填充有代表性的数据。
- 要恢复到以前的统计信息集:可以通过运行tune Table或显式设置统计信息来创建表的优化统计信息。通过导出这些统计信息,可以在尝试其他统计信息设置时保留它们。一旦确定了最佳统计信息集,就可以将它们重新导入到表中。
可以使用`$SYSTEM.SQL.Stats.Table.Export()`方法将调优表统计信息导出到`XML`文件。此方法可以导出名称空间中一个、多个或所有表的优化表统计信息,如以下示例所示:
```java
DO $SYSTEM.SQL.Stats.Table.Export("C:\AllStats.xml")
/* 导出当前命名空间中所有架构/表的TuneTable统计信息 */
```
```java
DO $SYSTEM.SQL.Stats.Table.Export("C:\SampleStats.xml","Sample")
/* 导出Sample模式中所有表的可调统计信息 */
```
```java
DO $SYSTEM.SQL.Stats.Table.Export("C:\SamplePStats.xml","Sample","P*")
/* 导出Sample模式中所有以字母“P”开头的表的可调统计信息 */
```
```java
DO $SYSTEM.SQL.Stats.Table.Export("C:\SamplePersonStats.xml","Sample","Person")
/* 导出Sample的可调统计信息Person */
```
可以使用`$SYSTEM.SQL.Stats.Table.Import()`方法重新导入使用`$SYSTEM.SQL.Stats.Table.Import()`方法导出的调优表统计信息。
`$SYSTEM.SQL.Stats.Table.Import()`有一个`KeepClassUpToDate boolean`选项。
如果为真(并且`update`为真),`$SYSTEM.SQL.Stats.Table.Import()`将用新的`EXTENTSIZE`和选择性值更新类定义,但类定义将保持最新。
但是,在许多情况下,最好在调优了类表之后重新编译类,这样类定义中的查询就可以重新编译,SQL查询优化器就可以使用更新后的数据统计信息。
默认值为`FALSE(0)`。请注意,如果该类已部署,则类定义不会更新。
`$SYSTEM.SQL.Stats.Table.Import()`有一个`ClearCurrentStats boolean`选项。
如果为`TRUE`, `$SYSTEM.SQL.Stats.Table.Import()`将在导入统计信息之前从现有表中清除所有先前的区段大小、选择性、块计数和其他调优表统计信息。
如果您想要完全清除导入文件中没有指定的那些表状态,而不是让它们在表的`persistent`类中定义,则可以使用此方法。
默认值是`FALSE(0)`。
如果`$SYSTEM.SQL.Stats.Table.Import()`没有找到相应的表,它将跳过该表并继续导入文件中指定的下一个表。
如果找到了一个表,但是没有找到一些字段,那么这些字段将被跳过。
无法继承类存储定义中映射的`BlockCount`。`BlockCount`只能出现在映射起源的类的存储定义中。如果映射源自超类,则`$SYSTEM.SQL.Stats.Table.Import()`仅设置投影表的`BlockCount`元数据,而不设置类存储`BlockCount`元数据。
文章
Michael Lei · 六月 23, 2021
部分 Ansible 帮助我解决了快速部署 Caché 和应用程序组件以进行数据平台基准测试的问题。 您可以使用相同的工具和方法来建立您的测试实验室、培训系统、开发或其他环境。 如果在客户站点部署应用程序,可以将大量部署自动化,并确保系统、Caché 和应用程序的配置符合您的应用程序最佳做法标准。
## 概述
作为一名技术架构师,我们的团队职责之一是在不同供应商的硬件和操作系统上对 InterSystems 数据平台进行基准测试。 通常,基础架构是预发布版,在必须归还或移交给其他人之前,我们的时间有限,因此,快速准确地设置基准测试,让我们有尽可能多的时间来做真正的基准测试工作,这一点至关重要。
多年来,我们通过 shell 脚本小程序来自动执行许多基准测试安装任务,并从速查表和检查清单中剪切和粘贴,但此类操作非常密集,而且容易出错,特别是有许多服务器并且在不同的操作系统之间切换时 - 在 SLES 11、Red Hat 6、Red Hat 7、AIX 等操作系统上安装或使用服务的差异可能很微小,让人厌烦。
在研究了几个可用于自动化配置和管理系统的软件选项之后,我选择了 Ansible 来执行预置数据平台、应用程序和基准测试组件的任务。 需要注意的是,我并没有规定 Ansible 是部署和配置的 **THE** 解决方案。 在选择 Ansible 之前,我研究了其他工具(如 Puppet 和 Chef)的功能和操作。 如果您的组织已经在使用其他工具,您可以使用它们 — 我在 Ansible 中使用的方法和命令等等应该可以转换到其他软件中,我希望这些帖子可以帮助您,不管您使用的是什么工具。
这是本系列的第一个帖子,将介绍在部署 InterSystems 数据平台应用程序时如何使用 Ansible。 本帖介绍如何通过安装 Caché 打下基础,下一帖将扩展解决方案以包括应用程序安装,包括使用 %installer 类。 本帖涵盖:
* Ansible 的概述和安装
* Ansible 便于管理和扩展的布局。
* 同时在一个或多个服务器上安装 Caché。
## 什么是 Ansible?
通过 Ansible 可以在配置一个或多个服务器的同时将复杂的任务自动化,并可以非常简单地添加新服务器。 任务会设计成幂等(您可以在同一台服务器上多次运行相同的脚本,得到的服务器配置将是相同的)。
我选择 Ansible 执行预置任务的一个关键原因是它对系统的要求最低(Python 2.7,Linux 服务器上自带 ),而且它是一个自包含解决方案 — Ansible 代码只安装在控制服务器上,并使用推送架构,通过 OpenSSH 在目标服务器上运行命令和脚本。 所预置的服务器上不需要任何代理。 作为对比,Chef 和 Puppet 采用拉取架构,软件在客户端服务器(Web、数据库等)上加载,并且客户端不断轮询主服务器以查找更新。 Ansible 的推送架构也适合按照您的计划需求逐步实施服务器。
Ansible 是开源的,由社区维护。 Ansible, Inc 从 2015 年开始为 Red Hat 所拥有。 Ansible, Inc 有一个高级的生命周期产品 (Ansible Tower),并提供收费的支持和培训,不过本帖中的所有内容均使用开源命令行版本。 还有一个活跃的社区 (Ansible Galaxy),您可以从中下载许多预制的解决方案来完成大量任务,如安装 Web 服务器、ftp、kerbros,不胜枚举。 例如,对于完整的基准测试部署项目,我包括了一个下载的 Apache 模块,并自定义成在 RHEL、SLES 或 Solaris(以及其他平台)上安装和配置 Apache 2.x。
Ansible 的下载和安装说明可以在 Ansible 网站和 Github 上找到。 如果您有问题或希望做出贡献,可以访问活跃社区。
https://www.ansible.com/get-startedhttp://docs.ansible.com
## 安装 Ansible
本帖中的示例已经在运行 Red Hat 7.0 和 7.2 的虚拟机上进行了测试 - 我也在我的安装了 Centos 7 的笔记本电脑上使用 virtual box 和 vagrant 对 Ansible 控制器服务器进行了初始测试。 Caché 不需要安装在控制器上,所以您的操作系统选择要多于 Caché 支持的平台列表。 为了简单起见,我使用了当前适用于 Red Hat 的 rpm 版本的 Ansible (Ansible 1.9.4),更高的版本可以从 GitHub 获取。
在示例中,我安装的是 cache-2015.2.2.805.0-lnxrhx64,但相同的常规过程也适用于 HealthShare 或 Ensemble 发行版。 您将在后面看到,我们使用特定文件名、目录路径等变量来参数化安装选项。
在第一个帖子中,我将任务削减为基本的 Caché 安装,因此大多数任务是独立于平台的。 当 Ansible playbook 启动时,首要任务之一是获取目标机器的清单 — 操作系统、接口卡、内存详细信息、CPU 数量、磁盘布局等,当运行命令以从目标上运行的实际命令(例如,Red Hat 上的 service start httpd 与 SLES 上的 /etc/init.d/apache2 restart)中提取 Ansible 脚本命令时,将使用目标操作系统的这些信息。
我假定您已读过说明,并且已按照您的平台说明在控制机上安装了 Ansible。
Ansible 必须使用 Linux 系统作为控制器,但目标系统可以是 Linux 或 Windows。 有关 Windows 目标的更多信息,请参见 Ansible 文档。
### 控制器系统安装示例:在 RHEL/CentOS 7 64 位上安装 Ansible
在 Red Hat 或 CentOS 上,必须先安装 epel-release (Extra Packages for Enterprise Linux) RPM,其中包含 Ansible。 epel 项目面向主要 Linux 发行版设计,提供了许多有用的开源软件包(网络、系统管理、监视等)。
[root@localhost tmp]# wget http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-5.noarch.rpm
:
:
[root@localhost tmp]# rpm -ivh epel-release-7-5.noarch.rpm
:
:
[root@localhost tmp]# yum --enablerepo=epel info ansible
Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager
Installed Packages
Name : ansible
Arch : noarch
Version : 1.9.4
Release : 1.el7
Size : 7.0 M
Repo : installed
From repo : epel
Summary : SSH-based configuration management, deployment, and task execution system
URL : http://ansible.com
License : GPLv3+
Description :
: Ansible is a radically simple model-driven configuration management,
: multi-node deployment, and remote task execution system. Ansible works
: over SSH and does not require any software or daemons to be installed
: on remote nodes. Extension modules can be written in any language and
: are transferred to managed machines automatically.
[root@localhost tmp]#
[root@localhost tmp]# sudo yum install ansible
:
:
[root@localhost tmp]# ansible --version
ansible 1.9.4
configured module search path = None
**很好... 准备开始!**
## Ansible 方面
关于不同的 Ansible 组件(清单、Playbook、模块、角色等)的用法,应查看 Ansible 文档。
为了简化管理以及避免使用大而复杂的脚本文件,使用了预定义的目录结构和搜索路径。 在本帖中,我将使用一个采用 Ansible 建议的文件结构,当您考虑构建更大的安装示例时,可以将本结构用作模型。
所使用的 Ansible 模块带有注释和自我说明,可以在 Github 上获取。 下载文件并通读以了解工作流程。
我们的示例的基本目录包含以下文件;
* ansible.cfg:对 Ansible 默认值的更改。
* 清单:定义和描述工作环境。 例如服务器名称/IP。
* <各种>.yml 文件:这些文件描述了将为特定服务器角色运行的任务集。
### 术语
为了使后面的讨论更清晰,下面是一些 Ansible 术语的快速解释。
模块是用于创建对系统执行的自动化操作的构建块。 每个模块都为特定任务构建,可以使用参数更改该任务。 例如复制文件、创建用户、运行命令、启动服务等。 目前,默认的 Ansible 安装已包含 400 多个模块,另外还有更多来自社区的模块,您也可以创建您自己的模块。
模块组合在一起生成 play 和 playbook,作为执行自动化工作流程的一种方式。 一个 Play 可以有多个任务,一个 Playbook 可以有多个 Play。
角色允许您组合 Playbook。 角色可以被视为根据目标服务器使用情况分组在一起的服务器组件配置。 在本帖的示例中,角色构建配置层以构建服务器。
在我的基准测试设置中,我有以下角色来构建服务器:
* hs\_server\_common:配置操作系统,安装 Apache,安装 Caché。
* webserver:复制 Web 文件(csp、html、js 等),针对应用程序配置 Apache。
* generator:复制文件,创建和配置 webstress 生成器数据库、命名空间、全局映射等。
* dbserver:复制文件,配置数据库服务器系统设置、应用程序数据库、命名空间、全局映射等。
可以组合这些角色来构建不同的服务器类型:
* hs\_server\_common + webserver + generator = webstress 生成器服务器。
* hs\_server\_common + webserver = 应用程序 Web 服务器。
* hs\_server\_common + dbsevrer = 数据库服务器。
角色的组成以及每个角色中包含的配置将非常特定于要部署的应用程序。 本帖中的示例将使用最小的任务集,并假定操作系统已预先配置,但是,使用 Ansible 和 Galaxy 上的模块可以实现更复杂和全功能的系统配置。
## 关于安装 Caché 的说明 {.MsoNormal}
我写了几个例子来介绍一些有趣和有用的特性,下面是一些精华部分。 注意:这些示例可以用作 InterSystems 数据平台(Caché、HealthShare 和 Ensemble)的安装指南。 我写的示例是安装 HealthShare,但 HealthShare 和 Caché 示例中的操作是相同的。
**./testserver/roles/hs\_server\_common/tasks/main.yml**
这是配置操作系统、安装 Apache、安装 Caché 等常见任务的主线脚本。 本帖中对其进行了删减,使其只包含用于 Red Hat 的文件,以及只安装和配置 Caché。 您可以看到,Ansible 在启动后已经在 _ansible_*_ 变量(包括 _ansible\_os\_family_)中保存了操作系统信息,我们可以在脚本中使用这些变量做出决策。
**./testserver/roles/hs\_server\_common/tasks/configure-healthshare2015.yml**
这是用于安装 Caché 的主脚本。 浏览该脚本,会看到针对目标执行的任务的逻辑工作流程,包括:
* 创建操作系统用户和组。
* 从控制器上的清单文件夹复制安装文件。
* 解压缩安装文件。
* 使用静默安装方式安装 Caché(请参见下面的注释)。
* 复制 Caché 密钥文件
* 设置默认 Caché 实例
* 重启 Apache
* 重启 Caché
Caché 的静默安装有几个选项,包括:
* 使用 parameters.is 文件。 模板 .isc 文件由以前的安装创建,可以按原样使用,也可以修改。
* 使用 cinstall_silent 以及环境中设置的键值对。
* 使用 %installer 类。
在此示例中,我选择了使用 install\_silent,但是我还包括了一个注释掉的使用参数文件的备用方法,以说明如何在 Ansible 中使用模板文件(请参见 /roles/hs\_server\_common/templates/parameters\_hs20152_rh64.isc)。
在以后的帖子中,我将说明如何使用 %installer 类安装 Caché 以及设置数据库和命名空间。 有关安装选项的详细信息,请参见 Caché 在线文档,社区中也有一个非常好的帖子介绍了 %installer 类的使用。
当您想要安装并配置 Caché,以将 CSPGateway 与除了旧版本 Caché 内部的 Apache 版本以外的 Web 服务器一起使用时,参数文件很有用。 自 Caché 2016.1 起,%installer 提供此功能。
**./testserver/roles/hs\_server\_common/tasks/setup_RedHat.yml**
包含此示例是为了说明如何使用系统特定变量 (ansible_*) 和设置操作系统变量。
**./testserver/roles/hs\_server\_common/vars/***
变量文件包含键:值对形式的变量,如您所见,这是一种在不同环境和情况下重复使用相同脚本的方法。
## 运行 Caché 安装
对于此示例,我假定系统可用,并按如下方式进行设置。
1. 控制器已安装 Ansible,并且以下目录填充了来自 Github 的文件和结构。
* **./testserver/***:包含清单、.yml 文件等等的目录树。 包括...
* **./testserver/Distribution_Files/Cache**:(包含 Caché 分发包和 cache.key 的清单)。
2. 目标机器已安装 Red Hat 和 Apache。
您需要编辑以下文件来为您的测试环境自定义安装。
1. **inventory_test**
您需要编辑测试服务器名称或 IP 地址。
2. .**/testserver/roles/hs\_server\_common/vars/healthshare2015.yml**
您必须编辑路径以适合您的测试环境。 查看目标服务器的以下路径:
* **common\_install\_base_path**:清单文件将复制到该位置后解压,并运行 Caché 安装。
* **ISC\_PACKAGE\_INSTALLDIR**:Caché 安装目录
如果目标服务器上不存在这些目录路径,将会创建。
注:自动化部署的特性之一是并行构建多个服务器。 如果清单文件中有多个服务器,在各个目标服务器上将并发运行每个步骤,直至该步骤完成,然后再在组中的每个服务器上开始下一个步骤。 如果任何服务器上有步骤失败,脚本将停止。 您将看到一条错误消息,帮助您更正问题。 更正错误后,只需从头重新运行 — 这是脚本的一个关键特性 — 脚本设计成幂等。 幂等的意思是,在某个步骤运行某个模块(例如复制文件)时,如果文件已经存在,则该步骤不会重新运行,脚本只是继续下一个步骤。 copy 之类的模块有参数可以设置为强制复制,但这不是默认设置。 仔细检查脚本会发现,在某些情况下使用了“creates”参数,例如:
- name: unattended install of hs using cinstall_silent
shell: >
ISC_PACKAGE_INSTANCENAME="{{ ISC_PACKAGE_INSTANCENAME }}"
ISC_PACKAGE_INSTALLDIR="{{ ISC_PACKAGE_INSTALLDIR }}"
ISC_PACKAGE_UNICODE="{{ ISC_PACKAGE_UNICODE }}"
ISC_PACKAGE_INITIAL_SECURITY="{{ ISC_PACKAGE_INITIAL_SECURITY }}"
ISC_PACKAGE_MGRUSER="{{ ISC_PACKAGE_MGRUSER }}"
ISC_PACKAGE_MGRGROUP="{{ ISC_PACKAGE_MGRGROUP }}"
ISC_PACKAGE_USER_PASSWORD="{{ ISC_PACKAGE_USER_PASSWORD }}"
ISC_PACKAGE_CACHEUSER="{{ ISC_PACKAGE_CACHEUSER }}"
ISC_PACKAGE_CACHEGROUP="{{ ISC_PACKAGE_CACHEGROUP }}" ./cinstall_silent
chdir="{{ common_install_base_path }}/{{ hs_install_unpack_path }}"
args:
creates: "{{ ISC_PACKAGE_INSTALLDIR }}/cinstall.log"
上面一节使用 creates 参数告诉 Ansible 模块(在本例中是 shell 模块),此操作创建 cinstall.log 文件。 如果模块发现该文件(Caché 已经安装),则此步骤将不会运行。
好了,全部设置完毕后,我们可以运行安装了。
$ ansible-playbook dbserver.yml
PLAY [dbservers] **************************************************************
GATHERING FACTS ***************************************************************
ok: [db1]
TASK: [hs_server_common | include_vars healthshare2015.yml] *******************
ok: [db1]
TASK: [hs_server_common | include_vars os-RedHat.yml] *************************
ok: [db1]
etc
etc
etc
TASK: [hs_server_common | Create default cache group] *************************
changed: [db1]
TASK: [hs_server_common | Create default cache manager group] *****************
changed: [db1]
TASK: [hs_server_common | Create default cache user] **************************
changed: [db1]
TASK: [hs_server_common | Create default cache system users] ******************
changed: [db1]
TASK: [hs_server_common | Create full hs install temp directory] **************
changed: [db1]
TASK: [hs_server_common | Check tar file (gunzipped already) does not exist] ***
ok: [db1]
TASK: [hs_server_common | Copy healthshare install file] **********************
changed: [db1]
TASK: [hs_server_common | un zip hs folder] ***********************************
changed: [db1]
TASK: [hs_server_common | un tar hs install] **********************************
changed: [db1]
TASK: [hs_server_common | Create hs install directory] ************************
changed: [db1]
TASK: [hs_server_common | touch ztrak.conf.] **********************************
changed: [db1]
TASK: [hs_server_common | Process parameters file] ****************************
changed: [db1]
TASK: [hs_server_common | unattended install of hs using cinstall_silent] *****
changed: [db1]
TASK: [hs_server_common | copy hs key] ****************************************
changed: [db1]
TASK: [hs_server_common | Set default hs instance] ****************************
changed: [db1]
TASK: [hs_server_common | restart apache to initialize CSP.ini file] **********
changed: [db1]
NOTIFIED: [hs_server_common | restart healthshare] ****************************
changed: [db1]
PLAY RECAP ********************************************************************
db1 : ok=32 changed=21 unreachable=0 failed=0
如果我们查看目标服务器 — 数据库服务器 Caché 现在已经启动并运行。
$ ccontrol list
Configuration 'H2015' (default)
directory: /test/hs2015
versionid: 2015.2.1.705.0
conf file: cache.cpf (SuperServer port = 1972, WebServer = 57772)
status: running, since Wed Feb 17 15:59:11 2016
state: ok
## 总结
在后续的帖子中,我将构建包含其他任务的脚本,如编辑配置文件和使用 %installer 类配置应用程序。
如果您对此感兴趣并开始创建您自己的部署,请随时与我联系,提出问题或建议。 我经常在全球峰会上发表关于虚拟化和性能的演讲 - 因此,如果您参加今年的全球峰会,请介绍一下您自己,我非常乐意和您聊一聊 Ansible 的使用经验或任何其他系统架构话题。
文章
姚 鑫 · 四月 16, 2021
# 第二章 定义和构建索引(四)
# 位片索引
当数字数据字段用于某些数值运算时,位片索引用于该字段。位片索引将每个数值数据值表示为二进制位串。位片索引不是使用布尔标志来索引数值数据值(如在位图索引中那样),而是以二进制值表示每个值,并为二进制值中的每个数字创建一个位图,以记录哪些行的该二进制数字具有1。这是一种高度专门化的索引类型,可以显著提高以下操作的性能:
- `SUM`、`COUNT`或`AVG` Aggregate计算。(位片索引不用于`COUNT(*)`计算。)。位片索引不用于其他聚合函数。
- 指定的字段 `TOP n ... ORDER BY field`
- 在范围条件运算中指定的字段,`WHERE field > n` 或 `WHERE field BETWEEN lownum AND highnum`、
SQL优化器确定是否应该使用定义的位片索引。通常,优化器仅在处理大量(数千)行时才使用位片索引。
可以为字符串数据字段创建位片索引,但位片索引将这些数据值表示为规范数字。换句话说,任何非数字字符串(如`“abc”`)都将被索引为0。这种类型的位片索引可用于快速计数具有字符串字段值的记录,而不计算那些为空的记录。
在下面的例子中,`Salary`是位片索引的候选项:
```sql
SELECT AVG(Salary) FROM SalesPerson
```
位片索引可用于使用`WHERE`子句的查询中的聚合计算。如果`WHERE`子句包含大量记录,则这是最有效的。在下面的示例中,SQL优化器可能会使用`Salary`上的位片索引(如果已定义);如果定义了位片索引,它还会使用`REGION`上的位图索引,使用定义的位图或为`REGION`生成位图临时文件:
```sql
SELECT AVG(Salary) FROM SalesPerson WHERE Region=2
```
但是,当索引无法满足`WHERE`条件时,不使用位片索引,而必须通过读取包含要聚合的字段的表来执行。以下示例将不使用`Salary`的位片索引:
```sql
SELECT AVG(Salary) FROM SalesPerson WHERE Name LIKE '%Mc%'
```
可以为任何包含数值的字段定义位片索引。InterSystems SQL使用`Scale`参数将小数转换为位字符串,如ObjectScript `$factor`函数中所述。可以为数据类型字符串的字段定义位片索引;在这种情况下,出于位片索引的目的,非数字字符串数据值被视为`0`。
可以为系统分配的行ID为正整数值的表中的字段定义位片索引,也可以为使用`%BID`属性定义以支持位图(和位片)索引的表中的字段定义位片索引。
位片索引只能为单个字段名定义,不能为多个字段的连接定义。
不能指定`WITH DATA`子句。
下面的例子比较了位片索引和位图索引。
如果你为1、5和22行创建一个位图索引,它会为这些值创建一个索引:
```java
^gloI("bitmap",1,1)= "100"
^gloI("bitmap",5,1)= "010"
^gloI("bitmap",22,1)="001"
```
如果为第1、2和3行的值1、5和22创建位切片索引,则会首先将这些值转换为位值:
```java
1 = 00001
5 = 00101
22 = 10110
```
然后,它为这些位创建索引:
```java
^gloI("bitslice",1,1)="110"
^gloI("bitslice",2,1)="001"
^gloI("bitslice",3,1)="011"
^gloI("bitslice",4,1)="000"
^gloI("bitslice",5,1)="001"
```
在本例中,位图索引中的值22需要设置1个全局节点;位片索引中的值22需要设置3个全局节点。
请注意,插入或更新需要在所有`n`个位片中设置一个位,而不是设置单个位串。这些附加的全局设置操作可能会影响涉及填充位片索引的插入和更新操作的性能。使用`INSERT`、`UPDATE`或`DELETE`操作填充和维护位片索引比填充位图索引或常规索引慢。维护多个位片索引和/或在频繁更新的字段上维护位片索引可能具有显著的性能成本。
在易失性表(执行许多插入、更新和删除操作)中,位片索引的存储效率可能会逐渐降低。`%SYS.Maint.Bitmap`实用程序方法同时压缩位图索引和位片索引,从而提高了还原效率。
# 重建索引
可以按如下方式构建/重新构建索引:
- 使用`BUILD INDEX` SQL命令构建指定索引,或构建为表、架构或当前命名空间定义的所有索引。
- 使用管理门户重建指定类(表)的所有索引。
- 使用`%BuildIndices()`(或`%BuildIndicesAsync()`)方法,如本节所述。
当前数据库访问确定应如何重建现有索引:
- 非活动系统(在索引构建或重建期间没有其他进程访问数据)
- `READONLY`活动系统(能够在索引构建或重建期间查询数据的其他进程)
- 读写活动系统(能够在索引构建或重建期间修改数据和查询数据的其他进程)
构建索引的首选方法是使用`%BuildIndices()`方法或`%BuildIndicesAsync()`方法。
- `%Library.Persistent.%BuildIndices()`:`%BuildIndices()`作为后台进程执行,但调用方必须等待`%BuildIndices()`完成才能接收回控制。
- `%Library.Persistent.%BuildIndicesAsync()`:`%BuildIndicesAsync()`将`%BuildIndices()`作为后台进程启动,调用方立即收到控制权。`%BuildIndicesAsync()`的第一个参数是`eueToken`输出参数。其余参数与`%BuildIndices()`相同。
`%BuildIndicesAsync()`返回`%Status`值:`Success`表示`%BuildIndices()`辅助作业已成功排队;失败表示该辅助作业未成功排队。
`%BuildIndicesAsync()`向`eueToken`输出参数返回一个值,该值指示`%BuildIndices()`完成状态。要获取完成状态,请通过引用将`eueToken`值传递`给%BuildIndicesAsyncResponse()`方法。还可以指定等待布尔值。如果`wait=1`,则`%BuildIndicesAsyncResponse()`将等待,直到由`eueToken`标识的`%BuildIndices()` JOB 完成。如果`wait=0`,`%BuildIndicesAsyncResponse()`将尽快返回状态值。如果返回时`%BuildIndicesAsyncResponse() ``eueToken`不为空,则`%BuildIndices()` job尚未完成。在这种情况下,可以使用`eueToken`再次调用`%BuildIndicesAsyncResponse()`。当`%BuildIndicesAsyncResponse()``eueToken`最终为`NULL`时,返回的`%BuildIndicesAsyncResponse()``%Status`值是`%BuildIndicesAsync()`调用的job的完成状态。
## 在非活动系统上构建索引
系统自动生成方法(由`%Persistent`类提供),这些方法构建或清除为类(表)定义的每个索引。可以通过以下两种方式之一使用这些方法:
- 通过管理门户进行交互。
- 以编程方式,作为方法调用。
构建索引执行以下操作:
1. 删除索引的当前内容。
2. 扫描(读取每一行)主表,并为表中的每一行添加索引项。如果可能,使用特殊的`$SortBegin`和`$SortEnd`函数来确保高效地构建大型索引。在构建标准索引时,除了在内存中缓存数据之外,使用`$SortBegin`/`$SortEnd`还可以使用`IRISTEMP`数据库中的空间。因此,在构建非常大的标准索引时,InterSystems IRIS可能需要`IRISTEMP`中大致等于最终索引大小的空间。
注:构建索引的方法仅为使用InterSystems IRIS默认存储结构的类(表)提供。映射到遗留存储结构的类不支持索引构建,因为它假定遗留应用程序管理索引的创建。
### 使用管理门户构建索引
可以通过执行以下操作来构建表的现有索引(重建索引):
1. 从管理门户中选择系统资源管理器,然后选择SQL。使用页面顶部的切换选项选择一个命名空间;这将显示可用命名空间的列表。选择命名空间后,选择屏幕左侧的`Schema`下拉列表。这将显示当前名称空间中的模式列表,其中带有布尔标志,指示是否有任何表或视图与每个模式相关联。
2. 从此列表中选择一个架构;该架构将显示在架构框中。它的正上方是一个下拉列表,允许选择属于该模式的表、系统表、视图、过程或所有这些。选择“表”或“全部”,然后打开“表”文件夹以列出此架构中的表。如果没有表,则打开文件夹将显示空白页。(如果未选择“表”或“全部”,则打开“表”文件夹将列出整个命名空间的表。)
3. 选择其中一个列出的表。这将显示表的目录详细信息。
- 要重建所有索引:单击操作下拉列表,然后选择重建表的索引。
- 要重建单个索引:单击索引按钮以显示现有索引。每个列出的索引都有重建索引的选项。
**注意:当其他用户正在访问表的数据时,不要重建索引。要在活动系统上重建索引,请参阅在活动系统上构建索引。**
### 以编程方式构建索引
为非活动表构建索引的首选方法是使用随表的`Persistent`类提供的`%BuildIndices()`(或`%BuildIndicesAsync()`)方法。
**若要以编程方式生成一个或多个索引,请使用`%Library.Persistent.%BuildIndices()`方法。**
生成所有索引:调用`%BuildIndices()`,不带参数生成为给定类(表)定义的所有索引(为其提供值):
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices()
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
生成指定索引:调用`%BuildIndices()`,并将`$LIST`索引名作为第一个参数,为给定类(表)生成指定的已定义索引(为其提供值):
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices($ListBuild("NameIDX","SSNKey"))
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
生成除以下项之外的所有索引:调用`%BuildIndices()`,并将索引名称的`$LIST`作为第七个参数来构建(为其提供值)给定类(表)的所有已定义索引(指定索引除外):
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices("",,,,,,$ListBuild("NameIDX","SSNKey"))
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
`%BuildIndices()`方法执行以下操作:
1. 对要重建的任何(非位图)索引调用`$SortBegin`函数(这将启动对这些索引的高性能排序操作)。
2. 循环遍历类(表)的主要数据,收集索引使用的值,并将这些值添加到索引(通过适当的排序转换)。
3. 调用`$SortEnd`函数来完成索引排序过程。
如果索引已经有值,则必须使用两个参数调用`%BuildIndices()`,其中第二个参数的值为1。
为此参数指定1将导致该方法在重新生成值之前清除这些值。
例如:
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices(,1)
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
清除并重建所有的索引。
你也可以清除并重建索引的子集,例如:
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices($ListBuild("NameIDX","SSNKey"),1)
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
注意:当表的数据被其他用户访问时,不要重建索引。
若要在活动系统上重建索引,请参见在活动系统上构建索引。
## 在活动系统上构建索引
在活动系统上构建(或重建)索引时,有两个问题:
- 除非正在构建的索引对`SELECT` `Query`隐藏,否则活动`Query`可能返回不正确的结果。这是在构建索引之前使用`SetMapSelecability()`方法处理的。
- 索引构建期间对数据的活动更新不会反映在索引条目中。这是通过在生成索引时使生成操作锁定单个行来处理的。
**注意:如果应用程序在单个事务内对数据执行大量更新,则可能会出现锁表争用问题。**
### 在Readonly主动系统上构建索引
如果表当前仅用于查询操作(`READONLY`),则可以在不中断查询操作的情况下构建新索引或重建现有索引。这是通过在重建索引时使索引对查询优化器不可用来实现的。
如果要为其构建一个或多个索引的所有类当前都是`READONLY`,请使用“在读写活动系统上构建索引”中描述的相同系列操作,但有以下区别:使用`%BuildIndices()`时,设置`pLockFlag=3`(共享区锁定)。
### 在读写活动系统上构建索引
如果持久化类(表)当前正在使用并且可用于读写访问(查询和数据修改),则可以在不中断这些操作的情况下构建新索引或重建现有索引。如果要为其重建一个或多个索引的类当前可读写访问,则构建索引的首选方法是使用与表的持久类一起提供的`%BuildIndices()`(或`%BuildIndicesAsync()`)方法。
**注意:以下信息适用于动态SQL查询,而不适用于嵌入式SQL。嵌入式SQL在编译时(而不是在运行时)检查`MapSelecability`设置。因此,关闭索引的`MapSelecability`对已经编译的嵌入式SQL查询没有任何影响。因此,嵌入式SQL查询仍可能尝试使用禁用的索引,并将给出不正确的结果。**
在并发读写访问期间,需要执行以下一系列操作来构建一个或多个索引:
1. 望构建的索引对查询不可用(读取访问权限)。这是使用`SetMapSelecability()`完成的。这使得查询优化器无法使用该索引。在重建现有索引和创建新索引时都应执行此操作。例如:
```java
SET status=$SYSTEM.SQL.Util.SetMapSelectability("Sample.MyStudents","StudentNameIDX",0)
```
- 第一个参数是`Schema.Table`名称,它是`SqlTableName`,而不是持久类名称。例如,默认模式是`SQLUser`,而不是`User`。该值区分大小写。
- 第二个参数是SQL索引映射名称。这通常是索引的名称,指的是磁盘上存储索引的名称。对于新索引,这是在创建索引时将使用的名称。该值不区分大小写。
- 第三个参数是`MapSelecability`标志,其中0将索引映射定义为不可选择(`OFF`),1将索引映射定义为可选择(`ON`)。指定0。
可以通过调用`GetMapSelecability()`方法来确定索引是否不可选。如果已将索引显式标记为不可选,则此方法返回0。在所有其他情况下,它返回1;它不执行表或索引是否存在的验证检查。请注意,`Schema.Table`名称是`SqlTableName`,并且区分大小写。
`SetMapSelecability()`和`GetMapSelecability()`仅适用于当前命名空间中的索引映射。如果该表映射到多个命名空间,并且需要在每个命名空间中构建索引,则应该在每个命名空间中调用`SetMapSelecability()`。
2. 在索引构建期间建立并发操作:
- 对于新索引:在类中创建索引定义(或在类的`%Storage.SQL`中创建新的SQL Index Map规范)。编译类。此时,索引存在于表定义中;这意味着对象保存、SQL `INSERT`操作和SQL `UPDATE`操作都记录在索引中。但是,由于在步骤1中调用了`SetMapSelecability()`,因此不会为任何数据检索选择此索引映射。`SetMapSelecability()`阻止查询使用区索引,但是数据映射将被投影到SQL以使用索引全局和数据全局。对于新索引,这是合适的,因为索引尚未填充。在对表运行查询之前,需要填充区索引。
- 对于现有索引:清除任何引用该表的缓存查询。索引构建执行的第一个操作是终止索引。因此,在重新生成索引时,不能依赖任何经过优化以使用该索引的代码。
3. 使用`pLockFlag=2`(行级锁定)的持久化类(表)的`%BuildIndices()`方法构建一个或多个索引。`PLockFlag=2`标志在重建过程中在单个行上建立独占写锁,以便并发数据修改操作与构建索引操作相协调。
默认情况下,`%BuildIndices()`构建为持久类定义的所有索引;可以使用`pIgnoreIndexList`从重建中排除索引。
默认情况下,`%BuildIndices()`为所有`ID`构建索引项。但是,可以使用`pStartID`和`pEndID`来定义`ID`范围。`%BuildIndices()`将仅为该范围内(含)的ID构建索引项。例如,如果使用带有`%NOINDEX`限制的`INSERT`将一系列新记录添加到表中,则可以稍后使用具有ID范围的`%BuildIndices()`为这些新记录构建索引项。还可以使用`pStartID`和`pEndID`在节中构建极大的索引。
`%BuildIndices()`返回`%Status`值。如果`%BuildIndices()`因检索数据时出现问题而失败,系统将生成一个`SQLCODE`错误和一条消息(`%msg`),其中包含遇到错误的`%ROWID`。
4. 构建完索引后,启用映射以供查询优化器选择。将第三个参数`MapSelecability`标志设置为1,如下例所示:
```java
SET status=$SYSTEM.SQL.Util.SetMapSelectability("Sample.MyStudents","StudentNameIDX",1)
```
5. 再次清除引用该表的所有缓存查询。这将消除在此程序中创建的缓存查询,这些查询无法使用索引,因此不如使用索引的相同查询最佳。
这就完成了这个过程。索引已完全填充,查询优化器能够考虑该索引。
注意:`%BuildIndices()`只能用于重建`ID`值为正整数的表的索引。如果父表具有正整数`ID`值,还可以使用`%BuildIndices()`重建子表中的索引。对于其他表,请使用`%ValidateIndices()`方法,如验证索引中所述。因为`%ValidateIndices()`是构建索引的最慢方法,所以只有在没有其他选项的情况下才应该使用它。
文章
Louis Lu · 五月 30, 2021
本文主要总结了在InterSystems IRIS 中如何保存、查询List类型数据
假设我们设计的对象中包含姓名,同时每个姓名下可以包含多个电话。我们可以使用下面方法进行处理。
1. 传统方式
我们可以把每一个姓名和电话放在不同列中。
Class Test.Person Extends %Persistent
{
Property Name As %String;
Property Phone As %String;
}
我们使用SQL语句插入数据:
insert into Test.Person values ('a','111-111-1111');
insert into Test.Person values ('b','222-111-1111');
insert into Test.Person values ('a','111-222-1111');
insert into Test.Person values ('c','333-111-1111');
insert into Test.Person values ('b','222-222-1111');
数据在表中是这样的:
Name
Phone
a
111-111-1111
b
222-111-1111
a
111-222-1111
c
333-111-1111
b
222-222-1111
这种情况下,我们可以使用下面的sql语句将结果返回:
SELECT
distinct %exact(Name) Name,
LIST(phone %foreach(Name)) Phonestr
FROM test.person
Name
Phonestr
a
111-111-1111,111-222-1111
b
222-111-1111,222-222-1111
c
333-111-1111
我们可以为电话号码创建索引,以提高搜索速度,如下:
Index IdxP On Phone;
使用这种方式保存list数据比较简单,当是当list数据非常多时,这种方法会使表格臃肿。
2. 保存在一个字符串字段中,使用分隔符区分
这里我们将所有电话号码保存在一个字符串字段中,每个号码之间用逗号区分
Class Test.Person2 Extends %Persistent
{
Property Name As %String;
Property PhoneStr As %String;
}
填充数据后,类似于这样
Name
PhoneStr
a
111-111-1111,111-222-1111
b
222-111-1111,222-222-1111
c
333-111-1111
d
333-111-1111,222-222-1111
这种情况下我们可以用下面方法实现对每个电话的索引
Index idxP On PhoneStr(ELEMENTS);
ClassMethod PhoneStrBuildValueArray(value, ByRef array) As %Status
{
if value="" {
s array(0)=value
}else{
s list=$lfs(value,","),ptr=0
while $listnext(list,ptr,item){
s array(ptr)=item
}
}
q $$$OK
}
这里用到了一个函数
ClassMethod propertynameBuildValueArray(value, ByRef valueArray) As %Status
其中:
value – 需要拆分的内容;
valueArray – 返回array类型的值,其中包含拆分的内容,格式为 array(key1)=value1, array(key2)=value2...
这时候我们的数据是这样:
USER>zw ^Test.Person2D
^Test.Person2D=4
^Test.Person2D(1)=$lb("","a","111-111-1111,111-222-1111")
^Test.Person2D(2)=$lb("","b","222-111-1111,222-222-1111")
^Test.Person2D(3)=$lb("","c","333-111-1111")
^Test.Person2D(4)=$lb("","d","333-111-1111,222-222-1111")
索引是这样:
USER>zw ^Test.Person2I
^Test.Person2I("idxP"," 111-111-1111",1)=""
^Test.Person2I("idxP"," 111-222-1111",1)=""
^Test.Person2I("idxP"," 222-111-1111",2)=""
^Test.Person2I("idxP"," 222-222-1111",2)=""
^Test.Person2I("idxP"," 222-222-1111",4)=""
^Test.Person2I("idxP"," 333-111-1111",3)=""
^Test.Person2I("idxP"," 333-111-1111",4)=""
这种情况下我们可以通过下面的SQL 语句查找 包含电话号码 333-111-1111 的姓名
select Name from test.person2 where phonestr ['333-111-1111'
select Name from test.person2 where phonestr like '%333-111-1111%'
但是当你检查查询计划的时候,却发现它并没有使用任何索引
我们只能通过类似于下面SQL语句的写法才能使用该索引
select Name from test.person2 where for some %element(Phonestr) (%value = '333-111-1111')
类似的还有下面的写法
(%Value %STARTSWITH 'а')
(%Value [ 'a' and %Value [ 'b')
(%Value in ('c','d'))
(%Value is null)
3. 使用 %List 类型
Class Test.Person3 Extends %Persistent
{
Property Name As %String;
Property PhoneList As %List;
Index idxP On PhoneList(ELEMENTS);
ClassMethod PhoneListBuildValueArray(value, ByRef array) As %Status
{
if value="" {
s array(0)=value
}else{
s ptr=0
while $listnext(value,ptr,item){
s array(ptr)=item
}
}
q $$$OK
}
}
插入数据
insert into Test.Person3 (Name,PhoneList) select 'a', $LISTBUILD('111-111-1111','111-222-1111')
insert into Test.Person3 (Name,PhoneList) select 'b', $LISTBUILD('222-111-1111','222-222-1111')
insert into Test.Person3 (Name,PhoneList) select 'c', $LISTBUILD('333-111-1111')
insert into Test.Person3 (Name,PhoneList) select 'd', $LISTBUILD('333-111-1111','222-222-1111')
数据和索引保存为
USER>zw ^Test.Person3D
^Test.Person3D=4
^Test.Person3D(1)=$lb("","a",$lb("111-111-1111","111-222-1111"))
^Test.Person3D(2)=$lb("","b",$lb("222-111-1111","222-222-1111"))
^Test.Person3D(3)=$lb("","c",$lb("333-111-1111"))
^Test.Person3D(4)=$lb("","d",$lb("333-111-1111","222-222-1111"))
USER>zw ^Test.Person3I
^Test.Person3I("idxP","111-111-1111",1)=""
^Test.Person3I("idxP","111-222-1111",1)=""
^Test.Person3I("idxP","222-111-1111",2)=""
^Test.Person3I("idxP","222-222-1111",2)=""
^Test.Person3I("idxP","222-222-1111",4)=""
^Test.Person3I("idxP","333-111-1111",3)=""
^Test.Person3I("idxP","333-111-1111",4)=""
同样可以使用下面的SQL语句查找包含电话333-111-1111的姓名
select Name from test.person2 where for some %element(phonelist) (%value = '333-111-1111')
4 使用 List Of、Array Of 保存
不需要定义propertynameBuildValueArray函数
Class Test.Person4 Extends %Persistent
{
Property Name As %String;
Property PhoneList As list Of %String;
Index idxP On PhoneList(ELEMENTS);
}
使用同样的方式插入数据
insert into Test.Person4 (Name,PhoneList) select 'a', $LISTBUILD('111-111-1111','111-222-1111')
insert into Test.Person4 (Name,PhoneList) select 'b', $LISTBUILD('222-111-1111','222-222-1111')
insert into Test.Person4 (Name,PhoneList) select 'c', $LISTBUILD('333-111-1111')
insert into Test.Person4 (Name,PhoneList) select 'd', $LISTBUILD('333-111-1111','222-222-1111')
数据和索引保存为
USER>zw ^Test.Person4D
^Test.Person4D=4
^Test.Person4D(1)=$lb("","a",$lb("111-111-1111","111-222-1111"))
^Test.Person4D(2)=$lb("","b",$lb("222-111-1111","222-222-1111"))
^Test.Person4D(3)=$lb("","c",$lb("333-111-1111"))
^Test.Person4D(4)=$lb("","d",$lb("333-111-1111","222-222-1111"))
USER>zw ^Test.Person4I
^Test.Person4I("idxP"," 111-111-1111",1)=""
^Test.Person4I("idxP"," 111-222-1111",1)=""
^Test.Person4I("idxP"," 222-111-1111",2)=""
^Test.Person4I("idxP"," 222-222-1111",2)=""
^Test.Person4I("idxP"," 222-222-1111",4)=""
^Test.Person4I("idxP"," 333-111-1111",3)=""
^Test.Person4I("idxP"," 333-111-1111",4)=""
使用同样的SQL查询可以得到结果
select Name from test.person4 where for some %element(Phonelist) (%value = '333-111-1111')
引申话题:针对日期字段的索引
日期格式通常是yyyy-mm-dd,我们经常要求按照某年或者某月查询数据,我们可以使用propertynameBuildValueArray函数设定保存的索引方式实现这个目的
Class Test.Person5 Extends %Persistent
{
Property Name As %String;
Property DOB As %Date;
Index idxD On (DOB(KEYS), DOB(ELEMENTS));
ClassMethod DOBBuildValueArray(value, ByRef array) As %Status
{
if value="" {
s array(0)=value
}else{
s d=$zd(value,3)
s array("yy")=+$p(d,"-",1)
s array("mm")=+$p(d,"-",2)
s array("dd")=+$p(d,"-",3)
}
q $$$OK
}
}
插入数据
insert into Test.Person5 (Name,DOB)
select 'a', {d '2000-01-01'} union all
select 'b', {d '2000-01-02'} union all
select 'c', {d '2000-02-01'} union all
select 'd', {d '2001-01-02'} union all
select 'e', {d '2001-01-01'} union all
select 'f', {d '2001-02-01'}
查看数据以及索引保存的内容
USER>zw ^Test.Person5D
^Test.Person5D=6
^Test.Person5D(1)=$lb("","a",58074)
^Test.Person5D(2)=$lb("","b",58075)
^Test.Person5D(3)=$lb("","c",58105)
^Test.Person5D(4)=$lb("","d",58441)
^Test.Person5D(5)=$lb("","e",58440)
^Test.Person5D(6)=$lb("","f",58471)
USER>zw ^Test.Person5I
^Test.Person5I("idxD","dd",1,1)=""
^Test.Person5I("idxD","dd",1,3)=""
^Test.Person5I("idxD","dd",1,5)=""
^Test.Person5I("idxD","dd",1,6)=""
^Test.Person5I("idxD","dd",2,2)=""
^Test.Person5I("idxD","dd",2,4)=""
^Test.Person5I("idxD","mm",1,1)=""
^Test.Person5I("idxD","mm",1,2)=""
^Test.Person5I("idxD","mm",1,4)=""
^Test.Person5I("idxD","mm",1,5)=""
^Test.Person5I("idxD","mm",2,3)=""
^Test.Person5I("idxD","mm",2,6)=""
^Test.Person5I("idxD","yy",2000,1)=""
^Test.Person5I("idxD","yy",2000,2)=""
^Test.Person5I("idxD","yy",2000,3)=""
^Test.Person5I("idxD","yy",2001,4)=""
^Test.Person5I("idxD","yy",2001,5)=""
^Test.Person5I("idxD","yy",2001,6)=""
执行下面 SQL 可以显示所有2月出生的信息
select * from Test.Person5 where for some %element(DOB) (%key='mm' and %value = 2)
这篇文章源自 这里,作者 Vitaliy Serdtsev
文章
姚 鑫 · 五月 15, 2021
# 第一章 单元测试概述
本教程的第一部分概述了单元测试。完成本教程的这一部分后,将能够:
- 定义单元测试并区分单元测试和集成测试
- 列出单元测试的几个好处
- 描述InterSystems IRIS `%UnitTest`包和`xUnit`测试框架之间的相似性。
- 列出软件开发中测试优先方法经常声称的几个好处。
# 什么是单元测试?
单元测试是对单个代码模块的正确性的测试,例如,方法或类的测试。通常,开发人员在开发代码时为其代码创建单元测试。典型的单元测试是一种执行方法的方法,该方法测试并验证该方法是否为给定的一组输入生成了正确的输出。
单元测试不同于集成测试。集成测试验证了一组代码模块交互的正确性。单元测试仅单独验证代码模块的正确性。一组代码模块的集成测试可能会失败,即使每个模块都通过了单元测试。
# 为什么要进行单元测试?
单元测试提供了许多好处,包括:
- 提供代码模块是否正确的验证。这是单元测试的主要原因。
- 提供自动回归测试。更改代码模块后,应重新运行单元测试,以确保代码模块仍然正确。也就是说,应该使用单元测试来确保更改没有破坏代码模块。理想情况下,所有代码模块的单元测试都应该在更改任何一个模块之后运行。
- 提供文档。通常,代码模块的单元测试与代码模块一起交付。检查单元测试提供了大量有关代码模块如何工作的信息。
# XUnit测试框架
单元测试框架是为开发和执行单元测试提供支持的类包。它们可以很容易地扩展以支持更具体或专门化类型的单元测试。
XUnit系列测试框架基于原始的`Sunit`框架(用于单元测试`SmallTalk`代码),包括以下框架:
- `JUnit-Java`代码的单元测试框架。
- `NUnit-C#`、`VB.NET`和其他`.NET`语言代码的单元测试框架。
- `CppUnit-C++`代码的单元测试框架。
- `PyUnit-Python`代码的单元测试框架。
# %UnitTest和xUnit框架的结构
`%UnitTest`包和`xUnit`框架共享相同的基本结构。熟悉任何`Unit`框架的开发人员都可以毫不费力地学习使用`%UnitTest`包。`%UnitTest`和`xUnit`框架都围绕以下基本测试结构组织:
- 测试装置-为一个测试或一组测试做准备和清理工作的代码。准备测试可能包括创建数据库连接,或使用测试数据初始化数据库。清理可能包括关闭数据库连接或恢复数据库状态。
- 测试用例-测试的最小单元。验证特定的一组输入是否会产生给定模块的特定输出。
- 测试套件-设计为一起执行的测试和测试套件的集合。
- Test Runner-用于执行测试并显示其结果的实用程序。
# 测试自动化
`%UnitTest`包和`xUnit`框架都支持测试自动化。当单元测试完成执行时,它会报告测试是通过还是失败。不需要解释测试结果。这是非常重要的。可以为每个代码更改执行大量单元测试。如果必须不断地阅读和解释结果,这个过程很快就会变得非常乏味和容易出错。
许多`xUnit`框架提供了汇总测试结果的图形用户界面(GUI)。`%UnitTest`会生成一个显示测试结果的网页。它以绿色显示有关通过的测试的信息,以红色显示有关失败的测试的信息。开发人员可以一目了然地判断是否有任何测试失败。
这是由`%UnitTest`单元测试生成的测试报告。用户可以通过单击页面上的超链接深入查看提供有关测试的更多详细信息的页面。
![image](6F95C9D03A024D2CB690E5213CF37A4B)
# 测试优先方法论
敏捷软件方法论,例如测试驱动开发(TDD)和极限编程,特别强调单元测试。事实上,这些方法使用单元测试来驱动开发过程。他们提倡“测试优先”的软件开发方法。在这种方法中,开发人员在编写代码模块的一行代码之前设计并编写代码模块的单元测试。然后,开发人员创建代码模块,目标是通过单元测试。
`Test First`方法的倡导者声称该方法具有以下好处:
- 它迫使开发人员在开发任何模块之前很久就决定代码模块的正确输入和输出。
- 它集中了开发人员在创建代码模块时的注意力。开发人员关注的是在创建模块时通过单元测试的具体目标。
- 它可以防止单元测试成为事后的想法。如果首先创建单元测试,则在项目结束之前不能忽略单元测试。
- 它确保了代码的高度测试覆盖率。
注意:测试优先开发的支持者通常主张在代码模块之前执行单元测试,而不仅仅是创建单元测试。当然,在这一点上测试应该会失败。他们甚至可能不会编译。
# Red – Green – Refactor
`XUnit`和`%UnitTest`测试报告GUI报告以绿色表示通过测试,以红色表示未通过测试。下面是使用测试优先开发方法的开发节奏:
1. 红色 - 编写一个不起作用的小测试,也许一开始不会编译。
2. 绿色 - 让测试快速运行,在测试过程中犯下所有必要的错误。
3. 重构 - 消除仅在使测试正常工作时产生的所有重复。
> Kent Beck,《测试驱动的设计》
文章
姚 鑫 · 八月 9, 2021
# 第八十一章 方法关键字 - SoapRequestMessage
当多个`web方法`具有相同的`SoapAction`时使用此方法。
在默认场景中,该关键字指定请求消息的`SOAP`正文中的顶级元素的名称。
仅适用于定义为`web服务`或`web客户端`的类。
# 用法
要在请求消息的`SOAP`体中指定顶级元素的名称,请使用以下语法:
```java
Method name(formal_spec) As returnclass [ WebMethod, SoapAction = "MyAct", SoapRequestMessage="MyReqMessage" ]
{ //implementation }
```
其中`soaprequestmessage`是有效的XML标识符。
# 详解
注意:此关键字仅对包装的文档/文字`document/literal`消息有效。
对于包装的文档/文字消息,该关键字指定请求消息的`SOAP`主体中的顶部元素的名称。(默认情况下,包装文档/文字消息。
如果对同一`web服务`中的多个`web方法`使用相同的`SoapAction`值,请指定此关键字。否则,一般不需要这个关键字。
# 与WSDL的关系
`SoapRequestMessage`关键字影响`web服务`的`WSDL`的``部分。例如,考虑以下web方法:
```java
Method Add(a as %Numeric,b as %Numeric) As %Numeric [ SoapAction = MyAct,SoapRequestMessage=MyReqMessage, WebMethod ]
{
Quit a + b
}
```
对于这个web服务,WSDL包含以下内容:
```xml
```
这些元素在``部分中相应地定义。
默认情况下,如果方法没有指定`SoapRequestMessage`关键字,``部分将改为如下所示:
```xml
```
如果使用`SOAP`向导从`WSDL` IRIS `web服务`或客户端, IRIS将此关键字设置为适合该WSDL的。
# 对Message的影响
对于前面显示的`web方法`,`web服务`需要以下形式的请求消息:
```xml
12
```
相反,如果该方法没有指定`SoapRequestMessage`关键字,则该消息将如下所示:
```xml
12
```
# 第八十二章 方法关键字 - SoapTypeNameSpace
为此`web方法`使用的类型指定`XML`命名空间。仅适用于定义为`web服务`或`web客户端`的类。
# 用法
若要重写类型的默认`XML`命名空间(当该方法用作web方法时),请使用以下语法:
```java
Method name(formal_spec) As returnclass [ SoapTypeNameSpace = "soapnamespace", SoapBindingStyle = document, WebMethod ]
{ //implementation }
```
其中`soapnamespace`是命名空间`URI`。请注意,如果`URI`包含冒号(`:`),则该字符串必须加引号。也就是说,可以使用以下内容:
```java
Method MyMethod() [ SoapTypeNameSpace = "http://www.mynamespace.org", SoapBindingStyle = document, WebMethod ]
```
或以下内容:
```java
Method MyMethod() [ SoapTypeNameSpace = othervalue, SoapBindingStyle = document, WebMethod ]
```
但不包括以下内容:
```java
Method MyMethod() [ SoapTypeNameSpace = http://www.mynamespace.org, SoapBindingStyle = document, WebMethod ]
```
重要提示:对于手动创建的`web服务`,该关键字的默认值通常是合适的。当使用SOAP向导从`WSDL`生成`web客户端`或服务时,InterSystems IRIS会将该关键字设置为适合该`WSDL`;如果修改该值,`web客户端`或服务可能不再工作。
# 详解
此关键字指定此`web方法`使用的类型的XML命名空间。
注意:只有当方法使用文档样式绑定时,此关键字才有作用。也就是说,方法(或包含它的类)必须用等于`document`的`SoapBindingStyle`标记。(对于使用`rpc-style`绑定的方法,指定这个关键字是没有意义的。)
# 默认
如果省略此关键字,则此方法的类型位于由`web服务`或`客户端`类的`TYPENAMESPACE`参数指定的命名空间中。如果未指定`TYPENAMESPACE`,则类型将位于由`web服务`或客户端的`are`参数指定的命名空间中。
# 与WSDL的关系
`SoapTypeNameSpace`关键字影响`WSDL`的以下部分:
``元素中的命名空间声明。指定的命名空间(例如,`http://www.customtypes.org`)将添加到这里。例如:
```xml
...
xmlns:ns2="http://www.customtypes.org"
xmlns:s0="http://www.wbns.org"
xmlns:s1="http://webservicetypesns.org"
...
targetNamespace="http://www.wbns.org"
```
在本例中,`http://www.customtypes.org`命名空间被分配给前缀`ns2`。
请注意,`WSDL`还像往常一样声明了以下名称空间:
- `Web服务`的命名空间(`http://www.wsns.org`),在本例中,它被分配给前缀`s0`,也用作`Web服务`的目标命名空间。
- 网络服务的类型命名空间`http://www.webservicetypesns.org`),在本例中它被分配给`前缀s1`。
如果在`web服务`类中没有指定类型命名空间,则该命名空间不包含在`WSDL`中。
- ``元素,它包含一个``元素,该元素的`targetNamespace`属性等于为`SoapTypeNameSpace`指定的命名空间:
```xml
...
...
```
相反,如果没有指定`SoapTypeNameSpace`,那么`WSDL`的这一部分将如下所示。请注意,``元素的`targetNamespace`是`web服务`类型的命名空间:
```xml
...
...
```
(此外,如果在`web服务类`中没有指定类型命名空间,则`targetNamespace`将改为`web服务`的命名空间。)
# 对消息的影响
`SOAP`消息可能如下所示(为了可读性,添加了换行符和空格):
```xml
3
```
请注意,``元素位于`“http://www.customtypes.org”`命名空间中。
相反,如果没有指定`SoapTypeNameSpace`关键字,则消息可以如下所示:
```xml
3
```
# 第八十三章 方法关键字 - SqlName
覆盖投影`SQL`存储过程的默认名称。
仅当此方法被投影为`SQL`存储过程时应用。
# 用法
要覆盖方法投射为`SQL`存储过程时使用的默认名称,请使用以下语法:
```java
ClassMethod name(formal_spec) As returnclass [ SqlProc, SqlName = sqlname ]
{ //implementation }
```
其中`sqlname`是`SQL`标识符。
# 详解
如果将此方法投影为`SQL`存储过程,则使用此名称作为存储过程的名称。
# 默认
如果忽略这个关键字, IRIS确定`SQL`名称如下:
```java
CLASSNAME_METHODNAME
```
默认使用大写字母。
但是,在调用存储过程时可以使用任何情况,因为SQL是不区分大小写的。
因此,在下面的示例中,默认的`SQL name`值是`TEST1_PROC1`。
这个默认值是在`SELECT`语句中指定的:
```java
Class User.Test1 Extends %Persistent
{
ClassMethod Proc1(BO,SUM) As %INTEGER [ SqlProc ]
{
///definition not shown
}
Query Q1(KD As %String,P1 As %String,P2 As %String) As %SqlQuery
{
SELECT SUM(SQLUser.TEST1_PROC1(1,2)) AS Sumd
FROM SQLUser.Test1
}
}
```
# 第八十四章 方法关键字 - SqlProc
指定是否可以作为`SQL`存储过程调用该方法。
只有类方法(而不是实例方法)可以作为SQL存储过程调用。
# 用法
要指定该方法可以作为`SQL`存储过程调用,请使用以下语法:
```java
ClassMethod name(formal_spec) As returnclass [ SqlProc ]
{ //implementation }
```
否则,忽略该关键字或将`Not`放在该关键字之前。
# 详解
该关键字指定可以作为`SQL`存储过程调用该方法。
只有类方法(而不是实例方法)可以作为`SQL`存储过程调用。
存储过程由子类继承。
# 默认
如果忽略此关键字,则该方法作为`SQL`存储过程不可用。
文章
姚 鑫 · 八月 15, 2022
# 第二章 使用管理门户(二)
# 管理门户概述
本节介绍管理门户页面的一些常见布局元素。
注意:在管理门户中的任何位置,将光标移到菜单项上都会显示该项目的描述。
## 管理门户主页
管理门户主页的标题是 `Welcome, `。在标题旁边,功能区包含以下选项:
- 两个视图按钮,可让指定如何在菜单列中显示链接。
- 搜索栏,位于功能区的右侧。当指定一个词并按 Enter 键时,将显示包含该词的所有页面的列表;然后,可以单击要显示的目标页面,而无需浏览子菜单。
以下部分描述了主页的区域:
### 管理门户菜单栏
位于主页左边缘的菜单栏是导航门户的主要方法。
### 管理门户欢迎窗格
欢迎窗格位于主页的中心,包括经常访问的页面的快捷方式。它包含以下字段:
- 收藏夹`Favorites` — 列出选择为收藏夹的管理门户页面(请参阅操作窗格);可以单击每个页面标题直接转到该页面。
- 最近`Recent` — 列出自上次启动 IRIS 以来最近显示的页面。
- `Did you know?` — 显示提示。
- 链接 `Links` - 指向可能想要访问的页面的链接。
### 管理门户消息窗格
位于主页右侧边缘的消息窗格显示一般系统信息并提供指向系统仪表板的链接。
如果实例是镜像成员,则消息窗格还显示它所属的镜像、其状态和成员类型以及指向镜像监视器的链接。
## 管理门户标题
页眉位于管理门户中每个页面的顶部,可用于快速导航门户。
标题包含以下链接:
- 主页`Home` — 显示管理门户主页。
- 关于`About` — 显示系统概览信息。
- 帮助`Help` — 显示正在查看的页面/主题的在线文档(帮助)。
- 联系方式`Contact` — 显示全球响应中心 (WRC) 的联系方式页面。
- 注销`Logout` — 注销您并带您进入管理门户的登录页面。
- 菜单`Menu` — 根据用户担任的角色显示常见任务列表。
标头还包含有用的信息,例如:
- 服务器`Server` — 运行 IRIS 的服务器的名称。
- 命名空间`Namespace` — 当前使用的命名空间的名称。要在不同的命名空间中工作,请单击切换并选择所需的命名空间。
- 用户`User` — 登录到管理门户的用户的名称。要更改用户的密码,请单击名称。
- 许可证`Licensed to` — 出现在许可证密钥信息中的客户名称。
- 实例`Instance` — 服务器上运行的 `IRIS` 实例的名称。
此外,可以显示系统模式标签(例如,测试系统);
管理门户标题的左侧显示正在使用的产品的名称。
## 管理门户功能区
功能区位于标题正下方,并显示特定于每个页面的不同内容。例如,数据库页面(`System Explorer` > `Databases`)的功能区如下图所示:
功能区的典型内容是:
- 正在显示的管理门户页面的标题。
- 当前页面的面包屑,直接列在页面标题上方。路径中列出的每个页面都是一个活动链接,可以使用它返回到先前显示的子菜单/列表。当在页面上进行未保存的更改时,会在面包屑中附加一个星号,例如系统 > 配置 >内存和启动 —(配置设置)*。在离开未保存的更改之前,系统始终会提示进行确认。
注意:页签不会列出路径中的每个页面,并且页签中的页面并不总是与导航菜单中的页面匹配。始终可以通过单击主页返回到管理门户主页并使用搜索工具导航到特定页面,本节稍后将对此进行介绍。
- 允许在页面上执行操作的几个按钮。例如,`Databases` 页面包含按钮 `Integrity Check` 和 `Integrity Log`。
- 刷新按钮,包含有关页面上次更新时间的信息。
## 系统概述信息
单击管理门户标题上的关于时,将显示一个表格,其中包含以下信息:
- 版本 — 此 `IRIS` 实例的特定构建信息,包括平台、构建号和构建日期。
- 配置 - 此实例正在使用的配置 (`.cpf`) 文件的名称和位置。
- 数据库缓存 (`MB`) — 为数据库分配的空间例程缓存 (`MB`) — 为例程分配的空间。
- 日志文件 - 当前日志文件的名称和位置。
- `SuperServer` 端口 — 运行 `IRIS` 服务器的端口号。
- `Web`服务器端口 — 运行私有 `IRIS Web` 服务器的端口号。
- 许可证服务器地址/端口 — `IRIS` 许可证服务器的 `IP` 地址和运行它的端口号。
- 许可给 — 出现在许可密钥信息中的客户名称。
- 集群支持 - 指示此实例是否是集群的一部分。
- 镜像 — 指示此实例是否是镜像的成员。
- `Time System Started` — 上次启动 `InterSystems IRIS` 实例的日期和时间。
- 加密密钥标识符 — 如果激活加密,则为加密密钥的 `GUID`(全局唯一 `ID`)。
- `NLS` 区域设置 — 国家语言支持区域设置。
- 此会话的首选语言 - 管理门户已本地化并可显示的语言的下拉列表。可以通过从下拉列表中选择新的语言来更改显示语言。最初,浏览会话的首选语言是为浏览器指定的语言,如果不支持浏览器语言,则为英语;在特定浏览器中选择首选语言后,即使更改了浏览器语言,该浏览器中的管理门户也会使用该语言。
文章
姚 鑫 · 七月 21, 2021
# 第五章 参数定义
描述参数定义的结构。
# 介绍
参数定义定义了一个给定类的所有对象都可用的常数值。创建类定义时(或在编译前的任何时候),可以设置其类参数的值。默认情况下,每个参数的值都是空字符串,但是可以在参数定义中指定一个非空值。在编译时,为类的所有实例建立参数值。除了极少数例外,该值不能在运行时更改。
# 详解
参数定义具有以下结构:
```java
/// description
Parameter name As parameter_type [ keyword_list ] = value ;
```
- `description`描述(可选)旨在显示在“类参考”中。默认情况下,描述为空白。
- `name`(必需)是参数的名称。这必须是有效的类成员名称,并且不能与任何其他类成员名称冲突。
- `parameter_type`(可选)指定参数的用户界面类型,由Studio用于在检查器内为参数提供输入验证。
**这不是类名;参见下一节。在大多数情况下,编译器会忽略这个关键字。**
如果省略参数类型,也要省略单词`As`
- `value`(可选)指定参数的值。如果省略值,也要省略等号=
- `keyword_list`(可选)是以逗号分隔的关键字列表,用于进一步定义参数。
如果省略此列表,也要省略方括号。
# 参数的允许类型
参数类型`parameter_type` 选项可以是下列值之一:
- `BOOLEAN` — true(1)或false(0)值。
- `CLASSNAME` — 有效的类名。
- `COSCODE` — ObjectScript代码。
- `COSEXPRESSION` — 有效的ObjectScript表达式。
**如果参数是`COSEXPRESSION`类型,则在运行时计算该表达式。**
与形参`Type`关键字的大多数其他值不同,这个值影响编译器。
- `COSIDENTIFIER` — 有效的ObjectScript标识符。
- `INTEGER` — 整数值。
- `SQL` — SQL语句
- `SQLIDENTIFIER` — 有效的SQL标识符。
- `STRING` —字符串值。
- `TEXT` — 多行文本值。
- `CONFIGVALUE` -可以在类定义之外修改的参数。
与形参`Type`关键字的大多数其他值不同,这个值影响编译器。
如果参数的类型是`CONFIGVALUE`,那么可以通过`$SYSTEM.OBJ.UpdateConfigParam()`修改参数。
例如,下面的代码更改了参数`MYPARM`(在类`MyApp`中)的值。
`MyClass`的新值为`42`:
```java
set sc=$system.OBJ.UpdateConfigParam("MyApp.MyClass","MYPARM",42)
```
**注意,`$SYSTEM.OBJ.UpdateConfigParam()`影响任何新进程所使用的生成的类描述符,但不影响类定义。
如果重新编译类,InterSystems IRIS将重新生成类描述符,该描述符现在将使用包含在类定义中的这个参数的值(从而覆盖通过`$SYSTEM.OBJ.UpdateConfigParam()`所做的更改)。**
也可以省略`parameter_type`,在这种情况下`Inspector`将允许参数的任何值。
```
/// web服务的名称。
Parameter SERVICENAME = "SOAPDemo" ;
```
# 第六章 映射定义
描述投影定义的结构。
# 介绍
投影定义指示类编译器在编译或删除类定义时执行指定的操作。
投影定义投影类的名称(来自`%Projection.AbstractProjection`)实现方法称为类的编译完成后,当一个类定义中删除(因为它被删除或者因为类即将重新编译)。
# 详情
投影定义有以下结构:
```java
/// description
Projection name As projection_class (parameter_list) ;
```
- `description`(可选)用于在类引用中显示(但请注意投影目前没有显示在类引用中)。
说明默认为空。
- `Name`(必需)是投影的名称。
这必须是一个有效的类成员名,并且不能与任何其他类成员名冲突。
- `projection_class` (required)是投影类的名称,它是`%Projection.AbstractProjection`的子类。
- `parameter_list`(可选)是一个以逗号分隔的参数及其值列表。
如果指定,这些应该是`projection_class`使用的参数。
如果省略了这个列表,也省略了括号。
- `Keyword_list`(可选)是一个逗号分隔的关键字列表,这些关键字进一步定义了投影。
如果省略了这个列表,也可以省略方括号。
# 第七章 属性定义
描述属性定义的结构。注意,关系是一种属性。
# 介绍
属性包含与类实例相关的信息。可以向对象类添加属性定义。它们在其他类中没有意义。
# 详情
属性定义有以下结构:
```java
/// description
Property name As classname (parameter_list) [ keyword_list ] ;
```
或者(对于列表属性):
```java
/// description
Property name As List Of classname (parameter_list) [ keyword_list ] ;
```
或者(对于数组属性):
```java
/// description
Property name As Array Of classname (parameter_list) [ keyword_list ] ;
```
或者(对于关系属性):
```java
/// description
Relationship name As classname [ keyword_list ] ;
```
- `description`(可选)用于在类引用中显示。说明默认为空。
- `name`(必需)是属性的名称。
这必须是一个有效的类成员名,并且不能与任何其他类成员名冲突。
- `classname`(可选)是该属性所基于的类的名称。
- `parameter_list`(可选)是参数及其值的逗号分隔列表。如果指定,这些应该是由类名使用的参数,或者是对所有属性都可用的参数。
如果省略此列表,也要省略括号。
- `keyword_list`(对于关系属性是必需的,但在其他方面是可选的)是一个逗号分隔的关键字列表,用于进一步定义属性。
如果省略此列表,也要省略方括号。
注意:分片类不支持属性关系。
```java
Property SSN As %String(PATTERN = "3N1""-""2N1""-""4N") [ Required ];
```
# 第八章 查询定义
描述查询定义的结构。
# 介绍
类查询是作为类结构一部分的命名查询,可以通过动态SQL进行访问。
**可以在任何类中定义类查询;不需要将它们包含在持久类中。**
# 详解
查询定义具有以下结构:
```java
/// description
Query name(formal_spec) As classname [ keyword_list ]
{ implementation }
```
- `description`描述(可选)旨在显示在“类别参考”中。默认情况下,描述为空白。
- `name`(必需)是查询的名称。这必须是有效的类成员名称,并且不能与任何其他类成员名称冲突。
- `formal_spec`(可选)指定传递给查询的参数列表。
具体来说,这是通过关联查询类的`Execute()`方法传递给查询的参数列表。
- `classname`(必需)指定此查询使用的查询类。
**对于基于SQL的查询,该值通常为`%SQLQuery`,对于自定义查询,该值通常为%Query。**
**注意:分片类不支持自定义类查询。**
- `keyword_list`(可选)是逗号分隔的关键字列表,用于进一步定义查询。
如果省略此列表,也要省略方括号。
- `implementation` 实现(可选)是定义查询的零行或多行代码。
文章
姚 鑫 · 六月 14, 2022
# 第九章 其他参考资料(二)
# 特殊变量 (SQL)
系统提供的变量。
```sql
$HOROLOG
$JOB
$NAMESPACE
$TLEVEL
$USERNAME
$ZHOROLOG
$ZJOB
$ZPI
$ZTIMESTAMP
$ZTIMEZONE
$ZVERSION
```
SQL直接支持许多对象脚本特殊变量。这些变量包含系统提供的值。只要可以在SQL中指定文字值,就可以使用它们。
SQL特殊变量名不区分大小写。大多数可以使用缩写来指定。
Variable| Name| Abbreviation| Data Type Returned Use
---|---|---|---
$HOROLOG| $H| %String/VARCHAR |当前进程的本地日期和时间
$JOB| $J| %String/VARCHAR |当前进程的 job ID
$NAMESPACE| none| %String/VARCHAR |当前命名空间名称
$TLEVEL| $TL| %Integer/INTEGER|| 当前事务嵌套级别
$USERNAME| none| %String/VARCHAR|当前进程的用户名
$ZHOROLOG| $ZH |%Numeric/NUMERIC(21,6)|自InterSystems IRIS启动后经过的秒数
$ZJOB| $ZJ| %Integer/INTEGER|当前进程的job状态
$ZPI| none| %Numeric/NUMERIC(21,18) |数值常量PI
$ZTIMESTAMP |$ZTS| %String/VARCHAR |协调世界时间格式的当前日期和时间
$ZTIMEZONE| $ZTZ| %Integer/INTEGER| 当地时区与GMT的偏移量
$ZVERSION| $ZV| %String/VARCHAR| IRIS的当前版本
# 示例
```sql
SELECT TOP 5 Name,$H
FROM Sample.Person
```
以下示例仅在时区位于大陆内时才返回结果集:
```sql
SELECT TOP 5 Name,Home_State
FROM Sample.Person
WHERE $ZTIMEZONE BETWEEN -480 AND 480
```
# 字符串操作(SQL)
字符串操作函数和运算符。
SQL 支持多种类型的字符串操作:
- 字符串可以通过长度、字符位置或子字符串值进行操作。
- 字符串可以通过指定的分隔符或分隔符字符串来操作。
- 字符串可以通过模式匹配和单词感知搜索来测试。
- 特殊编码的字符串(称为列表)包含嵌入的子字符串标识符,而不使用分隔符。各种 `$LIST` 函数对这些与标准字符串不兼容的编码字符串进行操作。唯一的例外是 `$LISTGET` 函数和 `$LIST` 的单参数和双参数形式,它们将编码字符串作为输入,但将单个元素值作为标准字符串输出。
SQL 支持字符串函数、字符串条件表达式和字符串运算符。
ObjectScript 字符串操作区分大小写。字符串中的字母可以转换为大写、小写或混合大小写。字符串排序规则可以区分大小写,也可以不区分大小写;默认情况下,SQL 字符串排序规则是不区分大小写的 `SQLUPPER`。 SQL 提供了许多字母大小写和排序规则函数和运算符。
当为数字参数指定字符串时,大多数 SQL 函数执行以下字符串到数字的转换: 非数字字符串转换为数字 0;将数字字符串转换为规范数字;并且混合数字字符串在第一个非数字字符处被截断,然后转换为规范数字。
# 字符串连接
以下函数将子字符串连接成字符串:
- `CONCAT`:连接两个子字符串,返回一个字符串。
- `STRING`:连接两个或多个子字符串,返回单个字符串。
- `XMLAGG`:连接列的所有值,返回单个字符串。
- `LIST`:连接列的所有值,包括逗号分隔符,返回单个字符串。
- 连接运算符 (`||`) 也可用于连接两个字符串。
# 字符串长度
以下函数可用于确定字符串的长度:
- `CHARACTER_LENGTH` 和 `CHAR_LENGTH`:返回字符串中的字符数,包括尾随空格。 `NULL` 返回 `NULL`。
- `LENGTH`:返回字符串中的字符数,不包括尾随空格。 `NULL` 返回 NULL。
- `$LENGTH`:返回字符串中的字符数,包括尾随空格。 `NULL` 返回为 0。
# Truncation and Trim
以下函数可用于截断或修剪字符串。截断限制字符串的长度,删除超出指定长度的所有字符。`Trim`从字符串中删除前导和/或尾随空格。
- `Truncation`: `CONVERT`, `%SQLSTRING`, and `%SQLUPPER`.
- `Trimming`: `TRIM`, `LTRIM`, and `RTRIM`.
# 子串搜索
以下函数在字符串中搜索子字符串并返回字符串位置:
- `POSITION`:按子字符串值搜索,找到第一个匹配项,返回子字符串开始的位置。
- `CHARINDEX`:按子字符串值搜索,找到第一个匹配项,返回子字符串开始的位置。可以指定起点。
- `$FIND`:按子串值搜索,找到第一个匹配项,返回子串结束的位置。可以指定起点。
- `INSTR`:按子字符串值搜索,找到第一个匹配项,返回子字符串开始的位置。可以指定起点和子串出现。
以下函数在字符串中按位置或分隔符搜索子字符串并返回子字符串:
- `$EXTRACT`:按字符串位置搜索,返回由开始位置或开始和结束位置指定的子字符串。从字符串的开头搜索。
- `SUBSTRING`:按字符串位置搜索,返回由开始位置或开始和长度指定的子字符串。从字符串的开头搜索。
- `SUBSTR`:按字符串位置搜索,返回由起始位置或起始和长度指定的子字符串。从字符串的开头或结尾搜索。
- `$PIECE`:按分隔符搜索,返回第一个分隔的子字符串。可以指定起点或默认为字符串的开头。
- `$LENGTH`:按分隔符搜索,返回分隔子串的数量。从字符串的开头搜索。
- `$LIST`:在特殊编码的列表字符串上按子字符串计数搜索。它通过子串计数定位子串并返回子串值。从字符串的开头搜索。
- 包含运算符 (`[`) 也可用于确定子字符串是否出现在字符串中。
- `%STARTSWITH` 比较运算符将指定的字符与字符串的开头进行匹配。
# 子串搜索和替换
以下函数在字符串中搜索子字符串并将其替换为另一个子字符串。
- `REPLACE`:按字符串值搜索,用新的子字符串替换子字符串。从字符串的开头搜索。
- `STUFF`:按字符串位置和长度搜索,用新的子字符串替换子字符串。从字符串的开头搜索。
# 字符类型和 Word-Aware 比较
`%PATTERN` 比较运算符将字符串与指定的字符类型模式匹配。
文章
姚 鑫 · 六月 24, 2021
# 第十七章 加密XML文档
本章介绍如何加密XML文档。
提示:发现在此命名空间中启用`SOAP`日志记录非常有用,这样就可以收到有关任何错误的更多信息。
# 关于加密的XML文档
加密的XML文档包括以下元素:
- ``元素,其中包含由随机生成的对称密钥加密的加密数据。(使用对称密钥加密比使用公钥加密更有效。)
- 至少有一个``元素。每个``元素携带用于加密数据的对称密钥的加密副本;它还包含一个带有公钥的`X.509`证书。拥有匹配私钥的接收方可以解密对称密钥,然后解密``元素。
- (可选)其他明文元素。
```xml
MIICnDCCAYQCAWUwDQYJKo... content omitted
J2DjVgcB8vQx3UCy5uejMB ... content omitted
LmoBK7+nDelTOsC3 ... content omitted
```
要创建加密文档,请使用类`%XML.Security.EncryptedData`和`%XML.Security.EncryptedKey`。这些启用XML的类投影到适当名称空间中的有效``和``元素。
# 创建加密的XML文档
创建加密的XML文档的最简单方法如下:
1. 定义并使用可以直接投影到所需XML文档的通用容器类。
2. 创建包含要加密的XML的流。
3. 加密该流,并将其与相应的加密密钥一起写入容器类的相应属性。
4. 为容器类生成XML输出。
## 加密的前提条件
在加密文档之前,必须创建包含要将加密文档发送到的实体的证书的 IRIS凭据集。在这种情况下,不需要(也不应该拥有)关联的私钥。
## 容器类的要求
一个通用容器类必须包括以下内容:
- 类型为`%XML.Security`的属性。
被投影为``元素的`EncryptedData`。
这个属性将携带加密的数据。
- 至少一个类型为`%XML.Security`的属性。被投影为``元素的`EncryptedKey`。
这些属性将携带相应的密钥信息。
示例如下:
```java
Class XMLEncryption.Container Extends (%RegisteredObject, %XML.Adaptor)
{
Property Data As %XML.Security.EncryptedData(XMLNAME = "EncryptedData");
Property Key As %XML.Security.EncryptedKey(XMLNAME = "EncryptedKey");
Parameter NAMESPACE = "http://www.w3.org/2001/04/xmlenc#";
}
```
## 生成加密的XML文档
要生成并编写加密文档,请执行以下操作:
1. 创建包含XML文档的流。
为此,通常使用`%XML.Writer`将启用XML的对象的输出写入流。
2. 创建`%SYS.X509Credentials`的至少一个实例,将访问要向其提供加密文档的实体的InterSystems IRIS凭据集。为此,请调用此类的`GetByAlias()`类方法。例如:
```java
set credset=##class(%SYS.X509Credentials).GetByAlias("recipient")
```
若要运行此方法,必须以该凭据集的`OwnerList`中包含的用户身份登录,否则`OwnerList`必须为空。
3. 至少创建`%XML.Security.EncryptedKey`实例。若要创建此类的实例,请使用此类的`CreateX509()`类方法。例如:
```java
set enckey=##class(%XML.Security.EncryptedKey).Createx509(credset,encryptionOptions,referenceOption)
```
- `credset`是`%SYS`的实例。
`x509credentials`在刚刚创建的新窗口中打开。
- `encryptionOptions`是`$$$SOAPWSIncludeNone`(还有其他选项,但它们不适用于此场景)。
此宏在`%soap.inc`包含文件中定义。
- `referenceOption`指定了对加密元素的引用的性质。
这里使用的宏在`%soap.inc`包含文件中定义。
4. 在创建`%Library.ListOfObjects`实例,并使用其`Insert()`方法在刚创建插入`%XML.Security.EncryptedKey`实例。
5. 使用`%New()`方法创建`%XML.Security.EncryptedData`实例。例如:
```java
set encdata=##class(%XML.Security.EncryptedData).%New()
```
6. 使用`%XML.Security.EncryptedData的EncryptStream()`实例方法加密在步骤2中创建的流。例如:
```java
set status=encdata.EncryptStream(stream,encryptedKeys)
```
- stream 流是在步骤1中创建的流。
- encryptedKeys是在步骤4中创建的密钥列表。
7. 创建并更新容器类的实例。
- 将键列表写入此类的相应属性。
- 将 `%XML.Security.EncryptedData`的实例写入此类的相应属性。
8. 使用`%XML.Writer`为容器类生成输出。
例如,前面显示的`CONTAINER`类还包括以下方法:
```java
/// w ##class(XMLEncryption.Container).Demo("E:\temp\SecurityXml.txt")
ClassMethod Demo(filename = "", obj = "")
{
#Include %soap
if (obj = "") {
s obj = ##class(MyApp.Person).%OpenId(1)
}
//从此启用XML的对象创建流
set writer = ##class(%XML.Writer).%New()
set stream = ##class(%GlobalCharacterStream).%New()
set status = writer.OutputToStream(stream)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit }
set status = writer.RootObject(obj)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit }
do stream.Rewind()
set container = ..%New() ; 这就是我们要写出的对象
set cred = ##class(%SYS.X509Credentials).GetByAlias("servercred")
set parts =$$$SOAPWSIncludeNone
set ref = $$$KeyInfoX509Certificate
set key = ##class(%XML.Security.EncryptedKey).CreateX509(cred, parts, ref)
set container.Key = key ; 这个细节取决于类
//需要创建一个键列表(本例中仅为一个)
set keys = ##class(%Collection.ListOfObj).%New()
do keys.Insert(key)
set encdata = ##class(%XML.Security.EncryptedData).%New()
set status = encdata.EncryptStream(stream, keys)
set container.Data = encdata ; 这个细节取决于类
// 为容器写输出
set writer = ##class(%XML.Writer).%New()
set writer.Indent = 1
if (filename'="") {
set status = writer.OutputToFile(filename)
if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}
}
set status = writer.RootObject(container)
if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}
}
```
此方法可以接受任何启用XML的类的`OREF`;如果没有提供,则使用默认值。
# 解密加密的XML文件
## 解密的前提条件
在解密加密的`XML`文档之前,必须同时提供以下两项:
- IRIS要使用的受信任证书。
- IRIS凭据集,其私钥与加密中使用的公钥匹配。
## 解密文档
要解密加密的XML文档,请执行以下操作:
1. 创建`%XML.Reader`实例打开并使用它打开文档。
2. 获取`Document`属性,`%XML.Reader`实例。
其中包含作为DOM的XML文档。
3. 使用阅读器的`correlation()`方法将``元素或元素与类`%XML.Security.EncryptedKey`关联起来。
例如:
```java
do reader.Correlate("EncryptedKey","%XML.Security.EncryptedKey")
```
4. 遍历文档以读取``元素或多个元素。
为此,可以使用阅读器的`Next()`方法,该方法通过引用返回一个导入的对象(如果有的话)。
例如:
```java
if 'reader.Next(.ikey,.status) {
write !,"Unable to import key",!
do $system.OBJ.DisplayError(status)
quit
}
```
导入的对象`是%XML.Security.EncryptedKey`的实例。
5. 创建`%Library.ListOfObjects`的实例。
并使用它的`Insert()`方法插入`%XML.Security.EncryptedKey`的实例。
刚从文档中获得的。
6. 调用类`%XML.Security.EncryptedData`的`ValidateDocument()`方法
```java
set status=##class(%XML.Security.EncryptedData).ValidateDocument(.doc,keys)
```
第一个参数(通过引用返回)是在第2步中检索到的DOM的修改版本。
第二个参数是上一步中的键列表。
7. 可以选择使用`%XML.Writer`为修改后的DOM生成输出。
例如,前面显示的`CONTAINER`类包含以下类方法:
```java
ClassMethod DecryptDoc(filename As %String)
{
#Include %soap
set reader = ##class(%XML.Reader).%New()
set status = reader.OpenFile(filename)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit }
set doc = reader.Document
//获取元素
do reader.Correlate("EncryptedKey","%XML.Security.EncryptedKey")
if 'reader.Next(.ikey,.status) {
write !,"无法导入密钥",!
do $system.OBJ.DisplayError(status)
quit
}
set keys = ##class(%Collection.ListOfObj).%New()
do keys.Insert(ikey)
// 以下步骤返回解密的文档
set status = ##class(%XML.Security.EncryptedData).ValidateDocument(.doc,keys)
set writer = ##class(%XML.Writer).%New()
set writer.Indent = 1
do writer.Document(doc)
quit $$$OK
}
```
文章
姚 鑫 · 六月 15, 2021
# 第七章 控制命名空间分配的外观
# 控制命名空间分配的外观
除了控制命名空间分配外,还可以控制命名空间分配在XML输出中的显示方式。具体地说,可以控制以下内容:
## 显式名称空间分配与隐式名称空间分配
将元素和属性分配给命名空间时,XML中有两种等效的表示形式,由编写器实例的`SuppressXmlns`属性控制。
为一个名为`Person`的对象生成XML输出,该对象被分配给名称空间`“http://www.person.org”`(通过前面讨论的`namespace`类参数)。
使用缺省输出(`SuppressXmlns`等于0)的示例如下:
```java
Uberoth,Amanda Q.
1952-01-13
```
另一种可能的形式完全相同,如下所示。
这是使用`SuppressXmlns`等于1生成的,它确保显式分配给名称空间的每个元素都显示为该名称空间的前缀。
```java
Uberoth,Amanda Q.
1952-01-13
```
请注意,此属性仅影响命名空间分配的显示方式;它不控制如何分配任何命名空间。如果不使用命名空间,则此参数无效。
## 为命名空间指定自定义前缀
当为对象生成XML输出时,系统会根据需要生成命名空间前缀。第一个名称空间前缀是`s01`,下一个是`s02`,依此类推。可以指定不同的前缀。为此,请在启用XML的对象本身的类定义中设置`XMLPREFIX`参数。此参数有两个效果:
- 它确保在XML输出中声明指定的前缀。也就是说,即使没有必要这样做,它也会被声明。
- 它使用该前缀,而不是在其他情况下会看到的自动生成的前缀。
# 控制空字符串(`""`)的导出方式
为对象启用XML时,需要指定将空值和空字符串投影到XML的方式
其中一个选项是在支持xml的类中将`XMLIGNORENULL`设置为`“RUNTIME”`(不区分大小写)。
在这种情况下,当使用%XML.Write的`RuntimeIgnoreNull`属性的值来确定如何处理任何等于`""`的属性,如下所示:
- **如果编写器的`RuntimeIgnoreNull`属性为0(默认值),则`XMLNIL`参数控制如何导出该属性。`XMLNIL`是一个类参数和一个属性参数;属性参数优先。**
- 如果`XMLNIL`为0(默认值),则不投影特性。也就是说,它不包含在XML文档中。
- 如果`XMLNIL`为1,并且该属性用作元素,则该属性将按如下方式导出:
```
```
- 如果`XMLNIL`为1并且特性用作属性,则不会输出特性。
- 如果编写器的`RuntimeIgnoreNull`属性为1,则该属性将导出为空元素或空属性(其导出方式与值`$char(0)`相同,后者始终导出为空元素或空导出)。
除非`XMLIGNORENULL`在启用xml的类中是`“RUNTIME”`,否则编写器的`RuntimeIgnoreNull`属性是无效的。
## 示例:`RuntimeIgnoreNull`为0(默认值)
```java
Class EmptyStrings.Export Extends (%Persistent, %XML.Adaptor)
{
Parameter XMLNAME="Test";
Parameter XMLIGNORENULL = "RUNTIME";
///把这个作为一个元素
///XMLNIL is 0, the default
Property Property1 As %String;
///把这个作为一个元素
///XMLNIL is 0, the default
Property Property2 As %String(XMLPROJECTION = "ATTRIBUTE");
///将其作为XMLNIL=1的元素
Property Property3 As %String(XMLNIL = 1);
///将其作为XMLNIL=1的属性进行项目
Property Property4 As %String(XMLNIL=1,XMLPROJECTION="ATTRIBUTE");
}
```
如果创建了这个类的一个新实例(并且没有设置任何属性的值),然后使用 `%XML.Writer`输出,如下所示:
```java
```
## 例如:`RuntimeIgnoreNull`为1
```java
```
**在本例中,因为`RuntimeIgnoreNull`为1,所以没有使用`XMLNIL`参数。
相反,`""`被导出为空属性或空元素。**
# 导出类型信息
默认情况下,XML编写器不写入类型信息。有两个选项可用于在输出中包括类型信息:
- 编写器的`OutputTypeAttribute`属性。如果此属性为1,则编写器包括其写入的对象内所有元素的XML类型信息(但不包括对象本身)。例如:
```java
Petersburg,Greta U.
1949-05-15
```
请注意,相应的命名空间将添加到XML文档的根。
- `Object()`和`RootObject()`方法的`className`参数。此参数用于指定对象的预期`ObjectScript`类型(类名)。
如果参数与实际类型相同,则编写器不包括对象的类型信息。
如果参数与实际类型不同,编写器将包括对象的实际XML类型(默认为类名)。例如,假设`Test2.PersonWithAddress`实例编写输出,并将`className`参数指定为`MyPackage.MyClass`。因为`MyPackage.MyClass`与实际的类名不同,所以编写器生成以下输出:
```java
Avery,Robert H.
Ukiah
82281
```
# 生成SOAP编码的XML
对于`%XML.Writer`,`Format`属性控制输出的整体格式。这是以下选项之一:
- `“literal”`,即默认值,在本书的大多数例子中都使用了它。
- `“encoded”`,按照SOAP 1.1标准中的描述进行编码。
- `“encoded12”`,按照SOAP 1.2标准中的描述进行编码。
## 创建内联引用
在编码格式中,任何对象值属性都被作为引用包含,被引用的对象被导出为单独的元素。
要将这些属性内联导出,而不是作为单独的元素,请将`ReferencesInline`属性设置为1。
如果格式是`“literal”`,`ReferencesInline`属性没有效果。
# 导出后控制unswizling
当导出一个支持xml的持久对象时,系统会像往常一样自动将所有需要的信息混合到内存中;该信息包括对象值属性。导出对象后,InterSystems IRIS将消除任何对象列表,但(默认情况下)不会消除单个对象引用。
对于大对象,这可能导致``错误。
在这种情况下,要使任何单个对象引用不被混合,请在支持xml的类中设置`XMLUNSWIZZLE`参数,如下所示:
```java
Parameter XMLUNSWIZZLE = 1;
```
该参数默认值为0。
# 控制元素的关闭
只包含属性的元素可以用以下任一方式表示:
```java
```
`Object()`方法始终使用第一个语法导出元素。如果需要使用此处显示的第二种语法关闭元素,请手动编写对象,如本章前面的“手动构造元素”中所述。
咨询一下,我也是初学,有没有类似于oracle的exp远程备份数据库的方法?
文章
王喆 👀 · 九月 13, 2022
背景
作为集成平台厂商,在医院同其它系统联调的时候所做的事情中,多的不是开发代码而是查消息。我先演示一下目前我正在使用的IRIS查消息的方式:
例子1:
需要看【个人信息注册服务】
我只需要在框中输入【个人信息注册】回车
点击【查看消息】,显示的是消息里面的内容,如图所示:
点击【查看流程】,显示的是IRIS消息可视化的页面,如图所示:
例子2:
需要查询患者ID为【2874621】这患者在集成平台调用了哪些服务
我只需要选择下拉选择【患者ID】,然后输入【2874621】,回车
这个是我们以用户需求的角度直观的查询到指定的消息,IRIS也有这个功能—消息查看器,它是如何使用的呢?首先,我们得知道这条消息使用了哪些Production组件,其次我们需要了解这个消息使用的实体类的结构。比如同样查这个人的消息,我需要输入request.Body.PatientID=“2874621”,选择消息类。如果我需要查多个服务的我还需要多选消息类 …… 本文不是来介绍如何使用消息查看器的,各位大概知道就好。
程序分析与设计思路
原始消息查看器的使用
我们先使用IRIS自带的【消息查看器】查患者ID为【2874621】个人信息注册服务,如图所示:
选时间,输入图中所示的条件去检索。步骤上好像也挺简单的,但是这里有两个前提条件,一是我得知道每个服务对应的消息类是哪个。二是我知道这个消息类里面患者ID对应的字段是哪个,而且这个字段不能在循环里面(好像说了3个条件 ~ 哈哈),如何处理摆在了我们的眼前。
SQL分析
使用IRIS自带的【显示查询】功能(这个如何开启使用可以看我的另外一篇文章 https://cn.community.intersystems.com/node/525741 ),如图所示:
找个位置格式化一下:
可以看到从这段SQL可以看出3点:
(1)IRIS消息记录的表是【Ens.MessageHeader】
(2)IRIS保存消息是给实体本身建立了一个表 比如上述的例子:
【BKIP_PatientInfo_MSG.PatientInfoRegister】
(3)head.MessageBodyId = BKIP_PatientInfo_MSG.PatientInfoRegister.%ID
通过这3条,我们可以得到下面这张图的信息:
倘若我们把SQL按照如上图所示的方式处理,然后反应在页面上供用户选择;患者ID也由用户选择或者输入;这样确实直观的解决了。但是,效率,上述消息查看器在用户选择时间的时候首先查到两个ID让查询就在这两个主键ID之间查找,增加了效率,我们当然可以使用。那么它是必须的么,去掉的话我们速度一定变慢?这是其一。第二点,如果患者ID是在循环中,甚至是在循环的循环中…… 消息查看器好像就没办法了(当然如果有大佬可以解决请接受我的膝盖)。
需要解决的问题
对照表,服务和消息类的对照表
如何把循环中的字段作为关键字进行查询
第一个问题就是解决方案,新建一张表维护服务和消息类的关系,第二个问题:我们要去看一下Ens.MessageHeader这张表:
好像看不出什么。。。
大家可以看看这个图
在图中红框框住的部分是一样的,同时我们进入消息可视化,如图:
我们可以不可以说这个会话ID就是代表了这个服务的这条消息呢?我们要查指定服务的指定人的消息可以理解为查这个会话ID也就是SessionID。我们把思路换一下,如果我把循环中的患者ID遍历出来存入一张表中,这一行的数据我有sessionID、患者ID、服务名。当我想查患者ID为【2874621】的个人信息注册服务得到sessionID之后,直接跳到可视化追踪,如果把患者ID换成医嘱ID也是一样的处理,其它关键字段也是一样,问题是不是迎刃而解?
程序设计(思路)
新建一个索引表 字段为 SessionID、服务名、属性名、属性值、创建时间,如图所示:
然后在每次服务被调用的时候取出我们需要的属性名、属性值和SessionID存入这张表中,如图所示:
我们在页面上进行查询的时候只需要编写如图所示的SQL,省略号代表and后面的条件。
后续的REST接口和前端的页面在此就【略】,大家可以参考我的另外一篇文章提供的思路编写提供出来Restful接口(https://cn.community.intersystems.com/node/525561 )。
总结
总的来说,我们思路是我们把原本数据量变小,把原本的多重循环的问题维护在一张单表的多行数据中去。这样把原本的多表联合查询改成了单表查询。这速度不快的飞起?目前这边只做思路分享,相信程序设计上大家肯定有自己的一套方式,我这边暂时不做过多展示。都看到这里了,给我点一个赞吧!!! 可以考虑使用ElasticSearch做全文搜索 是的,用ES是最好的。但是ES中有一个很重要的概念叫倒排索引,就是拆分词语存入倒排索引的库,方便在检索的时候分析,和我这边的把一个消息关键字段拆分成索引去检索是否有一点异曲同工捏。当然还是那句话ES是最好的,只不过我这边不需要那么高性能,而且也没试过IRIS结合ES去使用,这里只做分享哈。 还可以考虑用iknow(现在叫InterSystems NLP)实现全文检索
文章
姚 鑫 · 六月 17, 2023
# 第六十章 镜像中断程序 - 使用主 `ISCAgent` 的日志数据进行 `DR` 提升和手动故障转移
## 使用主 `ISCAgent` 的日志数据进行 `DR` 提升和手动故障转移
如果 `IRIS A` 的主机系统正在运行,但 `IRIS` 实例没有且无法重新启动,您可以使用以下过程在通过升级后使用来自 `IRIS A` 的最新日志数据更新升级的 `IRIS C IRIS A` 的 `ISCAgent`。
1. 推广 `IRIS C`,选择 `IRIS A` 作为故障转移伙伴。 `IRIS C` 被提升为故障转移成员,从 `IRIS A` 的代理获取最新的日志数据,并成为主要成员。
2. 重新启动 `IRIS A` 上的 `IRIS` 实例,它作为备份重新加入镜像。
3. 在 `IRIS A` 重新加入镜像并变为活动状态后,可以使用使用升级的 DR 异步临时替换故障转移成员中描述的过程,将所有成员返回到它们以前的角色,首先是正常关闭 `IRIS C` ,然后在 `IRIS B` 的配置参数文件的 `[MirrorMember]` 部分中设置 `ValidatedMember=0`(请参阅配置参数文件参考中的 `[MirrorMember]`),将 `IRIS B` 重新启动为 `DR` 异步,将 `IRIS B` 提升为备份,并以 `DR` 异步方式重新启动 `IRIS C`。
注意:如果 `IRIS A` 的主机系统已关闭,但 `IRIS B` 的主机系统已启动,尽管其 `IRIS` 实例未运行,请按照手动故障转移到活动备份中所述在 `IRIS B` 上运行 `^MIRROR` 例程以确定 是否`IRIS B` 在发生故障时是一个活动备份。如果是这样,使用前面的过程,但在升级期间选择 `IRIS B` 作为故障转移伙伴,允许 `IRIS C` 从 `IRIS B` 的 `ISCAgent` 获取最新的日志数据。
## 使用来自日志文件的日志数据进行 DR 提升和手动故障转移
如果 `IRIS A` 和 `IRIS B` 的主机系统都已关闭,但可以访问 `IRIS A` 的日志文件,或者 `IRIS B` 的日志文件和消息日志可用,您可以使用最新的日志数据更新 `IRIS C`从升级前的初级开始,使用以下过程。
1. 使用 `IRIS A` 或 `IRIS B` 的最新日志文件更新 `IRIS C`,如下所示:
- 如果 `IRIS A` 的日志文件可用,则将最新的镜像日志文件从 `IRIS A` 复制到 `IRIS C`,从 `IRIS C` 上的最新日志文件开始,并包括来自 `IRIS A` 的任何后续文件。例如,如果 `MIRROR -MIRRORA-20180220.001` 是 `IRIS C` 上的最新文件,复制 `MIRROR-MIRRORA-20180220.001` 和 `IRIS A` 上的任何更新文件。
- 如果 `IRIS A` 的日志文件不可用但 `IRIS B` 的日志文件和消息日志可用:
1. 确认`IRIS B`很可能已被捕获,如下所示:
a. 确认当`A`及其代理不可用时,`B`同时断开与 A的连接。可以通过在`Messages.log`文件中搜索类似于以下内容的消息来检查 `IRIS B`断开连接的时间:
```java
MirrorClient: Primary AckDaemon failed to answer status request
```
b. 通过在其 `messages.log` 文件中搜索类似于以下内容的消息,确认 IRIS B 在断开连接时是活动备份:
```java
Failed to contact agent on former primary, can't take over
```
注意:`messages.log` 文件中的如下消息表明 `IRIS B` 在断开连接时未处于活动状态:
```java
nonactive Backup is down
```
当无法确认它是否已被追上时强制提升的 `DR` 异步成为主数据库可能会导致它成为主数据库而没有镜像生成的所有日志数据。因此,一些全局更新操作可能会丢失,而其他镜像成员可能需要从备份中重建。
2. 如果可以确认 `IRIS B` 处于活动状态,请将最新的镜像日志文件从 `IRIS B` 复制到 `IRIS C`,从 `IRIS C` 上的最新日志文件开始,然后包括来自 `IRIS B` 的所有后续文件。例如,如果 `MIRROR-MIRRORA-20180220.001` 是 `InterSystems IRIS C` 上的最新文件,请从 `IRIS C` 复制 `MIRROR-MIRRORA-20180220.001` 和任何更新的文件。检查文件的权限和所有权,并在必要时更改它们以匹配现有日志文件。
2. 在不选择故障转移合作伙伴的情况下将 `IRIS C` 提升为故障转移成员。 `IRIS C` 成为主要的。
3. 当 `IRIS A` 和 `IRIS B` 的问题得到修复时,尽早并在重新启动 `IRIS` 之前,在每个成员上的 `IRIS` 实例的配置参数文件的 `[MirrorMember]` 部分中设置 `ValidatedMember = 0`(参见 `[ MirrorMember]` 在配置参数文件参考)。说明指出,此更改是必需的。完成此操作后,在每个成员上重新启动 `IRIS`,从 `IRIS A`(最近成为主成员的成员)开始。
1. 如果成员在 `IRIS` 重新启动时作为备份或 `DR` 异步加入镜像,则不需要进一步的步骤。任何在故障成员上但不在当前主成员上的日志数据都已被丢弃。
2. 如果在 `IRIS` 实例重新启动时成员无法加入镜像,如重建镜像成员中描述的引用不一致数据的消息日志消息所示,则成员上的最新数据库更改晚于存在于上的最新日志数据 `IRIS C` 成为主要时。要解决此问题,请按照该部分中的描述重建成员。
4. 在大多数情况下,`DR` 异步系统不是主要故障转移成员的合适永久主机。在 `IRIS A` 和 `IRIS B` 重新加入镜像后,使用使用升级的 `DR` 异步临时替换故障转移成员中描述的过程将所有成员返回到它们以前的角色。如果 `IRIS A` 或 `IRIS B` 作为备份重新启动,则在备份处于活动状态时从正常关闭 `IRIS C` 开始,以故障转移到备份;如果 `IRIS A` 或 `IRIS B` 都重新启动为 `DR` 异步,将其中一个提升为备份,然后在 `IRIS C` 上执行正常关闭。将另一个以前的故障转移成员提升为备份,然后将 `IRIS C` 作为 `DR` 异步重启。
文章
Guangliang Zhang · 十月 21, 2022
cache数据库自身带有系统监控Portal界面,但需要运维人员定期主动查看才能获取监控信息。当系统故障发生时,容易出现由于没有及时获取故障信息而不能及时处理,从而导致造成的影响扩大。本文研究通过解析cache数据库控制台日志(cconsole.log)进行监控信息获取并主动推送微信或短信实现cache数据库主动实时监控。
cache数据库在运行时会将所有控制台消息包括一般消息、系统错误、某些操作系统错误和网络错误都会发送到控制台日志文件,通过操作员控制台工具从其他系统远程启动的作业的成功或失败等信息也会写入控制台日志,因此通过对控制台日志的解析即可获取所需要监控信息。具体步骤方法如下:
解析控制台日志
控制台日志默认存储在install-dir\mgr路径下。
根据cache版本不同,使用的读取方法也不同。对于cache2016版本以上,系统提供了EnsLib.SQL.Snapshot类,可以直接获取日志的行和列信息,非常方便。对于cache2010及以下版本则无此方法,需要使用%File文件读取方法。
随着系统运行时间增加,控制台日志也会不断增大,造成每次检索时间加大,且不容易找出最近的监控信息,作出有效监控。较好的方式是每次解析时均从上次的节点继续进行,以便获取新的有效的监控信息。可以使用指定global来记录每次执行的最后一行ID,下次执行时从ID+1行开始解析来达到目的。对于控制台日志,EnsLib.SQL.Snapshot方法里提供了方法以获取当前行,对于cache2010及以下版本,则需要记录最后一次处理的内容,再次处理的时先找到本条内容的位置,从此位置继续处理。代码示例如下。
cache2016及以上版本代码示例:
ClassMethod GetAlertInfo() As %String{
//cconsole.log存储路径 set FilePath="D:\InterSystems\HealthShare\mgr\cconsole.log"
//使用EnsLib.SQL.Snapshot读取日志 set snap=##class(EnsLib.SQL.Snapshot).%New() set snap.MaxRowsToGet=-1 do snap.ImportFile(FilePath,," ") do Consolelogshow do snap.Clean() quit $$$OK
//处理日志内容Consolelogshow set colsCount=snap.ColCount
//获取上一次处理的最后一行行号 set snap.%CurrentRow=$g(^AlertCurrentRow("Consolelog","CurrentRow")) k ^AlertCurrentRow("Consolelog","PID") while snap.Next() { set alertLevel=snap.GetData(3) set pid=snap.GetData(2) set pid=$p(pid,"(",2) set pid=$p(pid,")",1) continue:pid=""
//0级别信息不处理 continue:((alertLevel=0))
//进程号已存在不处理,即只获取每次每个进程的第一条告警/错误信息 continue:($d(^AlertCurrentRow("Consolelog","PID",pid)))
//记录已处理的进程号 set ^AlertCurrentRow("Consolelog","PID",pid)=1
//定义并组装监控消息内容 set alertInfo="" i ((alertLevel=1)||(alertLevel=2)||(alertLevel=3)){ f i=4:1:colsCount{ set alertInfo=alertInfo_" "_snap.GetData(i) } set alertInfo=snap.GetData(1)_":"_alertLevel_":"_alertInfo continue:((alertLevel=1)&&(alertInfo'["Warning"))
//发送监控信息至信息推送平台,如微信或短信等 set rt=..sendAlertInfo(alertInfo) }
//记录当前处理的行 set ^AlertCurrentRow("Consolelog","CurrentRow")=snap.%CurrentRow } quit $$$OK}
cache2010及以下版本代码示例:
ClassMethod GetAlertInfo() As %String{ //使用%File读取日志 set file=##class(%File).%New("/cachesys/mgr/cconsole.log") set Status= file.Open("R") k ^AlertCurrentRow("Consolelog","PID")
//获取上次处理的信息内容 set LastPosition=$g(^AlertCurrentRow("Consolelog","LastPosition")) s:LastPosition="" LastPosition=0 set LastPosition=LastPosition+1 set int=0 while 'file.AtEnd{ i int=0{
//跳转到上次处理的最后位置 do file.MoveTo(LastPosition) } set file.LineTerminator=$c(10) set text= file.ReadLine(,.sc) set int=int+1
//解析日志 set pid=$p(text," ",2) set pid=$p(pid,"(",2) set pid=$p(pid,")",1) set alertLevel=$p(text," ",3) s:text'="" textLast=text
//定义并组装监控消息内容 i ((alertLevel=1)&&(alertLevel["Warning"))||(alertLevel=2)||(alertLevel=3){ set length=$length(text," ") set alert="",alertInfo="" f i=4:1:length{ set alert=$p(text," ",i) i i=4{ set alertInfo=alert }else{ set alertInfo=alertInfo_" "_alert } } set alertInfo=$p(text," ",1)_": "_alertLevel_": "_pid_":"_alertInfo set alertInfo=$zcvt(alertInfo,"O","UTF8") i '$d(^AlertCurrentRow("Consolelog","PID",pid)){
//记录已处理的进程号 set ^AlertCurrentRow("Consolelog","PID",pid)=1 //发送监控信息至信息推送平台,如微信或短信等 set rt=..sendAlertInfo(alertInfo) } } }
//记录最后处理的行内容 set ^AlertCurrentRow("Consolelog","LastPosition")=file.FindAt(1,textLast) do file.Close() quit $$$OK}
控制台日志在记录信息时,经常会记录一系列问题。记录模式为时间+进程ID+信息级别+详细信息。如图1所示。信息级别从0级到3级,问题严重程度依次增加。为0的一般为正常运行信息,也是占日志主要部分的内容。问题级别为1的为警告信息。问题级别为2的为错误信息。问题级别为3的为严重错误信息。因此信息级别为1以上均是监控需要关注的信息。需要将此部分信息及时发出。
图1 cconsole.log普通记录示例
有时候控制台日志也会有其它格式的信息出现,如图2所示。此类信息说明有比较严重的异常行为出现,需要重点关注,因为一般将其全部发出。
图2 cconsole.log特殊记录示例
监控信息推送
控制台日志会对一个进程的多个操作进行记录,如果将所有信息发出,则会造成信息量过大而找不到重点。一般将每个进程的第一个告警或错误信息发出即可。处理方式见上述示例代码。
将获取到所需要发送的监控信息调取微信(如图3)或短信(如图4)或其它信息推送终端的接口即可进行信息推送。
图3 微信告警示例 图4 短信告警示例
任务部署
将程序部署成定时任务,按照所需自定义监控频率即可实现cache数据库的主动实时监控。如图5所示。
图5 监控任务示例 确实升级后的Cache强了很多 这个极实用,推荐大伙收藏。 实用,收藏