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

Phi*_*ley 4 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'
 )
Run Code Online (Sandbox Code Playgroud)

(错误文件名称基于已配置的本地文件夹,正在加载的表,以及为每个批量插入运行新生成的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.

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

菲利普

Phi*_*ley 6

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

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

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

根据Pradeep,这里是批量插入如何工作的循序渐进过程:

  1. 提交BULK INSERT命令并解析语法错误
  2. 然后编译BULK INSERT命令以生成相同的执行计划
  3. 在编译阶段,如果在Query中我们已经指定了ERRORFILE参数,那么我们将创建ErrorFile.log和ErrorFile.Error.Txt到指定的文件夹位置(重要的是要理解这里文件的大小是0kb)
  4. 文件创建完成后,我们使用Windows API调用删除这两个文件
  5. 执行计划准备就绪后,我们进入执行阶段并尝试执行批量插入命令作为其中的一部分,我们将重新创建ErrorFile.log和ErrorFile.Error.Txt到指定的文件夹位置(As Per Books Online)文档错误文件不应该在这个位置,否则我们将无法执行http://msdn.microsoft.com/en-us/library/ms188365.aspx
  6. 执行完成后,如果批量插入中存在任何错误,则相应的错误将记录到创建的错误文件中,如果没有错误,将删除这两个文件.

在失败的运行期间运行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. 根据Bulk Insert的设计,我们在编译阶段创建ErrorFile,然后在编译阶段完成后删除它
  4. 同时另一个SSIS包被执行,它调用一个内部使用XP_CMDSHELL的存储过程并启动CMD.EXE枚举所有文件
  5. CMD.EXE是一个已在Parent Process SQLServr.exe下启动的子进程,因此默认情况下它继承了SQLServr.exe创建的所有句柄(因此,此进程获取由BULK INSERT创建的所有ERRORFILE句柄)第一个连接)
  6. 现在在第一个连接中,编译阶段结束了,因此我们试图删除我们必须关闭所有句柄的文件,我们确实看到CMD.EXE持有文件的句柄并且它仍然是打开的,因此我们无法删除该文件.因此,在不删除文件的情况下,我们继续执行阶段,在执行阶段,我们尝试创建一个具有相同名称的新ERRORFILE,但由于文件已经存在,我们失败并出现错误"操作系统错误代码80(文件存在) )".

我的短期解决方法是(1)实现重试循环,生成一个新的ErrorFile名称并在放弃之前尝试新的批量插入最多三次,以及(2)在我们的夜间进程上构建另一个例程以删除找到的所有文件在我们的"ErrorFile文件夹"中.

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

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