SQL 中的最小日志记录条件

9 sql-server-2008 sql-server bulk-insert

我已经编写了一个脚本来测试在这个页面http://technet.microsoft.com/en-us/library/dd425070(v=sql.100).aspx中标题为 Summarizing Minimal Logging conditions about when最小日志记录发生或不发生。

使用此脚本,我发现每种不同类型插入的日志记录长度总和如下:

  • 堆为空 没有块 60000
  • 堆为空,带有 tabblock 56000
  • Heap non empty no tabblock 60000
  • 堆非空,带有 tabblock 56000
  • 堆加索引为空 无标签块 126188
  • 堆加索引为空,带有 tabblock 114188
  • 堆加索引非空无标签块 138696
  • 堆加索引非空,带有 Tablock 112000
  • 集群空有序无标签 64168
  • 簇为空,用 tabblock 56168 排序
  • 集群空无序无标签块 73388
  • 集群空无序,带有 tabblock 65388
  • 集群非空无标签块 63912
  • 集群非空,带有 tabblock 55944
  • 簇加索引为空 无标签块 124336
  • 簇加索引为空,带有 tabblock 108336
  • 集群加索引非空无标签块 123876
  • 带有标签块的集群加索引非空 107924

其中一些数字似乎与 technet 页面上的表格不符。特别是:

  • 插入空表和非空表之间的日志记录似乎没有区别,但是该页面声称在插入到没有 tabblock 的非空集群时应该有完整的日志记录
  • 使用 Tablock 插入带有和索引的堆或集群似乎确实减少了日志记录,但该页面声称应该有完整的日志记录。
  • 使用insert的SELECT INTO方法时,fn_dblog中没有行是insert操作,但是页面把这个方法列为批量加载操作,应该有表中描述的行为

作为参考,这是在 SQL express 数据库上运行的,当我运行 DBCC TRACESTATUS (610) 时,一切都是 0。

任何人都可以帮助解释为什么我可能会看到这些差异?

参考代码如下:

SET NOCOUNT ON

CREATE TABLE numbers (num INT)
CREATE TABLE numbersUnordered (num INT)

Declare @cnt int
Select @cnt=0
while (@cnt<500)
BEGIN
INSERT INTO NUMBERS(num) SELECT @cnt
SELECT @cnt=@cnt+1
END

Select @cnt=0
while (@cnt<250)
BEGIN
INSERT INTO numbersUnordered(num) SELECT @cnt*2
SELECT @cnt=@cnt+1
END

Select @cnt=0
while (@cnt<250)
BEGIN
INSERT INTO numbersUnordered(num) SELECT @cnt*2+1
SELECT @cnt=@cnt+1
END


---- heap empty without tablock
CREATE TABLE noKey1 (val INT)

INSERT INTO noKey1 (val)
SELECT * FROM numbers

DECLARE @heapEmptyNoTablock INT

SELECT @heapEmptyNoTablock = SUM([Log Record Length])
FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%noKey1%'
AND operation like '%insert%'

---- heap empty with tablock
CREATE TABLE noKey2 (val INT)

INSERT INTO noKey2 WITH(TABLOCK) (val)
SELECT * FROM numbers

DECLARE @heapEmptyTablock INT

SELECT @heapEmptyTablock = SUM([Log Record Length])
FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%noKey2%'
AND operation like '%insert%'

---- heap non empty without tablock
CREATE TABLE noKey3 (val INT)

INSERT INTO noKey3 WITH(TABLOCK) (val)
SELECT * FROM numbers

INSERT INTO noKey3 (val)
SELECT num+5 FROM numbers

DECLARE @heapNonEmptyNoTablock INT

SELECT @heapNonEmptyNoTablock = SUM(a.[Log Record Length])
FROM 
(SELECT TOP 500 * FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%noKey3%'
AND operation like '%insert%'
ORDER BY [Current LSN] desc) a

---- heap non empty with tablock
CREATE TABLE noKey4 (val INT)

INSERT INTO noKey4 WITH(TABLOCK) (val)
SELECT * FROM numbers

INSERT INTO noKey4 WITH (TABLOCK) (val)
SELECT num+5 FROM numbers

DECLARE @heapNonEmptyTablock INT

SELECT @heapNonEmptyTablock = SUM(a.[Log Record Length])
FROM 
(SELECT TOP 500 * FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%noKey4%'
AND operation like '%insert%'
ORDER BY [Current LSN] desc) a

--- heap plus index empty without tablock
CREATE TABLE noKey5 (val INT)
CREATE INDEX MSindex1
ON noKey5 (val)

INSERT INTO noKey5 (val)
SELECT * FROM numbers

DECLARE @heapIndexEmptyNoTablock INT

SELECT @heapIndexEmptyNoTablock = SUM([Log Record Length])
FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%noKey5%'
AND operation like '%insert%'


--- heap plus index empty with tablock
CREATE TABLE noKey6 (val INT)
CREATE INDEX MSindex2
ON noKey6 (val)

INSERT INTO noKey6 WITH(TABLOCK) (val)
SELECT * FROM numbers

DECLARE @heapIndexEmptyTablock INT

SELECT @heapIndexEmptyTablock = SUM([Log Record Length])
FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%noKey6%'
AND operation like '%insert%'

--- heap plus index non empty without tablock
CREATE TABLE noKey7 (val INT)
CREATE INDEX MSindex3
ON noKey7 (val)

INSERT INTO noKey7 WITH(TABLOCK) (val)
SELECT * FROM numbers

INSERT INTO noKey7 (val)
SELECT num+5 FROM numbers

DECLARE @heapIndexNonEmptyNoTablock INT

SELECT @heapIndexNonEmptyNoTablock = SUM(a.[Log Record Length])
FROM 
(SELECT TOP 1000 * FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%noKey7%'
AND operation like '%insert%'
ORDER BY [Current LSN] desc) a

--- heap plus index non empty with tablock
CREATE TABLE noKey8 (val INT)
CREATE INDEX MSindex4
ON noKey7 (val)

INSERT INTO noKey8 WITH(TABLOCK) (val)
SELECT * FROM numbers

INSERT INTO noKey8 WITH(TABLOCK) (val)
SELECT num+5 FROM numbers

DECLARE @heapIndexNonEmptyTablock INT

SELECT @heapIndexNonEmptyTablock = SUM(a.[Log Record Length])
FROM 
(SELECT TOP 1000 * FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%noKey8%'
AND operation like '%insert%'
ORDER BY [Current LSN] desc) a

--- cluster empty ordered without tablock
CREATE TABLE withKey1 (val INT PRIMARY KEY)

INSERT INTO withKey1 (val)
SELECT * FROM numbers

DECLARE @clusterEmptyNoTablock INT

SELECT @clusterEmptyNoTablock = SUM([Log Record Length])
FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%withKey1%'
AND operation like '%insert%'

--- cluster empty ordered with tablock
CREATE TABLE withKey2 (val INT PRIMARY KEY)

INSERT INTO withKey2 WITH(TABLOCK) (val)
SELECT * FROM numbers

DECLARE @clusterEmptyTablock INT

SELECT @clusterEmptyTablock = SUM([Log Record Length])
FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%withKey2%'
AND operation like '%insert%'

--- cluster empty unordered without tablock
CREATE TABLE withKey5 (val INT PRIMARY KEY)

INSERT INTO withKey5 (val)
SELECT * FROM numbersUnordered

DECLARE @clusterEmptyNoTablockUnordered INT

SELECT @clusterEmptyNoTablockUnordered = SUM([Log Record Length])
FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%withKey5%'
AND operation like '%insert%'

--- cluster empty undordered with tablock
CREATE TABLE withKey6 (val INT PRIMARY KEY)

INSERT INTO withKey6 WITH(TABLOCK) (val)
SELECT * FROM numbersUnordered

DECLARE @clusterEmptyTablockUnordered INT

SELECT @clusterEmptyTablockUnordered = SUM([Log Record Length])
FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%withKey6%'
AND operation like '%insert%'

--- cluster non empty no tablock
CREATE TABLE withKey7 (val INT PRIMARY KEY)

INSERT INTO withKey7 (val)
SELECT num FROM numbers

INSERT INTO withKey7 (val)
SELECT num+500 FROM numbers

DECLARE @clusterNonEmptyNoTablock INT

SELECT @clusterNonEmptyNoTablock = SUM(a.[Log Record Length])
FROM 
(SELECT TOP 500 * FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%withKey7%'
AND operation like '%insert%'
ORDER BY [Current LSN] desc) a

--- cluster non empty with tablock
CREATE TABLE withKey8 (val INT PRIMARY KEY)

INSERT INTO withKey8 WITH(TABLOCK) (val)
SELECT num FROM numbers

INSERT INTO withKey8 WITH(TABLOCK) (val)
SELECT num+500 FROM numbers

DECLARE @clusterNonEmptyTablock INT

SELECT @clusterNonEmptyTablock = SUM(a.[Log Record Length])
FROM 
(SELECT TOP 500 * FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%withKey8%'
AND operation like '%insert%'
ORDER BY [Current LSN] desc) a

--- cluster plus index empty no tablock
CREATE TABLE withKey9 (val INT PRIMARY KEY)
CREATE INDEX MSindex5
ON withKey9 (val)

INSERT INTO withKey9 (val)
SELECT * FROM numbers

DECLARE @clusterIndexEmptyNoTablock INT

SELECT @clusterIndexEmptyNoTablock = SUM([Log Record Length])
FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%withKey9%'
AND operation like '%insert%'

--- cluster plus index empty with tablock
CREATE TABLE withKey10 (val INT PRIMARY KEY)
CREATE INDEX MSindex6
ON withKey10 (val)

INSERT INTO withKey10 WITH(TABLOCK) (val)
SELECT * FROM numbers

DECLARE @clusterIndexEmptyTablock INT

SELECT @clusterIndexEmptyTablock = SUM([Log Record Length])
FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%withKey10%'
AND operation like '%insert%'

--- cluster plus index nonempty no tablock
CREATE TABLE withKey11 (val INT PRIMARY KEY)
CREATE INDEX MSindex7
ON withKey11 (val)

INSERT INTO withKey11 (val)
SELECT num FROM numbers

INSERT INTO withKey11 (val)
SELECT num+500 FROM numbers

DECLARE @clusterIndexNonEmptyNoTablock INT

SELECT @clusterIndexNonEmptyNoTablock = SUM(a.[Log Record Length])
FROM 
(SELECT TOP 1000 * FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%withKey11%'
AND operation like '%insert%'
ORDER BY [Current LSN] desc) a

--- cluster plus index nonempty with tablock
CREATE TABLE withKey12 (val INT PRIMARY KEY)
CREATE INDEX MSindex8
ON withKey12 (val)

INSERT INTO withKey12 WITH(TABLOCK) (val)
SELECT num FROM numbers

INSERT INTO withKey12 WITH(TABLOCK) (val)
SELECT num+500 FROM numbers

DECLARE @clusterIndexNonEmptyTablock INT

SELECT @clusterIndexNonEmptyTablock = SUM(a.[Log Record Length])
FROM 
(SELECT TOP 1000 * FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%withKey12%'
AND operation like '%insert%'
ORDER BY [Current LSN] desc) a

--- select into
/*SELECT * 
INTO selectIntoTable
FROM numbers

SELECT * FROM sys.fn_dblog(null, null)
WHERE allocunitname like '%selectIntoTable%'
AND operation like '%insert%'

DROP TABLE selectIntoTable
*/


PRINT 'Heap empty no tablock ' + CAST(@heapEmptyNoTablock AS VARCHAR)
PRINT 'Heap empty with tablock ' + CAST(@heapEmptyTablock AS VARCHAR)
PRINT 'Heap non empty no tablock ' + CAST(@heapNonEmptyNoTablock AS VARCHAR)
PRINT 'Heap non empty with tablock ' + CAST(@heapNonEmptyTablock AS VARCHAR)
PRINT 'Heap plus index empty no tablock ' + CAST(@heapIndexEmptyNoTablock AS VARCHAR)
PRINT 'Heap plus index empty with tablock ' + CAST(@heapIndexEmptyTablock AS VARCHAR)
PRINT 'Heap plus index non empty no tablock ' + CAST(@heapIndexNonEmptyNoTablock AS VARCHAR)
PRINT 'Heap plus index non empty with tablock ' + CAST(@heapIndexNonEmptyTablock AS VARCHAR)
PRINT 'Cluster empty ordered no tablock ' + CAST(@clusterEmptyNoTablock AS VARCHAR)
PRINT 'Cluster empty ordered with tablock ' + CAST(@clusterEmptyTablock AS VARCHAR)
PRINT 'Cluster empty unordered no tablock ' + CAST(@clusterEmptyNoTablockUnordered AS VARCHAR)
PRINT 'Cluster empty unordered with tablock ' + CAST(@clusterEmptyTablockUnordered AS VARCHAR)
PRINT 'Cluster non empty no tablock ' + CAST(@clusterNonEmptyNoTablock AS VARCHAR)
PRINT 'Cluster non empty with tablock ' + CAST(@clusterNonEmptyTablock AS VARCHAR)
PRINT 'Cluster plus index empty no tablock ' + CAST(@clusterIndexEmptyNoTablock AS VARCHAR)
PRINT 'Cluster plus index empty with tablock ' + CAST(@clusterIndexEmptyTablock AS VARCHAR)
PRINT 'Cluster plus index non empty no tablock ' + CAST(@clusterIndexNonEmptyNoTablock AS VARCHAR)
PRINT 'Cluster plus index non empty with tablock ' + CAST(@clusterIndexNonEmptyTablock AS VARCHAR)


DROP TABLE numbers
DROP TABLE numbersUnordered
DROP TABLE noKey1
DROP TABLE noKey2
DROP TABLE noKey3
DROP TABLE noKey4
DROP TABLE noKey5
DROP TABLE noKey6
DROP TABLE noKey7
DROP TABLE noKey8
DROP TABLE withKey1
DROP TABLE withKey2
DROP TABLE withKey5
DROP TABLE withKey6
DROP TABLE withKey7
DROP TABLE withKey8
DROP TABLE withKey9
DROP TABLE withKey10
DROP TABLE withKey11
DROP TABLE withKey12
Run Code Online (Sandbox Code Playgroud)

Pau*_*ite 13

其中一些数字似乎与 technet 页面上的表格不符。

测试中生成的日志记录的大小存在细微差异,但这是由于其他内部日志记录行为造成的,而不是是否发生了最小日志记录

存储引擎团队的 Sunil Agarwal 提供了最小日志记录的一个很好的定义

不记录单个行记录对页面分配结构的更改

您看到记录的单个行更改(例如LOP_INSERT_ROWS)的任何测试都没有对关联的分配单元使用最少的日志记录。某些操作可以针对一个分配单元(例如索引)进行最低限度的记录,而不是针对另一个进行最低限度的记录。此外,在某些情况下,对现有页面的插入可能不会被最少记录,但对新分配的页面的更改可能会被记录。

大多数细节可以在一系列存储引擎团队博客文章中找到:

那里没有探讨的一个细节是,要最少记录(在 SQL Server 2008 或更高版本中)INSERT...SELECT对 b 树结构的更改必须将DMLRequestSort查询计划运算符属性设置为 true。这适用于数据加载性能指南显示“取决于”的情况:查询计划必须使用广泛(每个索引)维护和DMLRequestSort=true.

我在Minimal Logging with INSERT…SELECT 和 Fast Load Context 中写了更多关于这个的内容。