4

背景

我工作的主要应用程序主要基于 InterSystems 的 MUMPS 式 Caché 数据库引擎。一切都存储在全局数组中。从系统中获取数据以进行外部报告的范围从简单的痛苦到极其缓慢和痛苦。

Caché 为数据库提供了一个 ODBC 驱动程序,但除非涉及的全局数组碰巧由选择标准键入,否则它会求助于扫描,一个简单的查询将需要数小时才能运行。就规模而言,整个 Caché 生产命名空间约为 100GB。在这些情况下,我可以编写比 ODBC 驱动程序更快地提取数据的 ObjectScript(Intersystems 的 MUMPS 方言)程序。

我认为部分问题是应用程序供应商不使用 Caché 的对象持久性支持,而是将 SQL 表定义为全局数组的外观,并且它通常不适用于批处理请求。

我在 MS SQL Server 中构建了一个报告数据库,它提取最常见的数据(价值 2.5GB),即使它必须扫描每个表,所有结果也会在 3 秒内返回。不幸的是,刷新数据需要很长时间,所以我只能每周进行一次完全刷新,每天进行一次主动刷新。这足以满足大多数需求,但我想做得更好。

我在 Windows 7 和 Windows Server 2008 R2 上使用 Caché 2007、SQL Server 2008 R2、VS2010。

问题范围

我需要一种将源 Caché 数据库中的实时数据与 SQL Server 上的其他数据集成的方法。我希望能够将视图或表值函数集成到 SQL 查询中,并让它从源数据库中提取实时数据。

  • 实时数据必须在 SQL Server 中可用以进行处理。使用辅助应用程序执行此操作将是一个巨大的痛苦,并且不适用于只希望通过 ODBC 推送查询并获得正确格式的最终​​数据集的报告工具。

  • 我知道有一些方法可以将数据导入 SQL Server 或完成我想做的相同的一般事情。这不是这个问题的目的。

  • 数据需要来自在 Caché 上运行的 ObjectScript 程序,因为并非我需要的所有数据都通过 SQL 定义的表公开,并且我获得了使性能可用于 ObjectScript 所需的控制。

  • 我正在寻找有关任何新选项的建议,或者我如何改进我尝试或考虑过的选项之一,或者这些方法的其他优点或缺点。

到目前为止我尝试过的

这个项目是一个令人沮丧的练习,我研究过的每条有希望的途径要么很糟糕,要么由于某种原因不起作用。通常原因是对 SQLCLR 程序集的一些不必要的限制。

  1. 通过链接服务器通过 InterSystem 的 Caché ODBC 驱动程序提取所有内容。如果 SQL Server 无法将条件推送到远程服务器或必须在本地执行连接,则它通常会求助于扫描。对任何重要表的扫描都需要花费数小时并且是不可接受的。此外,Caché 中的 SQL 表定义错误地定义了许多列的长度;SQL Server 不喜欢这样并中止查询。请参阅这个 SO 问题。我无法更改表定义,供应商认为这不是问题,因为它可以与 MS Access 一起使用。

  2. 按需使用 OPENQUERY。这在某种程度上有效,但我仍然可以从上一项中遇到列长度问题,并且无法参数化 OPENQUERY 查询,因此提取上下文数据非常无用。

  3. 使用 SQLCLR 通过 CLR 表值函数调用 ODBC 数据提供程序。这解决了参数化和数据长度问题,尽管它确实需要我在每次需要新数据时定义或修改一个函数。不幸的是,并不是我感兴趣的所有数据元素都可以通过 SQL 获得。对于某些事情,我需要直接访问全局数组。

  4. Intersystems 提供了一个ActiveX 控件,允许您在服务器上通过 TCP 运行 ObjectScript 程序并获得结果。这在独立的 C# 应用程序中效果很好,但是一旦我尝试从 SQLCLR 程序集建立连接,我就会收到一个荒谬的 URI 错误:

    在执行用户定义的例程或聚合“GetActiveAccounts”期间发生 .NET Framework 错误:System.UriFormatException:无效的 URI:URI 为空。System.UriFormatException: 在 System.Uri.CreateThis(String uri, Boolean dontEscape, UriKind uriKind) 在 System.ComponentModel.Design.RuntimeLicenseContext.GetLocalPath(String fileName) 在 System.ComponentModel. Design.RuntimeLicenseContext.GetSavedLicenseKey(Type type, Assembly resourceAssembly) at System.ComponentModel.LicenseManager.LicenseInteropHelper.GetCurrentContextInfo(Int32& fDesignTime, IntPtr& bstrKey, RuntimeTypeHandle rth) at FacsAccess.GetActiveAccounts.Client.connect() at FacsAccess.GetActiveAccounts.Client.. FacsAccess.GetActiveAccounts.E1.GetEnumerator() 处的 ctor()

    请参阅这个未回答的 SO 问题。网上还有其他帖子,但似乎没有人知道。这是一个非常简单的 C++ DLL 的 COM 包装器;它对许可没有任何作用,也没有理由进入托管许可库。我想知道这是否是某种样板文件,它试图获取一个没有名称的程序集的名称,因为它已加载到 SQL 数据库中。

  5. Intersystems 还提供了更直接的非托管接口,但这些接口都是 C++,我无法通过 P/Invoke 使用它,也无法在 SQLCLR 中加载 C++/CLI 混合模式不纯程序集。

我考虑过但似乎有点糟糕的选项

  1. 我考虑过通过 SQL Server 的 COM 支持来尝试 ActiveX 控件,但这非常慢而且非常麻烦。

  2. 我可以创建一个进程外服务来代理流量,但我不能使用来自 SQLCLR 的 .NET 远程处理,而且你不应该使用 WCF,而且对于这样一个简单的接口来说,它真的很重。我宁愿推出自己的 IPC 接口。

  3. 我可以为 VisM 或 CacheDirect 接口编写某种带有 C 风格接口的额外非托管包装器,并通过 P/Invoke 访问它。

看起来这不应该那么难,但它真的把我逼到了墙角,我需要一些观点。

4

1 回答 1

2

我认为您可以通过链接服务器访问 Cache 数据库上的存储过程来使用 ODBC,这些存储过程对 ODBC 驱动程序可见并返回结果集,但未使用 SQL 实现。

我 100% 确定您可以创建此类存储过程并通过 ODBC 访问它们,但我从未尝试从 SQL Server 作为链接服务器访问它们。即使链接服务器不工作,通过 Intersystem 的 ODBC 驱动程序而不是 Active X 控件或 CacheDirect 访问似乎更可取。

对于这个问题,我有一个这样的程序示例。

如果链接失效,这里是代码:

Query OneGlobal(GlobalName As %String) As %Query(ROWSPEC = "NodeValue:%String,Sub1:%String,Sub2:%String,Sub3:%String,Sub4:%String,Sub5:%String,Sub6:%String,Sub7:%String,Sub8:%String,Sub9:%String") [SqlProc]
{
}

ClassMethod OneGlobalExecute(ByRef qHandle As %Binary, GlobalName As %String) As %Status
{
    S qHandle="^"_GlobalName
    Quit $$$OK
}

ClassMethod OneGlobalClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = OneGlobalExecute ]
{
    Quit $$$OK
}

ClassMethod OneGlobalFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = OneGlobalExecute ]
{

    S Q=qHandle  
    S Q=$Q(@Q)  b  
    I Q="" S Row="",AtEnd=1 Q $$$OK
    S Depth=$QL(Q)
    S $LI(Row,1)=$G(@Q)
    F I=1:1:Depth S $LI(Row,I+1)=$QS(Q,I)
    F I=Depth+1:1:9 S $LI(Row,I+1)=""
    S AtEnd=0
    S qHandle=Q
    Quit $$$OK
}

仅供参考,这不是在生产中使用的示例,因为它公开了来自所有全局变量的所有数据。

但是,听起来您似乎已经准备好编写缓存对象脚本来直接获取数据——这是一个这样做的模板。要了解的主要内容是 qHandle 将在每次调用时由 ODBC 驱动程序传回,因此您可以使用它来存储状态。如果有很多状态,使 qHandle 成为一个整数索引,指向一个临时全局保存“真实”状态,并在 close 方法中清理它。

由于您关心性能,您可能还想实现一个

MyQueryFetchRows (ByRef qHandle As %Binary, FetchCount As %Integer = 0, ByRef RowSet As %List, ByRef ReturnCount As %Integer, ByRef AtEnd As %Integer) As %Status

方法 - 有关详细信息,请参阅 %Library.Query 的文档。

如果您确实需要将其作为(只读)表而不是存储过程出现在 ODBC 中,我认为这可能是可能的 - 但我以前从未尝试过查看任意存储过程是否可以公开为读取 -只有桌子,我不确定它有多容易,或者它是否真的总是可能的。

于 2012-06-20T02:51:07.937 回答