为什么第二个INSERT
语句比第一个语句慢5倍?
从生成的日志数据量来看,我认为第二个不符合最小日志记录的条件。但是,《数据加载性能指南》中的文档指出,两个插入都应该能够被最小限度地记录。因此,如果最小日志记录是关键性能差异,那么为什么第二个查询不符合最小日志记录的条件?可以采取什么措施来改善这种情况?
查询#1:使用INSERT ... WITH(TABLOCK)插入5MM行
考虑以下查询,该查询将5MM行插入堆中。该查询在中执行1 second
并生成64MB
事务日志数据,如所报告sys.dm_tran_database_transactions
。
CREATE TABLE dbo.minimalLoggingTest (n INT NOT NULL)
GO
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that correctly estimates that it will generate 5MM rows
FROM dbo.fiveMillionNumbers
-- Provides greater consistency on my laptop, where other processes are running
OPTION (MAXDOP 1)
GO
查询2:插入相同的数据,但SQL低估了行数
现在考虑这个非常相似的查询,该查询对完全相同的数据进行操作,但碰巧是从SELECT
基数估计值太低的表(或在我的实际生产案例中,该表包含多个联接的复杂语句)中提取的。该查询在其中执行5.5 seconds
并生成461MB
事务日志数据。
CREATE TABLE dbo.minimalLoggingTest (n INT NOT NULL)
GO
INSERT INTO dbo.minimalLoggingTest WITH (TABLOCK) (n)
SELECT n
-- Any table/view/sub-query that produces 5MM rows but SQL estimates just 1000 rows
FROM dbo.fiveMillionNumbersBadEstimate
-- Provides greater consistency on my laptop, where other processes are running
OPTION (MAXDOP 1)
GO
完整脚本
有关完整的脚本集,请参阅此Pastebin,以生成测试数据并执行这两种情况中的任何一种。请注意,您必须使用SIMPLE
恢复模型中的数据库。
商业环境
我们半频繁地移动数百万行数据,因此对于执行时间和磁盘I / O负载而言,使这些操作尽可能高效是很重要的。最初,我们的印象是创建一个堆表并使用它INSERT...WITH (TABLOCK)
是执行此操作的一种好方法,但是现在,由于我们在实际的生产场景中观察到了上面演示的情况(尽管使用了更复杂的查询,而不是使用简化版)。
SELECT
包含大量联接的复杂语句,该语句为生成结果集INSERT
。这些联接对最终表插入运算符的基数估计值不佳(我已经通过错误UPDATE STATISTICS
调用在repro脚本中进行了模拟),因此,它并不比发出UPDATE STATISTICS
命令来解决问题那么简单。我完全同意,简化查询以使基数估计器更容易理解可能是一个好方法,但是实现给定的复杂业务逻辑并不是一个简单的方法。