iDb2Connection - 如何取消长时间运行的查询
介绍如何针对 iSeries DB2 运行多个查询并取消长时间运行的查询。
引言
我们最近开发的一个应用程序使用 iSeries 作为其数据源。应用程序的前端基本上是一个友好的查询设计器,显示允许用户从各种下拉列表中选择参数并提交信息以进行检索的屏幕。iSeries 库非常庞大,它是一个数据仓库,记录了过去十年来的所有库存和销售信息。
根据其性质,某些提交的查询可能需要一段时间才能运行。即使仓库文件已索引、反规范化,并且我们已经调整了许多可能的 SQL 语句,但仍可能有人决定查看“过去 6 个月内所有商店的每个收银机交易”。
我们希望应用程序具有管理员功能,允许用户取消在 iSeries 上运行的查询,从而释放该作业使用的所有数据库资源。
背景
由于许多查询会返回相同的结果(例如,销售代表请求查看昨天的前 100 名畅销书),因此我们决定在应用程序服务器上积极缓存数据。数据在 iSeries 上的“日终”作业运行时编译到数据仓库文件中,因此我们知道数据在办公时间内是静态的。一旦某人运行了特定的查询,就应该从缓存中检索相同的结果并提供给后续的请求。
但是,我们仍然需要考虑如何处理初始数据库操作。这种类型的数据访问必须是异步执行的,并且在我们的设计中还需要考虑一些额外的项目需求。
- 专用的 .NET 应用程序服务器将监听来自客户端的查询请求。
- 应用程序服务器将异步地针对 iSeries 数据库运行 SQL 查询。
- UI 中将为系统的管理员提供一个“查询管理器”屏幕,向他们显示应用程序服务器中当前正在运行的所有查询。这应包括相关的 iSeries 作业详细信息和查询持续时间。
- 管理员应能够取消 iSeries 上的查询,释放数据库资源。
- 当查询完成处理或由于查询取消而出现任何异常时,客户端将收到通知。
SqlClient 提供程序具有 BeginExecuteReader
方法,该方法允许异步访问,但不幸的是,它在 IBM 提供程序的当前版本中不可用。因此,我们研究了如何自己实现此功能,并特别关注监视和取消查询的能力。
iSeries 如何处理查询?
在深入研究查询取消之前,了解 iSeries 如何处理 TCP/IP 请求非常重要。任何熟悉 MS SQL 的人都会知道,活动连接都有一个关联的进程;在查询分析器中执行 sp_who2 将为您提供所有这些进程的列表。
iSeries 环境中使用了类似的概念;在这种情况下,工作单元由 iSeries 作业处理。这是 IBM 文档。
“每个 ODBC 连接都与一个系统数据库程序通信。此程序称为主机服务器程序。与 TCP/IP 一起使用的数据库服务器程序的名称是 QZDASOINIT。”
根据您用于连接到 iSeries 的提供程序,与您的连接关联的作业将在 iSeries 的特定子系统中分配。不同的提供程序将导致不同的子系统作业,但就本文而言,任何使用 IBM .NET 提供程序的连接都将从 QUSRWRK 子系统分配作业,并显示为 QZDASOINIT 作业。
注意:这取决于您的 iSeries 配置。您可以将 QZDASOINIT 作业配置为在不同的子系统中运行,您的系统管理员可能已选择这样做。最重要的是要注意服务器程序的名称(QZDASOINIT),无论底层子系统如何,它都将是相同的。
下面的屏幕截图显示了发出 WRKACTJOB 命令后在 QUSRWRK 子系统中运行的作业
在我们的环境中,这些作业被配置为 预启动作业,并监听传入的 TCP/IP 连接。当您从 .NET 打开连接时,您将连接到运行 QZDASOINIT 程序的作业。
以下代码应向您展示如何为 iDB2Connection
分配作业
using (iDB2Connection connection = new iDB2Connection("Your-Connection-String"))
{
/* When we open the connection, we are allocated a job on the AS400
* that will handle our SQL request.
*
* e.g 219418/QUSER/QZDASOINIT
*
*/
try
{
connection.Open();
Console.WriteLine(connection.JobName);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message)
}
}
在上面的代码片段中,在调用 Open()
之后,连接对象现在将具有与之关联的作业详细信息。尝试在 Console.WriteLine()
处设置断点,以便您的连接对象保持活动状态。现在,您可以在 iSeries 上运行以下命令,以便在客户端保持连接打开时验证连接。
WRKJOB JOB(QZDASONIT)
这将显示系统中当前列出的所有预启动作业。一个有用的命令,但我们无法轻易分辨出哪个是我们的
WRKJOB JOB(job_number/job_user/job_name)
这允许您查看您连接到的特定作业。运行此命令所需的参数在您的 .NET 代码中可用。connection.JobName
属性具有确切的语法,因此获取详细信息并将其输入到命令中。您现在可以看到承载您的 .NET TCP/IP 连接的作业。
当您针对连接对象运行 SQL 语句时,您被分配的作业将作为交互式进程处理 SQL。
iSeries 对提交批处理查询和选择特定队列来处理批处理作业提供了出色的支持。您可以调整系统设置以优先处理某些作业和队列,我曾希望从 .NET 环境中利用这一点。iSeries 还提供了一些软件,可以防止查询在执行前运行(请查看 查询管理器 文章)。
我们研究了许多方法,但从 .NET 操作这些方法时遇到了许多问题,我可能会在未来的文章中讨论它们!
好的,那么我如何取消长时间运行的查询?
一旦您掌握了处理 SQL 语句的 iSeries 作业,您就可以与该作业进行交互并对其发出进一步的命令。在我们的 .NET 代码中,查询的执行发生在新的线程中,因此我们需要在打开连接并开始执行 SQL 之前记录 iSeries 作业详细信息。
考虑以下工作流程
- UI 提交查询
- 我们的查询管理器(.NET 代码)创建线程 B,该线程 B 打开连接
- 分配给连接的作业名称将被记录并报告给调用者
- UI 存储作业详细信息,现在它知道一个线程已连接到 iSeries 以及与该线程关联的作业详细信息
- UI 现在拥有发出取消查询请求所需的所有详细信息
- 查询管理器针对作业详细信息交互式执行取消请求
- iSeries 结束指定作业
- 将通信错误报告给正在异步处理 SQL 的线程 B
- 处理异常,并向 UI 报告查询已被取消
现在,我们可以通过向 iSeries 提交 ENDJOB 命令来轻松实现此目的。提交针对 iSeries 的命令需要特定类型的命令语法;这是一个实现此目的的辅助函数
private string GetDb2CommandString(string commmand)
{
string commandLength = commmand.Length.ToString("0000000000",
System.Globalization.CultureInfo.CurrentCulture);
return String.Format("CALL QSYS.QCMDEXC('{0}', {1}.00000)",
commmand, commandLength);
}
我们可以通过使用命令字符串格式化程序并针对 iDb2Connection
对象执行语句来构建有效的 ENDJOB 命令。在下面的示例中,iDB2Helper
是 Microsoft 在其数据访问应用程序块之前发布的旧 SQLHelper
类的移植版本。我在这些示例中使用它,因为它更容易包含在演示项目中。
public void CancelQuery(string jobName)
{
string commandText = this.GetDb2CommandString("ENDJOB JOB(" +
jobName + ") OPTION(*IMMED) SPLFILE(*YES)");
iDB2Helper.ExecuteNonQuery(connectionString,
System.Data.CommandType.Text, commandText);
}
此命令是同步运行的;iSeries 将立即 (*IMMED) 结束作业,并终止您在线程 B 上运行的 SQL 检索。这会导致异常被抛回给调用者。
我们现在已经终止了处理 TCP/IP SQL 请求的预启动作业。这是一件好事;我们已经释放了数据库操作本身(也许它包含有问题的 SQL,也许它导致其他作业出现问题等),但了解我们所做的以及这可能产生的任何影响很重要。
预启动作业就是这样,它们在子系统启动时自动启动。作业的数量取决于您的系统配置。它们会保持活动状态并在接收到新的 TCP/IP 请求时被重新使用。与 iSeries 上的大多数事物一样,作业被重用的次数可以通过系统管理员进行配置。这是因为创建新作业比重用现有作业(想想 .NET 中的连接池)更耗费资源。
有关此内容的更多阅读,请参阅 调整预启动作业条目。
因此,终止这些作业将在某个时候产生开销。好消息是 iSeries 足够智能,可以知道它还剩下多少作业,并且仅在所有预启动作业都忙于处理其他请求时才创建新作业,因此您的开销会延迟,直到某个不幸的客户端尝试运行一些强制 iSeries 创建新作业的 SQL。
在我看来,这种性能损失可以忽略不计;由于我们已经以“即发即弃”的方式运行查询,因此客户端不会被占用,也不会真正注意到后台发生的额外工作。
要点
由于我们将数据检索控制到我们自己的线程中,因此连接字符串中有几个额外的属性需要指定,才能正确处理取消 iSeries 作业时报告回来的异常。
DATA SOURCE=MYISERIES;USER ID=USR;PASSWORD=PWD;
CheckConnectionOnOpen=true;EnablePreFetch=false
CheckConnectionOnOpen
:不完全必要,但在连接字符串中使用它总是有益的。它可以解决 iSeries 重新启动许多或所有作业时(例如,在 IPL 期间)可能遇到的一些连接池问题。完整详细信息可在 此处找到。
EnablePreFetch。必须指定此属性;否则,您将遇到致命异常。出于性能原因,iSeries 提供程序默认启用预取数据。在内部,提供程序将启动一个线程来开始数据检索。如果您不将其关闭,当我们取消 iSeries 作业时,与此通信错误相关的异常将在提供程序中而不是在我们的自定义代码中发生,并导致您的应用程序崩溃。
非常重要!
我演示的技术不应作为处理所有有问题的 QZDASOINIT 作业的万能方法。如果您一直遇到性能不佳的情况,那么您尝试运行的 SQL 可能存在缺陷,并导致 iSeries 进行不必要的工作。
始终首先调整和分析您的 SQL,并查看查询的执行计划。您是否在使用索引或导致表扫描?您能否使 SQL 更高效?
如前所述,我们将取消查询功能包含在我们的项目中,作为 UI 中仅管理员使用的功能。
演示项目
差不多就这样了——您现在应该了解 iSeries 如何运行您的 TCP/IP 请求,这在 .NET 代码中是如何解释的,以及我们如何利用这些信息与 iSeries 进一步交互。
我创建了一个演示项目,它将所有不同的概念结合在一起,您可以从文章顶部的链接下载。
该项目相当简单;它演示了向“查询管理器”提交多个查询的能力,该管理器将针对其自己的连接对象(以及相应的 QZDASONIT 作业)处理每个 SQL 语句。查询状态显示在屏幕底部,任何发生的错误也将显示在那里。
以下文本也包含在项目下载文件中,作为 ReadMe.txt
从 iSeries 进行异步数据访问
用户界面是一个简单的屏幕,允许您将 SQL 提交到您的 iSeries 并监视结果。
屏幕分为两部分
- 主区域:这是一个网格,允许您在查询完成后查看结果
- 底部区域:这是查询监视器屏幕,显示正在运行的查询和所有相关详细信息
运行程序前
您的环境中只需要配置一件事。在 App.config 文件中,为您的 iSeries 指定连接字符串。
我正在引用 IBM.Data.DB2.iSeries
提供程序,路径为 C:\Program Files\IBM\Client Access\IBM.Data.DB2.iSeries.dll。检查“DataAccess”项目中的引用是否已正确识别。如果您安装了 IBM iSeries V5R3,它应该可以正常工作。
如何提交查询
有两种提交查询的方法,都可以在“查询”菜单中找到。
- 打开 SQL。这允许您打开具有预定义语句的 txt\sql 文件,这是运行查询的快捷方式。
- 手动 SQL 输入。输入有效的 SQL 命令并为其提供简短描述。
完成上述任一操作后,查询将被提交并显示在屏幕的查询监视器部分。
如何取消查询
如果您的查询正在查询监视器中运行,请右键单击以查看上下文菜单,然后单击“取消查询”。
将向 iSeries 发送命令,您的查询将进入错误状态。
如何查看查询结果
查询完成后,它将在查询监视器中显示为绿色。双击该项目,结果将加载到主屏幕部分。
测试多个查询
如果您想同时测试许多正在运行的查询,最好使用方法(i)提交查询。设置几个将运行不同时长的 SQL 文件。例如,创建一个您知道可能需要至少 30 秒才能运行的 SQL 语句作为第一个文件。这将让您有机会取消。
- 设置几个文本文件,其中包含您想运行的一些 SQL,例如 3 或 4 个不同的查询。
- 重复方法(i)将所有文本文件提交到 iSeries。
- 每个查询将在查询监视器中显示,并分配一个不同的 iSeries 作业。
- 尝试取消长时间运行的查询。
问题
小心您的 SQL 将返回多少行,您可能会占用大量内存。查询被添加到 UI 中的 Set 以便我们可以访问 DataSet 结果。使用“删除查询”选项从列表中清除项目,并在完成后缓存它们。
最低要求
- Visual Studio 2005
- iSeries .NET 提供程序 (V5R3 或更高版本)
- 访问绿屏命令行将很有用,但非必需
历史
- 2010 年 6 月 7 日 - 初始发布。