搜索​​​​

清除过滤器
文章
Jingwei Wang · 十月 28, 2021

IRIS 2021 技术文档 First Look 31:InterSystems IRIS 和 UIMA

html {overflow-x: initial !important;}:root { --bg-color: #ffffff; --text-color: #333333; --select-text-bg-color: #B5D6FC; --select-text-font-color: auto; --monospace: "Lucida Console",Consolas,"Courier",monospace; --title-bar-height: 20px; } .mac-os-11 { --title-bar-height: 28px; } html { font-size: 14px; background-color: var(--bg-color); color: var(--text-color); font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; } body { margin: 0px; padding: 0px; height: auto; inset: 0px; font-size: 1rem; line-height: 1.42857143; overflow-x: hidden; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: inherit; tab-size: 4; background-position: inherit; background-repeat: inherit; } iframe { margin: auto; } a.url { word-break: break-all; } a:active, a:hover { outline: 0px; } .in-text-selection, ::selection { text-shadow: none; background: var(--select-text-bg-color); color: var(--select-text-font-color); } #write { margin: 0px auto; height: auto; width: inherit; word-break: normal; word-wrap: break-word; position: relative; white-space: normal; overflow-x: visible; padding-top: 36px; } #write.first-line-indent p { text-indent: 2em; } #write.first-line-indent li p, #write.first-line-indent p * { text-indent: 0px; } #write.first-line-indent li { margin-left: 2em; } .for-image #write { padding-left: 8px; padding-right: 8px; } body.typora-export { padding-left: 30px; padding-right: 30px; } .typora-export .footnote-line, .typora-export li, .typora-export p { white-space: pre-wrap; } .typora-export .task-list-item input { pointer-events: none; } @media screen and (max-width: 500px) { body.typora-export { padding-left: 0px; padding-right: 0px; } #write { padding-left: 20px; padding-right: 20px; } .CodeMirror-sizer { margin-left: 0px !important; } .CodeMirror-gutters { display: none !important; } } #write li > figure:last-child { margin-bottom: 0.5rem; } #write ol, #write ul { position: relative; } img { max-width: 100%; vertical-align: middle; image-orientation: from-image; } button, input, select, textarea { color: inherit; font-family: inherit; font-size: inherit; font-style: inherit; font-variant-caps: inherit; font-weight: inherit; font-stretch: inherit; line-height: inherit; } input[type="checkbox"], input[type="radio"] { line-height: normal; padding: 0px; } *, ::after, ::before { box-sizing: border-box; } #write h1, #write h2, #write h3, #write h4, #write h5, #write h6, #write p, #write pre { width: inherit; } #write h1, #write h2, #write h3, #write h4, #write h5, #write h6, #write p { position: relative; } p { line-height: inherit; } h1, h2, h3, h4, h5, h6 { break-after: avoid-page; break-inside: avoid; orphans: 4; } p { orphans: 4; } h1 { font-size: 2rem; } h2 { font-size: 1.8rem; } h3 { font-size: 1.6rem; } h4 { font-size: 1.4rem; } h5 { font-size: 1.2rem; } h6 { font-size: 1rem; } .md-math-block, .md-rawblock, h1, h2, h3, h4, h5, h6, p { margin-top: 1rem; margin-bottom: 1rem; } .hidden { display: none; } .md-blockmeta { color: rgb(204, 204, 204); font-weight: 700; font-style: italic; } a { cursor: pointer; } sup.md-footnote { padding: 2px 4px; background-color: rgba(238, 238, 238, 0.7); color: rgb(85, 85, 85); border-top-left-radius: 4px; border-top-right-radius: 4px; border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; cursor: pointer; } sup.md-footnote a, sup.md-footnote a:hover { color: inherit; text-transform: inherit; text-decoration: inherit; } #write input[type="checkbox"] { cursor: pointer; width: inherit; height: inherit; } figure { overflow-x: auto; margin: 1.2em 0px; max-width: calc(100% + 16px); padding: 0px; } figure > table { margin: 0px; } tr { break-inside: avoid; break-after: auto; } thead { display: table-header-group; } table { border-collapse: collapse; border-spacing: 0px; width: 100%; overflow: auto; break-inside: auto; text-align: left; } table.md-table td { min-width: 32px; } .CodeMirror-gutters { border-right-width: 0px; background-color: inherit; } .CodeMirror-linenumber { } .CodeMirror { text-align: left; } .CodeMirror-placeholder { opacity: 0.3; } .CodeMirror pre { padding: 0px 4px; } .CodeMirror-lines { padding: 0px; } div.hr:focus { cursor: none; } #write pre { white-space: pre-wrap; } #write.fences-no-line-wrapping pre { white-space: pre; } #write pre.ty-contain-cm { white-space: normal; } .CodeMirror-gutters { margin-right: 4px; } .md-fences { font-size: 0.9rem; display: block; break-inside: avoid; text-align: left; overflow: visible; white-space: pre; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: inherit; position: relative !important; background-position: inherit; background-repeat: inherit; } .md-fences-adv-panel { width: 100%; margin-top: 10px; text-align: center; padding-top: 0px; padding-bottom: 8px; overflow-x: auto; } #write .md-fences.mock-cm { white-space: pre-wrap; } .md-fences.md-fences-with-lineno { padding-left: 0px; } #write.fences-no-line-wrapping .md-fences.mock-cm { white-space: pre; overflow-x: auto; } .md-fences.mock-cm.md-fences-with-lineno { padding-left: 8px; } .CodeMirror-line, twitterwidget { break-inside: avoid; } .footnotes { opacity: 0.8; font-size: 0.9rem; margin-top: 1em; margin-bottom: 1em; } .footnotes + .footnotes { margin-top: 0px; } .md-reset { margin: 0px; padding: 0px; border: 0px; outline: 0px; vertical-align: top; text-decoration: none; text-shadow: none; float: none; position: static; width: auto; height: auto; white-space: nowrap; cursor: inherit; line-height: normal; font-weight: 400; text-align: left; box-sizing: content-box; direction: ltr; background-position: 0px 0px; } li div { padding-top: 0px; } blockquote { margin: 1rem 0px; } li .mathjax-block, li p { margin: 0.5rem 0px; } li blockquote { margin: 1rem 0px; } li { margin: 0px; position: relative; } blockquote > :last-child { margin-bottom: 0px; } blockquote > :first-child, li > :first-child { margin-top: 0px; } .footnotes-area { color: rgb(136, 136, 136); margin-top: 0.714rem; padding-bottom: 0.143rem; white-space: normal; } #write .footnote-line { white-space: pre-wrap; } @media print { body, html { border: 1px solid transparent; height: 99%; break-after: avoid; break-before: avoid; font-variant-ligatures: no-common-ligatures; } #write { margin-top: 0px; padding-top: 0px; border-color: transparent !important; } .typora-export * { -webkit-print-color-adjust: exact; } .typora-export #write { break-after: avoid; } .typora-export #write::after { height: 0px; } .is-mac table { break-inside: avoid; } .typora-export-show-outline .typora-export-sidebar { display: none; } } .footnote-line { margin-top: 0.714em; font-size: 0.7em; } a img, img a { cursor: pointer; } pre.md-meta-block { font-size: 0.8rem; min-height: 0.8rem; white-space: pre-wrap; background-color: rgb(204, 204, 204); display: block; overflow-x: hidden; } p > .md-image:only-child:not(.md-img-error) img, p > img:only-child { display: block; margin: auto; } #write.first-line-indent p > .md-image:only-child:not(.md-img-error) img { left: -2em; position: relative; } p > .md-image:only-child { display: inline-block; width: 100%; } #write .MathJax_Display { margin: 0.8em 0px 0px; } .md-math-block { width: 100%; } .md-math-block:not(:empty)::after { display: none; } .MathJax_ref { fill: currentcolor; } [contenteditable="true"]:active, [contenteditable="true"]:focus, [contenteditable="false"]:active, [contenteditable="false"]:focus { outline: 0px; box-shadow: none; } .md-task-list-item { position: relative; list-style-type: none; } .task-list-item.md-task-list-item { padding-left: 0px; } .md-task-list-item > input { position: absolute; top: 0px; left: 0px; margin-left: -1.2em; margin-top: calc(1em - 10px); border: none; } .math { font-size: 1rem; } .md-toc { min-height: 3.58rem; position: relative; font-size: 0.9rem; border-top-left-radius: 10px; border-top-right-radius: 10px; border-bottom-right-radius: 10px; border-bottom-left-radius: 10px; } .md-toc-content { position: relative; margin-left: 0px; } .md-toc-content::after, .md-toc::after { display: none; } .md-toc-item { display: block; color: rgb(65, 131, 196); } .md-toc-item a { text-decoration: none; } .md-toc-inner:hover { text-decoration: underline; } .md-toc-inner { display: inline-block; cursor: pointer; } .md-toc-h1 .md-toc-inner { margin-left: 0px; font-weight: 700; } .md-toc-h2 .md-toc-inner { margin-left: 2em; } .md-toc-h3 .md-toc-inner { margin-left: 4em; } .md-toc-h4 .md-toc-inner { margin-left: 6em; } .md-toc-h5 .md-toc-inner { margin-left: 8em; } .md-toc-h6 .md-toc-inner { margin-left: 10em; } @media screen and (max-width: 48em) { .md-toc-h3 .md-toc-inner { margin-left: 3.5em; } .md-toc-h4 .md-toc-inner { margin-left: 5em; } .md-toc-h5 .md-toc-inner { margin-left: 6.5em; } .md-toc-h6 .md-toc-inner { margin-left: 8em; } } a.md-toc-inner { font-size: inherit; font-style: inherit; font-weight: inherit; line-height: inherit; } .footnote-line a:not(.reversefootnote) { color: inherit; } .md-attr { display: none; } .md-fn-count::after { content: "."; } code, pre, samp, tt { font-family: var(--monospace); } kbd { margin: 0px 0.1em; padding: 0.1em 0.6em; font-size: 0.8em; color: rgb(36, 39, 41); background-color: rgb(255, 255, 255); border: 1px solid rgb(173, 179, 185); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; box-shadow: rgba(12, 13, 14, 0.2) 0px 1px 0px, rgb(255, 255, 255) 0px 0px 0px 2px inset; white-space: nowrap; vertical-align: middle; } .md-comment { color: rgb(162, 127, 3); opacity: 0.8; font-family: var(--monospace); } code { text-align: left; } a.md-print-anchor { white-space: pre !important; border: none !important; display: inline-block !important; position: absolute !important; width: 1px !important; right: 0px !important; outline: 0px !important; text-shadow: initial !important; background-position: 0px 0px !important; } .md-inline-math .MathJax_SVG .noError { display: none !important; } .html-for-mac .inline-math-svg .MathJax_SVG { vertical-align: 0.2px; } .md-fences-math .MathJax_SVG_Display, .md-math-block .MathJax_SVG_Display { text-align: center; margin: 0px; position: relative; text-indent: 0px; max-width: none; max-height: none; min-height: 0px; min-width: 100%; width: auto; overflow-y: visible; display: block !important; } .MathJax_SVG_Display, .md-inline-math .MathJax_SVG_Display { width: auto; margin: inherit; display: inline-block !important; } .MathJax_SVG .MJX-monospace { font-family: var(--monospace); } .MathJax_SVG .MJX-sans-serif { font-family: sans-serif; } .MathJax_SVG { display: inline; font-style: normal; font-weight: 400; line-height: normal; text-indent: 0px; text-align: left; text-transform: none; letter-spacing: normal; word-spacing: normal; word-wrap: normal; white-space: nowrap; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border: 0px; padding: 0px; margin: 0px; zoom: 90%; } #math-inline-preview-content { zoom: 1.1; } .MathJax_SVG * { transition: none; } .MathJax_SVG_Display svg { vertical-align: middle !important; margin-bottom: 0px !important; margin-top: 0px !important; } .os-windows.monocolor-emoji .md-emoji { font-family: "Segoe UI Symbol", sans-serif; } .md-diagram-panel > svg { max-width: 100%; } [lang="flow"] svg, [lang="mermaid"] svg { max-width: 100%; height: auto; } [lang="mermaid"] .node text { font-size: 1rem; } table tr th { border-bottom-width: 0px; } video { max-width: 100%; display: block; margin: 0px auto; } iframe { max-width: 100%; width: 100%; border: none; } .highlight td, .highlight tr { border: 0px; } mark { background-color: rgb(255, 255, 0); color: rgb(0, 0, 0); } .md-html-inline .md-plain, .md-html-inline strong, mark .md-inline-math, mark strong { color: inherit; } .md-expand mark .md-meta { opacity: 0.3 !important; } mark .md-meta { color: rgb(0, 0, 0); } @media print { .typora-export h1, .typora-export h2, .typora-export h3, .typora-export h4, .typora-export h5, .typora-export h6 { break-inside: avoid; } } .md-diagram-panel .messageText { stroke: none !important; } .md-diagram-panel .start-state { fill: var(--node-fill); } .md-diagram-panel .edgeLabel rect { opacity: 1 !important; } .md-require-zoom-fix foreignObject { font-size: var(--mermaid-font-zoom); } .md-fences.md-fences-math { font-size: 1em; } .md-fences-math .MathJax_SVG_Display { margin-top: 8px; cursor: default; } .md-fences-advanced:not(.md-focus) { padding: 0px; white-space: nowrap; border: 0px; } .md-fences-advanced:not(.md-focus) { background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: inherit; background-position: inherit; background-repeat: inherit; } .typora-export-show-outline .typora-export-content { max-width: 1440px; margin: auto; display: flex; flex-direction: row; } .typora-export-sidebar { width: 300px; font-size: 0.8rem; margin-top: 80px; margin-right: 18px; } .typora-export-show-outline #write { --webkit-flex: 2; flex: 2 1 0%; } .typora-export-sidebar .outline-content { position: fixed; top: 0px; max-height: 100%; overflow: hidden auto; padding-bottom: 30px; padding-top: 60px; width: 300px; } @media screen and (max-width: 1024px) { .typora-export-sidebar, .typora-export-sidebar .outline-content { width: 240px; } } @media screen and (max-width: 800px) { .typora-export-sidebar { display: none; } } .outline-content li, .outline-content ul { margin-left: 0px; margin-right: 0px; padding-left: 0px; padding-right: 0px; list-style: none; } .outline-content ul { margin-top: 0px; margin-bottom: 0px; } .outline-content strong { font-weight: 400; } .outline-expander { width: 1rem; height: 1.428571429rem; position: relative; display: table-cell; vertical-align: middle; cursor: pointer; padding-left: 4px; } .outline-expander::before { content: ''; position: relative; font-family: Ionicons; display: inline-block; font-size: 8px; vertical-align: middle; } .outline-item { padding-top: 3px; padding-bottom: 3px; cursor: pointer; } .outline-expander:hover::before { content: ''; } .outline-h1 > .outline-item { padding-left: 0px; } .outline-h2 > .outline-item { padding-left: 1em; } .outline-h3 > .outline-item { padding-left: 2em; } .outline-h4 > .outline-item { padding-left: 3em; } .outline-h5 > .outline-item { padding-left: 4em; } .outline-h6 > .outline-item { padding-left: 5em; } .outline-label { cursor: pointer; display: table-cell; vertical-align: middle; text-decoration: none; color: inherit; } .outline-label:hover { text-decoration: underline; } .outline-item:hover { border-color: rgb(245, 245, 245); background-color: var(--item-hover-bg-color); } .outline-item:hover { margin-left: -28px; margin-right: -28px; border-left-width: 28px; border-left-style: solid; border-left-color: transparent; border-right-width: 28px; border-right-style: solid; border-right-color: transparent; } .outline-item-single .outline-expander::before, .outline-item-single .outline-expander:hover::before { display: none; } .outline-item-open > .outline-item > .outline-expander::before { content: ''; } .outline-children { display: none; } .info-panel-tab-wrapper { display: none; } .outline-item-open > .outline-children { display: block; } .typora-export .outline-item { padding-top: 1px; padding-bottom: 1px; } .typora-export .outline-item:hover { margin-right: -8px; border-right-width: 8px; border-right-style: solid; border-right-color: transparent; } .typora-export .outline-expander::before { content: "+"; font-family: inherit; top: -1px; } .typora-export .outline-expander:hover::before, .typora-export .outline-item-open > .outline-item > .outline-expander::before { content: '−'; } .typora-export-collapse-outline .outline-children { display: none; } .typora-export-collapse-outline .outline-item-open > .outline-children, .typora-export-no-collapse-outline .outline-children { display: block; } .typora-export-no-collapse-outline .outline-expander::before { content: "" !important; } .typora-export-show-outline .outline-item-active > .outline-item .outline-label { font-weight: 700; } .CodeMirror { height: auto; } .CodeMirror.cm-s-inner { background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: inherit; background-position: inherit; background-repeat: inherit; } .CodeMirror-scroll { overflow: auto hidden; z-index: 3; } .CodeMirror-gutter-filler, .CodeMirror-scrollbar-filler { background-color: rgb(255, 255, 255); } .CodeMirror-gutters { border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: inherit; white-space: nowrap; background-position: inherit; background-repeat: inherit; } .CodeMirror-linenumber { padding: 0px 3px 0px 5px; text-align: right; color: rgb(153, 153, 153); } .cm-s-inner .cm-keyword { color: rgb(119, 0, 136); } .cm-s-inner .cm-atom, .cm-s-inner.cm-atom { color: rgb(34, 17, 153); } .cm-s-inner .cm-number { color: rgb(17, 102, 68); } .cm-s-inner .cm-def { color: rgb(0, 0, 255); } .cm-s-inner .cm-variable { color: rgb(0, 0, 0); } .cm-s-inner .cm-variable-2 { color: rgb(0, 85, 170); } .cm-s-inner .cm-variable-3 { color: rgb(0, 136, 85); } .cm-s-inner .cm-string { color: rgb(170, 17, 17); } .cm-s-inner .cm-property { color: rgb(0, 0, 0); } .cm-s-inner .cm-operator { color: rgb(152, 26, 26); } .cm-s-inner .cm-comment, .cm-s-inner.cm-comment { color: rgb(170, 85, 0); } .cm-s-inner .cm-string-2 { color: rgb(255, 85, 0); } .cm-s-inner .cm-meta { color: rgb(85, 85, 85); } .cm-s-inner .cm-qualifier { color: rgb(85, 85, 85); } .cm-s-inner .cm-builtin { color: rgb(51, 0, 170); } .cm-s-inner .cm-bracket { color: rgb(153, 153, 119); } .cm-s-inner .cm-tag { color: rgb(17, 119, 0); } .cm-s-inner .cm-attribute { color: rgb(0, 0, 204); } .cm-s-inner .cm-header, .cm-s-inner.cm-header { color: rgb(0, 0, 255); } .cm-s-inner .cm-quote, .cm-s-inner.cm-quote { color: rgb(0, 153, 0); } .cm-s-inner .cm-hr, .cm-s-inner.cm-hr { color: rgb(153, 153, 153); } .cm-s-inner .cm-link, .cm-s-inner.cm-link { color: rgb(0, 0, 204); } .cm-negative { color: rgb(221, 68, 68); } .cm-positive { color: rgb(34, 153, 34); } .cm-header, .cm-strong { font-weight: 700; } .cm-del { text-decoration: line-through; } .cm-em { font-style: italic; } .cm-link { text-decoration: underline; } .cm-error { color: red; } .cm-invalidchar { color: red; } .cm-constant { color: rgb(38, 139, 210); } .cm-defined { color: rgb(181, 137, 0); } div.CodeMirror span.CodeMirror-matchingbracket { color: rgb(0, 255, 0); } div.CodeMirror span.CodeMirror-nonmatchingbracket { color: rgb(255, 34, 34); } .cm-s-inner .CodeMirror-activeline-background { background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: inherit; background-position: inherit; background-repeat: inherit; } .CodeMirror { position: relative; overflow: hidden; } .CodeMirror-scroll { height: 100%; outline: 0px; position: relative; box-sizing: content-box; background-image: inherit; background-size: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: inherit; background-position: inherit; background-repeat: inherit; } .CodeMirror-sizer { position: relative; } .CodeMirror-gutter-filler, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-vscrollbar { position: absolute; z-index: 6; display: none; } .CodeMirror-vscrollbar { right: 0px; top: 0px; overflow: hidden; } .CodeMirror-hscrollbar { bottom: 0px; left: 0px; overflow: hidden; } .CodeMirror-scrollbar-filler { right: 0px; bottom: 0px; } .CodeMirror-gutter-filler { left: 0px; bottom: 0px; } .CodeMirror-gutters { position: absolute; left: 0px; top: 0px; padding-bottom: 30px; z-index: 3; } .CodeMirror-gutter { white-space: normal; height: 100%; box-sizing: content-box; padding-bottom: 30px; margin-bottom: -32px; display: inline-block; } .CodeMirror-gutter-wrapper { position: absolute; z-index: 4; border: none !important; background-position: 0px 0px !important; } .CodeMirror-gutter-background { position: absolute; top: 0px; bottom: 0px; z-index: 4; } .CodeMirror-gutter-elt { position: absolute; cursor: default; z-index: 4; } .CodeMirror-lines { cursor: text; } .CodeMirror pre { border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; border-width: 0px; font-family: inherit; font-size: inherit; margin: 0px; white-space: pre; word-wrap: normal; color: inherit; z-index: 2; position: relative; overflow: visible; background-position: 0px 0px; } .CodeMirror-wrap pre { word-wrap: break-word; white-space: pre-wrap; word-break: normal; } .CodeMirror-code pre { border-right-width: 30px; border-right-style: solid; border-right-color: transparent; width: fit-content; } .CodeMirror-wrap .CodeMirror-code pre { border-right-style: none; width: auto; } .CodeMirror-linebackground { position: absolute; inset: 0px; z-index: 0; } .CodeMirror-linewidget { position: relative; z-index: 2; overflow: auto; } .CodeMirror-wrap .CodeMirror-scroll { overflow-x: hidden; } .CodeMirror-measure { position: absolute; width: 100%; height: 0px; overflow: hidden; visibility: hidden; } .CodeMirror-measure pre { position: static; } .CodeMirror div.CodeMirror-cursor { position: absolute; visibility: hidden; border-right-style: none; width: 0px; } .CodeMirror div.CodeMirror-cursor { visibility: hidden; } .CodeMirror-focused div.CodeMirror-cursor { visibility: inherit; } .cm-searching { background-color: rgba(255, 255, 0, 0.4); } span.cm-underlined { text-decoration: underline; } span.cm-strikethrough { text-decoration: line-through; } .cm-tw-syntaxerror { color: rgb(255, 255, 255); background-color: rgb(153, 0, 0); } .cm-tw-deleted { text-decoration: line-through; } .cm-tw-header5 { font-weight: 700; } .cm-tw-listitem:first-child { padding-left: 10px; } .cm-tw-box { border-style: solid; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-color: inherit; border-top-width: 0px !important; } .cm-tw-underline { text-decoration: underline; } @media print { .CodeMirror div.CodeMirror-cursor { visibility: hidden; } } :root { --side-bar-bg-color: #fafafa; --control-text-color: #777; } @include-when-export url(https://fonts.loli.net/css?family=Open+Sans:400italic,700italic,700,400&subset=latin,latin-ext); /* open-sans-regular - latin-ext_latin */ /* open-sans-italic - latin-ext_latin */ /* open-sans-700 - latin-ext_latin */ /* open-sans-700italic - latin-ext_latin */ html { font-size: 16px; } body { font-family: "Open Sans","Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; color: rgb(51, 51, 51); line-height: 1.6; } #write { max-width: 860px; margin: 0 auto; padding: 30px; padding-bottom: 100px; } @media only screen and (min-width: 1400px) { #write { max-width: 1024px; } } @media only screen and (min-width: 1800px) { #write { max-width: 1200px; } } #write > ul:first-child, #write > ol:first-child{ margin-top: 30px; } a { color: #4183C4; } h1, h2, h3, h4, h5, h6 { position: relative; margin-top: 1rem; margin-bottom: 1rem; font-weight: bold; line-height: 1.4; cursor: text; } h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor { text-decoration: none; } h1 tt, h1 code { font-size: inherit; } h2 tt, h2 code { font-size: inherit; } h3 tt, h3 code { font-size: inherit; } h4 tt, h4 code { font-size: inherit; } h5 tt, h5 code { font-size: inherit; } h6 tt, h6 code { font-size: inherit; } h1 { font-size: 2.25em; line-height: 1.2; border-bottom: 1px solid #eee; } h2 { font-size: 1.75em; line-height: 1.225; border-bottom: 1px solid #eee; } /*@media print { .typora-export h1, .typora-export h2 { border-bottom: none; padding-bottom: initial; } .typora-export h1::after, .typora-export h2::after { content: ""; display: block; height: 100px; margin-top: -96px; border-top: 1px solid #eee; } }*/ h3 { font-size: 1.5em; line-height: 1.43; } h4 { font-size: 1.25em; } h5 { font-size: 1em; } h6 { font-size: 1em; color: #777; } p, blockquote, ul, ol, dl, table{ margin: 0.8em 0; } li>ol, li>ul { margin: 0 0; } hr { height: 2px; padding: 0; margin: 16px 0; background-color: #e7e7e7; border: 0 none; overflow: hidden; box-sizing: content-box; } li p.first { display: inline-block; } ul, ol { padding-left: 30px; } ul:first-child, ol:first-child { margin-top: 0; } ul:last-child, ol:last-child { margin-bottom: 0; } blockquote { border-left: 4px solid #dfe2e5; padding: 0 15px; color: #777777; } blockquote blockquote { padding-right: 0; } table { padding: 0; word-break: initial; } table tr { border: 1px solid #dfe2e5; margin: 0; padding: 0; } table tr:nth-child(2n), thead { background-color: #f8f8f8; } table th { font-weight: bold; border: 1px solid #dfe2e5; border-bottom: 0; margin: 0; padding: 6px 13px; } table td { border: 1px solid #dfe2e5; margin: 0; padding: 6px 13px; } table th:first-child, table td:first-child { margin-top: 0; } table th:last-child, table td:last-child { margin-bottom: 0; } .CodeMirror-lines { padding-left: 4px; } .code-tooltip { box-shadow: 0 1px 1px 0 rgba(0,28,36,.3); border-top: 1px solid #eef2f2; } .md-fences, code, tt { border: 1px solid #e7eaed; background-color: #f8f8f8; border-radius: 3px; padding: 0; padding: 2px 4px 0px 4px; font-size: 0.9em; } code { background-color: #f3f4f4; padding: 0 2px 0 2px; } .md-fences { margin-bottom: 15px; margin-top: 15px; padding-top: 8px; padding-bottom: 6px; } .md-task-list-item > input { margin-left: -1.3em; } @media print { html { font-size: 13px; } table, pre { page-break-inside: avoid; } pre { word-wrap: break-word; } } .md-fences { background-color: #f8f8f8; } #write pre.md-meta-block { padding: 1rem; font-size: 85%; line-height: 1.45; background-color: #f7f7f7; border: 0; border-radius: 3px; color: #777777; margin-top: 0 !important; } .mathjax-block>.code-tooltip { bottom: .375rem; } .md-mathjax-midline { background: #fafafa; } #write>h3.md-focus:before{ left: -1.5625rem; top: .375rem; } #write>h4.md-focus:before{ left: -1.5625rem; top: .285714286rem; } #write>h5.md-focus:before{ left: -1.5625rem; top: .285714286rem; } #write>h6.md-focus:before{ left: -1.5625rem; top: .285714286rem; } .md-image>.md-meta { /*border: 1px solid #ddd;*/ border-radius: 3px; padding: 2px 0px 0px 4px; font-size: 0.9em; color: inherit; } .md-tag { color: #a7a7a7; opacity: 1; } .md-toc { margin-top:20px; padding-bottom:20px; } .sidebar-tabs { border-bottom: none; } #typora-quick-open { border: 1px solid #ddd; background-color: #f8f8f8; } #typora-quick-open-item { background-color: #FAFAFA; border-color: #FEFEFE #e5e5e5 #e5e5e5 #eee; border-style: solid; border-width: 1px; } /** focus mode */ .on-focus-mode blockquote { border-left-color: rgba(85, 85, 85, 0.12); } header, .context-menu, .megamenu-content, footer{ font-family: "Segoe UI", "Arial", sans-serif; } .file-node-content:hover .file-node-icon, .file-node-content:hover .file-node-open-state{ visibility: visible; } .mac-seamless-mode #typora-sidebar { background-color: #fafafa; background-color: var(--side-bar-bg-color); } .md-lang { color: #b4654d; } /*.html-for-mac { --item-hover-bg-color: #E6F0FE; }*/ #md-notification .btn { border: 0; } .dropdown-menu .divider { border-color: #e5e5e5; opacity: 0.4; } .ty-preferences .window-content { background-color: #fafafa; } .ty-preferences .nav-group-item.active { color: white; background: #999; } .menu-item-container a.menu-style-btn { background-color: #f5f8fa; background-image: linear-gradient( 180deg , hsla(0, 0%, 100%, 0.8), hsla(0, 0%, 100%, 0)); } :root {--mermaid-font-zoom:1em ;} @media print { @page {margin: 0 0 0 0;} body.typora-export {padding-left: 0; padding-right: 0;} #write {padding:0;}} 目录 技术概要:InterSystems IRIS 和 UIMA关于 UIMAInterSystems IRIS 如何补充 UIMA Annotation Store(注释存储) InterSystems IRIS NLP在 InterSystems IRIS 中体验 UIMA 用前须知 创建新的命名空间 运行Java Gateway(Java 网关) 体验 UIMA 分析管道 浏览 Annotation Store(注释存储) 通过分析管道发送新文本 向 Aviation.Event 表添加记录 查看Annotation Store(注释存储)中的新条目了解更多关于 UIMA的资料 技术概要:InterSystems IRIS 和 UIMA 本技术概要(First Look)快速介绍了 InterSystems IRIS® 数据平台如何实施和补充非结构化信息管理架构 (UIMA)。在简要概述 UIMA 以及 InterSystems IRIS 如何对其进行补充之后,您将有机会通过基本的动手练习来在实际应用中了解 InterSystems IRIS。 要浏览技术概要(First Look)的所有内容,包括可以在InterSystems IRIS的免费评估实例上执行的内容,请参阅InterSystems 技术概要。 关于 UIMA UIMA 是管理分析非结构化信息(如文本和视频)的标准。对于非结构化信息,计算机通常需要几个步骤才能将信息转化为可操作的结构化数据。例如,扫描的文档将文本转变为机器可读版本之前需要OCR,即使这样,在应用额外的 NLP 策略之前,计算机也不能很好地处理自然语言文本。由于像这样的流程包括本质上非常不同的步骤,因此单个工具不太可能处理所有这些步骤。更可能的情况是,这个流程包括单独模块,这些模块由使用不同技术的不同主体执行,且需要协同工作。在 UIMA 中,这些模块被称为analysis engines(分析引擎)。 因为符合 UIMA 标准的分析引擎都遵循相同的标准,所以它们可以组合成一系列分析器(a UIMA analysis pipeline [UIMA 分析管道]),每个部分做自己最擅长的事情。源非结构化数据在通过此 UIMA 分析管道时不会发生改变,而是沿途生成annotations(注释)。UIMA 标准确保来自一个分析引擎的注释不会干扰来自不同分析引擎的注释。对于文本,这些注释基于文本中的字符位置。UIMA 的互操作性允许您将来自不同供应商和技术的分析引擎组合到一个管道中,而无需编写任何自定义代码,并且由于分析引擎引用原始源数据中的字符位置,因此它们的注释可以组合、比较和推理。UIMA 标准包括运行这些分析引擎中所执行的框架。 除了提供互操作性之外,UIMA 还提供了用于扩展和部署这些分析引擎的框架。这使供应商可以专注于开发分析引擎,而不必担心扩展和部署他们的解决方案。[]{#2_How_InterSystems_IRIS_Complements_UIMA .anchor}UIMA 标准还提供了在分布式架构中调用这些分析引擎的框架。 每个符合 UIMA 的分析引擎都必须附带一个 XML 描述符文件,该文件包含基本识别信息,例如分析引擎的名称和供应商。它还定义了对分析引擎生成的注释进行分类的注释类型。 InterSystems IRIS 如何补充 UIMA InterSystems IRIS 在三个方面补充了 UIMA: 引入功能索引以创建 UIMA 分析管道(analysis pipeline),并在 InterSystems IRIS 表中插入或更新记录时自动为该分析管道提供新文本。 将 UIMA 分析管道生成的注释存储在 InterSystems IRIS 中清晰、可通过 SQL 访问的Annotation Store(注释存储)中。 在 InterSystems IRIS 中体验 UIMA 确保 InterSystems IRIS Natural Language Processing(InterSystems IRIS 自然语言处理, NLP )符合 UIMA 标准,并可用作 UIMA 分析管道中的分析引擎。 创建和调用 UIMA 分析管道 InterSystems IRIS 使用functional index(功能索引)以创建 UIMA 分析管道,该管道使用 InterSystems IRIS 概念,而无需担心实现 Java 接口。功能索引是 InterSystems IRIS 数据库的一项功能,它允许在表中插入或更新记录时执行功能。在这种情况下,功能索引是定义在表的列上,该列包含您希望由 UIMA 分析管道分析的非结构化数据。设置该管道就像将分析引擎描述文件的位置添加到功能索引定义一样简单。 一旦定义了功能索引,只要在索引表列中插入或更新最新数据,InterSystems IRIS 就会自动将非结构化数据输入 UIMA 分析管道。例如,如果在包含报表的列上定义了功能索引,那么将新报表添加到表中后立即对其进行分析。没有[]{#2.2_Annotation_Store .anchor} InterSystems IRIS 中的这一特殊功能,每次您想要分析数据时,都需要在 Java 中以编程方式通过管道发送非结构化数据。 Annotation Store(注释存储) 默认情况下,UIMA 分析管道的结果被存储在冗长繁琐的 XML 文件中。由于 UIMA 标准没有提供更复杂的注释存储方法,InterSystems IRIS 通过使用灵活的、基于 SQL 的存储,将注释放在统一、持久的表中以便后续检索,从而扩展了 UIMA 分析管道。这种存储系统称为Annotation Store(注释存储)。 在您第一次编译包含为创建 UIMA 分析管道定义的功能索引的类时,会自动创建此Annotation Store(注释存储)。它直接链接到包含非结构化数据的原始表的列。 在架构上,Annotation Store(注释存储) 是通过添加一个特殊的分析引擎作为 UIMA 分析管道的最后一个组件来生成的。当您将 UIMA 功能索引添加到 InterSystems IRIS 类时,将会自动生成Annotation Store(注释存储)。在 InterSystems IRIS 之外开发的 UIMA 分析管道也可以将此特殊分析引擎添加到管道末端,以创建Annotation Store(注释存储)。这种实现方式超出了本技术概要(First Look)的讨论范围。 []{#2.3_InterSystems_IRIS_NLP .anchor}您还可以使用XData block在包含功能索引的类中自定义Annotation Store(注释存储)。例如,您可以为每个表定义额外的列和索引。您还可以筛选注释类型,以将它们排除在Annotation Store(注释存储)之外。 InterSystems IRIS NLP InterSystems IRIS Natural Language Processing(InterSystems IRIS 自然语言处理, NLP )被内置到 InterSystems IRIS® 数据平台中,可以在预先不了解这些非结构化文本主题的情况下,对这些文本进行文本分析。它通过应用识别语义实体的特定语言规则来实现这项功能。由于这些规则是专门针对语言而非内容,因此 InterSystems IRIS NLP可以在不使用字典或本体的情况下提供对文本内容的深入分析 您可以使用 InterSystems IRIS NLP 作为 UIMA 分析引擎,为 NLP 概念和上下文生成 UIMA 注释。这些注释与其他 UIMA 分析引擎提供的 UIMA 注释完全兼容。 在 InterSystems IRIS 中体验 UIMA 现在您已经了解了有关 UIMA 的一些基本信息,可以动手体验以了解它在 InterSystems IRIS 中的工作原理。在体验之前,您需要设置环境。 在 InterSystems IRIS 中体验 UIMA 用前须知 首先,请执行以下初步设置任务: 安装 Java Runtime Environment(Java 运行时环境)。 安装 InterSystems IRIS。 创建一个新的 InterSystems IRIS 命名空间。 启动 Java Gateway(Java 网关)。 安装 Java Runtime Environment(Java 运行环境) InterSystems IRIS 进行 UIMA 分析管道分析需要安装Java Runtime Environment(Java 运行时环境,JRE)。还需要一个指向 JRE 安装位置的环境变量。 如果您的机器上尚未安装 JRE,请从 Oracle® 下载并安装最新版本。 创建一个名为JAVA_HOME的环境变量,该变量指向 JRE 安装的位置。例如,在 Windows® 上,使用 Control Panel(控制面板)创建JAVA_HOME环境变量,并定义该变量到 JRE 安装位置的路径。 安装 InterSystems IRIS 要运行 UIMA 分析管道的演示,您需要一个运行中的 InterSystems IRIS 已授权的实例。 有关如何安装和授权 InterSystems IRIS 开发实例的说明,请参阅InterSystems IRIS Basics: Installation (《InterSystems IRIS 基础知识:安装》)。 创建新的命名空间 作为技术概要(First Look)体验的一部分,您将向 InterSystems IRIS 中的命名空间添加一个新类文件。为了使此示例数据与预定义的命名空间分开,请创建一个名为 SAMPLES 的新命名空间来保存与此技术概要(First Look)关联的代码和数据。创建一个新的命名空间: 在浏览器中打开 Management Portal (管理门户),使用 InterSystems IRIS Basics: Connecting an IDE(《INTERSYSTEMS IRIS 基础:连接一个IDE 》)的实例适用的URL。 选择 System Administration(系统管理) > Configuration(配置) > System Configuration(系统配置) > Namespaces(命名空间)。 在 Namespaces(命名空间)页面,选择 Create New Namespace(新建命名空间)。 在 New Namespace(新建命名空间)页面,输入SAMPLES作为新命名空间的名称。 点击 Create New Database(新建数据库),将显示 Database Wizard(数据库向导)。 在 Database Wizard(数据库向导)的第一页,在 Enter the name of your database(输入您的数据库名称)字段,输入您正在创建的数据库的名称,例如 Samplesdb。 输入数据库的目录,例如 C:\InterSystems\IRIS\mgr\Samplesdb 。 点击 Next(下一步)。 点击 Finish(完成)。 回到New Namespace(新命名空间)页面,在Select an existing database for Routines(为 Routine 选择一个现有数据库)下拉菜单,选择您刚刚创建的数据库Samplesdb。 点击靠近页面顶部的 Save(保存),然后在结果日志的末尾单击 Close(关闭)。 UIMA 分析管道 运行Java Gateway(Java 网关) Java Gateway(Java 网关)可以实例化外部 Java object(Java 对象),就好像它是 InterSystems IRIS 中的本机对象一样,可以对其进行操作。InterSystems IRIS 的 UIMA 策略使用Java Gateway(Java 网关),可以从command line (命令行)启动Java Gateway(Java 网关)。例如,在 Windows 上: 打开 Run(运行)对话框。 输入以下命令: %JAVA_HOME%\bin\java -classpath "C:\InterSystems\IRIS\dev\java\lib\JDK18\*;C:\InterSystems\IRIS\dev\java\lib\jackson\*;C:\InterSystems\IRIS \dev\java\lib\uima\*" com.intersystems.gateway.JavaGateway 5555 其中: JAVA_HOME 是指向Java Runtime Environment(Java 运行环境,JRE)安装目录位置的环境变量。 C:\InterSystems\IRIS 是您安装的 InterSystems IRIS 的目录。 JDK18 对应您的 JRE 版本。 如果在UNIX®上运行 ,请记住 -classpath 的句法使用冒号作为分隔符。 体验 UIMA 分析管道 现在您已经完成了准备工作,并已准备好在实际应用中查看 UIMA 分析管道的运行情况。在这次体验中,您将: 添加包含 UIMA 功能索引的类文件 编译包含功能索引的类。 查看 Annotation Store (注释存储)的表。 将非结构化数据添加到示例数据库。 浏览 Annotation Store(注释存储) 以获取分析管道生成的新数据。 添加带有 UIMA 功能索引的类文件 您可以通过为包含非结构化文本的表定义功能索引,向 UIMA 分析管道添加分析引擎。在本次体验中,您将向该分析管道添加 InterSystems IRIS NLP 分析引擎。 在体验的这一部分中,您将创建一个新的类文件。可以在您喜欢的文本编辑器或 IDE(例如 InterSystems Studio 或带有 ObjectScript 扩展名的 Visual Studio Code)中创建类文件。 在 IDE 或文本编辑器中创建一个新文件。 将以下内容复制并粘贴到类文件中: xxxxxxxxxx Class Sample.MyData Extends %Persistent ​ { ​ Property MyText As %String; ​ Index MyIndex On (MyText) As %UIMA.Index(AEDESCRIPTOR = \"classpath:/com/intersystems/uima/annotator/iKnowEngine.xml\"); ​ } 体验 UIMA 分析管道 其中: MyText 是包含非结构化文本的 Sample.MyData 表中的列。 MyIndex 是 UIMA 功能索引。 iKnowEngine.xml是 InterSystems IRIS NLP 分析引擎的描述符文件。 将文件另存为sample.cls。 编译表类 要自动生成 Annotation Store (注释存储),您只需使用InterSystems Terminal(InterSystems 终端)或 IDE 提供的工具编译包含功能索引的类。 建议: 使用 InterSystems Terminal(InterSystems 终端)时,您可以使用 Shift+Insert 命令将剪贴板的内容粘贴到终端命令提示符中。从本指南复制命令并将它们粘贴到终端中以减少错误,这种方式很有用。 加载和编译类: 打开 InterSystems Terminal(InterSystems 终端)。有关为您的实例打开Terminal (终端)的信息,请参阅InterSystems IRIS Basics: Connecting an IDE(《INTERSYSTEMS IRIS 基础:连接一个IDE 》)中的InterSystems IRIS Connection Information (《InterSystems IRIS 连接信息》)。 切换到您为此演示创建的命名空间。例如: set $namespace="samples" 输入以下命令,将类文件加载到命名空间中: do $system.OBJ.Load("<sample-dir>\sample.cls") 其中 <sample-dir> 是您保存 samples.cls 类文件的位置。 输入以下命令,编译您粘贴到sample.cls 的Sample.MyData 类: do $system.OBJ.Compile("Sample.MyData") 浏览 Annotation Store(注释存储) 既然您已经使用 Annotation Store UIMA 功能索引编译了类,可以浏览为保留 InterSystems IRIS NLP 生成的注释而创建的(Annotation Store )注释存储。 在浏览器中使用 URL 为实例打开 Management Portal (管理门户),如InterSystems IRIS Basics: Connecting an IDE(《INTERSYSTEMS IRIS 基础:连接一个IDE 》)的InterSystems IRIS Connection Information(InterSystems IRIS 连接信息)所述。 使用标题中的链接切换到 Samples 命名空间。 参观 UIMA 分析管道 进入 System Explorer(系统资源管理器)> SQL 。 展开左侧窗格中的 Tables(表) 列表。 可以看到 Annotation Store (注释存储) 的三个表。这些表的命名规则对应包含所分析的非结构化文本的表 (Sample.MyData)。 Sample_MyData.Type------包含此存储中使用的Annotation Types (注释类型)的概述。 Sample_MyData.Sofa------包含 sofas,这是 UIMA 分析引擎分析的文本对象。 Sample_MyData.Annotation------包含该分析引擎生成的注释。 您可以修改功能索引定义以创建多个注释表,然后根据注释类型将结果输出到正确的表中。 通过分析管道发送新文本 InterSystems IRIS 中 UIMA 分析管道的强大之处在于,新的非结构化文本会自动通过管道发送以进行分析,并将结果添加到 Annotation Store (注释存储)中。由于您已经创建了 Annotation Store(注释存储),可以看到新纪录如何被添加到 Sample.MyData 表,并生成添加到 Annotation Store的新条目。 向 Aviation.Event 表添加记录 在本步骤中,您将使用 SQL 向示例数据库中的 Sample.MyData 表添加一些非结构化文本。请记住,该表包含MyText 列,您已在该列上定义功能索引。正如您将看到的,当进行此插入操作时,会自动生成注释。 在System Explorer (系统资源管理器 )> SQL页,展开左侧窗格中的 Tables(表) 列表。 选择 Sample.MyData,这是包含通过分析管道发送的非结构化文本的表。 在右侧窗格中,单击 Execute Query(执行查询)标签。 要将新条目插入示例数据库,请在文本框中输入以下查询 xxxxxxxxxx INSERT INTO Sample.MyData (MyText) VALUES (\'First Look unstructured text\') 点击 Execute (执行)。 这会将短语"First Look unstructured text"放入 Sample.MyData 表的 MyText 列中。 了解更多关于 UIMA的资料 查看Annotation Store(注释存储)中的新条目 现在您已将新的非结构化文本添加到示例数据库中,可以查看 Annotation Store 以了解这些文本是如何通过分析管道自动发送的。您可以看到新的非结构化文本和来自 InterSystems IRIS NLP 的注释都添加到了Annotation Store(注释存储)中。 在 System Explorer (系统资源管理器 )> SQL页,展开左侧窗格中的 Tables(表) 列表。 选择 Sample_MyData.Sofa 表。 在右侧窗格中,单击 Open Table(打开表)。 您可以看到添加到 Annotation Store 的新记录。sofaString是由分析管道处理的一段非结构化文本。 点击 Close Window(关闭窗口)。 在左侧窗格中,选择 Sample_MyData.Annotation 表。 点击 Open Table(打开表)。 在coveredText列,您可以看到由 InterSystems IRIS NLP 分析引擎生成的注释。 了解更多关于 UIMA的资料 要了解有关 InterSystems IRIS 如何实施和补充 UIMA 的更多信息,请参阅 Using InterSystems UIMA(《使用 InterSystems UIMA》)。 有关 UIMA 标准的框架、基础设施和组件的详细概述,请参阅 Apache UIMA home page
文章
Lele Yang · 二月 18, 2022

FAQ 常见问题系列--系统管理篇 InterSystems产品的内存使用

InterSystems的产品包括Caché/Ensemble/Health Connect/IRIS/IRIS for Health,均基于进程,当它们及在它们之上开发的应用运行时,您能在操作系统上看到大量cache/irisdb进程。下面以InterSystems IRIS为例,来说明下InterSystems产品的内存使用。 InterSystems IRIS进程的内存使用主要有以下两大类, 第一类,进程私有内存。 私有内存只由该进程使用,且会为每个进程单独分配。进程初始时会被分配128KB的内存空间,随着进程运行根据需要,这个内存空间会自动扩展。InterSystems IRIS对于进程内存使用的限制是Maximum Per-Process Memory, 该参数的设置位置在系统管理门户SMP,System > Configuration > Memory and Startup,允许的取值范围是256KB到2147483647KB(2TB)。如果系统遇到<STORE>错误,那么可以尝试增加该数值,来解决进程运行时无法申请到更多内存的问题。从InterSystems IRIS开始,我们推荐将该值设置为-1, 也就是取上限2TB。 更多InterSystems IRIS进程内存使用可参见如下在线文档"Process Memory in InterSystems Products",https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=AVMEM 第二类,共享内存。共享内存是一个在进程之间共享的内存区域,因此内存中只有一个实体。 InterSystems中的共享内存包括如下,1. Database Cache, 也就是我们常说的Global Buffer,设置位置在SMP,System > Configuration > Memory and Startup。 2. Routine Cache, 也就是Routine Buffer,设置位置同上。3. gmheap,设置位置在SMP,System > Configuration > Advanced Memory Settings。我们通常所说的Process Table和Lock Table占用的内存也是从这部分内存中划分出来的,如果遇到Lock Table Full的问题,需要增加locksiz大小,请注意同步调大gmheap。 InterSystems IRIS在实例启动时,会首先尝试为这部分共享内存分配Huge Page/Large Page,以获得最佳性能实践,如果分配成功,您可以在日志文件messages.log中看到如下类似内容, 11/28/21-21:00:09:081 (41752) 0 [Generic.Event] Allocated 35046MB shared memory (large pages): 31000MB global buffers, 1024MB routine buffers 另外,除以上两大类之外,还有一类特殊的内存使用, 1.长字符串内存使用InterSystems IRIS默认启用长字符串,最多支持3,641,144个字符,当进程使用长字符串时,为其分配的内存直接来自于操作系统的malloc() buffer,它不占用进程的私有内存空间,因此为字符串实际分配的内存不受限于Maximum Per-Process Memory。 综上,InterSystems IRIS所使用的内存总量是所有InterSystems IRIS进程使用的内存之和,也就等于,(进程私有内存 * 进程数) + Global Buffer + Routine Buffer + gmheap + 长字符串总体使用内存。
文章
Jingwei Wang · 七月 14, 2022

精华文章 InterSystems SQL 的使用 - 第一部分 - 架构及特性介绍

InterSystems SQL为存储在IRIS数据库中的数据提供不折不扣的、标准的关系型访问。 InterSystems SQL具有以下优点。 高性能和可扩展性 - InterSystems SQL的性能和可扩展性优于其他关系型数据库产品。 与IRIS对象技术的集成 - InterSystems SQL与IRIS对象技术紧密集成。你可以混合使用关系型和对象型的数据访问,而不牺牲任何一种方法的性能。 低维护 - 与其他关系型数据库不同,IRIS应用程序不需要在部署的应用程序中重建索引和压缩表。 支持标准SQL查询 - InterSystems SQL支持SQL-92标准语法和命令。 你可以将InterSystems SQL用于许多目的,包括。 基于对象和Web应用程序 - 你可以在InterSystems Object和Caché Server Page应用程序中使用SQL查询,以执行强大的数据库操作,如查询和搜索。 在线事务处理 - InterSystems SQL为插入和更新操作以及通常在事务处理应用程序中发现的查询类型提供出色的性能。 BI和数据仓库 - IRIS多维数据库引擎和位图索引技术的结合使其成为数据仓库式应用的绝佳选择。 点对点查询和报告 - 你可以使用InterSystems SQL包含的全功能ODBC和JDBC驱动来连接到流行的报告和查询工具。 企业应用集成 - InterSystems SQL网关使你能以SQL方式无缝访问存储在符合ODBC或JDBC标准的外部关系型数据库中的数据。这使得在IRIS应用程序中整合各种来源的数据变得很容易。 架构 IRIS SQL的核心由以下部分组成。 统一数据字典 - 所有元信息的存储库,以一系列的类定义的形式存储。自动为每个存储在统一字典中的持久类创建关系访问(表)。 SQL处理器和优化器 - 是一套解析和分析SQL查询的程序,为给定的查询确定最佳搜索策略,并生成执行查询的代码。 InterSystems SQL Server - 一组InterSystems IRIS服务器进程,负责与InterSystems ODBC和JDBC驱动的所有通信。它还管理着一个常用查询的缓存;当同一个查询被多次执行时,它的执行计划可以从查询缓存中检索出来,而不必由优化器再次处理。 特性 InterSystems SQL包括一整套标准的关系型功能。这些功能包括。 定义表和视图的能力(DDL或数据定义语言)。 对表和视图执行查询的能力(DML或数据操作语言)。 执行事务的能力,包括INSERT、UPDATE和DELETE操作。当执行并发操作时,InterSystems SQL使用行级锁。 能够定义和使用索引以提高查询效率。 能够使用各种各样的数据类型,包括用户定义的类型。 能够定义用户和角色并给他们分配权限。 能够定义外键和其他完整性约束。 能够定义INSERT, UPDATE, 和DELETE触发器。 定义和执行存储程序的能力。 能够以不同的格式返回数据。ODBC模式用于客户端访问;显示模式用于基于服务器的应用程序(如CSP页面)。 SQL-92标准 InterSystems SQL支持完整的入门级SQL-92标准,但有以下例外: 不支持在表定义中添加额外的CHECK约束。 不支持SERIALIZABLE隔离级别。 分隔标识符不区分大小写;标准规定它们应该区分大小写,例如create table "user info" (id number); 和 create table "USER INFO" (id number); 在InterSystems SQL中不区分大小写。 在包含HAVING子句的子查询中,人们应该能够引用HAVING子句中 "可用 "的min(),max(),avg()。这不被支持。即 aggregate函数无法在 WHERE 或 GROUP BY 子句中使用。例如Select DeptID , Avg(Salary) From Employee GROUP BY DeptID, Avg(Salary) HAVING Avg(Salary) > 1000, 但是可以使用Select DeptID , Avg(Salary) From Employee GROUP BY DeptID HAVING Avg(Salary) > 1000 SQL-92标准在算术运算符优先级方面是不精确的;关于这个问题的假设在不同的SQL实现中是不同的。InterSystems SQL支持两种系统范围内的SQL算术运算符优先级的选择。 默认情况下,InterSystems SQL严格按照从左到右的顺序解析算术表达式,没有运算符优先级。这与ObjectScript中使用的惯例相同。因此,3+3*5=30。你可以使用圆括号来强制执行所需的优先级。因此,3+(3*5)=18。 你可以配置InterSystems SQL使用ANSI优先级来解析算术表达式,它给乘法和除法运算符的优先级高于加法、减法和连接运算符。因此,3+3*5=18。如果需要,你可以使用圆括号来覆盖这个优先级。因此,(3+3)*5=30。 扩展功能 InterSystems SQL支持一些有用的扩展。其中许多都与InterSystems IRIS同时提供对象和关系访问数据的事实有关。 其中一些扩展包括: 支持用户定义的数据类型和函数。 用于跟踪对象引用的特殊语法。 支持子类化和继承。 支持对存储在其他数据库中的外部表的查询。 控制用于表的存储结构的一些机制,以实现最大的性能。 支持ODBC,JDBC 使用JDBC驱动和ODBC驱动 嵌入式SQL 在ObjectScript中,InterSystems SQL支持嵌入式SQL:在方法(或其他代码)的内部放置SQL语句的能力。 使用嵌入式SQL,你可以查询一条记录,或者定义一个游标并使用它来查询多条记录。 嵌入式SQL是被编译的;它可以与ObjectScript例程同时被编译(默认),或者你可以将嵌入式SQL的编译推迟到运行时。 当与IRIS的对象访问能力结合使用时,嵌入式SQL是相当强大的。例如,下面的方法可以找到具有给定Name值的记录的RowID。 ClassMethod FindByName(fullname AS %String) { &sql(SELECT %ID INTO :id FROM Sample.MyTable WHERE name= :fullname) IF SQLCODE <0 {SET baderr="SQLCODE ERROR:"_SQLCODE_" "_%msg RETURN baderr} ELSEIF SQLCODE=100 {SET nodata="Query returns no data" RETURN nodata} RETURN "RowID="_id } Dynamic SQL 作为标准库的一部分,InterSystems IRIS提供了一个%SQL.Statement类,你可以用它来执行动态(即在运行时定义)的SQL语句。你可以在ObjectScript方法中使用动态SQL。例如,下面的方法查询指定数量的21世纪出生的人。该查询选择所有在1999年12月31日以后出生的人,按出生日期对所选记录进行排序,然后选择前 x 条记录。 ClassMethod Born21stC(x) [language=objectscript] { set myquery =2 set myquery(1) = "SELECT TOP ? Name, %EXTERNAL(DOB) FROM Sample.Person " set myquery(2) = "WHERE DOB >58073 ORDER BY DOB" set tStatement = ##class(%SQL.Statement).%New() set qStatus = tStatement.%Prepare(.myquery) IF qStatus '=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT} SET rset = tStatement.%Execute(x) Do rset.%Display() WRITE !, "End of data" } 当你prepare一个查询时,该查询的优化版本被存储为一个缓存的查询。这个缓存的查询在随后的查询调用中被执行,避免了每次执行查询时重新优化的开销。 限制 注意InterSystems SQL的以下限制: NLS可以用来指定$ORDER的行为,用于特定的国家地区语言行为的Global,以及当前运行进程中的本地变量。InterSystems SQL可以在任何国家语言区划内使用,并且运行良好。然而,目前InterSystems SQL的一个限制是,对于任何特定的进程,它所引用的所有相关的globals都必须使用与当前进程locale相同的国家locale。
文章
Jingwei Wang · 七月 29, 2022

InterSystems SQL 的优化 - 第一部分 - 定义和构建索引

什么时候使用索引 索引提供了一种机制,通过维护常用数据的分类子集来优化查询。确定哪些字段应该被编入索引需要一些思考:太少或错误的索引,关键查询会运行得太慢;太多的索引会减慢INSERT和UPDATE的性能(因为索引值必须被设置或更新)。 索引什么 为了确定添加索引是否能提高查询性能,从管理门户的SQL界面运行查询,并在Performance中注意 global引用的数量。添加索引,然后重新运行查询,注意 global引用的数量。一个有用的索引应该减少 global引用的数量。你可以通过使用%NOINDEX关键字作为WHERE子句或ON子句条件的前言来阻止索引的使用。 你应该对JOIN中指定的字段(属性)进行索引。例如,LEFT OUTER JOIN从左表开始,然后查看右表,因此,你应该对右表的字段进行索引。在下面的例子中,你应该为T2.f2编制索引。一个INNER JOIN应该在两个ON子句字段上都有索引。 FROM Table1 AS T1 LEFT OUTER JOIN Table2 AS T2 ON T1.f1 = T2.f2 如果查询计划中的第一个项目是 "read master map",或者查询计划调用的模块的第一个项目是 "read master map",那么查询的第一个map就是master map而不是索引map。因为主图读取的是数据本身,而不是数据的索引,这几乎总是表明一个低效的查询计划。除非该表相对较小,否则你应该创建一个索引,以便当你重新运行这个查询时,查询计划的第一张map显示 "read index map"。 你应该为WHERE子句中指定的相等条件的字段建立索引。 你可能希望为WHERE子句范围条件中指定的字段,以及GROUP BY和ORDER BY子句中指定的字段建立索引。 在某些情况下,基于范围条件的索引会使查询变慢。如果绝大多数的记录都满足指定的范围条件,就会出现这种情况。例如,如果查询子句WHERE Date < CURRENT_DATE被用于一个数据库,其中大部分记录来自以前的日期,在Date上建立索引实际上会降低查询速度。这是因为查询优化器假定范围条件将返回相对较少的行,并针对这种情况进行优化。你可以通过在范围条件前加上%NOINDEX来确定是否发生了这种情况,然后再次运行查询。 如果你使用一个索引字段进行比较,那么在比较中指定的字段应该具有和它在相应索引中相同的collation类型。例如,在SELECT的WHERE子句或JOIN的ON子句中的Name字段应该具有与为Name字段定义的索引相同的排序方式。如果字段的排序和索引的排序不匹配,那么索引的有效性就会降低,或者根本就不能使用。 在map中存储的表 一个SQL表被存储为一组map。每个表都有一个主图,包含了表中的所有数据;表还可能有其他图,如索引图和位图。每个map可以被想象成一个多维global,一些字段的数据在一个或多个子标中,其余字段存储在节点值中。这些子标控制着哪些数据被访问。 对于主映射,RowID或IDKEY字段通常被用作映射子标。 对于索引map,通常使用其他字段作为前导下标,RowID/IDKEY字段作为额外的低级下标。 对于一个位图,位图层可以被认为是一个额外的RowID下标层。然而,位图只能用于正整数的RowIDs。 Master Map 系统为每个表自动定义了一个主图(Data/Master)。master map不是一个索引,它是一个使用其map下标字段直接访问数据本身的map。默认情况下,主图的下标字段是系统定义的RowID字段。默认情况下,这种使用RowID字段的直接数据访问是用SQL map名称(SQL索引名称)IDKEY表示的。 默认情况下,一个用户定义的主键不是IDKEY。这是因为使用RowID整数的Master Map查询几乎总是比通过主键值查询更有效率。然而,如果你指定主键是IDKEY,主键索引就会被定义为表的主映射,SQL Map Name是主键SQL索引的名称。 对于一个单字段的主键/IDKEY,主键索引是Master Map,但是Master Map的数据访问列仍然是RowID。这是因为记录的唯一主键字段值和它的RowID值之间存在一对一的匹配,而且RowID被认为是更有效的查询。对于多字段主键/IDKEY,主映射被赋予主键索引名称,主映射的数据访问列是主键字段。 选择一个索引类型 下面是在位图索引和标准索引之间进行选择的一般指导原则: 一般来说,可以对这些类型使用标准索引:主键、外键、唯一键、关系、简单的对象引用。 否则,位图索引通常是比较好的(假设该表使用系统分配的数字ID号)。 其他因素:每个属性上的独立位图索引通常比多个属性上的位图索引有更好的性能。这是因为SQL引擎可以使用AND和OR操作有效地组合独立的位图索引。 如果一个属性(或一组你真正需要一起索引的属性)有超过10,000-20,000个不同的值(或值组合),请考虑标准索引。然而,如果这些值的分布非常不均匀,以至于少量的值占了相当一部分行,那么位图索引可能会好得多。一般来说,我们的目标是减少索引所需的总体大小。 定义和建立索引 以下是建立索引的原则: 你可以在表中的字段值上定义一个索引,或者在类中的相应属性上定义一个索引。 你也可以在几个字段/属性的组合值上定义一个索引。无论你是用SQL字段和表的语法,还是用类的属性语法来定义,都会创建相同的索引。 当某些类型的字段(属性)被定义时,InterSystems IRIS会自动定义索引,例如主键和唯一值属性。 你可以为同一个字段(属性)定义一个以上的索引,为不同的目的提供不同类型的索引。 每当对数据库进行数据插入、更新或删除操作时,InterSystems IRIS都会填充和维护索引(默认情况下),无论是使用SQL字段和表语法,还是使用类属性语法。你可以覆盖这个默认值(通过使用%NOINDEX关键字)来快速对数据进行修改,然后作为一个单独的操作建立或重建相应的索引。你可以在用数据填充一个表之前定义索引,你也可以为一个已经填充了数据的表定义索引,然后作为一个单独的操作填充(建立)索引。 InterSystems IRIS在准备和执行SQL查询时,会使用可用的索引。默认情况下,它选择使用哪些索引来优化查询性能。你可以覆盖这个默认值,以防止在特定的查询或所有的查询中使用一个或多个索引,视情况而定。 索引属性 每个索引都有一个唯一的名字。这个名称用于数据库管理目的(报告、建立索引、删除索引等等)。像其他SQL实体一样,一个索引也有一个SQL索引名和一个相应的索引属性名;这些名称在允许的字符、大小写敏感度和最大长度上有所不同。 如果使用SQL CREATE INDEX命令定义,系统会生成一个相应的索引属性名。如果使用持久化类定义,SqlName关键字允许用户指定一个不同的SQL索引名称(SQL映射名称)。管理门户SQL界面的目录详情显示了每个索引的SQL索引名称(SQL映射名称)和相应的索引属性名称(索引名称)。 索引类型是由两个索引类关键字Type和Extent定义的。InterSystems IRIS可用的索引类型包括: 标准索引(类型=索引)- 一个持久的数组,将索引值与包含该值的行的RowID联系起来。任何没有明确定义为位图索引、位片索引或范围索引的索引都是一个标准索引。 位图索引(类型=位图)--一种特殊的索引,它使用一系列的bitstring来表示对应于给定索引值的RowID值的集合;InterSystems IRIS包括一些针对位图索引的性能优化。 位片索引(类型=位片)--一种特殊的索引,能够非常快速地评估某些表达式,如sum范围条件。某些SQL查询会自动使用位片索引。 Extent Indices(位图范围索) - 一个范围内所有对象的索引。 一个表(类)的最大索引数是400。 系统自动定义的索引 当你定义一个表时,系统会自动定义某些索引。以下索引在你定义表时自动生成,并在你添加或修改表数据时被填充。如果你定义了: 一个不是IDKEY的主键,系统会生成一个Unique类型的相应索引。主键索引的名称可以是用户指定的,也可以是从表的名称中衍生出来的。例如,如果你定义了一个未命名的主键,相应的索引将被命名为tablenamePKEY#,其中#是每个唯一键和主键约束的一个连续的整数。 一个UNIQUE字段,InterSystems IRIS为每个UNIQUE字段生成一个索引,名称为tablameUNIQUE#,其中#是每个唯一键和主键约束的连续整数。 一个UNIQUE约束,系统为每个具有指定名称的UNIQUE约束生成一个索引,为共同定义一个唯一值的字段生成索引。 一个分片Shard Key,系统为分片键字段生成一个索引,名为ShardKey。 你可以通过管理门户的SQL目录细节标签查看这些索引。CREATE INDEX命令可以用来添加一个UNIQUE字段约束;DROP INDEX命令可以用来删除一个UNIQUE字段约束。 默认情况下,系统会在RowID字段上生成IDKEY索引。定义一个IDENTITY字段并不产生索引。然而,如果你定义了一个IDENTITY字段并使该字段成为主键,InterSystems IRIS会在IDENTITY字段上定义IDKEY索引并使其成为主键索引。这在下面的例子中显示。 CREATE TABLE Sample.MyStudents ( FirstName VARCHAR(12), LastName VARCHAR(12), StudentID IDENTITY, CONSTRAINT StudentPK PRIMARY KEY (StudentID) ) 同样,如果你定义了一个IDENTITY字段,并给该字段一个UNIQUE约束,InterSystems IRIS会明确地在IDENTITY字段上定义一个IdKey/Unique索引。这在下面的例子中显示。 CREATE TABLE Sample.MyStudents ( FirstName VARCHAR(12), LastName VARCHAR(12), StudentID IDENTITY, CONSTRAINT StudentU UNIQUE (StudentID) ) 这些IDENTITY索引操作只在没有明确定义的IdKey索引和表不包含数据的情况下发生。 手动定义索引 有两种方法来定义索引。 使用类定义来定义索引,其中包括: 可以被索引的属性 多个属性的索引 索引的整理 在索引中使用Unique、PrimaryKey和IdKey关键字 定义SQL搜索索引 用索引存储数据 对NULL进行索引 索引集合 阵列集合的索引 用(ELEMENTS)和(KEYS)对数据类型属性进行索引 为嵌入式对象(%SerialObject)属性建立索引 关于在类中定义的索引的说明 使用DDL定义索引 使用类定义来定义索引 Class MyApp.Student Extends %Persistent [DdlAllowed] { Property Prop1 As %String; Property Prop2 As %String; //单一属性定义索引 Index Prop1IDX On Prop1; //多属性值定义索引 Index MIXIDX On (Prop1, Prop2) //使用单一属性和collation定义索引 Index Prop2IDX On Prop2 As Exact; //使用多属性和collation定义索引 Index MIX2IDX On (Prop1 As SQLUPPER,Prop2 As Exact); //使用Unique关键字定义索引 Index Prop1IDX On Prop1 [Unique] //使用PrimaryKey关键字定义索引 Index Prop1IDX On Prop1 [PrimaryKey] //使用IdKey关键字定义索引 Index Prop1IDX On Prop1 [IdKey] //定义查询索引 -- 供文档查询使用 Index Prop1IDX On (Prop1) As %iFind.Index.Basic //List或者Array属性定义索引 Index EmpIndex On Employees(KEYS) Index EmpIndex On Employees(ELEMENTS) Index EmpIndex On (Employees(KEYS), Employees(ELEMENTS)); //对象属性定义索引 Index StateInx On Home.State; //定义位图索引 Index ReginIDX On Region [Type = bitmap] //定义位片索引 Index AgeIDX On Age [Type = bitslice] } 在类定义中处理索引时,有几点需要注意: 索引定义只从主(第一)超类中继承。 如果你使用Studio为一个有数据存储的类添加(或删除)一个索引定义,你必须通过构建索引来手动填充索引。 使用类定义定义%BID位图索引 如果表的ID不是正整数,你可以创建一个%BID属性,用来创建位图索引定义。你可以对具有任何数据类型的ID字段的表,以及由多个字段组成的IDKEY(其中包括子表)使用这个选项。可以为任一数据存储类型创建%BID位图:默认结构表或%Storage.SQL表。这个功能被称为 "任何表的位图Bitmaps for Any Table",或BAT。 要在这样的表中启用位图索引,你必须做以下工作。 为该类定义一个%BID属性/字段。这可以是该类的一个现有属性,也可以是一个新的属性。它可以有任何名字。如果这是一个新的属性,你必须为表中的所有现有行填充这个属性/字段。这个%BID字段必须用一个数据类型来定义,限制字段数据值为唯一的正整数。例如: Property MyBID As %Counter; 定义一个新的类参数来定义哪个属性是%BID字段。这个参数被命名为BIDField。这个参数被设置为%BID属性的SQLFieldName。例如,参数BIDField = "MyBID"。 Parameter BIDField = "MyBID"; 为%BID定义一个索引。例如, Index BIDIdx On MyBID [ Type = key, Unique ] 。 定义%BID定位器索引。这将%BID索引与表的ID关键字段联系起来。下面的例子是关于一个有两个字段组成的复合IDKey的表。 Index IDIdx On (IDfield1, IDfield2) [ IdKey, Unique ] 。 Index BIDLocIdx On (IDfield1, IDfield2, MyBID) [ Data = IdKey, Unique ] 。 这个表现在支持位图索引。你可以使用标准语法根据需要定义位图索引。例如: Index RegionIDX On Region [Type = bitmap] 该表现在也支持位片索引。你可以使用标准语法定义位片索引。 注意: 要建立或重建一个%BID位图索引,你必须使用%BuildIndices()。%ConstructIndicesParallel()方法不支持%BID位图索引。 使用DDL定义索引 DDL索引命令做了以下工作。 它们更新相应的类和表的定义,在这些定义上添加或删除索引。修改后的类定义被重新编译。 它们根据需要在数据库中添加或删除索引数据。CREATE INDEX命令使用当前存储在数据库中的数据来填充索引。同样,DROP INDEX命令从数据库中删除了索引数据(也就是实际的索引)。 CREATE INDEX //标准索引 CREATE INDEX StateIdx ON TABLE Sample.Person (Home_State) ​ //位图范围索引 CREATE BITMAPEXTENT INDEX Patient ON TABLE Sample.Patient ​ //位图索引 CREATE BITMAP INDEX RegionIDX ON TABLE MyApp.SalesPerson (Region) ​ //位片索引 CREATE BITSLICE INDEX AgeIDX ON TABLE MyApp.SalesPerson (Age) ​ DROP INDEX DROP INDEX PeopleIndex ON TABLE Employee 为嵌入式对象(%SerialObject)属性创建索引 要为嵌入式对象中的一个属性建立索引,你要在引用该嵌入式对象的持久化类中创建一个索引。属性名必须指定表(%Persistent类)中的引用字段的名称和嵌入对象(%SerialObject)中的属性,如下面的例子所示。 Class Sample.Person Extends (%Persistent) [ DdlAllowed ] { Property Name As %String(MAXLEN=50); Property Home As Sample.Address; Index StateInx On Home.State; } 这里Home是Sample.Person中的一个属性,它引用嵌入式对象Sample.Address,其中包含State属性,如下例所示。 Class Sample.Address Extends (%SerialObject) { Property Street As %String; Property City As %String; Property State As %String; Property PostalCode As %String; } 只有与持久化类属性引用相关的嵌入式对象实例中的数据值被索引。你不能直接索引%SerialObject属性。%Library.SerialObject(以及所有没有明确定义SqlCategory的%SerialObject的子类)的SqlCategory是STRING。 你也可以使用SQL CREATE INDEX语句在嵌入式对象属性上定义一个索引,如下面的例子所示。 CREATE INDEX StateIdx ON TABLE Sample.Person (Home_State) 位图索引 位图索引是一种特殊类型的索引,它使用一系列的bit字符串来表示与给定的索引数据值相对应的ID值集合。 位图索引有以下重要特点: 位图是高度压缩的:位图索引可以比标准索引小得多。这大大减少了磁盘和缓存的使用。 位图操作为事务处理进行了优化:你可以在表内使用位图索引,与使用标准索引相比,没有性能损失。 位图上的逻辑操作(计数、和、以及OR)被优化为高性能。 SQL引擎包括一些特殊的优化,可以利用位图索引的优势。 位图索引的创建取决于表的唯一标识字段的性质。 如果表的ID字段被定义为一个具有正整数值的单一字段,你可以使用这个ID字段为一个字段定义位图索引。这种类型的表要么使用系统分配的唯一正整数ID,要么使用IdKey定义自定义ID值,其中IdKey基于类型为%Integer且MINVAL>0的单一属性,或类型为%Numeric且SCALE=0且MINVAL>0的单一属性。 如果表的ID字段没有定义为具有正整数值的单一字段(例如,一个子表),你可以定义一个%BID(位图ID)字段,它采取正整数,作为一个代理ID字段;这允许你为这个表中的字段创建位图索引。 本章讨论了与位图索引有关的下列主题“ 位图索引操作 通过使用类定义来定义位图索引 使用DDL定义位图索引 生成一个位图范围索引 选择一个索引类型 对位图索引的限制 维护位图索引 位图分块的SQL操作 位图索引操作原理 位图索引的工作方式如下。假设你有一个包含若干列的Person表,这个表中的每一行都有一个系统分配的RowID号码(一组递增的整数值)。一个位图索引使用一组bitstring(一个包含1和0值的字符串)。在一个bitstring内,一个bit的序号位置对应于被索引表的RowID。对于一个给定的值,例如State是 "NY",有一个bitstring,对应于包含 "NY "的行,每个位置都是1,其他不包含'NY'的行,位置都是0。 例如,一个关于State的位图索引可能看起来像这样: 而关于年龄的索引可能看起来像这样。 注意: 这里显示的年龄字段可以是一个普通的数据字段,也可以是一个可以靠其他字段得出的字段(计算和SQLComputed)。 除了在标准操作中使用位图索引外,SQL引擎还可以使用位图索引,使用多个索引的组合有效地执行基于集合的特殊操作。例如,为了找到所有24岁并居住在纽约的Person实例,SQL引擎可以简单地执行Age和State索引的 AND 逻辑。结果如下: SQL引擎可以使用位图索引进行以下操作: 在一个给定的表上对多个条件进行ANDing。 在一个给定的表上的多个条件的ORing。 在一个给定的表上的RANGE条件。 在一个给定的表上进行COUNT操作。 位图索引的限制 所有的位图索引都有以下限制: 不能在一个UNIQUE列上定义一个位图索引。 不能在位图索引中存储数据值。 不能在一个字段上定义位图索引,除非该字段的SqlCategory是INTEGER, DATE, POSIXTIME, 或NUMERIC (with scale=0) 。 对于一个包含超过100万条记录的表来说,当唯一值的数量超过10,000时,位图索引的效率就不如标准索引。因此,对于一个大表,建议你避免对任何包含(或可能包含)超过10,000个唯一值的字段使用位图索引;对于任何规模的表,避免对任何可能包含超过20,000个唯一值的字段使用位图索引。这些都是一般的近似值,不是精确的数字。 你必须创建一个%BID属性来支持表上的位图索引,该表 使用一个非整数字段作为唯一的ID键。 使用一个多字段的ID键。 是父-子关系中的一个子表。 你可以使用$SYSTEM.SQL.Util.SetOption()方法SET status=$SYSTEM.SQL.Util.SetOption("BitmapFriendlyCheck",1,.oldval)来设置一个系统范围的配置参数,在编译时检查这个限制,确定%Storage.SQL类中是否允许定义位图索引。这个检查只适用于使用%Storage.SQL的类。你可以使用$SYSTEM.SQL.Util.GetOption("BitmapFriendlyCheck")来确定这个选项的当前配置。 应用逻辑的限制: 一个位图结构可以用一个bitstring数组来表示,其中数组的每个元素代表一个具有固定bit数的 "块"。因为未定义等同于一个全部为0位的块,所以数组可以是稀疏的。一个代表所有0 bit的块的数组元素根本就不需要存在。由于这个原因,应用逻辑应该避免依赖0值位的$BITCOUNT(str,0)计数。 因为一个bitstring包含内部格式化,应用逻辑不应该依赖于一个bitstring的物理长度,也不应该依赖于对两个具有相同位值的bitstring进行等价。在回滚操作之后,一个bitstring被恢复到事务之前的位值。然而,由于内部格式化,回滚的bitstring可能不等同于事务之前的bitstring,也不具有相同的物理长度。 维护位图索引 在一个不稳定的表(一个经历了许多INSERT和DELETE操作的表),位图索引的存储可能会逐渐变得不那么有效。 为了维护位图索引,你可以运行%SYS.Maint.Bitmap工具方法来压缩位图索引,将其恢复到最佳效率。你可以使用OneClass()方法来压缩单个类的位图索引。或者你可以使用Namespace()方法来压缩整个命名空间的位图索引。这些维护方法可以在一个生产系统上运行。 d ##class(%SYS.Maint.Bitmap).Namespace("Samples",1,1,"2014-01-17 09:00:00") d ##class(%SYS.Maint.Bitmap).OneClass("BitMap.Test",1,1) 运行%SYS.Maint.Bitmap实用方法的结果被写到调用该方法的进程中。这些结果也被写入%SYS.Maint.BitmapResults类中。 位片索引 当一个数字数据字段被用于某些数字操作时,位片索引被用于该字段。位片索引将每个数字数据值表示为一个二进制位串。比起使用布尔标志对数字数据值进行索引(如在位图索引中),位片索引用二进制表示每个值,并为二进制值中的每个数字创建一个位图来记录哪些行的二进制数字为1。这是一种高度专业化的索引类型,可以大幅度提高以下操作的性能。位片索引适用于以下类型: SUM, COUNT, 或AVG聚合计算。(位片索引不用于COUNT(*)计算) 位片索引不用于其他聚合函数。 在 TOP n ... ORDER BY field 的操作。 在一个范围条件操作中指定的字段,例如 WHERE field > n or WHERE field BETWEEN lownum AND highnum SQL优化器决定是否应该使用定义的位片索引。通常情况下,优化器只在处理大量的行(成千上万)时才使用位片索引。 在一个不稳定的表(一个经历了许多INSERT、UPDATE和DELETE操作的表)中,位片索引的存储效率会逐渐降低。%SYS.Maint.Bitmap工具方法同时压缩了位图索引和位片索引,恢复了效率。更多细节,请看 "维护位图索引"。 位图范围索引 位图范围索引是针对表的行的位图索引,而不是针对表的任何指定字段。在位图范围索引中,每个位代表一个连续的RowID整数值,每个位的值指定相应的行是否存在。InterSystems SQL使用这种索引来提高COUNT()的性能,COUNT()返回表中的记录数(行数)。一个表最多可以有一个位图范围索引。试图创建一个以上的位图范围索引,会导致SQLCODE -400错误,并出现%msg ERROR #5445: 定义了多个范围索引。 一个位图索引需要一个位图范围索引。只有在定义了一个或多个位图索引的情况下,定义一个持久化类才会生成一个位图范围索引。因此,当编译一个包含位图索引的持久化类时,如果没有为该类定义位图范围索引,类编译器会生成一个位图范围索引。 如果你从持久化类定义中删除了所有位图索引,那么位图范围索引会自动删除。但是,如果你重命名位图范围索引(例如,使用CREATE BITMAPEXTENT INDEX命令),删除位图索引并不会删除位图范围索引。 当为一个类建立索引时,如果你明确地建立了位图范围索引,或者你建立了位图索引而位图范围索引是空的,那么位图范围索引就会被建立。 一个类从主超类继承它的位图范围索引,如果它存在的话,可以定义或生成。一个位图范围索引被定义为:type = bitmap, extent = true。这意味着从主超类继承的位图范围索引被认为是一个位图索引,如果在子类中没有明确定义位图范围索引,则会触发在子类中生成一个位图范围索引。 InterSystems IRIS不会根据未来的可能性在超类中生成一个位图范围索引。这意味着InterSystems IRIS永远不会在一个持久性类中生成一个位图范围索引,除非有一个类型=位图的索引存在。假设某个未来的子类可能引入一个类型=位图的索引是不够的。 注意:在生产系统上为一个类添加位图索引的过程中需要特别注意(当用户使用一个特定的类,编译该类,并随后为它建立位图索引结构)。在这样的系统上,位图范围索引可能会在编译完成和索引建立过程的中间阶段被填充。这可能导致索引构建程序没有隐含地构建位图范围索引,这导致了部分位图范围索引不能被构建。 在一个经历了许多DELETE操作的表中,位图范围索引的存储会逐渐变得不那么有效。你可以通过使用BUILD INDEX命令重建位图范围索引,或者从管理门户选择表的Catalog Details标签,Maps/Indices选项并选择Rebuild Index。 构建索引 构建一个索引会做以下事情。 移除索引的当前内容。 扫描(读取每一行)主表,为表中的每一行添加索引条目。如果可能的话,使用特殊的$SortBegin和$SortEnd函数来确保建立大型索引的效率。当建立标准索引时,除了在内存中缓存数据外,这种对$SortBegin/$SortEnd的使用会占用IRISTEMP数据库的空间。因此,当建立一个非常大的标准索引时,InterSystems IRIS可能需要IRISTEMP中的空间,大致相当于最终索引的大小。 注意:建立索引的方法只提供给使用InterSystems IRIS默认存储结构的类(表)。 当前的数据库访问决定了你应该如何重建一个现有的索引: 非活跃系统(在索引建立或重建期间没有其他进程访问数据) 只读活跃系统(其他进程在索引建立或重建期间能够查询数据) 读和写活跃系统(其他进程能够修改数据,并在索引建立或重建期间查询数据)。 你可以按以下方式建立/重新建立索引。 使用BUILD INDEX SQL命令来构建指定的索引,或构建为一个表、一个Schema或当前命名空间定义的所有索引: //为整个表构建索引 BUILD INDEX FOR TABLE table-name //为一个Schema下的所有表构建索引 BUILD INDEX FOR SCHEMA schema-name //为整个命名空间构建索引 BUILD INDEX FOR ALL 使用管理门户为一个指定的类(表)重建所有的索引。 系统资源管理器 - SQL- 操作 - 重建表索引 使用%BuildIndices()(或%BuildIndicesAsync())方法,如本节所述。 建立索引的首选方法是使用%BuildIndices()方法或%BuildIndicesAsync()方法。 %Library.Persistent.%BuildIndices()。%BuildIndices()作为一个后台进程执行,但调用者必须等待%BuildIndices()完成后才能接收控制权。 %Library.Persistent.%BuildIndicesAsync()。BuildIndicesAsync()将%BuildIndices()作为一个后台进程启动,调用者立即收到控制权。%BuildIndicesAsync()的第一个参数是queueToken输出参数。其余的参数与%BuildIndices()相同。 %BuildIndicesAsync()返回一个%Status值:成功表示%BuildIndices()工作任务成功排队;失败表示工作任务没有成功排队。 %BuildIndicesAsync()向queueToken输出参数返回一个值,表示%BuildIndices()完成状态。为了获得完成状态,你通过引用queueToken值传递给%BuildIndicesAsyncResponse()方法。你还指定了wait布尔值。如果wait=1,%BuildIndicesAsyncResponse()将等待,直到由queueToken识别的%BuildIndices()作业完成。如果wait=0,%BuildIndicesAsyncResponse()将尽可能快地返回一个状态值。如果%BuildIndicesAsyncResponse()的queueToken在返回时不是NULL,那么%BuildIndices()作业还未完成。在这种情况下,queueToken可以用来再次调用%BuildIndicesAsyncResponse()。当%BuildIndicesAsyncResponse()的queueToken最终为空时,那么%BuildIndicesAsyncResponse()返回的%Status值就是由%BuildIndicesAsync()调用的作业的完成状态。 在一个不活跃的系统上构建索引: 系统自动生成方法(由%Persistent类提供),用于构建(即为其提供数值)或清除(即为其移除数值)为类(表)定义的每个索引。你可以通过以下两种方式使用这些方法。 通过管理门户: 系统资源管理器 - SQL- 操作 - 重建表索引 注意:当其他用户正在访问该表的数据时,不要重建索引。要在一个活跃系统上重建索引,请看在活跃系统上建立索引。 以编程方式调用。 构建所有索引:调用%BuildIndices(),没有参数,为给定的类(表)构建(提供)所有索引。 SET sc = ##class(MyApp.SalesPerson).%BuildIndices() IF sc=1 {WRITE !, "Successful index build" } ELSE {WRITE !, "Index build failed",! DO $System.Status.DisplayError(sc) QUIT} 构建指定的索引:调用%BuildIndices(),以$List的索引名称作为第一个参数,为一个给定的类(表)建立(提供)指定的索引。 SET sc = ##class(MyApp.SalesPerson).%BuildIndices($ListBuild("NameIDX", "SSNKey")) IF sc=1 {WRITE !, "Successful index build" } ELSE {WRITE !, "Index build failed",! DO $System.Status.DisplayError(sc) QUIT} 构建所有索引,除了某些指定索引:调用%BuildIndices(),将$List的索引名称作为第七个参数,为一个给定的类(表)构建(提供值)所有定义的索引,除了指定的索引。 SET sc = ##class(MyApp.SalesPerson).%BuildIndices("",,,,,,$ListBuild("NameIDX", "SSNKey")) IF sc=1 {WRITE !, "Successful index build" } ELSE {WRITE !"Index build failed",! DO $System.Status.DisplayError(sc) QUIT} %BuildIndices()方法做了以下工作: 在任何要重建的(非位图)索引上调用$SortBegin函数(这将为这些索引启动一个高性能的排序操作)。 循环处理该类的主要数据(表),收集索引使用的值,并将这些值添加到索引中(有适当的整理转换)。 调用$SortEnd函数来完成对索引的排序过程。 如果索引已经有了值,你必须用两个参数调用%BuildIndices(),其中第二个参数的值为1,为这个参数指定1会使该方法在重建索引之前清除这些值。比如说: SET sc = ##class(MyApp.SalesPerson).%BuildIndices(,1) IF sc=1 {WRITE !, "Successful index build" } ELSE {WRITE !, "Index build failed",! DO $System.Status.DisplayError(sc) QUIT} 这就清除并重建了所有的索引。你也可以清除和重建索引的一个子集,如: SET sc = ##class(MyApp.SalesPerson).%BuildIndices($ListBuild("NameIDX", "SSNKey"),1) IF sc=1 {WRITE !, "Successful index build" } ELSE {WRITE !, "Index build failed",! DO $System.Status.DisplayError(sc) QUIT} 注意:当其他用户正在访问该表的数据时,不要重建索引。要在一个活跃系统上重建索引,请看在活跃系统上建立索引。 在活跃系统上建立索引 当在一个活跃系统上建立(或重建)索引时,有两个问题: 活跃查询可能会返回不正确的结果,除非正在建立的索引被隐藏在SELECT查询之外。在建立索引之前,可以使用SetMapSelectability()方法来处理这个问题。 在建立索引期间对数据的主动更新可能不会反映在索引条目中。这可以通过在构建索引时让构建操作锁定个别行来处理。 注意:如果一个应用程序在一个事务中对数据进行大量的更新,可能会出现锁表争夺的问题。 在一个只读活跃系统上构建索引 如果一个表目前只用于查询操作(只读),你可以在不中断查询操作的情况下建立新的索引或重建现有索引。这是通过在重建这些索引时使索引对查询优化器不可用来实现的。 如果你想建立一个或多个索引的所有类目前都是只读的,使用 "在读和写活跃系统上建立索引 "中描述的相同系列操作,但有以下区别:当你使用%BuildIndices()时,设置pLockFlag=3(共享范围锁)。 在读和写活跃系统上建立索引 如果一个持久化的类(表)目前正在使用,并且可以进行读和写访问(查询和数据修改),你可以建立新的索引或者重建现有的索引而不中断这些操作。如果你想重建一个或多个索引的类目前可以被读和写访问,建立索引的首选方法是使用表的持久化类提供的%BuildIndices()(或%BuildIndicesAsync())方法。 在并发的读和写访问中,建立一个或多个索引需要进行以下一系列操作: 使你希望建立的索引对查询不可用(READ访问)。这是用SetMapSelectability()完成的。这使得该索引不能被查询优化器使用。这个操作应该在重建一个现有的索引和创建一个新的索引时执行。比如说 SET status=$SYSTEM.SQL.Util.SetMapSelectability("Sample.MyStudents", "StudentNameIDX",0) 第一个参数是Schema.Table名称,即SqlTableName,而不是持久化类的名称。例如,默认的Schema是SQLUser,而不是User。这个值是区分大小写的。 第二个参数是SQL索引图名称。这通常是索引的名称,指的是索引存储在磁盘上的名称。对于一个新的索引,这是你在创建索引时要使用的名称。这个值是不区分大小写的。 第三个参数是MapSelectability标志,其中0定义索引图为不可选择(关闭),1定义索引图为可选择(打开)。指定为0。 你可以通过调用GetMapSelectability()方法来确定一个索引是否是不可选择的。如果你已经明确地将一个索引标记为不可选择,这个方法返回0。在所有其他情况下,它返回1;它不对表或索引的存在进行验证检查。注意,Schema.Table名称是SqlTableName,并且区分大小写。 SetMapSelectability()和GetMapSelectability()仅适用于当前命名空间的索引映射。如果该表被映射到多个命名空间,并且需要在每个命名空间建立索引,则应在每个命名空间调用SetMapSelectability()。 在索引建立的过程中建立并发操作。 对于一个新的索引 在类中创建索引定义(或者在类的%Storage.SQL中创建新的SQL索引映射规范),编译该类。对于一个新的索引,这是合适的,因为索引还没有被填充。在可以对表进行查询之前,需要对范围索引进行填充。 对于一个现有的索引。清除任何引用该表的缓存查询。索引构建所执行的第一个操作是杀死索引。因此,当索引被重建时,你不能依靠任何被优化的代码来使用该索引。 //删除当前命名空间的所有缓存查询 PURGE CACHED QUERIES ​ //删除当前命名空间n天内的所有缓存查询 PURGE CACHED QUERIES BY AGE n Do $SYSTEM.SQL.Purge(n) ​ //删除某个表的缓存查询 PURGE CACHED QUERIES BY TABLE table-name 或者 Do $SYSTEM.SQL.PurgeForTable("MedLab.Patient") ​ //删除当类的缓存查询 PURGE [CACHED] QUERIES BY NAME class-name 使用你的持久化类(表)的%BuildIndices()方法,使用pLockFlag=2(行级锁)来建立索引。pLockFlag=2标志在重建过程中对个别行建立了一个排他性的写锁,这样并发的数据修改操作就可以与构建索引操作相协调。 默认情况下,%BuildIndices()会构建所有为持久化类定义的索引;你可以使用pIgnoreIndexList来排除重建索引。 默认情况下,%BuildIndices()为所有ID建立索引条目。然而,你可以你可以使用pStartID和pEndID来定义一个ID的范围。%BuildIndices()将只为该范围内的ID建立索引条目。例如,如果你使用带有%NOINDEX限制的INSERT将一系列新记录添加到表中,你可以随后使用带有ID范围的%BuildIndices()来为这些新记录建立索引条目。 %BuildIndices()返回一个%Status值。如果%BuildIndices()由于检索数据的问题而失败,系统会产生一个SQLCODE错误和一个消息(%msg),其中包括遇到错误的%ROWID。 一旦你完成了建立索引,请启用查询优化器的MapSelectability。设置第三个参数,MapSelectability标志为1,如下面的例子所示。 SET status=$SYSTEM.SQL.Util.SetMapSelectability("Sample.MyStudents", "StudentNameIDX",1) 再一次,清除任何引用该表的缓存查询。这将消除在此过程中创建的无法使用索引的缓存查询,因此,与使用索引的相同查询相比,缓存查询的效果较差。 这样就完成了这个过程。索引被完全填充,并且查询优化器能够考虑该索引。 注意:%BuildIndices()只能用于为有正整数ID值的表重建索引。如果父表有正的整数ID值,你也可以使用%BuildIndices()来重建子表的索引。对于其他表,使用%ValidateIndices()方法。因为%ValidateIndices()是建立索引的最慢的方法,所以只有在没有其他选择的情况下才应该使用它。 验证索引 你可以使用以下任一方法来验证索引: $SYSTEM.OBJ.ValidateIndices()验证一个表的索引,同时也验证该表的集合子表的任何索引。 %Library.Storage.%ValidateIndices()验证一个表的索引。集合子表的索引必须用单独的%ValidateIndices()调用进行验证。 这两种方法都检查指定表的一个或多个索引的数据完整性,并可选择纠正发现的任何索引完整性问题。它们分两步进行索引验证。 确认为表(类)中的每一行(对象)正确定义了一个索引实体。 遍历每个索引,对于每个被索引的条目,确保在表(类)中有一个值和匹配的条目。 如果任何一种方法发现不一致的地方,它可以选择性地纠正索引结构和内容。它可以验证并选择性地纠正标准索引、位图索引、位图范围索引和位片索引。默认情况下,这两个方法都验证索引,但不纠正索引。 只有在遵循以下条件的情况下,%ValidateIndices()才能用于纠正(建立)读和写活跃系统中的索引: SetMapSelectability()被使用,如上所述;%ValidateIndices()参数必须包括autoCorrect=1和lockOption>0。 因为%ValidateIndices()的速度明显较慢,%BuildIndices()是在活跃系统上建立索引的首选方法。 %ValidateIndices()通常从终端运行。它显示输出到当前设备。这个方法可以应用于指定的%List索引名称,或者为指定表(类)定义的所有索引。它只对那些起源于指定类的索引进行操作;如果一个索引起源于一个超类,该索引可以通过在超类上调用%ValidateIndices()进行验证。 只读类不支持%ValidateIndices()。 %ValidateIndices()被支持用于分片类和分片主类表(Sharded=1)。你可以调用%ValidateIndices,要么直接作为一个类方法,要么从$SYSTEM.OBJ.ValidateIndices上调用分片主类。然后在每个分片上的分片本地类上执行索引验证,并将结果返回给分片主类上的调用者。当在分片类上使用%ValidateIndices()时,verbose标志被强制为0,没有输出到当前设备。任何发现/纠正的问题都会在byreference errors()数组中返回。 下面的例子使用%ValidateIndices()来验证和纠正Sample.Person表的所有索引: SET status=##class(Sample.Person).%ValidateIndices("",1,2,1) IF status=1 {WRITE !, "Successful index validation/correction" } ELSE {WRITE !, "Index validation/correction failed",! DO $System.Status.DisplayError(status) QUIT}。 第一个参数("")指定要验证所有的索引; 第二个参数(1)指定要修正索引差异; 第三个参数(2)指定要对整个表进行独占锁定; 第四个参数(1)指定使用多个进程(如果有的话)来执行验证。该方法返回一个%Status值。 通过名称验证索引: SET IndList=$LISTBUILD("NameIDX", "SSNKey") SET status=##class(Sample.Person).%ValidateIndices(IndList,1,2,1) IF status=1 {WRITE !, "Successful index validation/correction" } ELSE {WRITE !, "Index validation/correction failed",! DO $System.Status.DisplayError(status) QUIT}。 %ValidateIndices()的第一个参数或$SYSTEM.OBJ.ValidateIndices()的第二个参数指定哪些索引将作为%List结构被验证。不管第一个参数的值如何,IdKey索引总是被验证的。你可以通过指定一个空字符串值("")来验证表的所有索引。你可以通过指定一个列表结构来验证该表的单个索引。下面的例子验证了IdKey索引和两个指定的索引。NameIDX和SSNKey。 对于这两种方法,如果索引列表包含一个不存在的索引名称,该方法不执行索引验证,并返回%Status错误。如果索引列表中包含一个重复的有效索引名称,该方法将验证指定的索引,忽略重复的索引,不发出错误。 索引信息查询 INFORMATION.SCHEMA.INDEXES持久化类显示当前命名空间中所有列索引的信息。它为每个被索引的列返回一条记录。它提供了一些索引的属性,包括索引的名称,表的名称,以及索引所对应的列的名称。每条列记录还提供了该列在索引图中的序号位置;除非索引映射到多个列,否则这个值是1。它还提供了布尔属性PRIMARYKEY和NONUNIQUE(0=索引值必须是唯一的)。 SELECT Index_Name,Table_Schema,Table_Name,Column_Name,Ordinal_Position, Primary_Key,Non_Unique FROM INFORMATION_SCHEMA.INDEXES WHERE NOT Table_Schema %STARTSWITH '%' 你可以使用管理界面的目录详情的映射/索引选项列出所选表的索引。这将为每个索引显示一行,并显INFORMATION.SCHEMA.INDEXES没有提供的索引信息。 使用索引对一个对象进行open、exist和delete的方法 InterSystems IRIS的索引支持以下操作: 通过索引键打开一个实例 检查一个实例是否存在 删除一个实例 通过索引key打开一个实例 对于ID键、主键或唯一索引,indexnameOpen()方法(其中indexname是索引的名称)允许你打开其索引属性值与所提供的值一致的对象。 例如,假设一个类包括下面的索引定义。 Index SSNKey On SSN [ Unique ] 。 那么,如果被引用的对象已经被存储到磁盘,并且有一个唯一的ID值,你可以按以下方式调用该方法。 SET person = ##class(Sample.Person).SSNKeyOpen("111-22-3333",2,.sc) 第一个参数对应于索引中的属性。 第二个参数指定要打开对象的并发值(这里是2 - 共享锁)。 第三个参数可以接受%Status代码,以防该方法无法打开一个实例,如果找到一个匹配的实例,该方法会返回一个OREF。如果该方法没有找到一个与所提供的值相匹配的对象,那么一个错误信息将被写入状态参数sc中。 这个方法被实现为%Compiler.Type.Index.Open()方法;这个方法类似于%Persistent.Open()和%Persistent.OpenId()方法,除了它使用索引定义中的属性而不是OID或ID参数。 检查一个对象是否存在 indexnameExists()方法(其中indexname是索引的名称)检查是否存在一个具有该方法的参数所指定的索引属性值的实例。该方法有一个参数对应于索引中的每个属性;它的最后一个可选参数可以接收对象的ID,如果有一个与提供的值相匹配的话。该方法返回一个布尔值,表示成功(1)或失败(0)。这个方法被实现为%Compiler.Type.Index.Exists()方法。 例如,假设一个类包括下面的索引定义。 Index SSNKey On SSN [ Unique ]; 那么,如果被引用的对象已经被存储到磁盘,并且有一个唯一的ID值,你可以按以下方式调用该方法。 SET success = ##class(Sample.Person).SSNKeyExists("111-22-3333",.id) 成功完成后,success等于1,id包含与找到的对象匹配的ID。 该方法返回所有索引的值,除了。 位图索引,或位图范围索引。 当索引包括(ELEMENTS)或(KEYS)表达式时。 删除一个对象 indexnameDelete()方法(其中indexname是索引的名称)是为了与Unique、PrimaryKey和/或IdKey索引一起使用;它删除其键值与提供的键属性/列值相匹配的实例。有一个可选的参数,你可以用它来指定该操作的并发设置。该方法返回一个%Status代码。它被实现为%Compiler.Type.Index.Delete()方法。
文章
Johnny Wang · 十二月 12, 2021

Ensemble 和 Caché 应该迁移至 InterSystems IRIS 的五个原因

您可能已经听说,我们目前正在为所有正在使用 Caché 和 Ensemble 的客户提供限时免费迁移到我们的下一代数据平台 InterSystems IRIS 的机会。 虽然我们依旧如往常一样全力支持那些正在使用 Caché 数据库和 Ensemble 集成引擎的客户,但我们还是认为 InterSystems IRIS 是未来的关键。它结合了 Caché 和 Ensemble 的所有功能,并添加了大量令人兴奋的强大功能,从机器学习到原生 Python。 这也正是我们为现有客户提供迁移到 InterSystems IRIS 并使用这些新功能的原因。 我们也通过就地迁移支持轻松迁移,这意味着无需数据库转换、分步迁移指南、教程等。 听起来挺有趣对吗? 以下是我针对当前 Caché 和 Ensemble 应迁移到 InterSystems IRIS 的五个主要原因。 1. 根据您的需求量身定制的工具: InterSystems IRIS 本身有标准工具,例如根据 InterSystems IRIS 开发人员量身定制的 Visual Studio Code 编辑器。InterSystems IRIS 允许您在容器中运行应用程序,在 Kubernetes 中工作,并在您选择的云中轻松部署,这对于初学者有非常大的帮助。 2. 面向 SQL 开发人员的机器学习: 我们在 InterSystems IRIS 中嵌入了 IntegratedML 功能,使 SQL 开发人员无需成为数据科学或机器学习工具方面的专家,只需要几个类似 SQL 的命令即可轻松开发机器学习模型。 最重要的是,这些功能使您能够将机器学习模型无缝嵌入到 InterSystems IRIS 应用程序中,从而将它们转换为支持机器学习的智能应用程序。 3. 使用嵌入式 Python 提高生产力: 由于我们实现了 Python 的内置服务器端支持,我们的下一代技术使应用程序开发人员的工作效率更高。所有强大的功能都可以使用 Python 或 ObjectScript 调用,并且您的 Python 代码可以与 ObjectScript 代码无缝交互。 此外,我们为大量开发语言提供广泛的客户端支持,包括 Python、Java、C# 和 Node.js 等等。 4. 为 InterSystems IRIS 准备您的应用程序: 我们已经记录了 Caché 和 Ensemble 以及 InterSystems IRIS 之间的差异。在某些情况下,您可能需要对现有应用程序进行一些调整以满足与 InterSystems IRIS 相关的要求。 或者,您也可以采用与 Caché、Ensemble 和 InterSystems IRIS 兼容的通用代码库。这种方法的好处是您可以立即开始,确保您只有一个代码库需要维护,并在执行迁移时消除任何额外的调整。 这是我们许多大型企业客户经常采用的方法。 5. 易于迁移: 将您现有的 Caché 和 Ensemble 应用程序迁移到 InterSystems IRIS 是快速、简单且经过验证的。 查看我们的“迁移到 InterSystems IRIS”操作指南,您可以从我们的全球响应中心(需要登录 WRC)下载该指南,以确保无缝迁移。 现在是最好的时间 迁移只是旅程的开始,因此为了确保您从强大的 InterSystems IRIS 功能中获得最大收益,我们创建了各种文档、视频和在线学习资源,以帮助您解锁所有这些强大的功能。 现在是迁移到 InterSystems IRIS 的最佳时机。 只需联系您的 InterSystems 销售工程师或销售总监,即可享受此限时优惠。 了解更多信息:InterSystems.com/migrate。 关于作者:Jeff Fried InterSystems 产品管理总监 Jeff Fried 是一位长期从事数据管理的,尤其热衷于帮助人们创建强大的数据驱动应用程序。 在加入 InterSystems 之前,Jeff 曾担任 BA Insight、Empirix 和 Teloquent 的 CTO,并负责 FAST Search and Transfer 和 Microsoft 的产品管理。 他在数据管理、文本分析、企业搜索和互操作性方面拥有丰富的经验。Jeff是该行业的常客和作家;拥有15项专利;并撰写了 50 多篇技术论文并合着了三本技术书籍。 查看原文
公告
Claire Zheng · 八月 26, 2022

InterSystems开发者社区令人惊喜的里程碑!10K...11K...5M...

社区开发者们,大家好! 我们很自豪地宣布我们的InterSystems开发者社区已经达到了几个令人惊喜的里程碑: 📝 10,000 帖子 👥 11,000 注册用户 👁 5,000,000 浏览量 亲爱的开发者成员们,感谢你们的贡献,我们才能达成这样的目标:11K会员、10K发帖、5M浏览量 🍋🍋🍋🍋🍋 !我们为这一成就感到自豪,衷心感谢创造这一切的你们! 我们要感谢你们每一个人,因为你们是我们志同道合的一员!感谢你的每一次提问和对话!感谢你的每一次知识分享!感谢你的中肯建议,推动我们思考,从而变得更好、更有用!每一个你,都让这个社区保持活力和繁荣的原动力!我们很高兴与你在一起!❤️ 以下消息让这篇帖子的基调更欢乐! 开发者社区Discord channel 已经有500成员了! 🎉在这个方向上,我们还有很多工作要做,但我们已经准备好迎接挑战了。我们希望你也能加入我们,获得更多有用的信息和想法. 点击加入 再次感谢!让我们一起继续前行,共同成长!
文章
Claire Zheng · 一月 30, 2023

如何将 InterSystems 认证添加到您的社区资料(Profile)中?

亲爱的社区开发者们, 有一些开发者已经通过了InterSystems 官方认证,并希望在个人资料头像上有一个漂亮的绿色标识 以及您社区中的所有证书,以便其他人更好地了解你的能力…… 那么,要将认证添加到您的社区资料中,您需要执行 3 个简单的步骤: 1️⃣ 打开社区个人主页(点击右上角头像即可) 2️⃣ 找到InterSystems 认证( InterSystems Certification) 3️⃣ 点击加载我的认证(Load my certification) 就这么简单! 系统将通过您社区关联的电子邮件将请求发送到 Credly。如果您的证书链接到同一电子邮件,您的证书将会自动加载: 如果没有,请按照页面上描述的详细步骤操作: 完成后,每个人都可以看到你的认证资质哦 ;) 预祝每位社区成员成功添加认证并通过!
公告
Michael Lei · 九月 29, 2021

新一轮InterSystems开发者竞赛开始啦: 互操作性大赛

亲爱的社区开发者们,大家好! 欢迎积极参与新一轮InterSystems系联开发者竞赛! 🏆 InterSystems 互操作性大赛 🏆 时间: 2021年10月4日--24日 总奖金池高达 9,450美金! 奖项设置 1. 专家提名奖(Experts Nomination) - 获奖者由我们特别挑选的专家团选出: 🥇 第1名 - $4,000 🥈 第2名 - $2,000 🥉 第3名 - $1,000 🌟 新设奖项: 第4到第10名 - $100 2. 社区提名奖(Community Nomination) -获得总投票数最多的应用: 🥇 第1名 - $1,000 🥈 第2名 - $500 🥉 第3名 - $250 如果同时多位参赛者获得同样的票数,均被视为优胜者,将平分奖金 谁可以参加? 任何开发者社区的成员均可参加,InterSystems内部员工除外(InterSystems contractor员工可以参加)。还没有账号?现在来建一个! 👥 开发者可以组团 创建一个协作应用程序,组团限定人数为2-5人。 请注意,要在您的README文件中标注您的团队成员——社区用户profile 参赛时间安排 🛠 10月 4日-17日: 应用开发、提交阶段 ✅ 10月 18日 - 24日: 投票阶段 注意:在整个参赛期间(开发与投票期间,即10月4日-10月24日),开发者可持续编辑、完善其应用程序 主题 💡 InterSystems IRIS 与 IRIS 医疗版互操作性解决方案 💡 基于InterSystems IRIS 或 InterSystems IRIS 医疗版开发一个互操作性解决方案或者能够帮助开发或维护互操作性的方案; 参赛须知: 有效应用程序:100%全新的Open Exchange Apps或已有的应用程序(但有显著提升)。所有参赛者/团队提交的应用程序只有经过我们团队的审核之后才会被批准参赛。 该应用应该在 IRIS Community Edition or IRIS for Health Community Edition or IRIS Advanced Analytics Community Edition 上工作。 该应用需开源并在GitHub上发布。 该应用的README文件应为英文,包含安装步骤,并包含视频demo或/和应用程序如何运行的描述。 技术资源 1. IRIS初学者: Build a Server-Side Application with InterSystems IRIS Learning Path for beginners 2. 应用样例: Ensemble/互操作性构成 IRIS-互操作性-模版 ETL-互操作性-适配器 InterSystems IRIS医疗版互操作性Demo ENSDEMO HL7 和 SMS 互操作性 Demo 医疗行业 HL7 XML RabbitMQ 适配器 基于Kafka的产品扩展 demo 3. 在线课程和视频: 企业中的互操作性 互操作性快速入门指南 互操作性资源 - 2019 智慧的互操作性 医疗互操作性概览 4. 如何将您的APP提交给大赛 如何在InterSystems Open Exchange上发布应用程序 如何把参赛APP提交给大赛 参赛评比 投票规则即将发布,敬请期待! 各就位,预备,跑! 期待您的精彩提交!加入我们的编程马拉松,赢取大奖 ❗️ 点击此处,查看 官方竞赛条款解读❗️
公告
Weiwei Gu · 九月 14, 2023

来认识 2023 年 8 月的InterSystems Global Masters获奖者!

是时候宣布八月份的获奖者了!请欢迎我们出色的Global Masters英雄! 热烈的掌声归于这些开发者以及他们在 8 月份为我们全球开发者社区做出的巨大贡献: 🥇@Sylvain.Guilbaud🥈@Ashok.Kumar 🥉 @罗伯特·森珀 请在下面了解有关比赛和我们的优秀获奖者的更多信息。 关于@Sylvain.Guilbaud 🔁 5 篇关于 DC 的文章翻译 📝 DC 上有 35 条评论 🎓 2 个已接受的答案 🛠 11 个Open Exchange pull请求 🏅 对 DC 上的帖子投 100 票即可获得专家作者徽章 🏅 DC 上 15 篇翻译文章获得铜译者徽章 🏅 Open Exchange 应用程序 1 个拉取请求的拉取请求贡献者徽章 🏅 在 Open Exchange 上获得 25 条评论,获得银牌 Open Exchange 审阅者徽章 恭喜,Sylvain! 经验: 自2001 年InterSystems France在法国地区开设办事处以来,Sylvain 一直担任该公司的售前工程师。作为数据库管理系统的专业工程师, Sylvain 自 1993 年以来一直参与多个软件编辑器的应用程序开发,涉及医疗领域或医疗保健最终用户,特别是在 APHP 总部。为了完善他在数据存储方面的专业知识,他随后加入了编辑器 Oracle,然后于 2001 年 6 月加入 InterSystems。因此,由于 InterSystems 的增强,他在数据处理方面的技能得到了扩展,达到了令人难以置信的速度。 其他情况: 出生于大西洋附近的南特,但很小的时候就被收养感受到了地中海的气息。文化爱好者,对音乐(只要有机会就弹钢琴)以及其他艺术和文学充满热情,尤其是从哲学角度来看,他绝对热爱世界的自然和狂野的一面。多萝西·奥柏林格 ( Dorothee OBERLINGER) 、吉米·亨德里克斯 (Jimi HENDRIX )、扮演瓦伦丁·西尔维斯特罗夫(Valentin SILVESTROV)的海伦·格里莫(Hélène GRIMAUD)、 《黑色安息日》 、奥菲莉·盖拉德、 《王肫与蜥蜴巫师》中的希拉怪物、刘小龙、泰姬陵、《鱼骨中的异果》 、埃曼纽尔·哈伊姆、乌鲁鲁岩来自Earthless的Alain PLANÈS 、来自Mulatu Astatke的Yèkèrmo Sèw 、 Dave van Ronk 、 Tom WAITS 、 Michel PETRUCCIANI或有史以来听过的最美丽的音乐之一,等等。 🔗立即与 Sylvain 联络,拓展您的技术人脉网络: 领英 开发者社区 关于@Ashok.Kumar 📝 DC 上有 4 个帖子和 21 条评论 🎓 8 个已接受的答案 💻 Open Exchange 上的 3 个应用程序 🏅在 Open Exchange 上发表 10 条评论即可获得铜牌 Open Exchange 审阅者徽章 🏅 DC 上接受 5 个答案即可获得答案大师徽章 🏅 DC 上 1 个问题即可获得 InterSystems 研究员徽章 🏅 DC 上 1 条评论可获得 DC 评论者徽章 🏅 1 篇 DC 文章的 DC 作者徽章 🏅 InterSystems Open Exchange 开发人员在 Open Exchange 上开发 1 个应用程序 恭喜,Ashok! 您从事这个职业/行业多久了? “我在系统间产品领域担任软件工程师已超过 9 年。我的职业生涯始于缓存开发人员。” 您的主要专业领域是什么? “HL7/FHIR 集成在分析、设计、实施和维护应用程序方面拥有丰富的经验。” 你从哪来? “我在印度出生和长大” 🔗立即与 Ashok 联系以拓展您的技术人脉网络: 开发者社区 关于@罗伯特·森珀 📝 DC 上有 5 个帖子和 41 条评论 🎓 11 个已接受的答案 💻 5 个 OEX 应用程序 🛠 11 个开放交换请求 恭喜,罗伯特! Robert C. Cemper是达姆施塔特 InterSystems 的前高级售前工程师。他在这个行业已经有 45 年的经验,并且直接在 InterSystems 工作了超过 12 年。个人使命宣言:“对于工程师来说‘不可能’并不存在” 在这篇文章中阅读完整且鼓舞人心的罗伯特的简历。 🔗 立即在开发者社区与 Robert 联系,以拓展您的技术人脉网络。 感谢所有获奖者对 InterSystems 开发者社区不断做出的巨大贡献! 让我们在下面的评论中祝贺我们的英雄! 关于全球大师倡导者中心的本月全球大师Global masters竞赛:我们每个月都会提名通过高度参与全球大师和开发者社区而付出额外努力的倡导者。获胜者将获得 1000 积分和特殊徽章。我们还为获奖者提供发布他们的简历的机会——现在在开发者社区的文章中!
文章
Michael Lei · 九月 27, 2024

使用 GitLab 持续交付 InterSystems 解决方案 – 第 4 部分:CD 配置

在这一系列文章中,我想向大家介绍并探讨使用 InterSystems 技术和 GitLab 进行软件开发可以采用的几种方式。 我将介绍以下主题: Git 101 Git 流程(开发流程) GitLab 安装 GitLab 工作流 持续交付 GitLab 安装和配置 GitLab CI/CD 在第一篇文章中,我们介绍了 Git 基础知识、深度理解 Git 概念对现代软件开发至关重要的原因,以及如何使用 Git 开发软件。 在第二篇文章中,我们介绍了 GitLab 工作流 – 一个完整的软件生命周期流程,并介绍了持续交付。 在第三篇文章中,我们介绍了 GitLab 安装和配置以及将环境连接到 GitLab 在这篇文章中,我们将介绍编写 CD 配置。 计划 环境 首先,我们需要多个环境以及与之对应的分支: 环境 分支 交付 有权提交的角色 有权合并的角色 测试 master 自动 开发者、所有者 开发者、所有者 预生产 preprod 自动 无 所有者 生产 prod 半自动(按下按钮进行交付) 无 所有者 开发周期 作为示例,我们将使用 GitLab 流程开发一个新功能,并使用 GitLab CD 进行交付。 在功能分支中开发功能。 对功能分支进行审查并将其合并到 master 分支中。 一段时间(合并了多个功能)后,将 master 分支合并到 preprod 分支中 一段时间(用户测试等)后,将 preprod 分支合并到 prod 分支中 具体如下图所示(我用草图标出了我们需要为 CD 开发的部分): 开发和测试 开发者将新功能的代码提交到单独的功能分支中 功能稳定后,开发者将功能分支合并到 master 分支中 来自 master 分支的代码被交付到测试环境,在其中进行加载和测试 交付到预生产环境 开发者创建从 master 分支到 preprod 分支的合并请求 仓库所有者在一段时间后批准合并请求 来自 preprod 分支的代码被交付到预生产环境 交付到生产环境 开发者创建从 preprod 分支到 prod 分支的合并请求 仓库所有者在一段时间后批准合并请求 仓库所有者按下“部署”按钮 来自 prod 分支的代码被交付到生产环境 也可以用示意图形式表示此流程: 应用程序 应用程序由两部分组成: 在 InterSystems 平台上开发的 REST API 客户端 JavaScript web 应用程序 阶段 通过上面的计划,我们可以确定需要在持续交付配置中定义的阶段: 加载 – 将服务器端代码导入 InterSystems IRIS 测试 – 测试客户端和服务器代码 封装 – 构建客户端代码 部署 – 使用 Web 服务器“发布”客户端代码 以下是它在 gitlab-ci.yml 配置文件中的样式: stages: - load - test - package - deploy 脚本 加载 下面我们来定义脚本。 脚本文档。 我们先来定义用于加载服务器端代码的脚本 load server: load server: environment: name: test url: http://test.hostname.com only: - master tags: - test stage: load script: csession IRIS "##class(isc.git.GitLab).load()" 脚本会执行哪些操作? load server 是脚本名称 接下来,我们来描述此脚本运行的环境 only: master – 告知 GitLab 此脚本仅应在向 master 分支进行提交时运行 tags: test 指定此脚本仅应在具有 test 标签的运行程序上运行 stage 指定脚本的阶段 script 定义要执行的代码 在本例中,我们从 isc.git.GitLab 类调用类方法 load 重要说明 对于 InterSystems IRIS,请将 csession 替换为 iris session。 对于 Windows,请使用:irisdb -s ../mgr -U TEST "##class(isc.git.GitLab).load() 现在,我们来编写相应的 isc.git.GitLab 类。 此类中的所有入口点如下所示: ClassMethod method() { try { // code halt } catch ex { write !,$System.Status.GetErrorText(ex.AsStatus()),! do $system.Process.Terminate(, 1) } } 请注意,可以通过两种方式结束此方法: 停止当前进程 – 在 GitLab 中注册为成功完成 调用 $system.Process.Terminate – 异常终止进程,GitLab 将此情况注册为错误 因此,加载代码如下: /// Do a full load /// do ##class(isc.git.GitLab).load() ClassMethod load() { try { set dir = ..getDir() do ..log("Importing dir " _ dir) do $system.OBJ.ImportDir(dir, ..getExtWildcard(), "c", .errors, 1) throw:$get(errors,0)'=0 ##class(%Exception.General).%New("Load error") halt } catch ex { write !,$System.Status.GetErrorText(ex.AsStatus()),! do $system.Process.Terminate(, 1) } } 调用了两个实用方法: getExtWildcard – 获取相关文件扩展名列表 getDir – 获取仓库目录 如何获取目录? 执行脚本时,GitLab 会先指定很多环境变量。 其中一个环境变量是 CI_PROJECT_DIR – 克隆仓库以及运行作业位置的完整路径。 我们可以通过 getDir 方法轻松获取: ClassMethod getDir() [ CodeMode = expression ] { ##class(%File).NormalizeDirectory($system.Util.GetEnviron("CI_PROJECT_DIR")) } 测试 以下是测试脚本: load test: environment: name: test url: http://test.hostname.com only: - master tags: - test stage: test script: csession IRIS "##class(isc.git.GitLab).test()" artifacts: paths: - tests.html 有哪些更改? 当然是名称和脚本代码,但还添加了工件。 工件是作业成功完成后附加到作业的文件和目录列表。 本例中,测试完成后,我们可以生成重定向到测试结果的 HTML 页面,并使其可以通过 GitLab 访问。 请注意,加载阶段有很多复制粘贴的内容 – 环境是相同的,脚本部分(例如环境)可以单独标记并附加到脚本。 我们来定义测试环境: .env_test: &env_test environment: name: test url: http://test.hostname.com only: - master tags: - test 现在,我们的脚本如下: load test: <<: *env_test script: csession IRIS "##class(isc.git.GitLab).test()" artifacts: paths: - tests.html 接下来,我们使用 UnitTest 框架执行测试。 /// do ##class(isc.git.GitLab).test() ClassMethod test() { try { set tests = ##class(isc.git.Settings).getSetting("tests") if (tests'="") { set dir = ..getDir() set ^UnitTestRoot = dir $$$TOE(sc, ##class(%UnitTest.Manager).RunTest(tests, "/nodelete")) $$$TOE(sc, ..writeTestHTML()) throw:'..isLastTestOk() ##class(%Exception.General).%New("Tests error") } halt } catch ex { do ..logException(ex) do $system.Process.Terminate(, 1) } } 本例中,测试设置是相对于存储单元测试的仓库根目录的路径。 如果此处为空,则跳过测试。 writeTestHTML 方法用于输出重定向到测试结果的 html: ClassMethod writeTestHTML() { set text = ##class(%Dictionary.XDataDefinition).IDKEYOpen($classname(), "html").Data.Read() set text = $replace(text, "!!!", ..getURL()) set file = ##class(%Stream.FileCharacter).%New() set name = ..getDir() _ "tests.html" do file.LinkToFile(name) do file.Write(text) quit file.%Save() } ClassMethod getURL() { set url = ##class(isc.git.Settings).getSetting("url") set url = url _ $system.CSP.GetDefaultApp("%SYS") set url = url_"/%25UnitTest.Portal.Indices.cls?Index="_ $g(^UnitTest.Result, 1) _ "&$NAMESPACE=" _ $zconvert($namespace,"O","URL") quit url } ClassMethod isLastTestOk() As %Boolean { set in = ##class(%UnitTest.Result.TestInstance).%OpenId(^UnitTest.Result) for i=1:1:in.TestSuites.Count() { #dim suite As %UnitTest.Result.TestSuite set suite = in.TestSuites.GetAt(i) return:suite.Status=0 $$$NO } quit $$$YES } XData html { <html lang="en-US"> <head> <meta charset="UTF-8"/> <meta http-equiv="refresh" content="0; url=!!!"/> <script type="text/javascript"> window.location.href = "!!!" </script> </head> <body> If you are not redirected automatically, follow this <a href='!!!'>link to tests</a>. </body> </html> } 封装 我们的客户端是一个简单的 HTML 页面: <html> <head> <script type="text/javascript"> function initializePage() { var xhr = new XMLHttpRequest(); var url = "${CI_ENVIRONMENT_URL}:57772/MyApp/version"; xhr.open("GET", url, true); xhr.send(); xhr.onloadend = function (data) { document.getElementById("version").innerHTML = "Version: " + this.response; }; var xhr = new XMLHttpRequest(); var url = "${CI_ENVIRONMENT_URL}:57772/MyApp/author"; xhr.open("GET", url, true); xhr.send(); xhr.onloadend = function (data) { document.getElementById("author").innerHTML = "Author: " + this.response; }; } </script> </head> <body onload="initializePage()"> <div id = "version"></div> <div id = "author"></div> </body> </html> 要进行构建,需要将 ${CI_ENVIRONMENT_URL} 替换为其值。 当然,实际应用程序可能需要 npm,但此处仅为了举例说明。 脚本如下: package client: <<: *env_test stage: package script: envsubst < client/index.html > index.html artifacts: paths: - index.html 部署 最后,我们将 index.html 部署到 Web 服务器根目录,以部署客户端。 deploy client: <<: *env_test stage: deploy script: cp -f index.html /var/www/html/index.html 就是这些! 多个环境 如果您需要在多个环境中执行相同(相似)的脚本,应该如何操作? 脚本部分也可以是标签,因此下面给出了在测试和预生产环境中加载代码的示例配置: stages: - load - test .env_test: &env_test environment: name: test url: http://test.hostname.com only: - master tags: - test .env_preprod: &env_preprod environment: name: preprod url: http://preprod.hostname.com only: - preprod tags: - preprod .script_load: &script_load stage: load script: csession IRIS "##class(isc.git.GitLab).loadDiff()" load test: <<: *env_test <<: *script_load load preprod: <<: *env_preprod <<: *script_load 通过这种方式,我们便无需复制粘贴代码。 有关完整的 CD 配置,请参阅此处。 该配置遵循在测试、预生产和生产环境之间移动代码的原始计划。 结论 可以将持续交付配置为自动执行任何所需的开发工作流。 链接 挂接仓库(和简单配置) 测试仓库 脚本文档 可用环境变量 后续内容 在下一篇文章中,我们将创建利用 InterSystems IRIS Docker 容器的 CD 配置。
文章
Michael Lei · 九月 27, 2024

使用 GitLab 持续交付 InterSystems 解决方案 – 第 9 部分:容器架构

在这一系列文章中,我想向大家介绍并探讨使用 InterSystems 技术和 GitLab 进行软件开发可以采用的几种方式。 我将介绍以下主题:Git 101Git 流程(开发流程)GitLab 安装GitLab 工作流持续交付GitLab 安装和配置GitLab CI/CD为何使用容器?容器基础架构使用容器的 CD使用 ICM 的 CD容器架构在本文中,我们将讨论如何构建并部署您自己的容器。Durable %SYS由于容器是临时的,它们不应存储任何应用程序数据。 持久化 %SYS 功能让我们能够实现这一点 – 将设置、配置、%SYS 数据等存储到主机卷上,即:iris.cpf 文件。/csp 目录,包含 Web 网关配置和日志文件。/httpd/httpd.conf 文件,实例的私有 Web 服务器的配置文件。/mgr 目录,包含以下内容:IRISSYS 系统数据库,包括 IRIS.DAT 和 iris.lck 文件、流目录,以及包含 IRISTEMP、IRISAUDIT、IRIS 和 USER 系统数据库的 iristemp、irisaudit、iris 和 user 目录。写入镜像日志文件,IRIS.WIJ。/journal 目录,包含日志文件。/temp 目录,用于存储临时文件。日志文件,包括 messages.log、journal.log 和 SystemMonitor.log。容器架构另一方面,我们需要将应用程序代码存储在我们的容器内,以便在需要时进行升级。这一切使我们实现了如下所示的架构:为了在构建过程中实现这一点,我们至少需要创建一个额外的数据库(用于存储应用程序代码)并将其映射到我们的应用程序命名空间。 在我的示例中,我将使用 USER 命名空间来保存应用程序数据,因为它已经存在且是持久化的。安装程序基于上述内容,我们的安装程序需要执行以下任务:创建 APP 命名空间/数据库将代码加载到 APP 命名空间将我们的应用程序类映射到 USER 命名空间完成所有其他安装(在本例中,我创建了 CSP Web 应用和 REST 应用) Class MyApp.Hooks.Local { Parameter Namespace = "APP"; /// See generated code in zsetup+1^MyApp.Hooks.Local.1 XData Install [ XMLNamespace = INSTALLER ] { <Manifest> <Log Text="Creating namespace ${Namespace}" Level="0"/> <Namespace Name="${Namespace}" Create="yes" Code="${Namespace}" Ensemble="" Data="IRISTEMP"> <Configuration> <Database Name="${Namespace}" Dir="/usr/irissys/mgr/${Namespace}" Create="yes" MountRequired="true" Resource="%DB_${Namespace}" PublicPermissions="RW" MountAtStartup="true"/> </Configuration> <Import File="${Dir}Form" Recurse="1" Flags="cdk" IgnoreErrors="1" /> </Namespace> <Log Text="End Creating namespace ${Namespace}" Level="0"/> <Log Text="Mapping to USER" Level="0"/> <Namespace Name="USER" Create="no" Code="USER" Data="USER" Ensemble="0"> <Configuration> <Log Text="Mapping Form package to USER namespace" Level="0"/> <ClassMapping From="${Namespace}" Package="Form"/> <RoutineMapping From="${Namespace}" Routines="Form" /> </Configuration> <CSPApplication Url="/" Directory="${Dir}client" AuthenticationMethods="64" IsNamespaceDefault="false" Grant="%ALL" Recurse="1" /> </Namespace> </Manifest> } /// This is a method generator whose code is generated by XGL. /// Main setup method /// set vars("Namespace")="TEMP3" /// do ##class(MyApp.Hooks.Global).setup(.vars) ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 0, pInstaller As %Installer.Installer) As %Status [ CodeMode = objectgenerator, Internal ] { Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "Install") } /// Entry point ClassMethod onAfter() As %Status { try { write "START INSTALLER",! set vars("Namespace") = ..#Namespace set vars("Dir") = ..getDir() set sc = ..setup(.vars) write !,$System.Status.GetErrorText(sc),! set sc = ..createWebApp() } catch ex { set sc = ex.AsStatus() write !,$System.Status.GetErrorText(sc),! } quit sc } /// Modify web app REST ClassMethod createWebApp(appName As %String = "/forms") As %Status { set:$e(appName)'="/" appName = "/" _ appName #dim sc As %Status = $$$OK new $namespace set $namespace = "%SYS" if '##class(Security.Applications).Exists(appName) { set props("AutheEnabled") = $$$AutheUnauthenticated set props("NameSpace") = "USER" set props("IsNameSpaceDefault") = $$$NO set props("DispatchClass") = "Form.REST.Main" set props("MatchRoles")=":" _ $$$AllRoleName set sc = ##class(Security.Applications).Create(appName, .props) } quit sc } ClassMethod getDir() [ CodeMode = expression ] { ##class(%File).NormalizeDirectory($system.Util.GetEnviron("CI_PROJECT_DIR")) } } 为了创建非持久化数据库,我会使用 /usr/irissys/mgr 的一个子目录,它不是持久的。 请注意,调用 ##class(%File).ManagerDirectory() 会返回持久化目录的路径,而不是内部容器目录的路径。 持续交付配置 查看第 7 部分以了解完整信息,但我们需要做的就是在现有配置中添加这两行(加粗)代码。 run image: stage: run environment: name: $CI_COMMIT_REF_NAME url: http://$CI_COMMIT_REF_SLUG.docker.eduard.win/index.html tags: - test script: - docker run -d --expose 52773 --volume /InterSystems/durable/$CI_COMMIT_REF_SLUG:/data --env ISC_DATA_DIRECTORY=/data/sys --env VIRTUAL_HOST=$CI_COMMIT_REF_SLUG.docker.eduard.win --name iris-$CI_COMMIT_REF_NAME docker.eduard.win/test/docker:$CI_COMMIT_REF_NAME --log $ISC_PACKAGE_INSTALLDIR/mgr/messages.log volume 参数将主机目录挂载到容器中,ISC_DATA_DIRECTORY 变量告诉 InterSystems IRIS 使用哪个目录。 引用文档内容: 当您使用这些选项运行 InterSystems IRIS 容器时,会发生以下情况: 指定的外部卷被挂载。如果由 ISC_DATA_DIRECTORY 环境变量指定的持久化 %SYS 目录(上述示例中的 iconfig/)已经存在且包含持久化 %SYS 数据,则实例的所有内部指针都会重置到该目录,实例使用其中包含的数据。如果 ISC_DATA_DIRECTORY 环境变量指定的持久化 %SYS 目录已经存在但不包含持久化 %SYS 数据,则不会复制数据,实例使用容器内安装树中的数据运行,这意味着实例特定的数据不是持久的。 因此,您可能需要在脚本中包含对此条件的检查,然后再运行容器。如果 ISC_DATA_DIRECTORY 指定的持久化 %SYS 目录不存在: 指定的持久化 %SYS 目录已创建。将持久化 %SYS 目录内容中列出的目录和文件从其安装位置复制到持久化 %SYS 目录(原始文件仍留在原位)。 实例的所有内部指针都会重置到持久化 %SYS 目录,实例使用其中包含的数据。 更新 当应用程序演进并发布新版本(容器)时,有时您可能需要运行一些代码。 这可能是预编译/后编译挂钩、架构迁移、单元测试,但归根结底,您需要运行任意代码。 这就是为什么您需要一个管理应用程序的框架。 在之前的文章中,我概述了这种框架的基本结构,但它当然可以大幅度扩展以满足特定应用程序的需求。 结论 创建容器化应用程序需要一些思考,但 InterSystems IRIS 提供了几个功能,可以让此流程更加轻松。 链接 索引文章代码测试项目完整 CD 配置带持久化 %SYS 的完整 CD 配置
文章
Michael Lei · 九月 27, 2024

使用 GitLab 持续交付 InterSystems 解决方案 – 第 10 部分:代码之外

经过将近四年的停顿,[我的 CI/CD 系列](https://community.intersystems.com/post/continuous-delivery-your-intersystems-solution-using-gitlab-index)又回来了! 这些年来,我与多个 InterSystems 客户合作,为不同的用例开发 CI/CD 管道。 希望本文中提供的信息对您有所帮助。 此[系列文章](https://community.intersystems.com/post/continuous-delivery-your-intersystems-solution-using-gitlab-index)探讨了使用 InterSystems 技术和 GitLab 进行软件开发可以采用的几种方式。 我们有一系列激动人心的话题要讨论:但今天,我们来谈谈代码之外的事情 – 配置和数据。 # 问题 之前我们探讨过代码提升,这在某种程度上是无状态的 – 我们总是从一个(大概)空实例到完整的代码库。 但有时,我们需要提供数据或状态。 不同的数据类型包括: - 配置:用户、Web 应用、LUT、自定义架构、任务、业务伙伴等 - 设置:环境特定的键值对 - 数据:为了让应用正常工作,通常必须提供引用表等 我们来探讨所有这些数据类型,以及如何先将它们提交到源代码控制工具中,然后进行部署。 # 配置 系统配置分布在许多不同的类中,但 InterSystems IRIS 可以将大多数类导出为 XML。 首先是一个[安全软件包](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&PACKAGE=Security),其中包含以下信息: - Web 应用程序 - DocDB - 域 - 审核事件 - KMIP 服务器 - LDAP 配置 - 资源 - 角色 - SQL 特权 - SSL 配置 - 服务 - 用户 所有这些类都提供 Exists、Export 和 Import 方法,允许您在不同的环境之间移动它们。 一些注意事项: - 用户和 SSL 配置可能包含敏感信息,例如密码。 出于安全原因,通常建议不要将它们存储在源代码控制工具中。 使用 Export/Import 方法来方便地进行一次性传输。 - 默认情况下,Export/Import 方法会将所有内容输出到一个文件中,这可能不适合源代码控制。 这里提供了一个[实用类](https://gist.github.com/eduard93/3a9abdb2eb150a456191bf387c1fc0c3),可用于导出和导入查找表、自定义架构、业务伙伴、任务、凭据和 SSL 配置。 它会每个文件导出一个条目,因此您会得到一个包含 LUT 的目录,另一个包含自定义架构的目录,等等。 对于 SSL 配置,它还会导出以下文件:证书和密钥。 另外值得注意的是,除了导出/导入,您还可以使用 [%Installer](https://community.intersystems.com/post/deploying-applications-intersystems-cache-installer) 或 [Merge CPF](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=ACMF) 来创建这些条目中的大多数。 这两个工具还支持创建命名空间和数据库。 Merge CPF 可以调整系统设置,例如全局缓冲区大小。 ## 任务 [%SYS.Task](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SYS.Task) 类存储任务并提供 `ExportTasks` 和 `ImportTasks` 方法。 您还可以查看上面的实用类,以逐个导入和导出任务。 请注意,当您导入任务时,如果 `StartDate` 或其他与计划相关的属性在过去的时间里,则可能会遇到导入错误 (`ERROR #7432: Start Date and Time must be after the current date and time`)。 解决方案是将 `LastSchedule` 设置为 `0`,InterSystems IRIS 会将新导入的任务重新安排为在不久的将来运行。 ## 互操作性 互操作性生产包含: - 业务伙伴 - 系统默认设置 - 凭据 - 查找表 前两个可以在 [Ens.Config](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=ENSLIB&PACKAGE=Ens.Config) 软件包中通过 `%Export` 和 `%Import` 方法获取。 使用上面的[实用类](https://gist.github.com/eduard93/3a9abdb2eb150a456191bf387c1fc0c3)导出凭据和查找表。 在最近的版本中,查找表可以通过 [$system.OBJ](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.OBJ) 类导出/导入。 # 设置 [系统默认设置](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=ECONFIG_other_default_settings#ECONFIG_other_default_settings_purpose) – 是环境特定设置的默认互操作性机制: > 系统默认设置的目的是简化将生产定义从一个环境复制到另一个环境的过程。 在任何生产中,一些设置的值作为生产设计的一部分确定;通常而言,这些设置在所有环境中都应相同。 不过,其他设置必须根据环境进行调整;这些设置包括文件路径、端口号等。 > > 系统默认设置应仅指定特定于安装 InterSystems IRIS 的环境的值。 相比之下,生产定义应指定在所有环境中都应当相同的设置值。 我强烈建议在生产环境中使用它们。 使用 [%Export](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=ENSLIB&CLASSNAME=Ens.Config.DefaultSettings#%25Export) 和 [%Import](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=ENSLIB&CLASSNAME=Ens.Config.DefaultSettings#%25Import) 传输系统默认设置。 ## 应用程序设置 您的应用程序可能也使用设置。 在这种情况下,我建议使用系统默认设置。 虽然这是一种互操作性机制,但可以通过以下方式访问设置:`%GetSetting(pProductionName, pItemName, pHostClassName, pTargetType, pSettingName, Output pValue)`([文档](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=ENSLIB&CLASSNAME=Ens.Config.DefaultSettings#%25GetSetting))。 您可以编写一个包装器来设置您不关心的默认值,例如: ```objectscript ClassMethod GetSetting(name, Output value) As %Boolean [Codemode=expression] { ##class(Ens.Config.DefaultSettings).%GetSetting("myAppName", "default", "default", , name, .value) } ``` 如果您想要更多类别,还可以公开 `pItemName` 和/或 `pHostClassName` 参数。 可以通过导入、使用系统管理门户、创建 `Ens.Config.DefaultSettings` 类的对象或设置 `^Ens.Config.DefaultSettingsD` 全局进行初始设置。 我的主要建议是将设置放在一个地方(可以是系统默认设置或自定义解决方案),应用程序必须仅通过提供的 API 获取设置。 这样,应用程序本身不知道环境,剩下的工作是为集中式设置存储提供环境特定的值。 为此,在您的仓库中创建一个包含设置文件的设置文件夹,文件名称与环境分支名称相同。 随后在 CI/CD 阶段,使用 `$CI_COMMIT_BRANCH` [环境变量](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html)加载正确的文件。 ``` DEV.xml TEST.xml PROD.xml ``` 如果每个环境有多个设置文件,请使用以环境分支命名的文件夹。 要从 InterSystems IRIS 内获取环境变量值,请[使用](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.Util#GetEnviron) `$System.Util.GetEnviron("name")`。 # 数据 如果您想让一些数据(引用表、目录等)可用,可以通过几种方式实现此目标: - Global导出。 使用二进制 [GOF 导出](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GGBL_managing#GGBL_managing_export)或新的 XML 导出。 使用 GOF 导出时,请记住源系统和目标系统上的区域设置必须匹配(或者至少目标系统上的全局排序规则必须可用)。 [XML 导出](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.OBJ)会占用更多空间。 您可以通过将Global导出为 `xml.gz` 文件进行改进,`$system.OBJ` 方法会根据需要自动(取消)归档 `xml.gz` 文件。 这种方式的主要缺点是数据无法被人类阅读,即使是 XML 也是如此 – 大部分采用 base64 编码。 - CSV。 [导出 CSV](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SQL.StatementResult#%25DisplayFormatted) 并通过 [LOAD DATA](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=RSQL_loaddata) 导入。 我更喜欢使用 CSV,因为它是最节省存储空间的人类可读格式,并且任何内容都可以导入。 - JSON。 使类[支持 JSON](https://docs.intersystems.com/iris20221/csp/docbook/DocBook.UI.Page.cls?KEY=GJSON_adaptor)。 - XML。 使类[支持 XML](https://docs.intersystems.com/iris20221/csp/docbook/DocBook.UI.Page.cls?KEY=GXMLPROJ_intro) 以将对象投射到 XML 中。 如果您的数据具有复杂结构,请使用该方式。 选择哪种格式取决于您的用例。 这里我按存储效率顺序列出了各个格式,但如果您没有大量数据,那就不必担心。 # 结论 状态为您的 CI/CD 部署管道增加了额外的复杂性,但 InterSystems IRIS 为此提供了大量管理工具。 # 链接 - [实用类](https://gist.github.com/eduard93/3a9abdb2eb150a456191bf387c1fc0c3) - [%Installer](https://community.intersystems.com/post/deploying-applications-intersystems-cache-installer) - [Merge CPF](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=ACMF) - [$System.OBJ](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SYSTEM.OBJ) - [系统默认设置](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=ENSLIB&CLASSNAME=Ens.Config.DefaultSettings#%25GetSetting)
文章
Michael Lei · 九月 27, 2024

使用 GitLab 持续交付 InterSystems 解决方案 – 第 11 部分:互操作性

欢迎来到[我的 CI/CD 系列](https://community.intersystems.com/post/continuous-delivery-your-intersystems-solution-using-gitlab-index)的下一个章节,我们将探讨使用 InterSystems 技术和 GitLab 进行软件开发可以采用的几种方式。 今天,我们来谈谈互操作性。 # 问题 当您有一个有效的互操作性生产时,您有两个独立的流程:一个是处理消息的可以正常运行的生产流程,另一个是更新代码、生产配置和[系统默认设置](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=ECONFIG_other_default_settings)的 CI/CD 流程。 显然,CI/CD 流程会影响互操作性。 但问题是: - 更新期间究竟发生了什么? - 我们需要做些什么以在更新期间尽可能缩短或消除生产停机时间? # 术语 - 业务主机 (BH) – 互操作性生产的一个可配置元素:业务服务 (BS)、业务流程(BP、BPL)或业务操作 (BO)。 - 业务主机作业 (Job) – 运行业务主机代码并由互操作性生产管理的 InterSystems IRIS 作业。 - 生产 – 业务主机的互联集合。 - 系统默认设置 (SDS) – 特定于安装 InterSystems IRIS 的环境的值。 - 有效消息 – 当前正在由某个业务主机作业处理的请求。 一个业务主机作业最多只能有一条有效消息。 没有有效消息的业务主机作业处于空闲状态。 # 发生了什么? 我们从生产生命周期开始。 ## 生产启动 首先,可以启动生产。 每个命名空间只能同时运行一个生产,通常而言(除非您真正知道自己在做什么以及为什么这样做),每个命名空间内只能运行一个生产。 不推荐在一个命名空间中于两个或多个不同的生产之间来回切换。 启动生产会启动生产中定义的所有已启用的业务主机。 某些业务主机启动失败不会影响生产启动。 提示: - 可以从系统管理门户或者调用以下代码来启动生产:`##class(Ens.Director).StartProduction("ProductionName")` - 通过实现 `OnStart` 方法可以在生产启动时(在启动任何业务主机作业之前)执行任意代码 - 生产启动是一个可审核事件。 您始终可以在审核日志中查看是谁在何时启动的生产。 ## 生产更新 在生产启动后,[Ens.Director](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=ENSLIB&CLASSNAME=Ens.Director) 会持续监视正在运行的生产。 生产存在两种状态:_目标状态_,在生产类和系统默认设置中定义;以及_运行状态_ – 当前运行的作业及其创建时应用的设置。 如果所需状态与当前状态相同,则一切正常;但如果有差异,就可以(也应该)更新生产。 通常,您会在系统管理门户的“生产配置”页面上看到一个红色的 `Update` 按钮。 更新生产意味着尝试使当前生产状态与目标生产状态匹配。 当您运行 `##class(Ens.Director).UpdateProduction(timeout=10, force=0)` 以更新生产时,它会为每个业务主机执行以下操作: 1. 将有效设置与生产/SDS/类设置进行比较 2. 当且仅当 (1) 显示不匹配时,业务主机会被标记为过时并需要更新。 为每个业务主机运行此操作后,`UpdateProduction` 会构建一组更改: - 要停止的业务主机 - 要启动的业务主机 - 要更新的生产设置 然后,应用这些更改。 通过这种方式,“更新”设置而不更改任何内容不会导致生产停机。 提示: - 可以从系统管理门户或者调用以下代码来更新生产:`##class(Ens.Director).UpdateProduction(timeout=10, force=0)` - 默认的系统管理门户更新超时为 10 秒。 如果您知道处理消息需要更长时间,请调用 `Ens.Director:UpdateProduction` 并设置更长的超时。 - 更新超时是一个生产设置,您可以将其更改为更大的值。 此设置适用于系统管理门户。 ## 代码更新 `UpdateProduction` 不会使用过时的代码更新 BH。 这是一种以安全为导向的行为,但如果您想要在底层代码更改时自动更新所有正在运行的 BH,请按以下步骤操作: 首先,按以下方式加载和编译: ```objectscript do $system.OBJ.LoadDir(dir, "", .err, 1, .load) do $system.OBJ.CompileList(load, "curk", .errCompile, .listCompiled) ``` 现在,`listCompiled` 将包含由于 `u` 标志而实际编译的所有条目(使用 [git 差异](https://github.com/intersystems-ru/GitLab/blob/master/isc/git/Diff.cls)来尽可能减小加载集)。 使用此 `listCompiled` 获取所有已编译类的 $lb: ```objectscript set classList = "" set class = $o(listCompiled("")) while class'="" { set classList = classList _ $lb($p(class, ".", 1, *-1)) set class=$o(listCompiled(class)) } ``` 然后,计算需要重启的 BH 的列表: ```sql SELECT %DLIST(Name) bhList FROM Ens_Config.Item WHERE 1=1 AND Enabled = 1 AND Production = :production AND ClassName %INLIST :classList ``` 最后,在获取 `bhList` 后,停止并启动受影响的主机: ```objectscript for stop = 1, 0 { for i=1:1:$ll(bhList) { set host = $lg(bhList, i) set sc = ##class(Ens.Director).TempStopConfigItem(host, stop, 0) } set sc = ##class(Ens.Director).UpdateProduction() } ``` ## 生产停止 生产可以停止,这意味着向所有业务主机作业发送关闭请求(如果存在,则在它们处理完有效消息后安全地关闭)。 提示: - 可以从系统管理门户或者调用以下代码来停止生产:`##class(Ens.Director).StopProduction(timeout=10, force=0)` - 默认的系统管理门户停止超时为 120 秒。 如果您知道处理消息需要更长时间,请调用 `Ens.Director:StopProduction` 并设置更长的超时。 - 关闭超时是一个生产设置。 您可以将其更改为更大的值。 此设置适用于系统管理门户。 - 通过实现 `OnStop` 方法可以在生产停止时执行任意代码 - 生产停止是一个可审核事件,您始终可以在审核日志中查看是谁在何时停止的生产。 重要的一点是,生产是业务主机的总和: - 启动生产意味着启动所有已启用的业务主机。 - 停止生产意味着停止所有正在运行的业务主机。 - 更新生产意味着计算出过时的业务主机的子集,因此首先停止它们,然后立即重新启动。 此外,新增的业务主机只会启动,从生产中删除的业务主机只会停止。 这会将我们带到业务主机的生命周期。 ## 业务主机启动 业务主机由相同的业务主机作业组成(根据池大小设置的值)。 启动业务主机意味着启动所有业务主机作业。 它们会并行启动。 单个业务主机作业的启动方式如下: 1. 互操作性作业是一个将成为业务主机作业的新进程。 2. 新进程注册为互操作性作业。 3. 业务主机代码和适配器代码加载到进程内存中。 4. 与业务主机和适配器相关的设置加载到内存中。 优先级顺序如下: a. 生产设置(覆盖系统默认设置和类设置)。 b. 系统默认设置(覆盖类设置)。 c. 类设置。 5. 作业准备就绪并开始接受消息。 在完成 (4) 后,作业无法更改设置或代码,因此当您导入新的/相同的代码和新的/相同的系统默认设置时,它不会影响当前正在运行的互操作性作业。 ## 业务主机停止 停止业务主机作业意味着: 1. 互操作性命令作业停止接受更多消息/输入。 2. 如果存在有效消息,业务主机作业具有一定的超时秒数来处理该消息(完成消息会结束 BO 的 `OnMessage` 方法,BS 的 `OnProcessInput`,BPL BP 的状态 `S` 方法以及 BP 的 `On*` 方法)。 3. 如果在超时前有效消息未被处理且 `force=0`,则生产更新会在该业务主机上失败(您会在 SMP 中看到一个红色的 `Update` 按钮)。 4. 如果此列表中的任何一项为真,则表示停止成功: - 没有有效消息 - 有效消息在 `timeout` 之前处理完 - 有效消息在超时之前未处理完但 `force=1` 5. 作业的互操作性取消注册并停止。 ## 业务主机更新 业务主机更新意味着停止当前运行的业务主机作业并启动新作业。 ## 业务规则、路由规则和 DTL 所有业务主机会在新版本的业务规则、路由规则和 DTL 可用时立即开始使用它们。 在这种情况下,不需要重启业务主机。 # 离线更新 不过,有时生产更新需要单个业务主机停机。 ## 规则取决于新代码 考虑接下来的情况。 您有一个当前的路由规则 X,它根据任意标准将消息路由到业务流程 A 或 B。 在新提交中,您同时添加: - 业务流程 C - 新版本的路由规则 X,可以将消息路由到 A、B 或 C。 在此场景下,您不能先加载规则,然后再更新生产。 原因在于,新编译的规则会立即开始将消息路由到业务流程 C,而 InterSystems IRIS 可能尚未编译该规则,或者互操作性尚未更新以供使用。 在这种情况下,您需要禁用包含路由规则的业务主机,更新代码,更新生产,然后再次启用业务主机。 注: - 如果您使用[生产部署文件](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=EGDV_deploying)更新生产,它会自动禁用/启用所有受影响的 BH。 - 对于 InProc 调用的主机,编译会使调用方持有的特定主机的缓存失效。 ## 业务主机之间的依赖关系 业务主机之间的依赖关系至关重要。 假设您有业务流程 A 和 B,其中 A 向 B 发送消息。 在新提交中,您同时添加: - 新版本的流程 A,可以在向 B 发送的请求中设置新属性 X - 新版本的流程 B,可以处理新属性 X 在此场景中,我们必须首先更新流程 B,然后更新流程 A。 您可通过以下两种方式之一完成此操作: - 在更新期间禁用业务主机 - 将更新拆分为两步:首先,仅更新流程 B,然后在单独的更新中开始从流程 A 向流程 B 发送消息。 这个主题一个更具挑战性的变体是,新版本的流程 A 和流程 B 与旧版本不兼容,这需要业务主机停机。 ## 队列 如果您知道更新后某个业务主机将无法处理旧消息,则需要确保在更新前该业务主机队列为空。 为此,请禁用所有向该业务主机发送消息的业务主机,并等到其队列变空。 ## BPL 业务流程中的状态更改 首先,简单介绍一下 BPL BP 的运作方式。 在您编译 BPL BP 后,会在与完整 BPL 类同名的软件包中创建两个类: - `Thread1` 类包含方法 S1、S2、... SN,对应于 BPL 中的活动 - `Context` 类包含所有上下文变量,以及 BPL 将执行的下一个状态(即 `S5`) 此外,BPL 类是持久类,可以存储当前正在处理的请求。 BPL 的工作方式是在 `Thread` 类中执行 `S` 方法并相应地更新 BPL 类表、`Context` 表和 `Thread1` 表,其中一条“正在处理”的消息是 BPL 表中的一行。 请求处理完后,BPL 会删除 BPL、`Context` 和 `Thread` 条目。 由于 BPL BP 是异步的,通过在 `S` 调用之间保存信息并在不同请求之间切换,一个 BPL 作业可同时处理多个请求。 例如,BPL 处理一个请求,直至其到达 `sync` 活动 – 等待来自 BO 的回答。 它会将当前上下文保存到磁盘中,同时将 `%NextState` 属性(位于 `Thread1` 类中)设置为响应活动 `S` 方法,并在 BO 回答前继续处理其他请求。 BO 回答后,BPL 会将上下文加载到内存中,并执行与 `%NextState` 属性中保存的状态对应的方法。 现在,当我们更新 BPL 时会发生什么? 首先,我们需要检查是否至少满足以下两个条件之一: - 更新期间,上下文表为空,这意味着没有正在处理的有效消息。 - 新状态与旧状态相同,或者新状态是在旧状态之后添加的。 如果至少满足一个条件,我们就可以开始更新。 要么没有需要更新后 BPL 处理的更新前请求,要么状态是在结束时添加的,这意味着旧请求也可以进入其中(假设更新前请求与更新后 BPL 活动和处理兼容)。 但是,如果您有正在处理的有效请求,而 BPL 更改了状态顺序,该怎么办? 理想情况下,如果您可以等待,则禁用 BPL 调用方并等待队列为空。 验证上下文表是否也为空。 请记住,队列只显示未处理的请求,而上下文表存储正在处理的请求,因此您可能会遇到一个非常繁忙的 BPL 显示零队列大小的情况,这是正常的。 之后,禁用 BPL,执行更新并启用所有之前禁用的业务主机。 如果无法实现(通常是在 BPL 非常长的情况下,例如,我记得我更新过一个花了大约一周时间处理请求的 BPL,或者更新窗口太短),请使用 [BPL 版本控制](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=EBPLR_process)。 或者,您也可以编写一个更新脚本。 在此更新脚本中,将旧的后续状态映射到新的后续状态并在 `Thread1` 表上运行,以便更新的 BPL 可以处理旧请求。 当然,在更新期间必须禁用 BPL。 也就是说,这是一种极为罕见的情况,通常您不必这样做,但如果您需要这样做,方法就是如此。 # 结论 为了将底层代码更改后实现生产所需的操作数降到最低,互操作性实现了一种复杂的算法。 每次 SDS 更新时,调用具有安全超时的 UpdateProduction。 对于每次代码更新,您都需要决定一种更新策略。 通过使用 [git 差异](https://github.com/intersystems-ru/GitLab/blob/master/isc/git/Diff.cls)减少编译的代码量有助于缩短编译时间,但利用自身“更新”代码并重新编译它或使用相同的值“更新”设置不会触发或要求进行生产更新。 更新和编译业务规则、路由规则和 DTL 可使它们在未进行生产更新的情况下立即可用。 最后,生产更新是一项安全操作,通常不需要停机。 # 链接 - [Ens.Director](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?LIBRARY=ENSLIB&CLASSNAME=Ens.Director) - [构建 git 差异](https://github.com/intersystems-ru/GitLab/blob/master/isc/git/Diff.cls) - [系统默认设置](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=ECONFIG_other_default_settings) 在本文撰写期间,@James.MacKeith、@Dmitry.Zasypkin 和 @Regilo.Souza 提供了宝贵的帮助,作者对此深表感谢。
文章
Michael Lei · 七月 18, 2024

InterSystems 常见问题系列--如何在global ^ERRORS 里面加入应用日志

InterSystems 常见问题系列 可以通过 TRY-CATCH 来完成: #dim ex As %Exception.AbstractException TRY { //Code that causes an error } CATCH ex { do ex.Log() } 如果用了 ^%ETN, 从BACK 接入点 (BACK^%ETN)处调用. 请参考另外一篇文章: 如何使用命令获得应用错误 (^ERRORS)
公告
Claire Zheng · 五月 8

InterSystems 2025开发者竞赛:FHIR和数字医疗健康互操作性

Hi开发者们, 我们很高兴邀请大家参加我们的新一轮开发者竞赛。 🏆 InterSystems开发者竞赛:FHIR和数字医疗健康互操作性 🏆 时间: 2025年5月12日-6月1日(美国东部时间) 奖金池: 12,000美元 主题 使用InterSystems IRIS for Health、Health Connect或FHIR服务器,开发任何基于FHIR的互操作性解决方案,或医疗健康互操作性解决方案,或帮助开发或维护互操作性解决方案的应用程序或库(解决方案)。 一般要求: 应用程序或库必须具有完整的功能。它不应该是另一种语言中已经存在的库的导入或直接接口(C++除外,在C++中,您确实需要做大量的工作来为IRIS创建接口)。它不应是现有应用程序或库的复制粘贴。 有效应用程序:100%全新的Open Exchange Apps或已有的应用程序(但有显著提升)。所有参赛者/团队提交的应用程序只有经过我们团队的审核之后才会被批准参赛。 该应用程序应在IRIS Community Edition 或 IRIS for Health Community Edition上运行。均可作为host (Mac, Windows)版从 Evaluation site 下载,或者可以按从 InterSystems Container Registry或Community Container中提取的容器形式使用: intersystemsdc/iris-community:latest or intersystemsdc/irishealth-community:latest 。 该应用需开源并在GitHub或GitLab上发布。 该应用的README文件应为英文,包含安装步骤,以及应用程序的工作原理的视频演示或描述。 每位开发者最多只能提交3个作品。 注:我们的专家将根据作品的复杂性和实用性最终决定是否批准该作品参赛。他们的决定是最终决定,不可上诉。 奖品 1. 专家提名奖(Experts Nomination)——获奖者由我们特别挑选的专家团选出: 🥇第一名 - 5,000 美元 🥈第二名 - 2,500 美元 🥉第三名 - 1,000 美元 🏅第四名 - 500 美元 🏅第五名 - 300 美元 🌟第 6-10 名 - 100 美元 2. 社区提名奖(Community Nomination)—— 获得总票数最多的应用程序: 🥇第一名 - 1,000 美元 🥈第二名 - 600 美元 🥉第三名 - 300 美元 🏅第四名 - 200 美元 🏅第五名 - 100 美元 ❗如果多名参与者获得相同票数,则他们都被视为获奖者,奖金将在获奖者之间平分。❗ 现金奖励仅发放给能够验证身份的获奖者。如有任何疑问,组织者将联系参与者并要求提供额外信息。 谁可以参加? 任何开发者社区的成员均可参加,InterSystems内部员工除外(InterSystems contractor员工可以参加)。还没有账号?现在来建一个! 👥开发人员可以组队创建协作应用程序。一个团队允许 2 到 5 名开发人员。 请注意,要在您的README文件中标注您的团队成员(社区用户主页)。 重要截止日期: 🛠 应用程序开发和注册阶段: 2025年5月12日 (美国东部时间00:00): 竞赛开始 2025年5月25日 (美国东部时间23:59): 应用提交截止 ✅ 投票时间: 2025年5月26日 (美国东部时间00:00): 投票开始 2025年6月1日(美国东部时间23:59): 投票截止 注意:在整个参赛期间(开发与投票期间),开发者可持续编辑、提升其应用。 资源助力 ✓ 文档 InterSystems IRIS for Health FHIR Components documentation InterSystems Cloud FHIR Server documentation InterSystems Interoperability documentation Healthcare Data Transformations documentation ✓ 工具 Clinfhir - FHIR visualization and developer tool. ✓ 示例应用 FHIR Server Template iris-healthtoolkit-template interoperability-embedded-python FHIR HL7 SQL Demo FHIR DropBox HL7 and SMS Interoperability Demo IrisHealth Ensdemo UnitTest DTL HL7 Healthcare HL7 XML FHIR Interoperability Examples FHIR-Orga-dt FHIR Peudoanonimisation Proxy FHIR-client-java FHIR-client-.net FHIR-client-python FHIR related apps on Open Exchange HL7 applications on Open Exchange ✓线上课程 Interactive Digital Health Interoperability Foundation - An intro course into Digital Health Interoperability productions built with InterSystems IRIS for Health FHIR Data Architecture FHIR Integrations HL7 Integrations Learn FHIR for Software Developers Exploring FHIR Resource APIs Using InterSystems IRIS for Health to Reduce Readmissions Connecting Devices to InterSystems IRIS for Health Monitoring Oxygen Saturation in Infants FHIR Integration QuickStart ✓ 视频 6 Rapid FHIR Questions SMART on FHIR: The Basics Developing with FHIR - REST APIs FHIR in InterSystems IRIS for Health FHIR API Management Searching for FHIR Resources in IRIS for Health ✓ IRIS初学者 Build a Server-Side Application with InterSystems IRIS Learning Path for beginners ✓ ObjectScript Package Manager (IPM) 初学者 How to Build, Test and Publish IPM Package with REST Application for InterSystems IRIS Package First Development Approach with InterSystems IRIS and IPM ✓ 如何向大赛提交应用? How to publish an application on Open Exchange How to submit an application for the contest 需要帮助? 加入InterSystems' Discord server上的竞赛频道,或者在这篇帖子下面跟帖留言。 期待您的精彩提交 - 加入竞赛,来赢得胜利!👍 ❗️参加本次比赛即表示您同意此处列出的比赛条款。请在继续之前仔细阅读它们。 ❗️