Gav*_*vin 14 sql-server transaction-log
我正在测试不同场景中的最少日志记录插入,并且从我读到的 INSERT INTO SELECT 到带有非聚集索引的堆中,使用 TABLOCK 和 SQL Server 2016+ 应该最少记录,但是在我的情况下,这样做时我得到完整记录。我的数据库处于简单恢复模型中,我成功地在没有索引和 TABLOCK 的堆上获得了最少记录的插入。
我正在使用 Stack Overflow 数据库的旧备份进行测试,并使用以下架构创建了 Posts 表的副本...
CREATE TABLE [dbo].[PostsDestination](
[Id] [int] NOT NULL,
[AcceptedAnswerId] [int] NULL,
[AnswerCount] [int] NULL,
[Body] [nvarchar](max) NOT NULL,
[ClosedDate] [datetime] NULL,
[CommentCount] [int] NULL,
[CommunityOwnedDate] [datetime] NULL,
[CreationDate] [datetime] NOT NULL,
[FavoriteCount] [int] NULL,
[LastActivityDate] [datetime] NOT NULL,
[LastEditDate] [datetime] NULL,
[LastEditorDisplayName] [nvarchar](40) NULL,
[LastEditorUserId] [int] NULL,
[OwnerUserId] [int] NULL,
[ParentId] [int] NULL,
[PostTypeId] [int] NOT NULL,
[Score] [int] NOT NULL,
[Tags] [nvarchar](150) NULL,
[Title] [nvarchar](250) NULL,
[ViewCount] [int] NOT NULL
)
CREATE NONCLUSTERED INDEX ndx_PostsDestination_Id ON PostsDestination(Id)
Run Code Online (Sandbox Code Playgroud)
然后我尝试将帖子表复制到该表中...
INSERT INTO PostsDestination WITH(TABLOCK)
SELECT * FROM Posts ORDER BY Id
Run Code Online (Sandbox Code Playgroud)
通过查看 fn_dblog 和日志文件使用情况,我可以看到我没有从中获得最少的日志记录。我读过 2016 年之前的版本需要跟踪标志 610 以最低限度地记录到索引表,我也尝试过设置它,但仍然没有乐趣。
我猜我在这里遗漏了什么?
编辑 - 更多信息
要添加更多信息,我正在使用我编写的以下程序来尝试检测最少的日志记录,也许我在这里出了点问题...
/*
Example Usage...
EXEC sp_GetLogUseStats
@Sql = '
INSERT INTO PostsDestination
SELECT TOP 500000 * FROM Posts ORDER BY Id ',
@Schema = 'dbo',
@Table = 'PostsDestination',
@ClearData = 1
*/
CREATE PROCEDURE [dbo].[sp_GetLogUseStats]
(
@Sql NVARCHAR(400),
@Schema NVARCHAR(20),
@Table NVARCHAR(200),
@ClearData BIT = 0
)
AS
IF @ClearData = 1
BEGIN
TRUNCATE TABLE PostsDestination
END
/*Checkpoint to clear log (Assuming Simple/Bulk Recovery Model*/
CHECKPOINT
/*Snapshot of logsize before query*/
CREATE TABLE #BeforeLogUsed(
[Db] NVARCHAR(100),
LogSize NVARCHAR(30),
Used NVARCHAR(50),
Status INT
)
INSERT INTO #BeforeLogUsed
EXEC('DBCC SQLPERF(logspace)')
/*Run Query*/
EXECUTE sp_executesql @SQL
/*Snapshot of logsize after query*/
CREATE TABLE #AfterLLogUsed(
[Db] NVARCHAR(100),
LogSize NVARCHAR(30),
Used NVARCHAR(50),
Status INT
)
INSERT INTO #AfterLLogUsed
EXEC('DBCC SQLPERF(logspace)')
/*Return before and after log size*/
SELECT
CAST(#AfterLLogUsed.Used AS DECIMAL(12,4)) - CAST(#BeforeLogUsed.Used AS DECIMAL(12,4)) AS LogSpaceUsersByInsert
FROM
#BeforeLogUsed
LEFT JOIN #AfterLLogUsed ON #AfterLLogUsed.Db = #BeforeLogUsed.Db
WHERE
#BeforeLogUsed.Db = DB_NAME()
/*Get list of affected indexes from insert query*/
SELECT
@Schema + '.' + so.name + '.' + si.name AS IndexName
INTO
#IndexNames
FROM
sys.indexes si
JOIN sys.objects so ON si.[object_id] = so.[object_id]
WHERE
si.name IS NOT NULL
AND so.name = @Table
/*Insert Record For Heap*/
INSERT INTO #IndexNames VALUES(@Schema + '.' + @Table)
/*Get log recrod sizes for heap and/or any indexes*/
SELECT
AllocUnitName,
[operation],
AVG([log record length]) AvgLogLength,
SUM([log record length]) TotalLogLength,
COUNT(*) Count
INTO #LogBreakdown
FROM
fn_dblog(null, null) fn
INNER JOIN #IndexNames ON #IndexNames.IndexName = allocunitname
GROUP BY
[Operation], AllocUnitName
ORDER BY AllocUnitName, operation
SELECT * FROM #LogBreakdown
SELECT AllocUnitName, SUM(TotalLogLength) TotalLogRecordLength
FROM #LogBreakdown
GROUP BY AllocUnitName
Run Code Online (Sandbox Code Playgroud)
使用以下代码插入没有索引和 TABLOCK 的堆...
EXEC sp_GetLogUseStats
@Sql = '
INSERT INTO PostsDestination
SELECT * FROM Posts ORDER BY Id ',
@Schema = 'dbo',
@Table = 'PostsDestination',
@ClearData = 1
Run Code Online (Sandbox Code Playgroud)
我得到这些结果
在 0.0024mb 日志文件增长时,日志记录大小非常小,而且很少,我很高兴这是使用最少的日志记录。
如果我然后在 id 上创建一个非聚集索引...
CREATE INDEX ndx_PostsDestination_Id ON PostsDestination(Id)
Run Code Online (Sandbox Code Playgroud)
然后再次运行我的相同插入...
我不仅没有在非聚集索引上获得最少的日志记录,而且我还在堆上丢失了它。在做了一些更多的测试之后,似乎如果我让 ID 聚集它会最少记录但从我读过的 2016+ 应该最少记录到使用 Tablock 时的非聚集索引的堆。
最终编辑:
我已在SQL Server UserVoice上向 Microsoft 报告了该行为,如果收到回复,我将进行更新。我还写了我无法在https://gavindraper.com/2018/05/29/SQL-Server-Minimal-Logging-Inserts/上工作的最小日志场景的完整细节
Pau*_*ite 12
我可以使用 Stack Overflow 2010 数据库在 SQL Server 2017 上重现您的结果,但不能(所有)您的结论。
对具有非聚集索引的堆使用with时,对堆的最小日志记录不可用,这是出乎意料的。我的猜测是不能同时使用(堆)和(b-tree)支持批量加载。只有 Microsoft 能够确认这是一个错误还是设计使然。INSERT...SELECT
TABLOCK
INSERT...SELECT
RowsetBulk
FastLoadContext
堆上的非聚集索引最少记录(假设 TF610 已打开,或使用 SQL Server 2016+,启用FastLoadContext
),但有以下注意事项:
为非LOP_INSERT_ROWS
聚集索引显示的 497个条目对应于索引的第一页。由于索引事先是空的,这些行被完全记录。其余的行都是最少记录的。如果已记录的跟踪标志 692 已启用 (2016+) 禁用FastLoadContext
,则所有非聚集索引行都将被最低限度地记录。
我发现,最小记录被施加到两个使用堆和当批量加载相同的表(具有索引)非聚集索引BULK INSERT
从一个文件:
BULK INSERT dbo.PostsDestination
FROM 'D:\SQL Server\Posts.bcp'
WITH (TABLOCK, DATAFILETYPE = 'native');
Run Code Online (Sandbox Code Playgroud)
我注意到这一点是为了完整性。批量加载使用INSERT...SELECT
不同的代码路径,因此行为不同的事实并非完全出乎意料。
有关使用和使用最小日志记录的完整详细信息,请参阅我在 SQLPerformance.com 上的三部分系列:RowsetBulk
FastLoadContext
INSERT...SELECT
评论已关闭,所以我将在这里简要介绍这些。
使用FastLoadContext
不带TABLOCK
. 唯一完全记录的行是那些插入到第一页的行,因为在事务开始时聚集索引是空的。
这也是使用FastLoadContext
. 添加到现有页面的行完全记录,其余的记录最少。
FastLoadContext
只要非聚集索引由单独的操作员维护,DMLRequestSort
设置为 true,并且满足我的帖子中列出的其他条件,也可以使用最少的日志记录。
下面的文档很旧,但仍然值得一读。
在 SQL 2016 中,跟踪标志 610 和 ALLOW_PAGE_LOCKS 默认情况下处于打开状态,但有人可能已禁用它们。
(3) 根据优化器选择的计划,表上的非聚集索引可以是完全记录的,也可以是最小记录的。
SELECT 语句可能是问题所在,因为您有 TOP 和 ORDER BY。您以与索引不同的顺序将数据插入表中,因此 SQL 可能会在后台执行大量排序。
更新2
您实际上可能会得到最少的日志记录。当 TraceFlag 610 ON 时,日志的行为有所不同,SQL 将在日志中保留足够的空间,以便在出现问题时执行回滚,但实际上不会使用日志。
这可能正在计算保留(未使用)的空间
EXEC('DBCC SQLPERF(logspace)')
Run Code Online (Sandbox Code Playgroud)
此代码将“保留”与“使用”分开
SELECT
database_transaction_log_bytes_used
,database_transaction_log_bytes_reserved
,*
FROM sys.dm_tran_database_transactions
WHERE database_id = DB_ID()
Run Code Online (Sandbox Code Playgroud)
我认为最小日志记录(就 Microsoft 而言)实际上是在日志上执行最少的 IO,而不是保留多少日志。
看看这个链接。
更新1
尝试使用 TABLOCKX 而不是 TABLOCK。使用 Tablock,您仍然拥有共享锁,因此 SQL 可能会记录日志,以防另一个进程启动。
TABLOCK 可能需要与 HOLDLOCK 结合使用。这会强制执行 Tablock,直到您的交易结束。
还要在源表 [Posts] 上加锁,可能会发生日志记录,因为在事务发生时源表可能会发生更改。当源不是 SQL 表时,Paul White 实现了最少的日志记录。
归档时间: |
|
查看次数: |
1690 次 |
最近记录: |