batchsize必须≥102400才能避免数据进入增量行组,确保直接压缩入列存储;小于该值或剩余行不足时,数据将载入delta rowgroup,引发性能瓶颈。

batchsize 必须 ≥ 102400 才能绕过 delta 行组
列存索引(尤其是聚集列存)的导入性能瓶颈,往往不是磁盘或网络,而是数据被错误地塞进 delta rowgroup。只要单次 bcp 批次行数 tuple mover 搬运合并,徒增延迟和资源争用。
实操建议:
- 显式指定
-b 102400或更大值(如-b 200000),避免依赖默认行为(默认 batchsize 是 1000) - 若源数据总量不能被 102400 整除,剩余行仍会进 delta;可提前用
SELECT COUNT(*)计算总数,再按需拆分多个bcp命令,确保每批都达标 - 不要为了“填满”而硬凑行数——比如拼接脏数据或伪造 ID,这会导致业务逻辑错乱或主键冲突
用 -n(本机格式)替代 -c,省掉字符编码转换开销
当源数据来自 SQL Server 自身(比如从另一台实例导出再导入),用 -n 参数走本机二进制格式,比 -c(字符格式)快得多。后者会强制做 OEM ↔ ANSI 字符转换,不仅慢,还可能损坏扩展字符(如中文、emoji)。
但要注意兼容性:
-
-n导出的文件只能被 SQL Server 读取,不能被人眼直接查看或被 Excel 打开 - 目标表结构必须与源完全一致(列数、类型、顺序、NULL 属性),否则导入时字段错位,值写到错误列上而不报错
- 如果必须支持跨平台或人工校验,改用
-w(Unicode 字符格式)+ XML 格式化文件,比-c更安全
并行导入无需 TABLOCK,但得避开同一张表的多路写入竞争
很多人误以为并行 bcp 必须加 -h "TABLOCK",其实这是堆表或 B-tree 索引的老习惯。对列存表,SQL Server 会自动为每个导入线程分配独立的压缩行组(或 delta 行组),各自持有独占锁,天然支持并发。
不过真实场景中容易踩坑:
- 别让多个
bcp进程同时往**同一张列存表**里灌数据——虽然语法允许,但行组分配策略可能退化,反而引发锁等待或内存争用 - 更稳妥的做法是:把源数据按逻辑切分成多个文件(如按日期分区),每个文件对应一张**临时列存表**,导入完成后再用
INSERT INTO ... SELECT合并到主表(此时主表只承受一次大事务) - 若用
BULK INSERT替代bcp,则必须加TABLOCK,否则无法触发最小日志记录
跳过格式化文件的前提是你完全掌控源/目标表结构
用 bcp 时不指定 -f,工具会按目标表定义反推字段映射——看似方便,实则危险。一旦源文件字段顺序、类型、长度与目标表稍有出入(比如 varchar(50) 导入到 varchar(30)),bcp 可能静默截断或报错中断,且错误信息模糊(常见如 Invalid character value for cast specification)。
建议:
- 首次导入前,先用
bcp database.schema.table format nul -f table.fmt -x -T生成 XML 格式化文件,人工检查<FIELD>和<COLUMN>的对应关系 - 字段名不匹配时,在格式文件里显式写
IDENTITY="YES"或Skip="YES"控制忽略列,比在命令行硬编码-F/-L更可靠 - 云环境(如 Azure Synapse)不支持
BULK INSERT和格式化文件,得换COPY INTO+ 外部文件路径
真正卡住批量导入的,往往不是参数没设对,而是没意识到列存的行组机制会把“小批次”自动降级成低效路径。确认 sys.dm_db_column_store_row_group_physical_stats 里 state_desc 是 COMPRESSED 而非 OPEN 或 CLOSED,才算落地成功。

















