MS SQL Server,多行插入

3

假设我写了以下查询语句:

INSERT INTO DestinationTable
(ColumnA, ColumnB, ColumnC, etc.)
SELECT FROM SourceTable
(ColumnA, ColumnB, ColumnC, etc.)

我的源表有2200万行。

SQL服务器填满了我的硬盘,并出现错误。

为什么SQL服务器不能处理我的查询?

我应该使用游标并逐个插入行吗?

附注:这是SQL Express 2005,但我可以尝试完整版。

更新:我还想提到,当我在管理工具中查看时,我的源表只占用约1GB的存储空间。然而,我的25GB的可用磁盘空间却被填满了?我还在使用两个不同的数据库Source.mdf->Destination.mdf,我不知道这是否有任何区别。


你为什么要插入2200万行数据,这些数据肯定不都是必需的。 - David Basarab
1
很遗憾,确实是这样。我不能透露具体细节,但它涉及到某个公司的每一个客户。 - jonathanpeppers
你是在尝试复制一张表吗?你尝试过使用 "select into" 吗? - roman m
我需要使用SUBSTRING和CONVERT对数据进行一些处理,否则我就不行了。 - jonathanpeppers
3
David说我们中的许多人每天都会插入很多记录,这取决于数据库的用途。我每个月会导入2000万条记录。 - HLGEM
这里有类似的情况,但行数要少得多 - http://stackoverflow.com/questions/20103557/pushing-inserts-into-sql-server-with-c - Steam
7个回答

8

批量更新...

INSERT INTO DestinationTable
    (ColumnA, ColumnB, ColumnC, etc.)
SELECT TOP 100000 ColumnA, ColumnB, ColumnC, etc.
FROM SourceTable
WHERE NOT EXISTS (SELECT *
    FROM DestinationTable
    WHERE DestinationTable.KeyCols = SourceTable.KeyCols)

WHILE @@ROWCOUNT <> 0
    INSERT INTO DestinationTable
        (ColumnA, ColumnB, ColumnC, etc.)
    SELECT TOP 100000 ColumnA, ColumnB, ColumnC, etc.
    FROM SourceTable
    WHERE NOT EXISTS (SELECT *
        FROM DestinationTable
        WHERE DestinationTable.KeyCols = SourceTable.KeyCols)

在处理检查点、日志文件管理以及需要将其放在一个事务中等方面,有不同的变化。


我认为我的光标建议可能比这个稍微简单一些。我会尝试两种方法,看哪种表现更好。 - jonathanpeppers
6
我认为如果使用光标插入你所有的数据的话,可能需要一年半的时间才能完成 ;) - womp
2
@Jonathan.Peppers:游标仍然需要资源、锁,取决于你如何声明它,可能会在tempdb中有2200万行。 - gbn
@gbn,您可以通过将外部_INSERT替换为SELECT 1_来消除插入的重复,因为它将生成@@ROWCOUNT以使循环第一次运行。 - KM.

4

1
我会使用一个以竖线为分隔符的 .txt 文件,而不是 .csv 文件,并使用 bulk insert 或 SSIS 进行批量插入,但 BCP 也可以正常工作。 对于乔纳森,我使用 bulk insert 将一个包含 2200 万条记录的文件导入到我的数据库中,需要 16 分钟。 - HLGEM
但是你建议将其导出为csv,然后再导入到SQL服务器中?我宁愿将它们备份在1.44软盘上,并使用打包和运输承诺。 - jonathanpeppers
1
尽管BCP/BULK INSERT对CSV格式的支持不够完善,也无法使用标准I/O,但是先将数据写入CSV文件,然后再使用BCP/BULK INSERT导入仍然比常规INSERT更快。那些讽刺的评论是没有必要的。 - sayap
OP的问题是SQL服务器填满了硬盘。BCP向TX日志中写入非常少量的数据,因此可以缓解驱动器空间问题。 - Raj More

2
你可以尝试将数据库恢复模式设置为“简单”而不是“完整”(默认设置)。这可以在管理工具中的数据库属性选项页面上完成。这样可以使你的事务日志大小保持较小。插入操作完成后,你可以随时将恢复模式设置回完整模式。

我会尝试一下,如果这最终变成了自动化任务,我就不喜欢这个解决方案。 - jonathanpeppers
1
如果数据库处于完全恢复状态并且正在进行日志备份,那么切换到简单恢复模式会中断日志链。在需要进行时间点恢复的生产环境中,这一点必须非常谨慎考虑。在切换回完全恢复模式后,必须进行完整数据库备份以重新启动日志链并允许进一步的日志备份。 - GilaMonster

1

我强烈建议您在进行大量批量数据操作时将数据库恢复模型设置为BULK_LOGGED。

默认情况下,数据库设置为简单或完整的恢复模型。

完整恢复模型完全记录所有事务,适用于正常使用。

批量记录恢复模型旨在在大型批量操作期间临时使用-假设它是受到批量记录恢复模型影响的批量操作之一(有关更多信息,请参见msdn.microsoft.com/en-us/library/ms191244.aspx中可以最小化记录的操作)。

BULK_LOGGED恢复模型最小化记录事务

您可以使用以下代码片段执行此操作

    --Determine the recovery model currently used for the database

    SELECT name AS [Database Name],
    recovery_model_desc AS [Recovery Model]
    FROM sys.databases 
    WHERE name=<database_name> ;

    --Remember this recovery model so that you can switch back to the same later

    --set the database recovery model to BULK_LOGGED

    ALTER DATABASE <database_name>  SET RECOVERY BULK_LOGGED;

    --Run your heavy data insert tasks
    INSERT INTO DestinationTable
    (ColumnA, ColumnB, ColumnC, etc.)
    SELECT FROM SourceTable
    (ColumnA, ColumnB, ColumnC, etc.)

    /*Again set the database recovery model to FULL or SIMPLE 
    (the result which we had got from first query)*/

    ALTER DATABASE <database_name>  SET RECOVERY FULL;   
    --OR 
    ALTER DATABASE <database_name>  SET RECOVERY SIMPLE;   

*注意-请在批量操作正在处理时保持耐心* [:P]

我以前做过很多次。请让我知道这是否对您有帮助。

您可以参考下面的MSDN文章,了解在msdn.microsoft.com/en-us/library/ms190203.aspx切换恢复模式时的考虑事项的详细信息。


BULK_LOGGED 在我的系统上接近实现,但并没有完全达到目标,即使它能够实现,我也需要缩小数据库/文件的大小才能将其降至可接受的范围内。我认为按照顶部答案所建议的分批插入是正确的方法。 - jonathanpeppers
1
简单恢复还允许某些操作进行最小化日志记录。只有在所有操作都完全记录时才是完整的。 - GilaMonster
@gbn,请参阅http://msdn.microsoft.com/en-us/library/ms190422(v=SQL.90).aspx。这是一项最小日志记录操作。Aamod错过了“WITH(TABLOCK)”部分,并且对于简单恢复模型是错误的。但是,使用最小日志记录的想法是正确的。在这种情况下进行1次INSERT,应该比循环插入解决方法快约10倍,因此是最好的选择。 - sayap
@saya:切换回完整恢复模式后备份数据库所需的时间是多少?如果您切换恢复模式,将会中断备份链。 - gbn
@gbn,首先,我认为在FULL和BULK_LOGGED之间切换不会破坏备份链。毕竟,两者都是日志备份,只是后者不能进行PITR。其次,即使链条被打断,也没有必要进行完整备份:http://www.sqlskills.com/BLOGS/PAUL/post/A-SQL-Server-DBA-myth-a-day-(2030)-restarting-a-log-backup-chain-requires-a-full-backup.aspx。最后,即使出于某种原因必须进行完整备份,节省的时间(我们谈论的是数量级)也足以抵消这一点。 - sayap
显示剩余3条评论

1

这篇文章介绍了将数据导入SQL Server的相关信息。

至于你的表填满的原因,我会查看表的架构,并确保列的大小尽可能小。

我会仔细分析所有数据是否都是必要的。


我移动表格的原因是为了让我的表格结构尽可能小。数据由第三方导入,否则我们就不会遇到这个问题。 - jonathanpeppers

0

使用INSERT INTO ... SELECT(2200万行)的问题在于它作为一个事务运行。因此,即使数据库处于简单恢复模式,您也可能会填满事务日志驱动器。

逐行插入是一个可怕的想法,这将需要很长时间。

使用BCP导出数据,并将其作为BULK INSERT导入,可能是最快的方法。但这需要学习如何使用BCP实用程序。

如果您决定在T-SQL中执行此操作,则必须将其分成批处理。前面答案中的INSERT ... SELECT TOP(n)... WHERE NOT EXISTS方法有效,但WHERE子句的执行时间可能会累加。为了使其更有效率但更复杂,我有时使用ROW_NUMBER() OVER(ORDER BY pk)和WHERE rn%(n)= 0为每个n行填充一个临时表的pk值。然后,您可以使用循环与INSERT INTO ... SELECT ... WHERE pk> @a AND pk <= @b,以及适当的代码来从临时表中的每次迭代更新变量。只需确保在第一次或最后一次迭代中不要错过任何行。

您可能想在Integration Services中执行此操作,它也可以进行批量插入。Microsoft有一篇白皮书介绍了如何在大约30分钟内加载数千兆字节的数据。他们将源数据(BCP?)导出到多个文件中,并创建与目标相同结构的多个表。然后将每个文件插入到一个单独的空表中,它们都可以作为最小日志记录运行。所有这些导入都作为单独的并行进程运行。最后使用表分区命令将每个导入表合并到目标表中。

在30分钟内加载数千兆字节的数据:https://technet.microsoft.com/zh-cn/library/dd537533(v=sql.100).aspx


0

真的吗?这是标准的INSERT,无法进行“最小日志记录”。自从SQL Server 2000以来,您应该使用ALTER DATABASE。 - gbn
...而且KB是古老的,甚至在选项适用时也被提及。 - gbn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接