Я тестирую минимальное количество вставок в журналы в разных сценариях, и из того, что я прочитал, 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)
Затем я пытаюсь скопировать таблицу сообщений в эту таблицу ...
INSERT INTO PostsDestination WITH(TABLOCK)
SELECT * FROM Posts ORDER BY Id
Посмотрев на 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
Вставка в кучу без индексов и TABLOCK, используя следующий код ...
EXEC sp_GetLogUseStats
@Sql = '
INSERT INTO PostsDestination
SELECT * FROM Posts ORDER BY Id ',
@Schema = 'dbo',
@Table = 'PostsDestination',
@ClearData = 1
Я получаю эти результаты
При росте файла журнала 0,0024 МБ, очень небольших размерах записи журнала и очень немногих из них я рад, что это использует минимальное ведение журнала.
Если я тогда создаю некластеризованный индекс по id ...
CREATE INDEX ndx_PostsDestination_Id ON PostsDestination(Id)
Затем запустите мою же вставку снова ...
Мало того, что я не получаю минимальное ведение журнала для некластеризованного индекса, но я также потерял его в куче. После еще нескольких тестов кажется, что если я сделаю ID кластеризованным, он будет вести минимальный журнал, но из того, что я прочитал в 2016+, следует минимально регистрироваться в куче с некластеризованным индексом при использовании таблока.
ЗАКЛЮЧИТЕЛЬНОЕ РЕДАКТИРОВАНИЕ :
Я сообщил Microsoft о поведении на SQL Server UserVoice и обновлю , если получу ответ. Я также написал полную информацию о сценариях минимального журнала, которые я не смог заставить работать по адресу https://gavindraper.com/2018/05/29/SQL-Server-Minimal-Logging-Inserts/
Ответы:
Я могу воспроизвести ваши результаты на SQL Server 2017, используя базу данных Stack Overflow 2010, но не (все) ваши выводы.
Минимальное протоколирование в куче недоступна при использовании
INSERT...SELECT
сTABLOCK
кучи с некластерного индексом, который является неожиданным . Я предполагаю, что неINSERT...SELECT
может поддерживать массовые загрузки, используяRowsetBulk
(куча) в то же время, какFastLoadContext
(b-дерево). Только Microsoft сможет подтвердить, является ли это ошибкой или умышленно.Некластеризованный индекс в куче минимально регистрируется (предполагается , что TF610 включен, или используется SQL Server 2016+, что позволяет
FastLoadContext
) со следующими оговорками:497
LOP_INSERT_ROWS
записей, показанных для некластеризованного индекса, соответствуют первой странице индекса. Поскольку индекс был заранее пуст, эти строки полностью регистрируются. Остальные строки все минимально зарегистрированы . Если задокументированный флаг трассировки 692 включен (2016+) для отключенияFastLoadContext
, все строки некластеризованного индекса минимально регистрируются.Я обнаружил , что минимальное протоколирование применяется для обоих кучи и некластеризованный индекс при массовой загрузке той же таблицы (с индексом) , используя
BULK INSERT
из файла:Я отмечу это для полноты. При массовой загрузке
INSERT...SELECT
используются разные пути кода, поэтому факт поведения отличается не совсем неожиданно.Для получения полной информации о минимальном ведении журнала с использованием
RowsetBulk
иFastLoadContext
сINSERT...SELECT
моими сериями из трех частей на SQLPerformance.com:Другие сценарии из вашего блога
Комментарии закрыты, поэтому я кратко остановлюсь здесь.
Пустой кластерный индекс с трассировкой 610 или 2016+
Это минимально зарегистрировано, используя
FastLoadContext
безTABLOCK
. Единственными полностью зарегистрированными строками являются те, которые вставлены на первую страницу, потому что кластерный индекс был пуст в начале транзакции.Кластерный индекс с данными и трассировкой 610 ИЛИ 2016+
Это также минимально зарегистрировано, используя
FastLoadContext
. Строки, добавленные на существующую страницу, полностью регистрируются, остальные - минимально.Кластерный индекс с некластерными индексами и TABLOCK или Trace 610 / SQL 2016+
Это также может быть минимально зарегистрировано, используя,
FastLoadContext
пока некластеризованный индекс поддерживается отдельным оператором,DMLRequestSort
установлен в true, и другие условия, изложенные в моих сообщениях , выполнены.источник
Приведенный ниже документ старый, но все еще отлично читается.
В SQL 2016 флаг трассировки 610 и ALLOW_PAGE_LOCKS включены по умолчанию, но кто-то может их отключить.
Руководство по загрузке данных
Оператор SELECT может быть проблемой, потому что у вас есть TOP и ORDER BY. Вы вставляете данные в таблицу в порядке, отличном от индекса, поэтому SQL может выполнять большую сортировку в фоновом режиме.
ОБНОВЛЕНИЕ 2
Возможно, вы на самом деле получаете минимальное ведение журнала. При включенном TraceFlag 610 журнал ведет себя по-разному, SQL будет резервировать достаточно места в журнале, чтобы выполнить откат, если что-то пойдет не так, но фактически не будет использовать журнал.
Это, вероятно, подсчет зарезервированного (неиспользованного) пространства
Этот код отделяется зарезервировано от б
Я полагаю, что минимальное ведение журнала (насколько это касается Microsoft) на самом деле связано с наименьшим количеством операций ввода-вывода в журнале, а не с тем, какая часть журнала зарезервирована.
Посмотрите на эту ссылку .
ОБНОВЛЕНИЕ 1
Попробуйте использовать TABLOCKX вместо TABLOCK. С Tablock у вас все еще есть общая блокировка, поэтому SQL может регистрироваться в случае запуска другого процесса.
TABLOCK может потребоваться использовать вместе с HOLDLOCK. Это применяет Таблок до конца вашей транзакции.
Также установите блокировку на исходную таблицу [Posts], регистрация может происходить, потому что исходная таблица может измениться во время вашей транзакции. Пол Уайт достиг минимальной регистрации, когда источником не была таблица SQL.
источник