使用ErrorFile选项时,同时SQL BULK INSERT会生成失败

时间:2012-01-20 17:06:25

标签: sql-server operating-system guid bulkinsert

Windows Server 2008 R2 Enterprise,SQL Server 2008 X64,SP3,开发人员版

我构建并动态执行(通过sp_executesql)BULK INSERT命令。一般形式是:

BULK INSERT #HeaderRowCheck
 from "\\Server\Share\Develop\PKelley\StressTesting\101\DataSet.csv"
 with
 (
   lastrow = 1
  ,rowterminator = '\n'
  ,tablock
  ,maxerrors = 0
  ,errorfile = 'C:\SQL_Packages\TempFiles\#HeaderRowCheck_257626FB-A5CD-41B8-B862-FAF8C591C7A9.log'
 )

(错误文件名基于已配置的本地文件夹,正在加载的表,以及为每次批量插入运行新生成的guid - 它是一个包含在自己的存储过程中的子例程。)

外部进程(是SQL代理,现在是WCF服务)启动DTEXEC,它启动一个SSIS包,该包调用循环遍历集合的数据库中的存储过程,构建查询并为每个执行查询。最多可以有四个负载同时从/进入给定数据库,并且SQL实例上的多个数据库可以同时运行 - 尽管历史上,数量一直很低,而且我们通常只有一个实例一次运行它。我们做了很多,并且它已经完成了两年多的完美工作 - 安全性已正确配置,必要的文件和文件夹存在,通常都是这样。 (运气?我不想。)

我们现在预计会有一些严重的工作负载,所以我们正在做一些压力测试,我在其中启动8次运行,每次运行有4个进程,其中一组四个将分开并逐个处理要加载的文件(即执行多达32个同时批量插入。就像我说的,压力测试。)低,看,启动时,一个或多个将在执行过程中失败,并出现如下错误信息:

Error #4861 encountered while loading header information from file "DataSet.csv": Cannot bulk load because the file "C:\SQL_Packages\TempFiles\#HeaderRowCheck_D0070742-76A5-4175-A1A7-16494103EF25.log" could not be opened. Operating system error code 80(The file exists.).

从运行到运行,不会对同一文件,数据集或整体处理点发生错误。

从表面上看,听起来有两个进程正在尝试访问同一个错误文件,这意味着它们会独立生成相同的guid(!)。我的理解是,这应该是不可能的。另一种理论是,同时发生了很多事情(可能最多同时运行32个BULK INSERT命令),SQL和/或OS在某种程度上变得混乱(我是DBA,而不是网络管理员)。我可以做一个解决方法,构建我的try-catch块以检查错误4861并重试最多三次,但我宁愿避免这样的kludgery。

我已经抛出一个例程,在使用它之前将错误文件的名称(使用guid)记录到表中。经过多次运行并且多次运行失败后,我看到(a)我的表中记录了失败的文件+ guid,以及(b)没有记录重复的guid。

任何人都知道可能会发生什么?

菲利普

1 个答案:

答案 0 :(得分:6)

我在微软技术支持部门开了一个案例,经过不少的反复,Pradeep M.M. (SQL Server支持技术主管)全力以赴。

一般过程:读入文件夹中的文件列表,并逐个对这些文件执行一系列批量插入(首先读取第一行,我们解析列,然后从中读取数据)第二行+。所有批量插入都使用“ErrorFile”选项,以便在用户数据格式错误时为用户提供哪些信息。过程已经工作了3年多,但在最近的压力测试条件下(单个SQL Server实例最多同时运行8次,所有文件格式正确),我们得到了上面列出的错误。

我们最初虽然生成GUID时出现错误,因为“已经打开”错误,但这个想法最终被丢弃 - 如果newid()运行不正常,会有更多人拥有更多严重的问题。

根据Pradeep,以下是批量插入工作原理的分步流程:

  1. 提交BULK INSERT命令并解析语法错误
  2. 然后编译BULK INSERT命令以生成执行 计划相同的
  3. 在编译阶段,如果在Query中,我们是否已指定 然后我们将创建ErrorFile.log和ERRORFILE参数 ErrorFile.Error.Txt到指定的文件夹位置(重要 这里要理解的是文件大小为0kb)
  4. 文件创建完成后,我们使用删除这两个文件 Windows API调用
  5. 执行计划准备就绪后,我们将进入执行阶段 并尝试执行批量插入命令作为其中的一部分 重新创建ErrorFile.log和ErrorFile.Error.Txt到该文件夹 指定的位置(如每本书在线文档错误 文件不应该在这个位置,否则我们将失败 执行http://msdn.microsoft.com/en-us/library/ms188365.aspx
  6. 执行完成后,如果有任何错误 批量插入相应的错误将记录到错误文件中 如果没有错误,则创建这两个文件将被删除。
  7. 在失败的运行期间运行ProcMon(进程监视器)显示ErrorFile已成功创建并在步骤3中打开,但未在步骤4中关闭,导致步骤5生成我们看到的错误。 (对于成功运行,文件已按预期创建和关闭。)

    对ProcMon的进一步分析表明,在批量插入尝试之后,运行CMD.EXE的另一个进程正在对文件发出“关闭句柄”操作。我们使用涉及xp_cmdshell的例程来检索要处理的文件列表,这将是CMD.EXE过程的原因。这是踢球者:

    ...有一些业务逻辑在SQL Server中启动CMD.EXE,并且由于CMD.EXE是子进程,它继承了父进程打开的所有句柄(所以这可能是CMD中的某种时序问题。 EXE保存文件的句柄,这些文件在启动时打开,并且CMD.EXE继承的所有处理文件都无法删除,只能在CMD.EXE被销毁后才能释放。

    就是这样。单个运行永远不会遇到此问题,因为在发出批量插入之前,xp_cmdshell调用已完成。但是对于并行运行,特别是对于许多并行运行(我只遇到5个或更多的问题),出现了一个时间问题:

    1. 其中一个SSIS包执行并调用存储过程 它在内部使用XP_CMDSHELL并启动CMD.EXE枚举 文件
    2. 与SQL Server相同的连接完成文件枚举 然后启动批量插入活动,它在编译中 BULK INSERT命令的阶段
    3. 根据批量插入的设计,我们创建了ErrorFile 在编译阶段,然后在编译后删除它 阶段已完成
    4. 同时执行另一个SSIS包并调用它 内部使用XP_CMDSHELL并启动的存储过程 CMD.EXE枚举所有文件
    5. CMD.EXE是在Parent下启动的子进程 处理SQLServr.exe,默认情况下它会继承所有句柄 由SQLServr.exe创建(因此这个过程获取所有句柄 由BULK INSERT在First中创建的ERRORFILE 连接)
    6. 现在在第一个连接中,编译阶段结束了 因此我们试图删除我们必须关闭的文件 所有句柄,我们确实看到CMD.EXE持有一个句柄 文件,它仍然打开,因此我们无法删除该文件。所以 在不删除文件的情况下,我们将继续执行执行阶段 执行阶段我们正在尝试用。创建一个新的ERRORFILE 同名但由于文件已经存在,我们失败并出现错误 “操作系统错误代码80(文件存在。)。”
    7. 我的短期解决方法是(1)实现重试循环,生成新的ErrorFile名称并在放弃之前尝试新的批量插入最多三次,以及(2)在我们的夜间进程上构建另一个例程以删除在我们的“ErrorFile文件夹”中找到的所有文件。

      长期修复是将我们的代码修改为不通过xp_cmdshell列出文件。这似乎是可行的,因为整个ETL过程都包含在SSIS包中并由其管理;或者,CLR例程可以构建和工作。目前,考虑到我们预期的工作负载,解决方案就足够了(特别是考虑到我们刚才正在处理的其他工作),所以可能在我们实现最终结果之前固定。

      张贴后人,万一发生在你身上!