断点续传不能靠bulk insert自身实现,必须通过数据分片、状态记录和可重入逻辑三者结合:用openrowset(bulk...)配合firstrow/lastrow读取指定行段,控制表记录批次状态,存储过程实现幂等导入与错误捕获。

断点续传不能靠 BULK INSERT 自身实现
BULK INSERT 是原子操作,不支持“从第 N 行继续”。它要么全成功,要么失败后整个批次回滚,错误日志里只报“批量加载失败”,不提示具体哪一行出错。想靠重试 BULK INSERT 语句本身来续传,行不通。
真正可行的断点续传,必须把“数据分片 + 状态记录 + 可重入逻辑”这三件事拆开做:
- 把源数据(如 CSV 文件)按固定行数切分成多个块,每块生成唯一标识(如
batch_id = '20260826_001') - 用一张控制表(如
import_status)记录每个batch_id的状态:'pending'/'processing'/'success'/'failed' - 存储过程每次只处理一个
pending批次,并在开始时更新为processing,成功后更新为success;若中途出错,状态卡在processing,下次调用可跳过或重试
用 OPENROWSET(BULK...) 加 FIRSTROW/LASTROW 实现分片读取
直接用 BULK INSERT 无法指定起始行和结束行——但 OPENROWSET(BULK...) 支持 FIRSTROW 和 LASTROW 参数,能精准读取文件某一段。
示例:读取 CSV 文件第 10001–15000 行
INSERT INTO target_table (col1, col2, col3)
SELECT col1, col2, col3
FROM OPENROWSET(
BULK 'D:\data\input.csv',
FORMAT = 'CSV',
FIRSTROW = 10001,
LASTROW = 15000,
FIELDQUOTE = '"',
FIELDTERMINATOR = ',',
ROWTERMINATOR = '\n',
CODEPAGE = '65001'
) AS data(col1 VARCHAR(100), col2 INT, col3 DATETIME)
注意点:
-
FIRSTROW和LASTROW是物理行号(含表头),若文件有标题行,FIRSTROW要 +1 -
OPENROWSET需启用ad hoc distributed queries(sp_configure 'show advanced options', 1; RECONFIGURE; sp_configure 'Ad Hoc Distributed Queries', 1; RECONFIGURE) - 路径必须对 SQL Server 服务账户可见,不能是客户端本地路径
- 字段类型必须严格匹配,否则整段失败,且无明细错误定位
存储过程里必须做幂等性控制
断点续传的核心不是“恢复执行”,而是“避免重复插入”。哪怕某批次执行了两次,结果也得一致。
推荐做法:
- 导入前先用
WHERE NOT EXISTS (SELECT 1 FROM target_table WHERE import_batch_id = @batch_id)过滤已导入的数据(前提是目标表有import_batch_id字段) - 或者用
MERGE语句,WHEN NOT MATCHED THEN INSERT,并以业务主键(如订单号、单据日期+编号)为匹配条件 - 绝对不要依赖
@@ROWCOUNT或临时表计数来判断是否已执行——它们不跨会话,也不持久 - 控制表
import_status的batch_id必须建唯一索引,防止并发写入重复批次
错误捕获要细化到批次级别,别丢掉原始上下文
SQL Server 的 TRY...CATCH 捕获的是语句级异常,但 OPENROWSET(BULK...) 出错时,错误信息极简(比如 "Cannot bulk load. The file ... does not exist."),根本看不出是哪个 batch_id 导致的。
实操建议:
- 在
CATCH块中显式更新import_status表,把当前batch_id状态设为'failed',并写入ERROR_MESSAGE()和ERROR_LINE() - 加日志字段如
error_context NVARCHAR(500),存入本次读取的FIRSTROW/LASTROW、文件路径、时间戳 - 不要用
RAISERROR把错误原样抛给上层——应用层很难解析这种低层级错误;而是统一返回结构化消息,比如'batch_id=20260826_003 failed at line 47: column mismatch in row 10223'
最难缠的其实是文件内容本身:换行符嵌套在字段里、引号未闭合、NULL 值被写成空字符串却定义为 NOT NULL。这些不会触发语法错误,但会让整批导入静默失败——所以首次上线前,务必用小样本跑通全流程,并保留原始文件分片用于复现。











