向SQL Server批量插入百万条数据报错的解决方案咨询
解决SQL Server插入100万条记录时"嵌套太深"的问题
这个错误的根源很明确:你把100万条IF NOT EXISTS...INSERT的单条语句堆在了同一个SQL脚本里,SQL Server的查询解析器处理不了这么多连续的嵌套逻辑块(每条IF都是一个嵌套层级)。下面给你几个实用的解决方案,按推荐优先级排序:
1. 改用批量插入+存在性检查(最优方案)
单条循环插入100万条数据不仅会触发嵌套错误,性能也会极差(每条都要单独编译、执行)。更好的方式是先把所有待插入数据导入临时表,再通过一次批量操作完成插入+去重。
步骤:
第一步:创建临时表存储待插入数据
CREATE TABLE #TempAccounts ( AccountID INT, Name NVARCHAR(100), Comment NVARCHAR(MAX), IsMachine BIT, UserID NVARCHAR(50), Prefix INT, Action NVARCHAR(50), Initials NVARCHAR(10), TimeStamp DATETIME, Reason NVARCHAR(50), Iscal NVARCHAR(10) );
注意:你原来的INSERT语句里重复写了
[Name]列,这是笔误,一定要修正,不然会报错。
第二步:批量导入待插入数据
你可以把原脚本里所有VALUES后的内容提取出来,批量插入到临时表:
-- 示例:批量插入多条数据,你可以把100万条VALUES都放到这里 INSERT INTO #TempAccounts VALUES (117242, 'blabla', 'The users project)', 1, 'val', 39, 'val', 'blabla', CAST(N'2013-01-16 05:53:50.490' AS DateTime), 'NORMAL', '0'), (117243, 'anotherName', 'Another comment', 0, 'user2', 40, 'action2', 'init2', CAST(N'2013-01-17 06:00:00.000' AS DateTime), 'NORMAL', '0'), -- ... 剩下的999998条数据
如果数据量太大,用BULK INSERT导入CSV文件会更快:
BULK INSERT #TempAccounts FROM 'C:\Users\name\Desktop\OutPut\AccountsData.csv' WITH ( FIELDTERMINATOR = ',', ROWTERMINATOR = '\n', FIRSTROW = 2 -- 如果CSV有表头的话 );
第三步:批量插入到目标表并去重
用MERGE语句或者INSERT...SELECT来完成存在性检查+插入:
方法A:使用MERGE(推荐,逻辑更清晰)
MERGE INTO [dbo].[tblAccount] AS Target USING #TempAccounts AS Source ON Target.AccountID = Source.AccountID AND Target.TimeStamp = Source.TimeStamp WHEN NOT MATCHED THEN INSERT ([AccountID], [Name], [Comment],[IsMachine], [UserID], [Prefix], [Action], [Initials], [TimeStamp], [Reason], [Iscal]) VALUES (Source.AccountID, Source.Name, Source.Comment, Source.IsMachine, Source.UserID, Source.Prefix, Source.Action, Source.Initials, Source.TimeStamp, Source.Reason, Source.Iscal);
方法B:使用INSERT...SELECT
INSERT INTO [dbo].[tblAccount] ([AccountID], [Name], [Comment],[IsMachine], [UserID], [Prefix], [Action], [Initials], [TimeStamp], [Reason], [Iscal]) SELECT Source.AccountID, Source.Name, Source.Comment, Source.IsMachine, Source.UserID, Source.Prefix, Source.Action, Source.Initials, Source.TimeStamp, Source.Reason, Source.Iscal FROM #TempAccounts AS Source WHERE NOT EXISTS ( SELECT 1 FROM [dbo].[tblAccount] AS Target WHERE Target.AccountID = Source.AccountID AND Target.TimeStamp = Source.TimeStamp );
最后记得清理临时表:
DROP TABLE #TempAccounts;
2. 拆分脚本为多个小文件(备选方案)
如果你坚持要用原来的单条IF NOT EXISTS逻辑,可以把大脚本拆分成多个小文件(比如每个文件1000条语句),然后逐个执行。
用PowerShell拆分脚本:
$sourceFile = "C:\Users\name\Desktop\OutPut\Result tblAccount.sql" $outputDir = "C:\Users\name\Desktop\OutPut\Batches\" $batchSize = 1000 # 每个文件包含1000条语句 # 创建输出目录 if (-not (Test-Path $outputDir)) { New-Item -ItemType Directory -Path $outputDir | Out-Null } $content = Get-Content $sourceFile $totalBatches = [math]::Ceiling($content.Count / $batchSize) for ($i=0; $i -lt $totalBatches; $i++) { $startIndex = $i * $batchSize $endIndex = [math]::Min(($i+1)*$batchSize -1, $content.Count -1) $batchContent = $content[$startIndex..$endIndex] $batchFile = Join-Path $outputDir "Batch_$i.sql" $batchContent | Out-File $batchFile -Encoding utf8 }
修改bat脚本循环执行小文件:
@echo off setlocal enabledelayedexpansion set "sqlServer=SQLSERVER" set "batchDir=C:\Users\name\Desktop\OutPut\Batches\" for %%f in ("%batchDir%*.sql") do ( echo !TIME! Starting execution of %%~nf.sql sqlcmd -S "!sqlServer!" -i "%%f" echo !TIME! Finished %%~nf.sql ) pause
3. 额外注意事项
- 你原来的INSERT语句里重复了
[Name]列(列列表和VALUES里都有两个),这是语法错误,必须修正,否则即使解决了嵌套问题,插入也会失败。 - 批量操作前建议关闭目标表的非聚集索引,插入完成后再重建,能大幅提升插入速度。
内容的提问来源于stack exchange,提问作者Mindan
相关产品推荐
相关产品推荐

